30分鐘掌握 Webpack

語言: CN / TW / HK

本文基於: 峰華前端工程師--30分鐘掌握Webpack

為什麼使用 Webpack

在我們進行傳統網頁開發中,會在 index.html 中引入大量的 jscss 檔案,不僅可能會導致命名衝突,還會使頁面體積變大,因為如果引用了第三方庫,需要載入所有的程式碼。而在 node.js 出現後, javascript 專案支援通過 require 支援模組化開發了,並且支出 npm 方便地管理依賴。

藉著 node.js 和瀏覽器js 的一致性,前端專案開始在 node.js 下開發,完成之後把程式碼構建成瀏覽器支援的形式。對於 reactvue 這種元件化的開發方式,因為有很多分散的檔案,那麼久特別需要這樣的構建操作。

Webpack 就是進行這一步構建操作的,把 node.js 模組化的程式碼轉化為瀏覽器可執行的程式碼。它提供了 importexport ES6模組化語法的支援,然後通過分析程式碼中 import 匯入的依賴,把需要的程式碼載入進來。在 Webpack 中,任何檔案都可以通過 import 匯入,只要有對應的 loader 就可以了。在打包過程中,還可以通過外掛來干預打包過程,例如剔除不必要的程式碼,形成體積更小的專案。

建立專案

  • 開啟命令列工具,我們在這裡建立一個名為 blog 的部落格專案:

    mkdir blog
    cd blog
    yarn init -y
      (或者npm init)
  • 新增 webpack 依賴:

    yarn add webpack webpack-cli --dev
  • 使用 VSCode 開啟專案:

    code .

初出茅廬

  • blog 專案下新建 src 檔案下,建立 index.js

    console.log("Hello World");

    我們先只做一個簡單的輸出.

  • blog 專案下新建 index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <h1>Hello World</h1>
      <script src="./dist/index.js"></script>
    </body>
    </html>
  • 使用 LiveServer 檢視剛剛所編寫的 html頁面:

    可以看到瀏覽器成功顯示了 "Hello World",並且控制檯也能夠成功輸出.

  • 嘗試使用 webpack 打包

    開啟控制檯,輸入 npx webpack

npx 能夠直接執行 node_modules 下面安裝的庫的自帶的命令列,而不用寫 node_modules 下一串的絕對路徑。

打包完成後可以看到專案裡面生成了一個 dist 目錄,裡面有一個 main.js 檔案,其內部的內容和我們剛剛所編寫的 index.js 的檔案一模一樣,這是因為我們並沒有使用任何 import 來匯入其他的依賴。

使用 import 匯入依賴

  • src 下新建一個 data.js 檔案:

    export function getBlogPosts() {
      return ["部落格1", "部落格2", "部落格3"];
    }
  • index.js 中匯入 data.js 中的函式並呼叫:

    import { getBlogPosts } from "./data";
    
    console.log(getBlogPosts());
  • 重新使用 webpack 打包以下,檢視結果:

    npx webpack

    現在進入 /dist/main.js 中看一下,程式碼被簡化成了直接輸出剛才我們所編寫的陣列的語句,說明 webpack 自動判斷了我們所編寫程式碼的邏輯,並通過 import 語句得到了我們所引入的程式碼,分析後生成新的 js 檔案.

  • 在網頁中檢視下結果:

    成功地輸出了陣列.

Webpack 配置檔案

webpack 的配置檔案可以說是 webpack 最核心的部分了,我們可以在配置檔案中修改入口和出口檔案、通過 loader 載入不同型別的檔案和使用 plugins 對程式碼做其他的操作。

嘗試修改打包後生成檔案的名稱

  • 在專案中新建 webpack.config.js 檔案:

    const path = require("path");
    
    module.export = {
      mode: "development",
      // 設定為開發模式,方便除錯
      entry: "./src/index.js",
      output: {
        filename: "dist.js",
        path: path.resolve(__dirname, "dist"),
        // 設定檔名和目錄
      }
    }
  • 執行 npx webpack 命令打包

    可以看到 dist 資料夾下多了個 dist.js 檔案.

使用模組化語法

  • 修改 /index.htmlscript 標籤:

    <script src="./dist/dist.js"></script>

    main.js 修改成為我們剛剛使用 webpack 打包生成的 dist.js

  • 修改 /src/index.js 檔案,將之前編寫的陣列變成 ul 元素插入頁面中:

    const blogs = getBlogPosts();
    
    const ul = document.createElement("ul");
    blogs.forEach(blog => {
      const li = document.createElement("li");
      li.innerText = blog;
      ul.appendChild(li);
    })
    
    document.body.appendChild(ul);
  • npx webpack

    列表成功插入到了頁面.

引入 css

  • src 下新建 style.css 檔案:

    h1 {
      color: blueviolet;
    }

    這裡只是簡單地改變 h1 標籤的顏色.

  • /src/index.js 中引入 css 檔案:

    import "./style.css";
  • 安裝 loader

    開啟控制檯,輸入:

    yarn add --dev style-loader css-loader
  • webpack.config.js 中配置 loader

    module.exports = {
      mode: "development",
      output: {...},
      ...
    
      module: {
        rules: [{
          test: /\.css$/i,
          use: ["style-loader", "css-loader"],
        }]
      }
    }

    module -> rules -> test 內是一個正則表示式,表示以 .css 結尾的檔案.

    use 屬性下是所使用的 loader

  • 再次使用 npx webpack 打包:

    可以看到,剛才寫的樣式生效了.

載入圖片

  • webpack 原生支援圖片等靜態檔案,但是需要在 webpack.config.js 中編寫配置:

    module.exports = {
      ...
      module: {
        rules: [
        ...
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i,
          type: "assets/resource",
        }]
      }
    }
  • src 下載新建 assets/ 資料夾,放入一張圖片:

  • index.js 中引入圖片:

    import AbcImage from "./assets/abc.png"
    
    ...
    
    const image = document.createElement("img");
    image.src = AbcImage;
    
    document.body.prepend(image);
  • npx webpack

自動生成 HTML

上述的例子中,我們只是使用 webpack 來打包 jscss 檔案,並手動的匯入編寫好的 html 中,使用下面的外掛,就可以自動為我們生成 HTML 檔案。

  • 安裝外掛:

    yarn add html-webpack-plugin --dev
  • 使用外掛:

    const HtmlWebpackPlugin = require("html-webpack-plugin");
    ...
    
    plugins: [new HtmlWebpackPlugin({
      title: "部落格列表",
      // 這裡可以自定義網頁的標題
    })],
  • npx webpack

    可以看到生成了和剛才相同的頁面

使用 webpack 打包出來的網頁沒有顯示 <h1>Hello World</h1> 的原因是 這個標籤是我們自己寫的:open_mouth:

  • 定義打包好 html 檔案的網頁標題

    我們使用 html-webpack-plugins 打包生成的檔名預設是 Webpack App ,其實網頁標題是可以在外掛內傳遞引數來更改的:

    plugins: [new HtmlWebpackPlugin({
      title: "部落格列表",
    })],

Babel

有時我們的程式碼可能需要執行在更低版本的瀏覽器上,這些瀏覽器可能並不支援我們所寫的更高階的程式碼,這時就需要用到 babel 轉譯工具來使我們的程式碼變成瀏覽器能夠識別的程式碼。

  • 安裝 babel-loader 相關依賴:

    yarn add --dev babel-loader @babel/core @babel/preset-env
  • webpack.config.js 中新增配置:

    rules: [
    ...
    {
      test: /\.js$/, // 識別js為結尾的檔案
      exclude: /node_modules/, //不解釋node_modules/下的檔案
      use: {
        loader: "babel-loader",
        options: {
          presets: ["@babel/preset-env"]
        }
      }
    }]

    這裡可以在 module.exports 下配置 devtool="inline-source-map" 方便檢視打包後的原始碼

  • 使用 npx webpack 重新打包下,進入 dist/dist.js 檢視相關程式碼:

    /* 打包前的 */
    blogs.forEach(blog => {
      const li = document.createElement("li");
      li.innerText = blog;
      ul.appendChild(li);
    })
    blogs.forEach(function (blog) {
      var li = document.createElement("li");
      li.innerText = blog;
      ul.appendChild(li);
    });

    可以看到 箭頭函式已經被轉換成了 .forEach 的形式,增強了對瀏覽器的相容性。

使用 Terser 外掛壓縮打包後的程式碼

  • 安裝外掛:

    yarn add --dev terser-webpack-plugin
  • 配置:

    // webpack.config.js
    const TerserPlugin = require("terser-webpack-plugin");
    
    module.exports = {
      ...
      optimization: {
        minimize: true,
        minimizer: [new TerserPlugin()],
      },
    }
  • 打包: npx webpack

    可以看到我們打包生成的 js 檔案都被緊密地寫在了一起,右鍵屬性檢視檔案大小也要比之前的小一些。:sunglasses:

使用 DevServer 外掛自動打包

  • 安裝:

    yarn add -dev webpack-dev-server
  • 配置:

    module.exports = {
    ...
      devServer: {
        static: "./dist",
      },
    }

    為了我們以後方便執行開發伺服器,還需要在 package.json 裡新增一個 script

    {
      ...
      "scripts": {
        "start": "webpack serve --open"
      },
    }
  • 啟動伺服器:

    yarn start
    // 或
    npm run start
  • 嘗試一下 "熱更新?"

    index.js 下新增如下程式碼:

    const header1 = document.createElement("h1");
    header1.innerText = "Hello";
    document.body.appendChild(header1);
  • 儲存,檢視網頁

    這裡不在使用 VsCodeLive Server 外掛了,而是在瀏覽器位址列輸入命令列中輸出的地址:

    成功!:clap:

    那麼 webpack 是如何實現熱更新的呢?其實是在我們每次儲存時,外掛自動生成新的 dist.js 並把之前的 dist.js 寫入快取,這或多或少會增加我們電腦的開銷。但 webpack 貼心的為我們想到了這一點。

  • 每次打包生成新的 /dist.js

    配置 webpack.config.js

    output: {
      filename: "[name].[contenthash].js",
      // name 預設為 main
      path: path.resolve(__dirname, "dist"),
    },

    更改下 index.js 程式碼,執行 npx webpack

    可以看到生成了檔案字尾名不同的檔案,這樣可以避免由於瀏覽器快取機制而導致更新不及時的問題。

配置目錄別名:

和之前寫的 http://www.cnblogs.com/hhsk/p/16460701.html 大同小異

  • 配置:
    // webpack.config.js
    module.exports = {
    ...
      resolve: {
        alias: {
          assets: path.resolve(__dirname, "src/assets")
        }
      }
    }