Webpack DLL动态链接库的应用和思考

DLL是"动态链接库"的意思,假设项目中用了 react 、lodash,这些静态资源可以被单独打包,且独立于项目。这样每次主项目打包的时候,都可以直接使用过打包好的 react、lodash,节省了性能。

原理:

  • 单独一份ddl config文件单独对react、lodash打包,会将打包产物和一个manifest文件放在某个目录下,manifest文件。
  • 打包的产物是以全局变量的方式导出模块的,manifest文件用来告诉使用方react、lodash打包后生成的全局变量叫什么,
  • 使用方在遇到react、lodash时候,就无需再次打包,将使用react和lodash模块的地方,根据manifest文件的指引去通过全局变量访问。

简单的例子:

单独开一个dll config文件 webpack.dll.config.js

js 复制代码
const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode: 'production',
  entry: {
     // 将react和lodash以及你想要预打包的静态文件打包成一个名为vendor的bundle
     // 这里没有路径,直接就一个react,就是去打包纯node_modules下的文件
    // 当然我们没有loader,webpack默认只能打包js和json文件。如果这里出现一个css入口文件,必然会发生打包错误。
    vendor: ['react', 'react-dom', 'lodash'] 
  },
  output: {
     // 将生成的bundle生成在dll目录下
    path: path.join(__dirname, 'dll'),
    filename: '[name]_[hash].dll.js',
     // 因为dll对打包生成的模块的导出方式是全局变量的方式,需要通过library告诉webpack应该将模块导出维护在哪个全局变量上。这个全局变量叫做 '[name]_[hash]',实际编译后即 vendor_<hash> 
    library: '[name]_[hash]'
  },
  plugins: [
    new CleanWebpackPlugin(),
     // 使用DllPlugin,这是webpack内置的plugin
    new webpack.DllPlugin({
       // 将生成的bundle生成在dll目录下,并且生成的manifest文件应该是[name]-manifest.json的形式
      path: path.join(__dirname, 'dll', '[name]-manifest.json'),
       // 表明这些模块的导出的全局变量名,必须和 output.library一致
      name: '[name]_[hash]'
    })
  ]
};

然后是主体项目的webpack文件 webpack.config.js

js 复制代码
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
     // 应用方必须配合使用 DllReferencePlugin 才能完成工作
    new webpack.DllReferencePlugin({
       // 告诉manifest文件在哪
      manifest: require('./dll/vendor-manifest.json')
    }),
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
     // 需要注意的是,我们需要手动将react、lodash这样被预打包生成的bundle引入在index.html中
     // 可以选择直接手动通过<scripe>标签的方式引入,但这样不如直接使用 AddAssetHtmlPlugin 享受工程化带来的福利,比如 publicPath,以及index.html删除后还能自动引入
    new AddAssetHtmlPlugin({
      filepath: path.resolve(__dirname, 'dll/vendor_*.dll.js'),
      publicPath: './'
    })
  ]
};

随后可以通过往package.json中加一些npm指令。在发布部署(CICD)时先npm run dll,然后npm run build

打包后的运行时状态(伪代码):

vendor.dll.js 伪代码,大概的原理

js 复制代码
var vendor_2e8f1b = (function(modules) {
  // ▼ 模块缓存机制
  var installedModules = {};
  
  // ▼ 模块加载实现 (类似__webpack_require__)
  function __dll_require__(moduleId) {
    if(installedModules[moduleId]) 
      return installedModules[moduleId].exports;
    
    var module = installedModules[moduleId] = {
      id: moduleId,
      loaded: false,
      exports: {}
    };
    modules[moduleId].call(module.exports, module, exports, __dll_require__);
    module.loaded = true;
    return module.exports;
  }
  
  // ▼ 暴露全局变量
  return __dll_require__(0);
})({
  /*! ▼ 预编译的第三方库模块集合 */
  0: function(module, exports) {
    // React核心实现
    module.exports = window.React = ... // 完整React库代码
  },
  1: function(module, exports) {
    // ReactDOM实现
    module.exports = window.ReactDOM = ... 
  },
  2: function(module, exports) {
    // lodash工具库
    module.exports = window._ = ... 
  }
});

index.bundle.js 中访问lodash

js 复制代码
 // 在bundle中动态绑定DLL模块,通过访问直接变量的方式来读取react、lodash。也就是说打包后的react和lodash的源码将不会出现在 index.bundle.js 中。 
__webpack_require__.d(exports, "lodash", () => window.vendor_2e8f1b[2]);

Manifest 文件

js 复制代码
{
  "name": "vendor_2e8f1b",
  "content": {  // 提供给使用方的预打包bundle信息
    "./node_modules/react/index.js": {
      "id": 1,  // react的id为1,使用方的 import { useState } from 'react'; 打包后会通过 window.vendor_2e8f1b[1].useState 访问
      "buildMeta": {"providedExports": true}
    },
    "./node_modules/lodash/lodash.js": {
      "id": 2,
      "buildMeta": {"usedExports": ["default"]}
    }
  }
}

学习Dll时思考的一些问题:

Q1:如果使用dll将react、lodash等第三方包预打包出去了,主项目对react和lodash是不是就无法启用treeshaking特性了?

A1: 是的,因为在主项目中直接绕过了react和lodash的打包,使用了现成的打包产物,并采用全局变量的方式去访问由dll分离出去的包,自然无法对react和lodash走一遍打包编译的过程,也就没有静态分析的过程,也就是无法treeshaking了。要知道treeshaking影响的是主项目打包后的产物,而不是主项目外的打包产物。webpack总不能会根据manifest文件去反向分析dll预打包的产物,然后修改dll下的产物吧。

Q2: dll能否解决Qiankun微前端资源共用问题?

A2:是的,dll可以解决微前端资源共用问题,因为在微前端中,对公共资源的处理有externals方案。externals原理也是将公共依赖的模块抛出打包工程,根据配置使用cdn链接,进而通过全局变量式使用外置模块。而dll原理类似,并且dll无需独立部署外置模块的cdn了,所以也可以。

大体思路流程:

这样的缺点就是子应用没法脱离主应用独立运行。当然如果有子应用需要独立运行的需求,可以有两种办法:

  • 无论主应用还是子应用都通过addAssetHtmlPlugin实现将cdn下的所有js文件引入在html模板中。
  • 或在子应用运行时,率先判断是否为独立运行状态,然后通过动态创建script标签的方式将所有cdn下的js文件引入,待script标签都加载完成后执行后续逻辑。

通过AddAssetHtmlPlugin实现对cdn下的文件

js 复制代码
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 准备CDN链接数组
const cdnAssets = [
  'https://cdn.example.com/library/vendor.react.js',
  'https://cdn.example.com/library/vendor.lodash.js
];

module.exports = {
  // ... 其他webpack配置
  plugins: [
    new HtmlWebpackPlugin(),
    // 使用 AddAssetHtmlPlugin 并传入CDN链接数组
    new AddAssetHtmlPlugin({ 
      files: cdnAssets 
    })
  ]
};

Q3:dll能否替代external解决方案?

A3:还是不能完全替代external方案的。虽然两者具备相似的原理。

Q4:我的第三方库包含vue3,我希望转换为es5,但dll中的打包是es6的,那该怎么办?重新用dllplugin打包一个es5版本的么?

A4:是的。思路是需要把webpack.dll.config.js中增加babel-loader相关配置,来使得dll下的产物也是es5的。下面是代码仅描述意思,仅供参考:

js 复制代码
module.exports = {
  module: {
    rules: [
      {
        test: /.js$/, // 匹配所有JavaScript文件
        use: {
          loader: 'babel-loader', // 必须使用loader而非plugin
          options: {
            presets: ['@babel/preset-env'] // 指定ES5转换规则
          }
        }
      }
    ]
  },
  plugins: [
    new webpack.DllPlugin({ /* DLL配置 */ }) // Plugin负责库生成,不处理文件转换
  ]
};
相关推荐
猫猫不是喵喵.2 分钟前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~1 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华2 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子4 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅4 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni5 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学6 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师6 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc886 小时前
微软EDGE浏览器功能学习
前端·学习·edge