Learning Webpack from Scratch


Abstract

在網路上逛到了 Webpack for everyone 這個線上課程。

正好對於 Webpack 不太熟悉,加上今年 Webpack 剛出了穩定的 2.2.0 版本。 於是想藉由這個機會,從這個簡短的課程快速了解 Webpack ,以及常見的使用情境。

這篇文章會以初學者的角度,紀錄這門課程的一些心得。


What’s Webpack?

根據從官網的 README , Webpack 就是一個 把網頁資源(以 JavaScript 為主)打包為 Module 的工具

這樣做的好處是,傳輸的檔案數量減少了,可以減少伺服器負荷。 另一方面包裝成模組也能讓程式碼重複利用。

Webpack 重要的功能就是打包時處理各資源之間複雜的 相依性

而在打包的過程中,Webpack 可以引入很多外掛, 例如:壓縮 JavaScript,或是處理 Sass,壓縮圖片等。

接著就讓我們跟著課程來紀錄學到的技巧吧!


Lec 1: Compile

一開始不免俗的要先建立環境,做個像是 Hello World 般的打包測試。

a. 建立 webpack-learning 資料夾,當中建立 src 作為打包的資料來源。

`mkdir webpack-learning && cd webpack-learning; mkdir src`

b. 建立一個打包來源 main.js,撰寫一行程式碼。

`echo "alert('hello world')" > src/main.js`

c. 由 npm 作為開發專案環境及下載 Webpack 工具。

`npm init && npm install webpack --save-dev`

d. 可以開始打包囉,工具放在 node_modules/.bin/webpack

`./node_modules/.bin/webpack src/main.js dist/bundle.js`

可以看出打包產生了 bundle.js 放在 dist 之下, 而這個指令也可以搭配 --watch 選項來監控來源檔案的變化以自動更新打包。

最後,我們可以從 bundle.js 中搜尋到 main.js 中所寫的程式碼,確認 bundle.js 真的包含來源的程式碼。


Lec 2: compile with webpack.config.js

而一般來說打包牽涉的檔案很多,不方便用一行一行指令完成, 所以接著介紹如何使用一般的設定檔來設定 Webpack, 建立模組 webpack.config.js,打包時會參考這個模組。

模組的輸出(export)會包含來源以及目標,之後還會加入規則(rule)。 這就是打包流程的核心所在。

// webpack.config.js
const webpack = require('webpack');
const path = require('path');

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

Lec 3: Modules Are Simply Files

這堂課主要是在說 如果來源的 main.js 如果有 import 其他模組, Webpack 也會一並將該模組帶入處理,

可以從 bundle.js 看到所引入的模組的程式碼。

例外講者另外介紹了 Node 模組與 ES6 模組的語法差異。 兩者引入模組與輸出模組使用了不同的語法。 而 Webpack 對兩者都可以支援。


* Lec 4: Loaders Are Transformers

這堂課介紹了兩個核心觀念:Loaderrule

首先介紹 Loader,功能是讓 JavaScript 以外的檔案也能夠被處理,打包成模組。 有了各種 Loader 我們就可以打包其他資源,像是圖片,CSS 等等。

接著使用 npm install css-loader --save-dev 來安裝 css-loader 和 style-loader 這兩個 Loader 的功能分別是: * css-loader 讓 Webpack 讀懂 CSS * style-loader 讓 Webpack 知道如何將 CSS 帶入到模組中

接著我們還要將 Loader 帶進 webpack.config.js 方法就是建立一個 rule,讓 Webpack 知道哪些副檔名的檔案要套用 css-loader

module.exports 建立 module.rules,塞入一個 object literal, 當中包含 testuse ,指定檔名和 loader

要注意 use 陣列所輸入的 loader 順序和打包處理順序是相反的。 打包的順序應該是先經過 css-loader 再經過 style-loader, 但是輸入的陣列卻要反過來寫:

use: ['style-loader', 'css-loader']

結果就是這樣:

// webpack.config.js
const webpack = require('webpack');
const path = require('path');

module.exports = {
  entry: path.resolve('src', 'main.js'), // abs path
  output: {
    path: path.resolve('dist'), // abs path
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,  // css file path regex
        use: ['style-loader', 'css-loader']  // NOTE right to left
  }]}};

Lec 5: ES2015 Compilation With Babel

打包的過程中也可以借助 Babel 將 ES6 轉譯,以支援更多瀏覽器。 這時候要使用 Webpack plugin,首先下載對應的套件與 Babel preset

npm install --save-dev babel-loader babel-core babel-preset-es2015

建立 .babelrc

echo "{\"presets\":[\"es2015\"]}" > .babelrc

就可以讓打包工作支援 ES6 了!

// webpack.config.js
const webpack = require('webpack');
const path = require('path');

module.exports = {
  entry: path.resolve('src', 'main.js'), // abs path
  output: {
    path: path.resolve('dist'), // abs path
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
  }]}};

Lec 6: Minification and Environments

這堂課介紹在 Webpack.config.js 中加入 plugin

webpack.optimize.UglifyJsPlugin()

就能夠將打包的檔案進行壓縮處理(minify)。

我們也可以由環境變數來控制是否打包的時候要壓縮。

const webpack = require('webpack');
const path = require('path');
const inProduction = (process.env.NODE_ENV === 'production');

module.exports = {
  entry: path.resolve('src', 'main.js'), // abs path
  output: {
    path: path.resolve('dist'), // abs path
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
  }]},
  plugins: []
};

if (inProduction) {
  module.exports.plugins.push(
    new webpack.optimize.UglifyJsPlugin()
  );
}

Lec 7: Sass Compilation

介紹如何使用 sass-loader 將 Sass 檔案轉成 CSS 並套用在網頁上

現在應該可以發現愈來愈能抓到 Webpack 使用的模式了

就是安裝對應的 Loader 然後加一個對應的 Rule

npm install --save-dev sass-loader node-sass

use 的部份要想一下 loader 如何串起來。 處理 Sass 檔案的 rule 應該會是這樣:

{
  test: /\.s[ac]ss$/,  // sass, scss
  use: ['style-loader', 'css-loader', 'sass-loader']
},

一樣處理順序是從最後開始,先經過 sass-loader 處理 Sass 成 CSS, 再經過 css-loader 理解 CSS, 最後再經過 style-loader 將樣式帶到 bundle.js 中。


* Lec 8: Extract CSS to a Dedicated File

這一堂課討論的情境是, 有時你還是希望 Sass 檔案不要被包進模組, 而是以獨立 CSS 檔案的方式輸出,以供他人維護。

也就是說,你想要 Webpack 處理 Sass 成為 CSS, 但是最後又不要將它包到最終的 bundle.js 而是以 app.css 輸出。

這時候我們要再裝一個新的外掛 extract-text-webpack-plugin

npm install --save-dev extract-text-webpack-plugin

這個外掛可以將現有的 loader 組成一個新的 “Extract Loader”。 接著將特定的資源從來源中抽取出來,以不同的方式輸出。 新的 module.rules 項目變成這樣:

const ExtractTextPlugin = require('extract-text-webpack-plugin');

// ...
{
  test: /\.s[ac]ss$/,  // sass, scss
  use: ExtractTextPlugin.extract({
    use: ['css-loader', 'sass-loader'],
    fallback: 'style-loader'
  })
},

可以發現它使用了 css-loadersass-loader ,將之組成一個 “Extract Loader” 。 畢竟 Webpack 還是需要這兩個 loader 才能轉換 Sass 成 CSS。 而 fallback 設定 style-loader 是針對沒有辦法被抽取出來的 CSS, 要繼續用 style-loader 將樣式帶進 bundle.js

接著在 plugins 指定 CSS 輸出的名稱:

//...
plugins: [
  new ExtractTextPlugin('app.css')
]

大功告成啦!最後得到了 dist/bundle.jsdist/app.css


Summary