iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 29
0
Modern Web

從0開始的網頁生活!30天從網頁新手到網頁入門系列 第 29

Day29-JS模組化!(套件結合篇)

本文已搬家到筆者自己的部落格嘍,有興趣的可以點這個連結

前言

今天是模組化的最後一篇文章啦,終於快把模組化講完了XD

今天要介紹的可以說是近年來網頁必會的技能了,同時也是將複雜的網頁變得有條理的最要功臣,今天筆者就要來介紹 Webpack 這個前端套件打包工具。

為了方便讀者可以快速上手 Webpack ,這邊筆者會介紹最新的 Webpack4 ,v4相較於v3比較精簡而且效能也比較好。

什麼是Webpack

Webpack 是一個開源的前端打包工具。 Webpack 提供了前端開發缺乏的模組化開發方式,將各種靜態資源視為模組,並從它生成優化過的程式碼,通常都會寫一個 webpack.config.js 來管理各個檔案。

Webpack 的核心功能如下

  • 可同時整合 CommonJS 和 ES6 module

  • 轉換 JSX, Coffee Script, TypeScript 等

  • 分散封裝專案使用的程式碼,使載入頁面時只需載入當頁所需的程式碼以加速載入速度

  • 整合樣式表 (css, sass, less 等)

  • 處理圖片與字型

  • 建置 production-ready 的程式碼 (壓縮)

看下圖大概就知道 Webpack 最重要的功能是哪些了XD

簡單來說 Webpack 就是負責包裝前端全部檔案的一個強大套件。

Webpack安裝說明

要安裝 Webpack 很簡單,直接在終端機下 npm install webpack -D 就可以了,可是 Webpack4 非常奇怪, Webpack4 把以往都綁在 Webpack 內的 Webpack-cli 挪出來另外安裝,所以除了安裝 Webpack 外還要記得安裝 webpack-cli ,不然在下任何跟 Webpack 有關的指令都會出錯喔!

Imgur

CommonJS

在開始正式介紹 Webpack 基本設定之前要先來提一下 CommonJS ,因為 Webpack 的設定檔要用 CommonJS 的規範來撰寫。

這邊筆者為了不要讓這篇文章變得太過複雜,所以就簡單提一下 CommonJS 的語法。

  • 匯入

    CommonJS 的匯入寫法跟宣告變數很像,寫成 const name = require('module') ,基本上 CommonJS 的匯入觀念就跟昨天教的 ES6 modules 一模一樣,所以這邊筆者就不細談一些比較細節的觀念了,就單純提一下語法。

  • 匯出

    CommonJS 的匯出就比較特別了,由於 CommonJS 是 Node.js 的規範,而 Node.js 有一個內建的函式庫叫 Modules,大家可以稍微看一下底下這張圖就可以了解 Modules 的組成,但也不用刻意去了解全部的成員只要了解裡面的 exports 就好。

    Imgur

    看到 exports 的預設值了嗎?大家應該猜得到 exports 必須要接的是一個物件,既然要操作的是一個物件想必匯出的寫法就跟物件有關了,沒錯匯出的寫法就跟一般在改寫物件內的 value 一樣,所以匯出的寫法就像這樣 module.exports = { module }

如果有興趣想更深入了解 CommonJS 的規範可以參考這個網站

Webpack基本設定

Webpack最基本的設定值有以下幾個。

  • entry

    用來設定 JS 檔的進入點,也就是網頁最一開始要先讀取的 JS 檔。

  • output

    用來設定打包後的 JS 檔要叫什麼名字以及要擺在哪邊,通常都會命名為 bundle.js 而這個 bundle.js 通常都會擺在 dist 的資料夾。

  • module

    用來設定 Webpack 的各種 loaders ,例如最有名的轉譯套件 babel 就是寫在這邊,等等會詳細介紹 babel

    loaders 通常會擺在 module 內的 rules 並且用一個陣列包起來,寫法就像這樣 module: {rules: [ loaders ]}

  • plugin

    用來設定各種插件,例如 uglifyjs 以及 minifyjs 就是寫在這邊,不過 Webpack4 已經將壓縮檔案獨立成一個 mode 可以直接利用指令的方式輸入,所以也不用在 plugin 裡面寫了XD

總結一下上面所講的基本設定,下面來示範一下 Webpack 設定檔的基本寫法吧!

const webpack = require('webpack')
const path = require('path')    // Node.js 提供快速獲得當前專案路徑的函式庫

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',  
  },
  module: {
    rules: [
      // loaders
    ],
  },
  plugins: [
    // plugins
  ],
}

Webpack loader介紹

前面提到 Webpack 是個強大的前端打包套件,其實 Webpack 會強大也是因為內部有非常多的 loader 可以使用,而 loader 有個非常重要的觀念: Webpack 的 loader 是由後往前讀取 ,所以底下有些 loader 其實有擺放順序喔!這邊筆者稍微介紹幾個自己常用的 loader

  • babel

    還記得之前文章提到的 JS 版本差異嗎?雖然 ES6 在 2015 年的時候發表,可是到現在 2018 年了仍然不是所有的瀏覽器都支援 ES6 的語法,瀏覽器支援程度可以參考這個網站,但我們一定會用 ES6 的語法來加速開發,這時候就必須要仰賴 babel 的幫忙了。

    由於我們要寫的是 loader ,所以要參考的是 babel-loader 這個 loader 來使用 babel 強大的轉譯能力,基本上 Webpack 的設定檔以及要安裝的項目都照著連結裡面的 Readme 操作即可,不用刻意去了解沒關係。

  • style-loader

    將讀取 CSS 檔後的樣式擺在 HTML style tag 內,設定檔的寫法就參考連結內的 Readme 即可。

  • css-loader

    將利用 ES6 moduleimport 的 CSS 進行讀取,設定檔的寫法就參考連結內的 Readme 即可。

    結合上面兩個 loader 應該可以猜到 style-loader 要擺在 css-loader 前面,因為先讀取檔案再讀取內部的樣式,所以這邊讀者在寫設定檔的時候記得不要擺錯了喔!

  • sass-loader

    還記得最一開始文章介紹的 SCSS 嗎?其實瀏覽器本身是不支援 SCSS 的寫法,因此我們必須要利用 Webpack 內的 sass-loader 進行 SCSS to CSS 的轉譯過程,就好比 ES6 經由 babel 轉成 ES5 的寫法一樣,設定檔的寫法就參考連結內的 Readme 即可。

    讓我們來結合一下上面三個跟 CSS 有關的 loader ,這邊應該也不難猜到 sass-loader 要擺在 css-loader 之後,因為先進行 SCSS to CSS 的轉譯,再讀取轉譯後的檔案最後才是讀取檔案內部的樣式,所以這邊讀者在寫設定檔的時候記得不要擺錯了喔!

  • file-loader

    前面只有講到如何將 CSS 以及 JS 檔進行 import 後的處理,可是上面的 Webpack 圖片還有講到也可以處理圖片檔,這時候就必須要仰賴 file-loader 的幫忙了。

    file-loader 最主要的作用就是將 import 後的圖片進行讀取,設定檔的寫法就參考連結內的 Readme 即可。

  • url-loader

    講完 file-loader 再來就是要講 url-loader 了, url-loader 的功能也是進行圖片的讀取,但其實 url-loaderfile-loader 兩者是差蠻大的。

    url loader 做的事情就是幫圖片檔進行 Base64 的轉換進而加速網頁讀取速度, Base64 簡單來說就是一串非常難讀的字串,而字串的讀取速度永遠比檔案來得快,這也是 url-loader 最主要在做的事情,設定檔的寫法就參考連結內的 Readme 即可。

    溫馨小提醒:大家在看 url-loader 的 Readme 應該有看到 limit 這個設定,其實這個就是限制圖片檔的大小(單位為KB),如果圖片檔超過 limit 所設定的大小就會用 file-loader 的方式讀取圖片,反之則會用 url-loader 的方式將圖片轉成 Base64 編碼進行讀取,所以總結來說就是直接寫 url-loader 就好不用再額外寫 file-loader 啦!

Webpack進階應用

  • chunk

    chunk 是指可以將共同用到的套件拉出來獨立成打包成一個 JS 檔,通常都會命名成 vendor.js ,這麼做有甚麼好處呢?最直觀的就是在重新打包檔案的過程中不會重複在打包一樣的東西而造成打包時間過長,而且最重要的是可以讓 bundle.js 整體進行一個大瘦身,在 Webpack4 的設定非常簡單,只要設定好 optimization 即可,寫法如下。

    optimization: {
      splitChunks: {
        // 設定成 all 時,Webpack就會在打包的過程中在 node_modules 內查看哪些套件最常被使用到
        // 並自動在 dist 資料夾匯出成一個切割套件後的 JS 檔
        // 筆者習慣多設定一個 name 讓輸出的檔名變成 vendor.js
        chunks: 'all',
        name: 'vendor'
      }
    }
    

    下面這張圖可以比對一下第一次打包以及第二次打包過程中的差異,可以發現第二次打包就不會有 vendor.js 了,這會讓開發效能提高。

    Imgur

  • webpack-merge

    看到 merge 這個字大概就知道是要進行融合了, webpack-merge 就是負責進行 Webpack 檔案與檔案之間的結合,會有這個東西通常是因為我們在寫 Webpack 的設定檔時,會寫成一個共用的 webpack.config.js 設定檔以及一個開發用的 webpack.dev.js 這樣就不用一直改 webpack.config.js 內的設定值了,所以在 webpack.dev.js 內的寫法會長這樣。

    const merge = require('webpack-merge')
    const common = require('./webpack.config.js')
    
    module.exports = merge(common, {
      // some development setting
    })
    

webpack-dev-server

講了這麼多 Webpack 設定終於要來講跟開發有關的了,這邊要介紹的是 webpack-dev-server 這個前端專用的開發用環境。

webpack-dev-server 的設定也很簡單,只要在剛剛提到的 webpack.dev.js 內新增 devServer 的設定值就可以了,這邊筆者就不細談 devServer 內部的設定值了,這邊要談一個蠻重要的設定 devtool

devtool 裡面提供了非常多的設定值可以用,由於 Webpack 不管在開發或正式環境都會打包成一個很大的檔案,這會讓除錯變得比較麻煩很難找到發生錯誤的檔案是哪個,這時候就可以在 webpack.dev.js 裡面設定 "devtool": "source-map" ,這樣在開發的過程中就會在瀏覽器的 dev tool 中顯示各個檔案內部的程式碼以方便工程師除錯,所以在寫 webpack.dev.js 的時候記得一定要加上這行喔!

webpack-dev-server 的好處就在直接改變程式碼時,網頁也會跟著變動不用再重新整理非常方便。

Imgur

最後就簡單地寫一下 webpack.dev.js 內部的設定值以供讀者們參考。

const webpack = require('webpack')
const merge = require('webpack-merge')
const path = require('path')

module.exports = merge(common, {
  devtool: 'source-map',
  devServer: {
    port: 8080,
    contentBase: path.join(__dirname, 'src'), // 告訴devServer內容來源位置
    publicPath: '/dist/',
    historyApiFallback: true,
  },
})

npm scripts

最後要來講一下 npm scripts ,其實 npm scripts 就是指寫在 package.json 內的 scripts ,之後只要在終端機下 npm run scriptName 就會跑該指令,舉例來說我今天要透過 scriptswebpack-dev-server 的指令,這時候 package.jsonscripts 會長這樣。

"scripts": {
  "dev": "webpack-dev-server -w --config webpack.dev.js --mode development"
}

之後只要在終端機下 npm run dev 就會執行 webpack-dev-server -w --config webpack.dev.js --mode development 了,這時候你可能會有一個疑問,我直接在終端機下那行指令就好了啦,為啥還要額外寫個 scripts 呢?

其實原因只有一個,因為我們沒有把 webpack-dev-server 裝在全域只有裝在專案內,所以直接打指令會出現錯誤,假如今天是裝在全域的話就可以直接下指令了喔!

總結

今天介紹的東西非常多,也不太好消化,所以有任何問題都歡迎在下面留言給我,我會一一解惑,其實 Webpack 能講的東西真的太多了,能玩的東西也太多了,筆者在這邊只講了最基礎以及一點點的進階應用而已,連 Webpack 打包效能都還來不及說篇幅就這麼大了,所以前端工程師真的不好當,要學的東西真的太多了XD

最後為了讓各位可以更快的上手 Webpack ,筆者也會把上面所教寫成一個簡單的範例專案丟在 Github 上,專案連結點這裡


上一篇
Day28-JS模組化!(匯入匯出篇)
下一篇
Day30-結語
系列文
從0開始的網頁生活!30天從網頁新手到網頁入門30

尚未有邦友留言

立即登入留言