Webpack 新手教學

Photo by Rayia Soderberg on Unsplash
Photo by Rayia Soderberg on Unsplash
Webpack 是什麼呢?就如它的官網所言,它是可以用來打包 (Bundle) scripts、styles、images、assets。在 Bundle 的過程中,它會解析這些檔案與模組之間的依賴關係,最後輸出靜態的檔案。在 Runtime 時,不需要再安裝第三方模組。

Webpack 是什麼呢?就如它的官網所言,它是可以用來打包 (Bundle) scripts、styles、images、assets。在 Bundle 的過程中,它會解析這些檔案與模組之間的依賴關係,最後輸出靜態的檔案。這些靜態的檔案裡已經包含第三方模組。也就是說,在 Runtime 時,不需要再安裝第三方模組。目前主流的 SPA framework(React、Angular、Vue.js 和 React Native)都是利用 Webpack 來實現 Bundle 的功能。

Webpack
Webpack

完整程式碼可以在 下載。

建立 Webpack 專案

建立一個 NPM 專案。

% mkdir WebpackExample
% cd WebpackExample
WebpackExample % npm init -y

安裝 webpack 和 webpack-cli 套件。

WebpackExample % npm install webpack webpack-cli --save-dev

建立第一個 JavaScript 檔案叫 index.js。在 index.js 裡面我們會用到 Lodash 模組,所以也先安裝它。

WebpackExample % npm install --save lodash
WebpackExample % mkdir src
WebpackExample % cd src
WebpackExample/src % vi index.js

在 index.js 裡,我們用 lodash 簡單地印出 Hello Webpack!。

import _ from 'lodash';
console.log(_.join(['Hello', 'Webpack'], ' '));

建立 Webpack 的設定檔。它的預設檔名是 webpack.config.js。這設定檔中,entry 指定入口程式檔,而 output 則是設定了輸出的目錄和檔案名稱。

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

基本的 Webpack 專案這樣就完成了。讓我們來用 Webpack 指令 Bundle src/ 下面的程式碼。如果剛剛 webpack 的設定檔不是 webpack.config.js 的話,那就用 --config 參數來指定設定檔的路徑。

WebpackExample% npx webpack
Hash: 4cdaba133dc7948617d8
Version: webpack 4.43.0
Time: 42ms
Built at: 07/06/2020 11:13:20 AM
   Asset       Size  Chunks             Chunk Names
index.js  959 bytes       0  [emitted]  main
Entrypoint main = index.js
[0] ./src/index.js 31 bytes {0} [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

我們可以看到專案下多了 dist 資料夾,裡面還有 Bundle 好的 dist/index.js。

WebpackExample % ls dist
index.js

打開 dist/index.js,可以看到 Lodash 模組也被編譯在裡面了。Webpack 將數個 JavaScript 檔案 Bundle 成單一個 JavaScript 檔案,而且還會 Minify 整個檔案。

最後讓我們把 npx webpack 指令加到 NPM 腳本。修改 package.json 如下:

{
  "name": "WebpackExample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12"
  }
}

加入 NPM 腳本後,就可以用 npm run build 指令來 Bundle 專案。

WebpackExample % npm run build

HtmlWebpackPlugin

Webpack 最主要的功能就是 Bundle 數個 JavaScript 檔,變成單一個 JavaScript 檔。當然它的功能還不僅僅如此。JavaScript 主要是用來開發網頁,那就一定會有 HTML 檔。所以 Webpack 也可以幫我們輸出 HTML 檔。

安裝 HtmlWebpackPlugin

WebpackExample% npm install --save-dev html-webpack-plugin

新增一個 HTML 範本檔。

WebpackExample % mkdir public
WebpackExample % cd public
WebpackExample/public % vi index.html

HTML 範本檔如下。它是很簡單的 HTML 檔,Webpack 會自動把編譯好的 index.js 加入到範本檔裡面。

<html>
<head>
</head>
<body>
<div id="app">
</div>
</body>
</html>

將 src/index.js 修改如下:

import _ from 'lodash';
const app = document.getElementById('app');
app.innerHTML = _.join(['Hello', 'Webpack'], ' ');

在 webpack.config.js 裡面,我們在 plugins 裡加入 HtmlWebpackPlugin。其中 template 就是要設定為我們的 HTML 範本檔,filename 就是要輸出到 dist 下面的檔名,而 inject 就是指要不要注入 index.js 到 HTML 範本檔。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Home',
      filename: 'index.html',
      template: 'public/index.html',
      inject: true,
    }),
  ],
};

讓我們在 Bundle 一次專案吧!

WebpackExample% npm run build

Bundle 好後,我們再看一下 dist 下面,這次多了 index.html。用瀏覽器打開 index.html 就可以看到 Hello Webpack。打開看一下 index.html 原始碼,你可以看到,不但 index.js 被注入,整個檔案也被 Minified。

WebpackExample % ls dist
index.html index.js

CleanWebpackPlugin

每次我們 Bundle 專案時,它都是直接輸出到 dist 資料夾,並覆蓋掉已經存在的檔案。但是它不會先清空上一次 Bundle 的輸出檔,而是直接覆蓋。這樣當程式碼有變動時,dist 可能會殘留我們不要的檔案。我們可以利用 CleanWebpackPlugin 來清空上一次的 Bundle。

安裝 CleanWebpackPlugin。

WebpackExample% npm install --save-dev clean-webpack-plugin

在 webpack.config.js 裡加入 CleanWebpackPlugin。這樣每次 Bundle 時,都會先清空上一次的 Bundle。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'index.js,
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Home',
      filename: 'index.html',
      template: 'public/index.html',
      inject: true,
    }),
    new CleanWebpackPlugin(),
  ],
};

改變專案進入點

webpack.cofig.js 中的 entry 可以是 string 也可以是 object。當他是 string 時,它的預設名稱是 index;而當它是 object 時,我們可以設定多個 entry 還有名稱。

將 webpack.config.js 修改如下。現在 entry 下有個 app。在 output 那邊,我們就可以設定,根據 entry 的名稱來決定輸出的檔名。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Home',
      filename: 'index.html',
      template: 'public/index.html',
      inject: true,
    }),
  ],
};

Bundle 後,dist/ 下就會是 app.bundle.js。

WebpackExample % npm run build
WebpackExample % ls dist
app.bundle.js index.html

CSS Loaders

Webpack 預設是處理 js 檔,但它可以加入不同的 Loader 來處理其他的檔案類型。接下來,我們來讓我們的專案可以處理 CSS 檔案。

安裝 style-loadercss-loader

WebpackExample % npm install --save-dev css-loader style-loader

建立 src/style.css 檔案。

.hello {
  color: red;
}

在 src/index.js 中,我們可以用 import 來引入 CSS 檔案。

import _ from 'lodash';
import './style.css';

const app = document.getElementById('app');
app.innerHTML = _.join(['Hello', 'Webpack'], ' ');
app.classList.add('hello');

最後,我們要在 webpack.config.js 中設定 CSS 的 loader 如下。在 rules 裡面加入 style-loader 和 css-loader,並且指明 loader 可以處理的檔案類型。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Home',
      filename: 'index.html',
      template: 'public/index.html',
      inject: true,
    }),
    new CleanWebpackPlugin(),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
        ],
      },
    ],
  },
};

Bundle 一下專案。發現 dist 資料夾下並沒有 .css 檔。這是因為 CSS 被 Bundle 進 app.bundle.js 裡面了。

File Loaders

網頁除了 CSS 之外,最常處理到的檔案類型還有圖檔。藉由 file-loader,Webpack 也可以處理圖檔。接下來,我們來加入處理圖檔的 Loader。

安裝 file-loader。

npm install --save-dev file-loader

然後在 src/ 放入一個圖檔,檔名為 src/icon.png。在 src/index.js 裡,就可以用 import 來引入圖檔。

import _ from 'lodash';
import './style.css';
import Icon from './icon.png';

const app = document.getElementById('app');
app.innerHTML = _.join(['Hello', 'Webpack'], ' ');
app.classList.add('hello');

const icon = new Image();
icon.src = Icon;
app.appendChild(icon);

最後在 webpack.config.js 裡,加入 file-loader,並設定可以處理的檔案類型。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Home',
      filename: 'index.html',
      template: 'public/index.html',
      inject: true,
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
        ],
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader',
        ],
      },
    ],
  },
};

Bundle 一下專案,然後看一下 dist。下面會多一個 .png 檔。檔名已經被重新編碼過。

WebpackExample % ls dist
99d4ae376af2a9084b5f65b2fc8b8156.png app.bundle.js                        index.html

HotReload/LiveReload

我們來介紹 Webpack 超強的功能,就是 Hot Reload 和 Live Reload。兩者都是指,當程式有任何的變更時,它可以馬上刷新畫面。不同的是,Hot Reload 是只有更新變更的部分;而 Live Reload 則是整個重新刷新。目前流行的 SPA framework 都有支援 Hot/Live Reload。在 Webpack 中加入這功能是很簡單的。

安裝 webpack-dev-server

npm install --save-dev webpack-dev-server

在 webpack.config.js 中,我們加入了 devtool 和 devServer。devtool 可以指定不同的 source map。

devServer 的設定:

  • port:指定 port number。開啟的網頁就會是 http://localhost:[port]。
  • transportMode:是指網頁和 devServer 用什麼方式溝通。ws 是指用 WebSocket。預設是 sockjs,它會使用 sockjs-node 模組,這是因為怕有些瀏覽器還不支援 WebSocket。
  • hot/liveReload:指的是要用 Hot Reload 還是 Live Reload。二擇一。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: {
    app: './src/index.js',
  },
  devtool: 'inline-source-map',
  devServer: {
    transportMode: 'ws',
    hot: false,
    liveReload: true,
    port: 9000,
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Home',
      filename: 'index.html',
      template: 'public/index.html',
      inject: true,
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
        ],
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader',
        ],
      },
    ],
  },
};

把啟動 devServer 的指令加到 package.json。–open 是讓 Webpack 自動幫我們打開瀏覽器,並直接瀏覽 http://localhost:[port]。

{
  "name": "WebpackExample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "start": "webpack-dev-server --open"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.6.0",
    "file-loader": "^6.0.0",
    "html-webpack-plugin": "^4.3.0",
    "style-loader": "^1.2.1",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "lodash": "^4.17.15"
  }
}

最後試著啟用 devServer。啟用後,修改一下 Hello Webpack 字串,就可以看到它馬上刷新畫面!

WebpackExample % npm run start

TypeScript

最後,如果你想讓專案用 TypeScript 來開發的話,可以參考下面這篇文章,裡面有介紹如何加入 ts-loader。

結語

雖然我們介紹了 Webpack 不少的功能。但是,這些僅僅是冰山一角,只可以讓你大概了解 Webpack 的功能。藉由 Plugins 和 Loaders 可以擴充 Webpack 的功能,你也可以自己開發。雖然我們可能不會真的去建立自己的 Webpack 專案,但是如果你是網頁開發者的話,你一定會用到 React/Angular/Vue.js。它們都是使用 Webpack 來 Bundle 專案。了解 Webpack 可以讓我們更了解這些 Framework。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

You May Also Like
Photo by Berkay Gumustekin on Unsplash
Read More

TypeDoc 教學

TypeDoc 是一個 TypeScript 的 API 文件產生器。它將程式碼中的註解提取出來,並轉換成網頁 API 文件。本文章將介紹如何在專案中使用 TypeDoc,以及如何用 TypeDoc 在程式碼中撰寫 API 文件。
Read More
Photo by Harley-Davidson on Unsplash
Read More

如何建立 Node.js CLI 專案

Node.js 讓我們可以用 JavaScript 開發命令列應用程式 (CLI, Command-line interface, application) 或是服務器端應用程式 (Server side application)。本篇會介紹如何建立一個 Node.js CLI 專案。
Read More