首页 > 其他分享 >Webpack 5

Webpack 5

时间:2025-01-01 20:31:18浏览次数:1  
标签:... exports webpack module js Webpack loader

0x01 概述

(1)简介

  • 官网链接:https://webpack.js.org
  • Webpack 是一个打包工具,作为前端工程化的重要工具被广泛应用
  • Webpack 相比过去使用 JavaScript 脚本的方式,能够解决作用域、代码拆分、浏览器模块支持等问题
    • 对于作用域问题,Webpack 采用 IIFE(立即调用函数表达式)的方式解决
    • 对于代码拆分问题,Webpack 通过运行在 NodeJS 环境中,使用 CommonJS 规范下的 require 解决
  • 类似的工具包括:PARCEL、rollup.js、Vite

(2)环境搭建

  1. 新建一个空目录作为项目目录,其中使用命令 npm init -y 快速创建 NodeJS 环境

    版本信息:Node v23.2.0,npm v10.9.0

  2. 使用命令 npm install --save-dev webpack webpack-cli 安装 Webpack 及其脚手架工具

  3. 创建以下文件:

    1. src\say.js

      function say() {
        console.log("hello world");
      }
      
      export default say;
      
    2. src\main.js

      import say from "./say";
      
      say();
      
    3. index.html

      <!DOCTYPE html>
      <html>
      
      <head></head>
      
      <body></body>
      
      </html>
      
  4. 在项目根目录创建 Webpack 配置文件 webpack.config.js

    const path = require("path");
    
    module.exports = {
      entry: "./src/main.js",
      output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "dist"),
      },
      mode: "none",
    };
    
  5. 使用命令 npx webpack 打包,或使用命令 npx webpack --stats detailed 打包并查看打包过程

  6. 修改 index.html

    <!DOCTYPE html>
    <html>
    
    <head></head>
    
    <body>
      <script src="dist/bundle.js"></script>
    </body>
    
    </html>
    

0x02 引入插件

(1)自动引入资源

a. html-webpack-plugin

用于自动生成一个 HTML 文件,其中导入了打包好的 JavaScript 脚本文件

  1. 使用命令 npm install --save-dev html-webpack-plugin 安装插件

  2. 修改 webpack.config.js,实例化插件

    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const path = require("path");
    
    module.exports = {
      // ...
      plugins: [
        new HtmlWebpackPlugin()
      ]
    };
    
  3. 使用命令 npx webpack 打包,此时 dist 目录中会生成 bundle.js 和 index.html

  4. 修改 webpack.config.js,添加一些配置

    module.exports = {
      // ...
      plugins: [
        new HtmlWebpackPlugin({
          template: "./index.html", // 采用根目录下的 index.html 作为模板
          filename: "app.html", // 生成的 HTML 文件命名为 app.html
          inject: "body", // 将打包后的 JavaScript 插入 body 中
        }),
      ],
    };
    

b. 清理 dist

修改 webpack.config.js,添加配置项 output.clean

module.exports = {
  // ...
  output: {
  	// ...
    clean: true,
  },
  // ...
};

(2)开发环境搭建

  1. 修改 webpack.config.js,修改模式 mode 为开发模式

    module.exports = {
      // ...
      mode: "development",
      // ...
    };
    
  2. 修改 webpack.config.js,引入一种 source-map

    module.exports = {
      // ...
      devtool: "inline-source-map",
    };
    

    并修改 src/say.js,添加一处报错

    function say() {
      console.log("hello world");
      throw new Error("error");
    }
    
    export default say;
    
  3. 使用命令 npx webpack --watch 打包,其中 --watch 选项能够监听源码变更并实现热更新

  4. 使用命令 npm install --save-dev webpack-dev-server 安装用于实时刷新浏览器的插件

  5. 修改 webpack.config.js,实例化插件

    module.exports = {
      // ...
      devServer: {
        static: "./dist",
      },
    };
    
  6. 使用命令 npx webpack-dev-server --open 打包并开启开发服务器,默认将项目运行在 8080 端口,其中 --open 选项能够自动开启浏览器并进入 http://localhost:8080/

最终的 webpack.config.js

const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");

module.exports = {
entry: "./src/main.js",
output: {
 filename: "bundle.js",
 path: path.resolve(__dirname, "dist"),
 clean: true,
},
plugins: [
 new HtmlWebpackPlugin({
   template: "./index.html",
   filename: "app.html",
   inject: "body",
 }),
],
mode: "development",
devtool: "inline-source-map",
devServer: {
 static: "./dist",
},
};

(3)资源模块

  • Webpack 提供了内置的资源模块(asset module)来引入任何其他类型的资源

  • 资源模块类型包括:

    1. asset/resource:发送一个单独的文件并导出 URL
    2. asset/inline:导出一个资源的 Data URL
    3. asset/source:导出资源的源代码
    4. asset:在发送一个单独的文件导出一个资源的 Data URL之间自动选择
  • 修改 webpack.config.js,配置资源文件

    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.png$/,
            type: "asset/resource",
            generator: {
              filename: "assets/[hash][ext]", // 输出格式为`哈希+后缀`的文件名
            },
          },
          {
            test: /\.svg$/,
            type: "asset/inline",
            generator: {
              filename: "assets/[hash][ext]",
            },
          },
          {
            test: /\.txt$/,
            type: "asset/source",
            generator: {
              filename: "assets/[hash][ext]",
            },
          },
          {
            test: /\.jpg$/,
            type: "asset",
            generator: {
              filename: "assets/[hash][ext]",
            },
            parser: {
              dataUrlCondition: {
                maxSize: 4 * 1024, // 当图片小于 4kb 的时候,使用 base64 编码
              },
            },
          },
        ],
      },
    };
    

(4)资源载入

  • Webpack 支持通过加载器(loader)引入其他资源

a. CSS

I. 载入

  1. 使用命令 npm install --save-dev style-loader css-loader 安装相关加载器

    • style-loader 将 CSS 代码插入到 JavaScript 模块中
    • css-loader 将 CSS 文件转换为 CommonJS 模块
  2. 修改 webpack.config.js,配置资源文件

    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.css$/i,
            use: ["style-loader", "css-loader"],  // 顺序不可颠倒
          },
        ],
      },
    };
    
    • 先使用 style-loader,再使用 css-loader,最后使用 CSS 预编译工具,如 Less、Sass 等

    • 以 Less 文件为例:

      module.exports = {
        // ...
        module: {
          rules: [
            {
              test: /\.(css|less)$/i,
              use: ["style-loader", "css-loader", "less-loader"],
            },
          ],
        },
      };
      
  3. 在 src 目录下创建样式文件 style.css

    body {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      background-color: skyblue;
      color: red;
    }
    
  4. 修改 main.js,导入 CSS 文件

    import "./style.css";
    
    // ...
    

II. 抽离

  1. 使用命令 npm install --save-dev mini-css-extract-plugin 安装用于将 CSS 抽离成单独文件的插件

    该插件需要在 Webpack5 中使用

  2. 修改 webpack.config.js,实例化插件

    // ...
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = {
      // ...
      plugins: [
      	// ...
        new MiniCssExtractPlugin(),
      ],
      // ...
      module: {
        rules: [
          {
            test: /\.css$/i,
            use: [MiniCssExtractPlugin.loader, "css-loader"],
          },
        ],
      },
    };
    

III. 压缩

  1. 使用命令 npm install --save-dev css-minimizer-webpack-plugin 安装用于将 CSS 抽离成单独文件的插件

  2. 修改 webpack.config.js,实例化插件

    // ...
    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
    
    module.exports = {
      // ...
      mode: "production",
      // ...
      optimization: {
        minimizer: [new CssMinimizerPlugin()],
      },
    };
    

b. 数据

  • 数据文件格式一般为 JSON、CSV、TSV、XML 等,其中:
    • JSON 默认内置支持
    • CSV、TSV 需要 csv-loader
    • XML 需要 xml-loader

I. 类 JSON

类 JSON 的数据格式包括 json5、toml、yaml 等

  1. 使用命令 npm install --save-dev json5 toml yaml 安装相关包

  2. 修改 webpack.config.js,配置资源文件

    // ...
    const json5 = require("json5");
    const toml = require("toml");
    const yaml = require("yaml");
    
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.json5$/i,
            type: "json",
            parser: {
              parse: json5.parse,
            },
          },
          {
            test: /\.toml$/i,
            type: "json",
            parser: {
              parse: toml.parse,
            },
          },
          {
            test: /\.yaml$/i,
            type: "json",
            parser: {
              parse: yaml.parse,
            },
          },
        ],
      },
    };
    
  3. 修改 main.js,导入数据文件

    import json5 from "data.json5";
    import toml from "data.toml";
    import yaml from "data.yaml";
    
    console.log(json5, toml, yaml);
    
    // ...
    

II. CSV、TSV、XML

  1. 使用命令 npm install --save-dev csv-loader xml-loader 安装相关的加载器

  2. 修改 webpack.config.js,配置资源文件

    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.(csv|tsv)$/i,
            use: "csv-loader",
          },
          {
            test: /\.xml$/i,
            use: "xml-loader",
          },
        ],
      },
    };
    
  3. 修改 main.js,导入数据文件

    import ID from "data.csv";
    import Name from "data.xml";
    
    console.log(ID, Name);
    
    // ...
    

(5)Babel

Babel 用于将 ES6 语法转换成低版本浏览器也可以支持的语法

  1. 使用命令 npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/runtime @babel/plugin-transform-runtime 安装相关包

    • babel-loader 用于在 Webpack 中加载 Babel
    • @babel/core 是 Babel 核心模块
    • @babel/preset-env 是 Babel 的预设
    • @babel/runtime 包含了必要的 regeneratorRuntime
    • @babel/plugin-transform-runtime 可以在需要 regeneratorRuntime 的地方自动导入包
  2. 修改 webpack.config.js,配置资源文件

    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.js$/i,
            exclude: /node_modules/,
            use: {
              loader: "babel-loader",
              options: {
                presets: ["@babel/preset-env"],
                plugins: [["@babel/plugin-transform-runtime"]],
              },
            },
          },
        ],
      },
    };
    
  3. 修改 main.js,使用一些 ES6 语法

    // ...
    
    (async () => {
      await new Promise((resolve) => {
        setTimeout(resolve, 2000);
        console.log("waiting...");
      });
    })();
    

(6)JavaScript 压缩

  1. 使用命令 npm install --save-dev terser-webpack-plugin 安装相关插件

  2. 修改 webpack.config.js,实例化插件

    // ...
    const TerserPlugin = require("terser-webpack-plugin");
    
    module.exports = {
      // ...
      mode: "production",
      // ...
      optimization: {
        minimizer: [new TerserPlugin()],
      },
    };
    

(7)PostCSS 与 CSS 模块

PostCSS 是用 JavaScript 转换 CSS 的工具,官网链接:https://postcss.org/

  1. 使用命令 npm install --save-dev style-loader css-loader postcss-loader autoprefixer 安装相关加载器与插件

  2. 修改 Webpack 配置文件

    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.css$/i,
            // include: path.resolve(__dirname, "/src"),
            exclude: /node_modules/,
            use: [
              {
                loader: "style-loader",
              },
              {
                loader: "css-loader",
                options: {
                  importLoaders: 1, // 在处理 CSS 文件时,会额外使用一个加载器来处理 @import 语句
                  modules: true, // 启用 CSS 模块化,使得每个 CSS 类名都具有局部作用域
                  localIdentName: "[local]-[hash:base64:6]", // 定义生成的局部类名的格式 [原始类名]-[6 位 base64 编码哈希值]
                },
              },
              {
                loader: "postcss-loader",
              },
            ],
          },
        ],
      },
    };
    
  3. 创建 postcss.config.js 作为 PostCSS 配置文件

    module.exports = {
      plugins: [require("autoprefixer")],
    };
    

(8)TypeScript

  1. 使用命令 npm install --save-dev typescript ts-loader 安装相关插件和加载器

  2. 使用命令 npx tsc --init 生成 TypeScript 配置文件 tsconfig.json,在其中开启(取消注释)需要的配置

  3. 修改 Webpack 配置文件

    module: {
      rules: [
        {
          test: /\.ts$/i,
          exclude: /node_modules/,
          use: "ts-loader",
        },
      ],
    },
    resolve: {
      extensions: [".ts", ".js"],
    },
    

0x03 输出管理

(1)代码分离

a. 入口起点

通过不同的入口起点实现代码分离

  1. 修改 webpack.config.js

    module.exports = {
      entry: { // 多个入口
        main: "./src/main.js",
        say: "./src/say.js",
      },
      output: {
        filename: "[name].bundle.js", // [name] 是 webpack 提供的占位符,用来表示当前 chunk 的名称
        path: path.resolve(__dirname, "dist"),
        clean: true,
      },
      // ...
    };
    

    此时,不同的入口使用重复的代码或依赖会被重复打包

  2. 继续修改 webpack.config.js,通过添加一些配置防止重复打包

    1. 入口依赖

      module.exports = {
        entry: {
          main: {
            import: "./src/main.js",
            dependOn: "shared",
          },
          say: { import: "./src/say.js", dependOn: "shared" },
          shared: "./src/shared.js", // 共享模块
        },
        // ...
      };
      
    2. 内置插件

      module.exports = {
        entry: {
          main: "./src/main.js",
          say: "./src/say.js",
        },
        output: {
          filename: "[name].bundle.js",
          path: path.resolve(__dirname, "dist"),
          clean: true,
        },
        optimization: { // 使用内置插件
          splitChunks: {
            chunks: "all",
          },
        },
        // ...
      };
      

b. 动态导入

通过 import 语法或 require.ensure 语法实现动态代码拆分

  1. 修改 say.js

    function say(text) {
      return text;
    }
    
    export default say;
    
  2. 在 src 目录下创建 async-module.js 文件

    function getComponent() {
      return import("./say.js").then(({ default: say }) => {
        const element = document.createElement("div");
    
        element.innerHTML = say("hello world");
    
        return element;
      });
    }
    
    getComponent().then((element) => {
      document.body.appendChild(element);
    });
    
  3. 修改 main.js

    import "./async-module.js";
    
  4. 修改 webpack.config.js

    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const path = require("path");
    
    module.exports = {
      entry: "./src/main.js",
      output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "dist"),
        clean: true,
      },
     
      mode: "development",
      plugins: [
        new HtmlWebpackPlugin({
          template: "./index.html",
          inject: "body",
        }),
      ],
      devtool: "inline-source-map",
      devServer: {
        static: "./dist",
      },
      optimization: {
        splitChunks: { // 当需要同时实现动态导入和静态导入时开启
          chunks: "all",
        },
      },
    };
    

c. 按需加载(懒加载)

修改 main.js

const buttonElement = document.createElement("button");
buttonElement.innerText = "Click me";
buttonElement.addEventListener("click", () => {
  import(/* webpackChunkName: 'say' */ "./say.js").then(({ default: say }) => {
    const element = document.createElement("div");
    element.innerHTML = say("hello world");
    document.body.appendChild(element);
  });
});
document.body.appendChild(buttonElement);

d. 预获取 / 预加载

修改 main.js,通过魔法注释 webpackPrefetch: true 开启预获取,实现预加载

const buttonElement = document.createElement("button");
buttonElement.innerText = "Click me";
buttonElement.addEventListener("click", () => {
  import(/* webpackChunkName: 'say', webpackPrefetch: true */ "./say.js").then(({ default: say }) => {
    const element = document.createElement("div");
    element.innerHTML = say("hello world");
    document.body.appendChild(element);
  });
});
document.body.appendChild(buttonElement);

(2)浏览器缓存

浏览器通过缓存技术,根据资源名称,当网站的请求命中缓存时使用缓存,从而减少网络流量,提高加载速度;当新版本的资源文件名没有改变,浏览器则会继续使用缓存中的旧版本,造成问题

  1. 修改 webpack.config.js,调整输出文件的文件名

    module.exports = {
      // ...
      output: {
        filename: "[name].[contenthash].js",
        // ...
      },
      // ...
    };
    
  2. 继续修改 webpack.config.js,缓存第三方库

    module.exports = {
      // ...
      optimization: {
        splitChunks: {
          cacheGroups: {
            vendor: {
              test: /[\\/]node_modules[\\/]/,
              name: "vendors",
              chunks: "all",
            },
          },
        },
      },
    };
    

0x04 环境分合

  • 项目的环境主要包括开发环境(development)与生产环境(production)
  • 可以将开发环境与生产环境的配置间存在的相同部分抽离,并基于 Webpack 命令的 --env 选项传值,实现分环境打包
  1. 在项目根目录下新建 config 目录,其中创建以下三个配置文件,实现配置拆分

    1. webpack.config.dev.js

      module.exports = {
        output: {
          filename: "[name].js",
        },
        mode: "development",
        devtool: "inline-source-map",
        devServer: {
          static: "./dist",
        },
      };
      
    2. webpack.config.prod.js

      const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
      const MiniCssExtractPlugin = require("mini-css-extract-plugin");
      const TerserPlugin = require("terser-webpack-plugin");
      
      module.exports = {
        output: {
          filename: "[name].[contenthash].js",
          publicPath: "",
        },
        mode: "production",
        plugins: [
          new MiniCssExtractPlugin({
            filename: "[name].[contenthash].css",
          }),
        ],
        optimization: {
          minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
        },
        performance: {
          hints: false,
        },
      };
      
    3. webpack.config.common.js

      const HtmlWebpackPlugin = require("html-webpack-plugin");
      const path = require("path");
      
      module.exports = {
        entry: "./src/main.js",
        output: {
          path: path.resolve(__dirname, "../dist"),
          clean: true,
        },
        plugins: [
          new HtmlWebpackPlugin({
            template: "./index.html",
            inject: "body",
          }),
        ],
        optimization: {
          splitChunks: {
            cacheGroups: {
              vendor: {
                test: /[\\/]node_modules[\\/]/,
                name: "vendors",
                chunks: "all",
              },
            },
          },
        },
      };
      
  2. 使用命令 npm install --save-dev webpack-merge 安装合并配置的依赖

  3. 修改 webpack.config.js,合并配置文件

    const { merge } = require("webpack-merge");
    
    const common = require("./config/webpack.config.common");
    const production = require("./config/webpack.config.prod");
    const development = require("./config/webpack.config.dev");
    
    module.exports = (env) => {
      if (env.production) {
        return merge(common, production);
      } else {
        return merge(common, development);
      }
    };
    
  4. 修改 package.json,通过 npm 脚本简化打包命令

    {
      // ...
      "scripts": {
        "dev": "webpack serve --env development",
        "build": "webpack --env production"
      },
      // ...
    }
    

    其中,命令选项 --env 用于配置环境变量

0x05 开发效率

(1)source-map

  • 在 Webpack 的配置文件中,属性 devtool 用于配置 source-map

  • source-map 用于在调试过程中,把报错的代码映射到源码,因此不建议将 source-map 用于生产环境

  • Webpack 内置了七种 source-map:

    名称 功能
    eval 每个模块会封装到 eval() 中执行,并在结尾追加 //# sourceURL=webpack://webpack/[文件相对路径]
    eval-source-map 与 eval 类似,但对应的 DataUrl 形式的 source-map 生成在结尾的注释中
    source-map 每个模块会生成对应的 .map 文件
    hidden-source-map 与 source-map 类似,但不会在结尾追加注释
    cheap-source-map 生成没有列信息的 .map 文件,不包含加载器的 source-map
    cheap-module-source-map 与 cheap-source-map 类似,但加载器的 source-map 被简化为只包含对应行
    inline-source-map 每个模块会在各自打包文件结尾,生成对应的 DataUrl 形式的 source-map

(2)devServer

  • devServer 用于启动一个 Web 服务来模拟访问,其中包含多个配置项,如 Web 服务根目录、端口等

    devServer: {
      static: {
        directory: path.join(__dirname, "dist"),
      },
      port: 8081,
    },
    
  • devServer 还提供了代理功能

    devServer: {
      proxy: {
        "/api": {
          target: "http://localhost:3000",
          pathRewrite: {
            '^/api': "", // 将请求接口时开头的 /api 字符串删除
          },
        },
      },
    },
    

    上述代理实现了把对 /api/auth 的请求代理到 http://localhost:3000/auth

  • devServer 支持使用 HTTPS、HTTP/2 等协议

    devServer: {
      // https: true, // 将本地 HTTP 服务变成 HTTPS 服务
      https: { // 提供证书
        cacert: "./server.pem",
      },
      http2: true, // 开启 HTTP/2
    },
    
  • devServer 支持通过 historyApiFallback 配置 404 时的静态资源响应

    devServer: {
      // historyApiFallback: true, // 默认重定向到 index.html
      historyApiFallback: {
        rewrites: [
          { from: /^\$/, to: "a.html" },
          { from: /./, to: "b.html" },
        ]
      }
    },
    
  • devServer 支持同一局域网下的其他用户访问该 Web 服务

    devServer: {
      host: "0.0.0.0",
    },
    
  • devServer 支持配置模块热替换与热更新

    devServer: {
      hot: true, // 热替换
      liveReload: true, // 热更新
    },
    

0x06 模块与依赖

(1)模块

  • Webpack 模块主要包括:

    • ECMAScript 模块,如 import handler from "./module";
    • CommonJS 模块,如 const handler = require("./module");
    • AMD 模块,如 RequireJS
    • Assets 模块,如 CSS 的 url()

    同时 Webpack 支持通过加载器导入其他类型的模块

  • Webpack 模块的解析通过配置文件中的属性 resolve 来配置

    resolve: {
      alias: { // 别名
        '@': path.resolve(__dirname, './src'),
      },
      extensions: [ // 按顺序依次尝试导入同名不同扩展名的模块
        ".json",
        ".js",
        ".vue",
      ],
    },
    
  • 对于如 jQuery 等通过 CDN 引入不会被打包过程修改的第三方库,可以在配置文件中,通过外部扩展属性 externals 的方式导入

    externals: {
      jquery: '$',
    },
    

(2)依赖

  • 依赖:当 A 文件的一些功能需要借助 B 文件实现时,则称 A 依赖 B

  • Webpack 会从入口 entry 开始,根据文件间的依赖关系,递归地构建出依赖图

  • 举例:通过官方分析工具查看依赖图

    1. 使用命令 npm install --save-dev webpack-bundle-analyzer 安装相关工具

    2. 修改开发环境的配置文件

      const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
      
      module.exports = {
        // ...
        plugins: [new BundleAnalyzerPlugin()],
      };
      

(3)预置依赖

a. Shimming

Webpack 通过 Shimming 技术实现

  • Shimming 译为垫片,用于在现有代码中插入小的代码片段,以增加功能或修复问题
  1. 修改配置文件,将自定义变量与引入的模块关联起来,以 Lodash 为例

    const webpack = require("webpack");
    
    module.exports = {
      // ...
      plugins: [
        // ...
        new webpack.ProvidePlugin({
          _: "lodash",
        }),
      ],
    };
    

    此时,在其他模块使用 Lodash 时,无需使用语句 import _ from 'lodash'; 而可以直接使用 Lodash 的语法

  2. 使用命令 npm install --save-dev imports-loader 安装用于解决 CommonJS 上下文中 this 指向错误的加载器

  3. 修改配置文件,配置加载器

    module: {
      rules: [
        {
          test: require.resolve("./src/main.js"),
          use: "imports-loader?wrapper=window",
        },
      ],
    },
    
  4. 使用命令 npm install --save-dev exports-loader 安装用于把某个模块创建为一个全局变量的加载器

  5. 在项目根目录下创建 global.js

    const file = "filename";
    
    const helpers = {
      test: () => console.log("test"),
      parse: () => console.log("parse"),
    };
    
  6. 修改配置文件,配置加载器

    module: {
      rules: [
        {
          test: require.resolve("./src/global.js"),
          use: "exports-loader?type=commonjs&exports=file,multiple|helpers.parse|parse",
        },
      ],
    },
    

    其中,关于加载器的配置内容的可以理解为

    module.exports = {
      ...file,
      parse: helpers.parse,
    };
    

b. Polyfill

Webpack 通过 Polyfill 技术结合 Shimming 实现在旧浏览器中不支持的现代 Web API

  1. 安装 Babel 相关包和加载器

  2. 使用命令 npm install --save-dev core-js@3 安装 core-js

  3. 修改配置文件

    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
              loader: "babel-loader",
              options: {
                presets: [
                  [
                    "@babel/preset-env",
                    {
                      targets: [
                        "last 1 version", // 最新一个版本
                        "> 1%", // 使用率超过 1% 的浏览器
                      ],
                      useBuiltIns: "usage", // 按需加载
                      corejs: 3, // core-js 版本为 3
                    },
                  ],
                ],
                plugins: [["@babel/plugin-transform-runtime"]],
              },
            },
          },
        ],
      },
    };
    

也可以使用命令 npm install --save-dev @babel/polyfill 安装 Babel 提供的 Polyfill 插件,在相关模块中直接导入,但是会导致打包体积骤增,不建议直接使用

(4) Tree Shaking

  • Tree Shaking 是用于描述 “移除 JavaScript 上下文中未引用的代码” 的术语

    • 自 Webpack2 开始应用了 Tree Shaking
  • 举例:

    1. 在 src 目录下创建 math.js

      export const add = (a, b) => a + b;
      export const sub = (a, b) => a - b;
      
    2. 修改 main.js

      import { add } from "./math";
      
      console.log(add(1, 2));
      // 未使用 sub
      
    3. 修改配置文件

      module.exports = {
        // ...
        optimization: {
          usedExports: true,
        },
      };
      
    4. 使用命令 npx webpack 完成打包

      • 开发环境:

        // ...
        /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        
        /* harmony export */ __webpack_require__.d(__webpack_exports__, {
        /* harmony export */   add: () => (/* binding */ add)
        /* harmony export */ });
        /* unused harmony export sub */
        const add = (a, b) => a + b;
        const sub = (a, b) => a - b;
        
        
        /***/ })
        // ...
        
      • 生产环境:

        (()=>{"use strict";console.log(1+2)})();
        
  • 对于如 CSS 之类代码的导入属于虽然未在 JavaScript 中使用,但依然不可删除,这种代码被称为具有副作用;对于有副作用的代码,需要 Tree Shaking 保留

  • Webpack4 默认所有代码都有副作用,即默认不进行 Tree Shaking;Webpack5 默认进行 Tree Shaking,需要在 package.json 中配置 sideEffects 属性,该属性取值包括:

    取值 说明
    true 设置所有代码具有副作用
    false 设置所有代码具有副作用
    [ ] 数组,指定具有副作用的代码,如 ["*.css"]

0x07 应用程序

(1) 多页面应用(MPA)

  • 多页面应用由多个独立的 HTML 页面组成,每个页面都有自己独立的 URL,用户在浏览器中访问不同的 URL 时会加载不同的页面资源

  • 举例:配置同一网站的全球版和中文版

    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    module.exports = {
      entry: {
        global: {
          import: ["./src/global.js", "./src/global2.js"],
          filename: "global/main.js",
        },
        "zh-CN": {
          import: "./src/zh-CN.js",
          filename: "zh-CN/main.js",
        },
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: "global", // 设定标题,可以在模板中使用
          template: "./global/index.html", // 使用 page1 目录下的模板
          inject: "body",
          filename: "global/index.html", // 输出到 page1 目录
          chunks: ["global"], // 使用名为 main 的入口串口
          publicPath: "example.com/", // 配置全球版公共路径
        }),
        new HtmlWebpackPlugin({
          title: "中文",
          template: "./zh-CN/index.html",
          inject: "body",
          filename: "zh-CN/index.html",
          chunks: ["zh-CN"],
          publicPath: "example.com/zh-CN/", // 配置中文版公共路径
        }),
      ],
      // ...
    };
    

(2)渐进式网络应用(PWA)

  • 渐进式网络应用是一种利用现代 Web 技术提升用户体验,使其能够提供类似原生应用的交互和功能,同时保持网页的可访问性和连接性的网络应用

    • 之前的应用都是需要服务器在线才能正常访问
  • 举例:创建一个 PWA

    1. 使用命令 npm install --save-dev workbox-webpack-plugin 安装 Workbox 插件

    2. 修改配置文件,实例化插件

      const WorkboxPlugin = require("workbox-webpack-plugin");
      
      module.exports = {
        // ...
        plugins: [
          // ...
          new WorkboxPlugin.GenerateSW({
            clientsClaim: true, // 客户端获取到新的资源后,立即获取
            skipWaiting: true, // 跳过等待
          }),
        ],
      };
      
    3. 修改 main.js,注册 Service Worker

      // ...
      
      if ("serviceWorker" in navigator) {
        window.addEventListener("load", () => {
          navigator.serviceWorker
            .register("/service-worker.js")
            .then((registration) => {
              console.log("SW registered: ", registration);
            })
            .catch((registrationError) => {
              console.log("SW registration failed: ", registrationError);
            });
        });
      }
      
    4. 使用命令 npx webpack serve --open 打包并运行

(3) 自定义库(轮子)

修改配置文件

module.exports = {
  // ...
  output: {
    // ...
    library: {
      name: "myLib", // 库名称
      type: "umd", // 输出类型(umd 兼容 cjs、ejs、amd 等类型)
      globalObject: "globalThis", // 全局对象
    }
  },
  //...
};

登录后发布至 npm

(4) 模块联邦(微前端)

  • 模块联邦用于解决多前端项目间模块共享的问题

    • 模块列表是 Webpack5 支持的新特性
  • 举例:在 A 项目中使用 B 项目的模块

    1. 修改 B 项目的配置文件

      const { ModuleFederationPlugin } = require("webpack").container;
      
      module.exports = {
        // ...
        plugins: [
          // ...
          new ModuleFederationPlugin({
            name: "bProject", // 模块联邦名称
            filename: "bRemoteEntry.js", // 模块联邦入口文件
            remotes: {}, // 模块联邦远程模块
            exposes: { // 模块联邦暴露模块
              // 暴露模块
              "./myMath": "./src/math.js",
            },
            shared: [], // 模块联邦共享模块
          }),
        ],
      };
      
    2. 修改 A 项目的配置文件

      const { ModuleFederationPlugin } = require("webpack").container;
      
      module.exports = {
        // ...
        plugins: [
          // ...
          new ModuleFederationPlugin({
            name: "aProject",
            filename: "aRemoteEntry.js",
            remotes: {
              bModules: "bProject@http://localhost:8081/bRemoteEntry.js",
            },
            exposes: {},
            shared: [],
          }),
        ],
      };
      

      其中,远程模块的格式为:自定义名称: "模块联邦名称@模块联邦地址/模块联邦入口文件"

    3. 在 A 项目的某个文件中导入 B 项目的模块

      import("bModules/myMath").then((module) => {
        console.log(module.add(1, 2));
      });
      

      其中,导入的格式为:import("自定义名称/暴露模块名称")

0x08 性能优化

构建性能的优化主要分为三个方向:

  1. 通用环境,即包括开发环境和生产环境
  2. 开发环境
  3. 生产环境

(1)通用环境

  • 版本:更新 Webpack 以及 NodeJS 相关工具和环境到最新版本
  • 加载器:将加载器应用于最少数量的必要模块(includeexclude
  • 工具:尽量减少额外插件、加载器等工具的使用;对于自定义的工具需要进行概要分析,避免引入性能问题
  • 模块解析:减少 resolve 项中 modulesextensionsmainFilesdescriptionFiles 的条目数量,及时关闭 symlinkscacheWithContext 等未使用的选项
  • 体积:减少编译结果的整体大小
    • 使用体积小、数量少的依赖
    • 在 MPA 中使用 SplitChunksPlugin 并开启异步模式
    • 合理引入 Tree Shaking
  • 缓存:将 webpack.config.js 的 cache 类型设置为内存或文件系统,并使用 package.json 中的 postinstall 清除缓存目录
  • webpack.ProgressPlugin:合理使用 webpack.ProgressPlugin,视情况删除后可以缩短构建时间
  • webpack.DllPluginwebpack.DllPlugin 可以为更改不频繁的代码(如第三方库)生成单独的编译结果(缺点:增加构建过程的复杂度)
  • worker 池:通过 thread-loader 将非常消耗资源的加载器分流给一个 worker 池(不宜使用过多的 worker)

(2)开发环境

  • 增量编译:在打包开发时,通过 Webpack 命令内置的 --watch 开启监听模式,实现增量编译
  • 内存中编译:通过 webpack-dev-server 等工具实现在内存中编译,替代写入磁盘
  • stats.toJson 加速:Webpack4 默认使用 stats.toJson 输出大量数据,非必要时避免使用
  • 调试工具:对于不同的 source-map,最好的选择是使用 eval-cheap-module-source-map
  • 环境区分:避免在开发环境使用一些生产环境需要的工具,如代码压缩、混淆等工具
  • 最小化入口串口:确保在生成入口串口时,尽量减少其体积以提供性能,可以通过 optimization.runtimeChunk 为运行时创建额外的串口
  • 避免额外优化:Webpack 通过执行额外的算法任务来优化输出结果的体积和加载性能,但仅适用于小型代码库,对于大型代码库需要关闭 optimization.removeAvailableModulesoptimization.removeEmptyChunksoptimization.splitChunks 等优化项
  • 避免额外输出:Webpack 打包过程中会默认输出生成的路径信息,可以通过 output.pathinfo 关闭

(3)生产环境

环境区分:避免在生产环境使用一些开发环境需要的工具,如 source-map、devServer 等工具

-End-

标签:...,exports,webpack,module,js,Webpack,loader
From: https://www.cnblogs.com/SRIGT/p/18646259

相关文章

  • 如果不使用脚手架,如果用webpack构建一个自己的react应用
    以下是使用Webpack构建一个自己的React应用的基本步骤:1.项目初始化首先创建一个项目文件夹,并在其中初始化package.json文件(用于管理项目的依赖和脚本等),打开命令行,进入项目文件夹,执行以下命令:mkdirmy-react-appcdmy-react-appnpminit-y2.安装必要的依赖需要安装React......
  • 请说说webpack的热更新原理
    Webpack的热更新原理主要基于模块热替换(HotModuleReplacement,简称HMR)实现,这是一种在不重新加载整个页面的情况下,动态替换页面中部分模块的技术。以下是Webpack热更新原理的详细解释:文件监听:Webpack通过内置的文件系统监听器,实时监测项目文件的变动。当开发者修改了源代码并保......
  • 请说说webpack的模块加载原理
    Webpack的模块加载原理是Webpack作为前端模块化打包工具的核心机制之一,它允许Web应用将各种资源视为模块,并通过特定的加载机制来组织和打包这些模块。以下是Webpack模块加载原理的详细解释:初始化:Webpack首先会读取配置文件(通常是webpack.config.js),从中获取项目的入口文件、输出......
  • 如何减少Webpack的打包体积?
    减少Webpack的打包体积是前端开发中常见的优化手段,旨在提高应用的加载速度和性能。以下是一些有效的方法来减少Webpack的打包体积:提取第三方库:将第三方库单独打包,并通过CDN引入。这样不仅可以减少打包体积,还能利用CDN的缓存优势来提高加载速度。例如,vue、vue-router、vuex、el......
  • 【WEB安全】利用shuji还原webpack打包源码
    一、前言二、webpack简介三、怎么确定是webpack打包站点呢四、shuji(周氏)配置4.1安装nodejs环境4.2安装shuji工具4.3.js.map文件存放位置4.4运行shuji反编译.js.map文件获取源码4.5代码审计五、实战记录免责声明本公众号所分享内容仅用......
  • [4428] 14 增量构建:Webpack 中的增量构建
    开始课程前,我先来解答上一节课的思考题:课程中介绍的几种支持缓存的插件(TerserWebpackPlugin,CSSMinimizerWebpackPlugin)和Loader(babel-loader,cache-loader)在缓存方面有哪些相同的配置项呢?通过对比不难发现,这些工具通常至少包含两个配置项:第一项用于指定是否开启缓存,以及指定缓存......
  • [4426] 12 打包提效:如何为 Webpack 打包阶段提速?
    上节课我们聊了Webpack构建流程中第一阶段,也就是编译模块阶段的提效方案,这些方案可以归为三个不同的优化方向。不知道大家课后有没有对照分析自己在项目里用到了其中的哪些方案呢?今天我们就来继续聊聊Webpack构建流程中的第二个阶段,也就是从代码优化到生成产物阶段的效率提升......
  • [4425] 11 编译提效:如何为 Webpack 编译阶段提速?
    上一课我们聊了Webpack的基本工作流程,分析了其中几个主要源码文件的执行过程,并介绍了Compiler和Compilation两个核心模块中的生命周期Hooks。上节课后的思考题是,在Compiler和Compilation的工作流程里,最耗时的阶段分别是哪个。对于Compiler实例而言,耗时最长的显然是......
  • [4429] 15 版本特性:Webpack 5 中的优化细节
    开始课程前,我们先来解答上一节课的思考题:为什么在开启增量构建后,有时候rebuild还是会很慢呢?我们可以从两方面来找原因。首先,Webpack4中的增量构建只运用到了新增模块与生成Chunk产物阶段,其他处理过程(如代码压缩)仍需要通过其他方式进行优化,例如分包和压缩插件的缓存。其次,过......
  • Webpack DLL(Dynamic Link Library)和 `manifest.json`
    webpack使用dll实现编译缓存,manifest.json作为缓存目录功能使用在Webpack中,DLL(DynamicLinkLibrary)和manifest.json是两个不同的概念,它们在构建过程中扮演着不同的角色:DLL(动态链接库):DLL是一个包含预编译代码的二进制文件。它包含了第三方库或应用程序代码的编译结果......