
webpack 实战基于 DllReferencePlugin 的应用端接入——复用预构建 vendor DLL 并排除其参与二次编译【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through loaders, modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack本指南以仓库中的 examples/dll-app-and-vendor/1-app/template.md 为主体结合同目录 0-vendor 一侧与 lib/dll 下的真实实现完整讲解“DLL 分包”模式中**应用端app part**如何接入。你将掌握DllReferencePlugin如何读取 manifest 并让 vendor 模块不再进入应用编译、应用侧代码如何通过全局变量vendor_lib_xxxx间接引用 dll 内模块以及两个配置如何配合运行与验证。一、例子定位先有 vendor dll后有 app本仓库在 examples/dll-app-and-vendor/README.md 中给出了一段说明This example shows how to use the DllPlugin to separate vendor and app build. This can boost the speed of the app build because vendors are no longer included, but built separately.整个示例被拆成两个相互独立、按顺序构建的子工程0-vendorvendor 侧负责把第三方依赖示例中为example-vendor单独打包成一个 dll同时导出一份 manifest1-app应用侧也就是本篇文章的核心关联文档所在目录负责消费上一步的产物。我们聚焦的 template.md 开头就点明了应用端的全部机制The previously built vendor dll is used. The DllReferencePlugin reads the content of the dll from the manifest file and excludes all vendor modules from the compilation. Instead references to these modules will be loaded from the vendor dll via a global variable (vendor_lib_xxxx).翻译成三层事实应用构建时不重新打包 vendor而是直接使用此前已构建好的 dllDllReferencePlugin从 manifest 中读出 dll 的模块登记信息并把所有 vendor 模块从本次 compilation 中排除应用代码中对这些模块的引用最终被替换为从一个全局变量示例命名vendor_lib_xxxx中按模块 id 取用。这正是“应用构建变快”的根源vendor 代码量大但几乎不变只有在 vendor 依赖列表变化时才需要重新构建详见 0-vendor/template.md正常开发周期内应用侧每次编译都不再触碰它们。二、应用端 webpack.config.js 逐行拆解应用侧完整配置位于 examples/dll-app-and-vendor/1-app/webpack.config.jsuse strict; const path require(path); const webpack require(../../../); const manifest ../0-vendor/dist/vendor-manifest.json; /** type {import(webpack).Configuration} */ const config { // mode: development || production, context: __dirname, entry: ./example-app, output: { filename: app.js, path: path.resolve(__dirname, dist) }, plugins: [ new webpack.DllReferencePlugin({ manifest: require(manifest) }) ] }; module.exports config;关键配置说明配置项本例取值作用context__dirname模块解析的基准目录保证./example-app指到应用侧自身的源码entry./example-app应用入口对应 example-app.jsoutput.filename/output.pathapp.js/dist应用 bundle 输出到dist/app.jsplugins[0]DllReferencePlugin应用端接入 dll 的唯一入口manifestrequire(../0-vendor/dist/vendor-manifest.json)以对象字面量直接传入 vendor 侧产出的 manifest注意这里manifest与new webpack.DllPlugin({...})一样是挂在webpack命名空间上的内置插件因此无需额外安装任何包。manifest 的两种传法从源码看DllReferencePlugin的manifest选项既支持对象也支持文件路径字符串。lib/dll/DllReferencePlugin.js 的beforeCompile钩子中做了区分处理当manifest是字符串时插件通过compiler.inputFileSystem.readFile读取该文件再用parseJson解析成 manifest 对象lib/util/parseJson.js 是仓库内部的 JSON 解析工具若 manifest 文件为空或格式错误插件不会直接杀死进程而是把错误暂存起来稍后作为compilation error抛出源码中为此专门定义了DllManifestError从而保留一次可读的构建失败信息解析成功的 manifest 还会被加入compilation.fileDependencies保证 watch 模式下 manifest 变更能触发重新编译。示例中require(manifest)相当于在 Node 侧直接同步载入 JSON属于对象传法二者效果等价。选用哪一种取决于你希望配置是“可读路径”还是“已解析对象”。name / sourceType / content 的自动推断如果应用端只传了manifest而没有显式传name插件在compile钩子中会从 manifest 自动补齐默认值见 lib/dll/DllReferencePlugin.jsif (!name) name manifest.name; if (!sourceType) sourceType manifest.type; if (!resolvedContent) resolvedContent manifest.content;也就是说vendor 侧在 manifest 里写明的name即全局库名、type全局暴露方式、content模块名到内部 id 的映射会被应用端直接沿用。这也是为什么两个工程必须保持“同一套 manifest 契约”。此外插件还在validate钩子里对 options 做 schema 校验lib/dll/DllReferencePlugin.js对应的校验描述文件位于 schemas/plugins/dll/DllReferencePlugin.json。若你传入的context、scope、type、extensions等可选项拼写错误或类型不符构建会提前给出 schema 报错而不是运行时才暴露问题。三、应用源码import 一个“不参与打包”的 vendor 模块应用入口 example-app.js 非常简短import { square } from example-vendor; console.log(square(7)); console.log(new square(7));example-vendor是 vendor 侧的演示依赖模块源码位于 examples 体系下的 node_modules 中0-vendor/template.md 中通过../node_modules/example-vendor.js引用它。它对外导出了squaresquare(7)以函数方式调用输出49之类的计算结果new square(7)以构造器方式调用说明这个导出同时具备可调用/可实例化的形态对应用侧而言只是“从全局 dll 里取到的对象”具体形态无关紧要。值得强调的是这段import不会触发 vendor 模块进入应用侧的模块图。因为 manifest 里已经声明了example-vendor及其内部模块 idDllReferencePlugin在compile阶段就把它们映射成了对外部全局变量的引用详见下文“源码纵深”一节因此应用 compilation 中既不会解析、也不会打包example-vendor的任何源码——这正是应用构建提速的直接原因。四、HTML 中的加载顺序先 vendor后 appexample.html 展示了 dll 模式下的页面引用方式html head/head body script src../0-vendor/js/vendor.js charsetutf-8/script script srcjs/app.js charsetutf-8/script /body /html加载顺序是硬约束浏览器必须先执行vendor.js把内部模块加载函数挂到全局变量vendor_lib_[fullhash]上之后执行app.js应用代码运行时再通过该全局变量按 id 取用 dll 内模块。若顺序颠倒app.js执行到引用 vendor 模块的语句时会因全局变量尚未定义而报错。此 HTML 是仓库内的演示页模板实际脚本目录以你本地构建产物位置为准把 webpack.config.js 跑起来即可得到可用的dist/app.js。五、产物与两种构建模式dist/app.js 里“看不到” vendor 代码template.md 后续章节列出的dist/app.js、stdoutUnoptimized与production:stdoutProduction mode均为示例构建时由文档生成工具注入的真实产物与构建日志模板中的{{_..._}}占位符由 examples 体系下的模板工具替换这些生成物未提交到仓库。你可以这样理解它们的含义dist/app.js只包含应用自身的业务代码 一批“代理模块”delegated module。这些代理不携带 vendor 实现只负责在运行时调用全局 dll 完成模块解析。相比把整个 vendor 打进来体积显著更小。Unoptimizeddevelopmentstdout开发模式下的构建统计侧重构建耗时与模块数量Production mode stdout生产模式下的构建统计此时 webpack 会开启压缩、tree shaking 等优化示例配置里mode被注释掉运行时通过--mode或mode字段切换。在仓库测试体系中test/Examples.test.js 会在 development 与 production 两种模式下分别编译 examples 并断言产物与输出因此你可以直接以它为准验证 dll 示例在两种模式下的行为。六、源码纵深DllReferencePlugin 到底把“引用”替换成了什么应用端之所以能“只引用不打包”关键在于 lib/dll/DllReferencePlugin.js 在compile钩子中做的两件事/** type {Externals} */ const externals {}; const source dll-reference ${name}; externals[source] name; const normalModuleFactory params.normalModuleFactory; new ExternalModuleFactoryPlugin(sourceType || var, externals).apply( normalModuleFactory ); new DelegatedModuleFactoryPlugin({ source, type: this.options.type, scope: this.options.scope, context: this.options.context || compiler.context, content: resolvedContent, extensions: this.options.extensions, associatedObjectForCache: compiler.root }).apply(normalModuleFactory);构造 external 映射插件生成一个特殊的 source 标识dll-reference name并把name映射为要访问的全局变量名。随后用ExternalModuleFactoryPlugin把它注册为“外部模块”sourceType默认为var即最终代码按var全局方式取用。委托模块工厂DelegatedModuleFactoryPlugin拿到 manifest 中的content模块名 → 内部 id 的映射结合scope、type、extensions等选项把凡是能命中 content 的模块请求改造成 DelegatedModule。这些委托模块不会携带任何实现代码只登记“从哪个全局变量、取哪个 id”。模块图的连接方式插件在compilation钩子中把DelegatedSourceDependency这类“委托来源依赖”注册到normalModuleFactory见 lib/dll/DllReferencePlugin.js从而把引用关系以依赖边的形式纳入模块图但绝不参与源码打包。落到运行时可以概括为应用 bundle 中引用example-vendor的地方最终会在全局对象vendor_lib_[fullhash]上按 vendor 侧登记的内部模块 id 取出模块并执行。由于 manifest 中的name写的是vendor_lib_[fullhash]构建后的实际全局名会带上 vendor 构建的哈希后缀——这正是 0-vendor 配置中output.library与DllPlugin.name同时使用[fullhash]的原因vendor 内容一旦变化全局名随之变化应用侧引用随之失效从机制上规避了缓存串扰。想要深入代理模块的运行时行为可以继续阅读 lib/dll/DelegatedModule.js、lib/dll/DelegatedModuleFactoryPlugin.js 与 lib/dll/DelegatedSourceDependency.js。七、vendor 侧快速回顾这套引用关系从哪来虽然本文主题是应用端但要让例子真正跑通必须先构建 vendor。vendor 侧配置在 examples/dll-app-and-vendor/0-vendor/webpack.config.jsuse strict; const path require(path); const webpack require(../../../); /** type {import(webpack).Configuration} */ const config { // mode: development || production, context: __dirname, entry: [example-vendor], output: { filename: vendor.js, // best use [fullhash] here too path: path.resolve(__dirname, dist), library: vendor_lib_[fullhash] }, plugins: [ new webpack.DllPlugin({ name: vendor_lib_[fullhash], path: path.resolve(__dirname, dist/vendor-manifest.json) }) ] }; module.exports config;对照 0-vendor/template.md 的说明vendor 侧解决三个问题把 dll 暴露为全局DllPlugin配合output.library把 dll 内部的模块加载函数暴露为目标环境的全局变量产出 manifestDllPlugin.path指定 manifest 写到dist/vendor-manifest.json其中登记“模块名 → 内部 id”的映射供应用端DllReferencePlugin读取契约一致性DllPlugin.name必须与output.library完全一致都含[fullhash]保证 manifest 里记录的name就是真实全局名应用端才能按名引用。从 lib/dll/DllPlugin.js 的实现看DllPlugin内部组合了三块能力DllEntryPlugin负责把 vendor 入口标记成 dll 入口LibManifestPlugin负责序列化并写出 manifestoptions 会连同entryOnly一起透传当entryOnly: false时还会通过FlagAllModulesAsUsedPlugin阻止 vendor 内部模块被当作未使用代码裁掉。示例采用默认entryOnlytrue只登记入口能触达的模块映射。八、端到端运行与验证步骤由于两个子工程都在仓库 examples 目录内、且webpack通过require(../../../)引用仓库自身建议使用仓库构建好的 webpack 或 webpack-cli 依次编译# 1) 先构建 vendor产出 dist/vendor.js 与 dist/vendor-manifest.json cd examples/dll-app-and-vendor/0-vendor webpack # 2) 再构建应用产出 dist/app.js cd ../1-app webpack验证要点确认 1-app/webpack.config.js 中引用的../0-vendor/dist/vendor-manifest.json已存在且包含name、content字段应用构建日志中的模块数量应远小于 vendor 全量重打的模块数体现“排除 vendor 模块”的效果打开页面时先加载 vendor.js 再加载 app.js控制台可观察到square(7)的正常输出且 Network 面板中不存在对 vendor 源码的二次请求。九、参考文件清单用途路径示例总览含背景与构建动机examples/dll-app-and-vendor/README.md应用侧模板文档本文主文档examples/dll-app-and-vendor/1-app/template.md应用侧配置examples/dll-app-and-vendor/1-app/webpack.config.js应用侧入口源码examples/dll-app-and-vendor/1-app/example-app.js应用侧 HTMLexamples/dll-app-and-vendor/1-app/example.htmlvendor 侧配置examples/dll-app-and-vendor/0-vendor/webpack.config.jsDllReferencePlugin 实现lib/dll/DllReferencePlugin.jsDllPlugin 实现lib/dll/DllPlugin.js委托模块运行时实现lib/dll/DelegatedModule.jsdll 模块解析与工厂lib/dll/DllModuleFactory.js、lib/dll/DelegatedModuleFactoryPlugin.js示例自动化测试双模式验证test/Examples.test.js【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through loaders, modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考