本文基于:峰华前端工程师--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>
      Hello World
      <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 打包出来的网页没有显示 Hello World 的原因是 这个标签是我们自己写的😮

  • 定义打包好 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 文件都被紧密地写在了一起,右键属性查看文件大小也要比之前的小一些。😎

使用 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 插件了,而是在浏览器地址栏输入命令行中输出的地址:

    成功!👏

    那么 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

    可以看到生成了文件后缀名不同的文件,这样可以避免由于浏览器缓存机制而导致更新不及时的问题。

配置目录别名:

和之前写的 https://www.cnblogs.com/hhsk/p/16460701.html 大同小异

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