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")
        }
      }
    }