Webpack5.0 学习笔记

Webpack

一、简介

Webpack 是一个前端资源构建工具,静态模块打包器(module bundler)。它根据模块的依赖关系,经过打包生成对应的静态资源

1.打包流程

index.js
| jQuery less ... (静态模块)
| chunk(代码块)
| | [less->css][js->浏览器可编译的js]
| | 打包
| | bundle

2.五个核心

2.1 Entry

入口:指示 Webpack 以哪个文件为入口进行打包,并分析其内部依赖关系

2.2 Output

输出:指示 Webpack 打包后的 bundle 输出到哪个目录下,以及包名

2.3 Loader

加载器:让 Webpack 处理非 js 文件(Webpack 本身只能处理 js 文件)

2.4 Plugins

插件:比 Loader 执行的范围更广泛,插件的范围包括:从打包优化和压缩,一直到重新定义环境中的变量。

2.5 Mode

模式:指示 Webpack 使用对应的模式进行配置

模式描述特点
development 开发模式会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPluginNamedModulesPlugin可以让代码进行本地调试的环境
production 生产模式会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPluginUglifyJsPlugin.可以让代码优化上线的环境

二、初次使用

1.index.js

入口起点文件

1.1 运行指令

  • 开发环境
webpack ./src/index.js -o ./build/built.js --mode=development

流程:Webpack./src/index.js为入口进行打包,输出到./build/built.js。打包模式为开发环境

  • 生产环境
webpack ./src/index.js -o ./build/built_product.js --mode=production

流程:Webpack./src/index.js为入口进行打包,输出到./build/built_product.js。打包模式为生产环境,生产环境会压缩 js 代码

1.2 结论

1.webpack可以处理js/json资源,不能处理css/img等资源 2.生产环境将 ES6 模块化编译成浏览器可识别的模块化 3.生产环境压缩了 js 代码

三、打包 HTML 资源

  • Loader: 下载 -> 配置
  • Plugins: 下载 -> 引入 -> 配置

webpack.config.js

// 引入
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  /* ... */
  plugins: [
    new HtmlWebpackPlugin({
      template: "./index.html", // 入口html
      filename: "app.html", // 出口html
      inject: "body", // 在指定标签中生成script标签
    }),
  ],
  /* ... */
};

html-webpack-plugin

默认创建一个空的 HTML,自动引入打包输出的所有资源 如果需要指定某个有结构的 HTML 文件:

new HtmlWebpackPlugin({
  // 复制该文件,并自动引入打包输出的所有资源
  template: "./src/index.html",
});

四、开发环境配置

1.使用 source map

webpack.config.js

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

配置inline-source-map后,浏览器可以查找到原本的 js 文件代码,而非webpack打包后的代码

2.使用 watch mode

打包时使用命令:

webpack --watch

开启 watch mode,使 webpack 能够监听代码的修改从而自动进行响应式的打包操作

3.使用 webpack-dev-server

安装 webpack-dev-server

npm i webpack-dev-server -D

使用webpack-dev-server启动项目

npx webpack-dev-server

使用webpack-dev-server能够实现项目代码实时更新,提高开发效率与webpack的编译效率

五、资源模块

1.简介

资源模块asset module

资源模块允许webpack打包其他类型的资源文件

资源模块类型asset module type通过四种新的类型模块替换所有的loader

四种类型:

  1. asset/resource 发送一个单独的文件并导出URL
  2. asset/inline导出资源的uri
  3. asset/source导出资源的源代码
  4. asset会在导出单独文件或导出uri之间自动进行选择

2.resource 资源

打包png类型资源

module: {
    rules: [
        {
            test: /\.png$/,
            type: "asset/resource",
        },
    ],
},

指定资源输出目录及文件名

  1. output中定义
output: {
  assetModuleFilename: "image/test.png"; // 输出至打包路径下的image目录中,命名为test.png
}
output: {
  assetModuleFilename: "image/[contenthash][ext]"; // 输出至打包路径下的image目录中,命名为webpack自动生成的hash,后缀为原文件后缀
}
  1. generator中定义
rules: [
  {
    test: /\.png$/,
    type: "asset/resource",
    generator: {
      filename: "images/[contenthash][ext]",
    },
  },
];

对于相同的配置,如同时在generatoroutput中定义图片输出路径,将以后面定义的为准

3.inline 资源

rules: [
  {
    test: /\.svg$/,
    type: "asset/inline",
  },
];

使用inline打包的图像文件不会出现在dist目录下

文件会以base64的形式直接添加在html文件中

4.source 资源

rules: [
  {
    test: /\.txt$/,
    type: "asset/source",
  },
];

可以直接将txt文件中的文字(源代码)添加到html容器中

5.asset 自动解析

rules: [
  {
    test: /\.jpg$/,
    type: "asset",
    parser: {
      dataUrlCondition: {
        maxSize: 4 * 1024 * 1024, // 文件大于4M生成资源,否则生成base64
      },
    },
  },
];

六、Loader

1.加载样式文件

1.1 加载原生 CSS

rules: [
    {
        test: /\.css$/,
        use: ["style-loader", "css-loader"], // 先使用css-loader识别css文件,再使用style-loader将样式放入文件
    },
],

webpack不能直接打包样式文件,引入css样式后,如要使用loader用来加载样式文件

加载css,需要使用style-loadercss-loader

use数组中的loader会按照从后向前的顺序加载:链式逆序加载

1.2 加载预处理器

rules: [
    {
        test: /\.(css|scss)$/,
        use: ["style-loader", "css-loader", "sass-loader"],
        // 先使用sass-loader将sass转换成css,然后使用css-loader识别css文件,再使用style-loader将样式放入文件
    },
],

2.抽离和压缩样式文件

2.1 抽离样式

安装插件 mini-css-extract-plugin

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

将原本的style-loader替换成了mini-css-extract-plugin插件的loader

执行打包命令后,在输出目录下生成了一个css文件,,并且引入到了目标html文件中

2.2 压缩样式

安装插件 css-minimizer-webpack-plugin

npm i css-minimizer-webpack-plugin -D
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
  // 打包模式需为production
  mode: "production",
  // 在优化配置中加载该插件,而不是plugins中
  optimization: {
    minimizer: [new CssMinimizerPlugin()],
  },
};

执行打包命令后,输出目录下生成了压缩后的样式文件

3.加载字体文件

rules: [
  {
    test: /\.(woff|woff2|eot|ttf|otf)$/,
    type: "asset/resource",
  },
];

asset/resource可以加载任何类型的资源

4.加载数据

  • JSON:Nodejs 本身支持,可直接加载;

  • XML:安装 xml-loader

    npm i xml-loader -D
    

    加载后,转化为 js 对象形式;

  • CSV/TSV:安装csv-loader

    npm i csv-loader -D
    

    加载后,转为数组形式。

  • toml/yaml/json5: 安装toml yaml json5

    npm i toml yaml json5 -D
    
    const toml = require("toml");
    const yaml = require("yaml");
    const json5 = require("json5");
    
    module.exports = {
      /*...*/
      rules: [
        {
          test: /\.toml$/,
          type: "json",
          parser: {
            parse: toml.parse,
          },
        },
        // 其余同理
      ],
      /*...*/
    };
    

七、Babel loader

1.用途

在开发过程中,一定会用到 ES6 语法,而当webpack对项目进行打包时,ES6 相关代码会原封不动地输出,如果浏览器不支持 ES6 语法,则会出现错误。

这时便需要使用到babel-loader,将babelwebpack进行结合

2.使用

安装:

npm install -D babel-loader @babel/core @babel/preset-env
  • babel-loader:在webpack中应用babel解析ES6的桥梁
  • @babel/core:babel 核心模块
  • @babel/preset-env:babel 预设,一组 babel 插件的集合

配置:

rules: [
  {
    test: /\.js$/,
    // 对除了nodemodules以外的代码进行编译
    exclude: /node_modules/,
    use: {
      loader: "babel-loader",
      options: {
        presets: ["@babel/preset-env"],
      },
    },
  },
];

3. Error : regeneratorRuntime is not defined

regeneratorRuntime 用于在 babel 编译中兼容async/await

需要额外添加两个库

  • @babel/runtime 包含了 regeneratorRuntime 运行时需要的内容
  • @babel/plugin-transform-runtime 在需要regeneratorRuntime 的地方自动require导包

配置

rules: [
  {
    test: /\.js$/,
    // 对除了nodemodules以外的代码进行编译
    exclude: /node_modules/,
    use: {
      loader: "babel-loader",
      options: {
        presets: ["@babel/preset-env"],
        // 添加这个插件
        plugins: ["@babel/plugin-transform-runtime"],
      },
    },
  },
];

八、代码分离

把多个模块共享的代码分离出去,减少入口文件大小,提高首屏加载速度

常见的代码分离方法:

  • 入口起点:使用entry配置,手动的分离代码;
  • 防止重复:使用 Entry dependencies 或者SplitChunkPlugin去重和分离代码;
  • 动态导入:通过模块内联函数调用分离代码

1.入口起点

module.exports = {
  // 多入口
  entry: {
    index: "./src/index.js",
    another: "./src/another.js",
  },
  output: {
    // 按照entry中的key命名
    filename: "[name].bundle.js",
    path: resolve(__dirname, "build"),
    clean: true,
  },
};

打包后的目录:

another.bundle.js
index.bundle.js
index.html

缺点:如果两个入口都对某个包进行引入(公共模块,如lodash等),那么导出的每个chunk都会对引用的包进行导入,造成代码冗余

2.防止重复

针对代码冗余,修改后的配置:

  entry: {
    index: {
      import: "./src/index.js",
      dependOn: "shared",
    },
    another: {
      import: "./src/another.js",
      dependOn: "shared",
    },
    shared: "lodash",
  },

含义:如果indexanother都含有lodash模块的话,将其抽离出来,创建名为sharedchunk

打包后的目录:

another.bundle.js
index.bundle.js
index.html
shared.bundle.js

使用 webpack 内置插件:split-chunks-plugin

可以自动抽离公共代码

entry: {
  index: "./src/index.js",
  another: "./src/another.js",
}
optimization: {
  splitChunks: {
    chunks: "all",
  },
}

3.动态导入

function getComponent() {
  return import("lodash").then(({ default: _ }) => {
    const element = document.createElement("div");
    element.innerHTML = _.join(["hello", "webpack"], " ");
    return element;
  });
}

利用import函数进行动态导入模块,打包后,模块会被拆分为一个单独的chunk

动态-静态混合导入

需要在配置中添加

splitChunks: {
  chunks: "all",
}

4.懒加载

利用import方法实现js文件的懒加载

const button = document.createElement("button");
button.textContent = "加法";
button.addEventListener("click", () => {
  // 魔法注释 用于更改导出后包的命名
  import(/*webpackChunkName:"math"*/ "./math.js").then(({ add }) => {
    console.log(add(1, 2));
  });
});
document.body.appendChild(button);

在开发者工具中Network可以看到,点击按钮前,并未调用math.js文件,点击按钮调用add方法时,才调用了该文件,实现了懒加载

5.预获取/预加载模块

声明import时,使用以下内置指令,让webpack输出“resource hint资源提示”,告知浏览器:

  • prefetch 预获取:将来某些导航下可能需要的资源
  • preload 预加载:当前导航下可能需要的资源

5.1 prefetch

import时,添加注释

import(/* webpackPrefetch: true* / "./math.js").then(()=>{})

打包后,页面头部标签里会加入一个link元素,预获取了打包后的math.js

<link
  rel="prefetch"
  as="script"
  href="http://127.0.0.1:5500/8.%E4%BB%A3%E7%A0%81%E5%88%86%E7%A6%BB/build/math.bundle.js"
/>

效果:在页面内容加载完毕,网络空闲时,加载math.bundle.js

5.2 preload

/*webpackPreload: true*/

效果类似懒加载。

最优方案:使用 prefetch 预获取

九、缓存

每次访问网站,浏览器获取服务器上的前端资源都会消耗时间,所以浏览器通常会自带缓存技术,利用缓存,降低网络请求流量,以使网站加载速度更快。

但是,浏览器缓存是通过文件名判断,发包后文件名没有更改,浏览器会使用已缓存的版本渲染,对用户和开发都不够友好。

1.输出文件名

利用contenthash:根据文件内容生成hash来修改文件名

output: {
  filename: "[name].[contenthash].js";
}

2.缓存第三方库

一般采取将第三方库提取到单独的vendor chunk文件中的方法。

第三方库很少像本地代码一样频繁修改,通过以上步骤,利用client长效缓存机制,命中缓存从而消除请求,减少向服务器获取资源的同时,还能保证客户端、服务端代码版本一致。

简单来说,就是将第三方代码单独缓存到浏览器,只有本地代码更新时,浏览器缓存才会更新,而第三方库始终可以使用浏览器缓存的。

node_modules路径下所有的库打包进vendors.js中:

  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          chunks: "all",
        },
      },
    },
  },

十、拆分环境配置

1.公共路径

服务器静态资源存放路径:影响打包后的index.html文件中路径的引入(默认使用相对路径引入)

output: {
  publicPath: "http://localhost:8080/";
}

2.环境变量

配置中的module.exports可以定义为函数,有默认的参数,参数为当前的环境变量

module.exports = (env) => {
  return {
    // 各种webpack配置
  };
};

在输入命令行进行打包时,用户可以自己输入环境变量

npx webpack --env production --env name="apple"

此时,打印函数中的env可以得到

{
  WEBPACK_BUNDLE: true,
  WEBPACK_BUILD: true,
  production: true,
  name: 'apple'
}

根据用户输入的环境变量,对配置做出相应的改变,如打包模式

mode: env.production ? "production" : "development";

注意:此时生产环境打包后,js 代码并没有被折叠,是因为引入了 CssMinimizerPlugin。需要再引入一个 js 的 minimizer 插件:terser-webpack-plugin 并进行实例化

3.拆分配置文件

如果所有环境都在同一个配置文件中,就只能通过 2 中的方法,使用逻辑判断语句,判断环境变量达到修改配置的目的,如果各环境打包方式区别很大,这样的方法管理起来会十分棘手,就需要针对各环境,对配置文件进行拆分。