一、webpack5中的新特性

  • 启动命令
  • 持久化缓存
  • 资源模块
  • modules和chunks的优化
  • 更强大的tree shaking
  • 联邦模块

    二、启动命令的改变

    在webpack5中启动webpack-dev-server的命令发生过改变。在webpack4中启动命令为webpack-dev-server,在webpack5中启动命令为webpack serve
    1. "scripts": {
    2. "build": "webpack",
    3. "start": "webpack serve" // 启动webpack-dev-serve的命令
    4. },

    三、持久化缓存

    webpack会缓存生成的webpack模块和chunk,来改善构建的速度。缓存的位置是在node_modules中的.cache文件夹中。

    缓存在webpack5中默认开启,缓存默认是在内存里,但可以对cache进行设置。

    webpack追踪了每个模块的依赖,并创建了文件系统的快照。此快照会与真实的文件系统进行比较,当检测到差异时,将触发模块的重新构建。当重新打包后只会重新编译发生变更的文件和变更文件相关联的文件。其他未变更的文件不会被编译。
    1. module.exports = {
    2. cache: {
    3. // 缓存的类型, memory速度会更快一些
    4. type: 'filesystem', // 有两个可选值,filesystem文件中和memory内存中
    5. // 缓存的路径,下方可以不写,默认值就是下方的路径
    6. cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/webpack')
    7. }
    8. }

    注意:当cache的type为filesystem的时候不要使用cnpm安装包,这样会导致冲突,导致缓存失效。

    四、资源模块

    资源模块是一种模块类型,它允许使用资源文件(字体、图标等)而无需额外配置loader。
  • raw-loader转换成asset/source,导出原文件的loader。

  • file-loader转成asset/resource,打包成单独的文件。
  • url-loader转成asset/inline,将符合规则的文件打包成base64字符串。

    1. {
    2. test: /\.png$/,
    3. type: 'asset/resource' // 对标file-loader生成文件
    4. },{
    5. test: /.\ico$/,
    6. type: 'asset/inline' // 对标url-loader,模块大小小于限制生成base64字符串
    7. },{
    8. test: /\.txt$/,
    9. type: 'asset/source' // 对标raw-loader读取原来文件
    10. },{
    11. test: /\.jpg$/,
    12. type: 'asset', // 告诉webpack这是一个文件资源,需要加载我们自定义的加载器
    13. parser: { // 当小于限制的时候是url-loader,大于的时候是file-loader
    14. dataUrlCondition: {
    15. maxSize: 1 * 1024
    16. }
    17. }
    18. }

    五、moduleIds和chunkIds的优化

  • module:每一个文件就可以看成一个module。

  • chunk:webpack打包最终生成的代码块,代码块会生成文件,一个文件对应一个代码块。

    在webpack5之前,没有通过entry入口打包的chunk文件(import(‘*‘)或者splitchunk方式),都会以1、2、3的文件命名方式输出,删除某个文件可能会导致缓存失效。

    在生产模式下,默认启用这写功能:chunkIds: ‘deterministic’, moduleIds: ‘deterministic’,此方法将短数字ID的hash值的方式分配给moduleIds和chunkIds。

    chunkIds设置为deterministic,output中的chunkFilename里的[name]会被替换成确定性的数字ID的hash值。
    1. module.exports = {
    2. optimization: {
    3. moduleIds: 'deterministic',
    4. chunkIds: 'deterministic'
    5. }
    6. }

    六、更强大的Tree-shaking

    tree-shaking就是在打包的时候剔除无用的代码,webpack4本身的tree-shaking比较简单,主要是找一个import进来的变量是否在模块中使用。

    在webpack4中,tree-shaking不是特别智能,如下图中所示,module1和module2中所有的内容都会被打包。

    image.png

    webpack5可以根据作用域之间的关系来进行比较。

    在mode为development模式下,可以通过配置以下参数看到哪些变量未被用到,如果没被用到在production模式下就会被tree-shaking掉。
    1. optimization: {
    2. usedExports: true
    3. },

    我们也可以在package.json中设置sideEffects的方式来开进行筛选tree-shaking,当这个值设置为false的时候会认为所有模块都没有副作用,都会被tree-shaking掉。
    1. // a.js
    2. document.title = "改标题";
    3. export function getTitle(){
    4. console.log('getTitle');
    5. }
    6. // b.js
    7. import './a.js'
    8. /*
    9. 以上的代码中如果sideEffects未设置值,默认会将getTitle函数tree-shaking掉,
    10. 因为第一句默认为是一个有副作用的。
    11. 当sideEffects为false的时候,会将所有内容tree-shaking掉
    12. 因为这个配置会默认为我们所有的文件都没有副作用。
    13. 但都设置为false会存在问题,当我们引入一个css模块时候,import './xxx.css',
    14. 设置sideEffects为false会认为所有模块没有副作用,所哟这个css也会tree-shaking掉,
    15. 正确配置方法是sideEffects: [*.css],这样会任务css文件有副作用,不会被tree-shaking掉。
    16. */

    7、模块联邦

    1、出现模块联邦的因素:

  • Module Federation的动机是为了不同开发小组之间共同开发一个或者多个应用。

  • 应用被划分为更小的应用块,一个应用块,可以是比如一些前端组件,也可以是数据获取逻辑的组件。
  • 每个应用块由不同的小组开发,应用或者应用块共享其他的应用或者应用块。

    2、什么是模块联邦

  • 使用Module Federation时,每个应用块都是单独构建的,这些构建都将编译为容器。

  • 容器可以被其他应用或者其他容器应用。
  • 一个被引用的容器被成为remote,引用者成为host,remote暴露模块给host,host则可以使用这些暴露的模块。

image.png

3、Module Federation实践

1、webpack中配置

  1. // 以下为remote配置项,为向外提供组件服务的代码
  2. const { ModuleFederation } = require('webpack').container;
  3. module.exports = {
  4. plugins: [
  5. new ModuleFederation({
  6. filename: 'remoteEntry.js', // 提供给其他项目加载的文件
  7. name: 'app2', // 唯一Id,用于记录当前提供出模块的名字
  8. exposes: { // 向外暴露的内容
  9. // key为host引入的名字, value为导出的文件
  10. './Newlist': './src/NewList'
  11. }
  12. })
  13. ]
  14. }
  15. // 以下为host的配置,为消费remote组件的容器
  16. const { ModuleFederation } = require('webpack').container;
  17. module.exports = {
  18. plugins: [
  19. new ModuleFederation({
  20. remote: {
  21. // 这个key值就是组建内部引入的开头,vaule中@前面的字符串是remote中name的名字
  22. app2: 'app2@http://localhost:3000/remoteEntry.js'
  23. }
  24. })
  25. ]
  26. }

在上方的代码中我们只关注exposes/remote这个两个东西
  • 提供了exposes选项的表示对象内的组件是一个remote,exposes内的模块可以被其他的host引用,引用的方式为import(${name}/${exposes})。
  • 提供了remote选项的表示当前应用是一个host,可以引入remote中的exposes中暴露的组件。

    2、项目中引入

    ```javascript // 以下为host应用,应用remote组件的方式 import React from “react”; import Slides from ‘./Slides’; // 引入remote的组件,’app2’为当前应用webpack中remote的key值,/NewsList为remote中webpack构建的exposes的key值 const RemoteNewsList = React.lazy(() => import(“app2/NewsList”)); const App = () => (

    App1, Local Slides, Remote NewsList

    1. <RemoteNewsList />
    );

export default App;

  1. <a name="LWzzh"></a>
  2. #### 3、加载逻辑
  3. <a name="QZokv"></a>
  4. ##### 有一点需要注意下,就是入口文件index.js本身不存在什么逻辑,反而将逻辑抽入到bootstrap中,index.js只是动态加载bootstrap.js。
  5. ```javascript
  6. // index.js
  7. import("./bootstrap");
  8. // bootstrap.js
  9. import React from "react";
  10. import ReactDOM from "react-dom";
  11. import App from "./App";
  12. ReactDOM.render(<App />, document.getElementById("root"));

主要原因是remote暴露的js文件要优先加载,所以保证bootstrap.js是一个异步加载的逻辑。

4、shared共享模块的能力

提供一个shared配置,主要用来避免项目中多次出现一个公共依赖。如果A中依赖react/react-dom,而B项目中暴露的组建也是依赖react/react-dom的。如果不采用这种方式就会引入两次react/react-dom这两个库,这样会增加构建的体积,用shared的方案会解决这个问题。
// 项目 B 的 webpack 配置
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      shared: {
        react: { singleton: true },
        "react-dom": { singleton: true }
      }
    })
  ]
};

// 项目 A 的 webpack 配置
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      shared: {
        react: { singleton: true },
        "react-dom": { singleton: true }
      },
    })
  ]
};