尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

webpack 代码分割进阶:用 entry.dependOn 搭建多入口共享依赖链,从配置到产物逐字节拆解

webpack 代码分割进阶:用 entry.dependOn 搭建多入口共享依赖链,从配置到产物逐字节拆解 webpack 代码分割进阶用 entry.dependOn 搭建多入口共享依赖链从配置到产物逐字节拆解【免费下载链接】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导读本文围绕 webpack 官方示例 examples/code-splitting-depend-on-advanced其文档主体为 template.md渲染结果见同目录 README.md深入讲解多入口场景下如何利用entry的dependOn选项在入口点之间声明依赖关系从而把 React、lodash 等共享第三方库抽成独立 vendor 分块、让多个页面入口按顺序加载并复用它们。读完本文你将掌握entry描述对象的完整字段与dependOn语义含链式依赖与传递闭包、runtimeChunk: single与chunkIds: named的搭配用法以及如何解读 stats 输出中的 chunk 关系符号{...}、{...}和产物分块代码理解底层调用链lib/EntryOptionPlugin.js、lib/Entrypoint.js、lib/ChunkGraph.js与官方 schemaschemas/WebpackOptions.json对dependOn的完整定义。一、示例场景多页面应用如何共享 vendorcode-splitting-depend-on-advanced构建了一个典型的多页面Multi-Page App依赖网络其中既有业务入口依赖 vendor 入口也有页面入口依赖另一个页面入口的链式关系。仓库中该示例共包含下列文件文件角色webpack.config.js配置 4 个 entry 及 runtime / chunk 命名策略app.js业务入口一使用isomorphic-fetch与lodashpage1.js业务入口二使用react/react-dom并动态import(./lazy)lazy.js被 page1 懒加载的模块依赖lodash与prop-typesother-vendors.js额外共享依赖入口装载 lodash、isomorphic-fetch 并做附加初始化模块依赖图如下react / react-dom / prop-types ──► react-vendorsvendor 入口 lodash / isomorphic-fetch ──────► other-vendorsvendor 入口 other-vendors ◄──dependOn── app.js app / react-vendors ◄──dependOn── page1.js ──► import() ──► lazy.jspage1在依赖react-vendors之外还依赖app这就演示了dependOn的进阶advanced用法——被依赖的入口本身也可以是一个依赖了其他入口的业务入口形成一条other-vendors → app → page1的加载顺序链。若把page1对app的依赖去掉则退化为仓库中更简单的 code-splitting-depend-on-simple 场景那里只有app → react-vendors一层依赖。二、配置全解析webpack.config.js以下为 webpack.config.js 的完整内容也是原文档 template.md 的核心配置段use strict; /** type {import(webpack).Configuration} */ const config { entry: { app: { import: ./app.js, dependOn: [other-vendors] }, page1: { import: ./page1.js, dependOn: [app, react-vendors] }, react-vendors: [react, react-dom, prop-types], other-vendors: ./other-vendors }, optimization: { runtimeChunk: single, chunkIds: named // To keep filename consistent between different modes (for example building only) }, stats: { chunks: true, chunkRelations: true } }; module.exports config;2.1 entry 描述对象的两种形态这里同时展示了entry值的两种写法纯字符串/数组如react-vendors: [react, react-dom, prop-types]、other-vendors: ./other-vendors简写形态等价于{ import: [...] }。对象描述形态如app、page1import声明入口模块dependOn声明该入口依赖的其它入口名。2.2 dependOn 的官方定义schemas/WebpackOptions.json 对EntryDescription.dependOn的定义是The entrypoints that the current entrypoint depend on. They must be loaded when this entrypoint is loaded.即当某个入口被加载时它dependOn列出的那些入口点必须已经被加载。schema 允许两种形态——单个字符串或多个字符串组成的数组且要求元素minLength: 1、uniqueItems: true不能重复声明同一个入口。同时required: [import]表明对象形态必须提供import。同文件还定义了其余可用的入口描述字段可作为实战参考asyncChunks是否允许生成按需加载的异步 chunk、baseUri、chunkLoading、filename、library、layer、publicPath、runtime、wasmLoading、worker等见 schemas/WebpackOptions.json。2.3 两个关键优化项的作用optimization.runtimeChunk: single把 webpack 运行时runtime代码抽取为单独一个runtime.jschunk避免每个入口都内联一份 bootstrap 代码。本示例中入口react-vendors与other-vendors的 Entrypoint 输出均为runtime.js 自身 chunk见后文 stats而业务入口则通过dependOn间接复用该 runtime。optimization.chunkIds: named用可读名字而非数字编号作为 chunk 文件名如lazy_js.js。配置注释说明这是为了让不同模式如仅构建 vs 生产下产出文件名保持一致便于在 README.md 中对照非优化与生产两次构建的产物。stats.chunksstats.chunkRelations在构建统计中输出每个 chunk 的关联关系父 chunk、子 chunk、sibling 共享 chunk便于观察dependOn生成的依赖图。三、dependOn 的语义顺序、链式与去重3.1 入口代码回顾四个源文件都非常薄只是为了演示哪些模块出现在哪个分块app.jsimport isomorphicFetch from isomorphic-fetch; import lodash from lodash;page1.js额外引入react、react-dom并在末尾import(./lazy)lazy.js引入lodash、prop-typesother-vendors.js再次引入lodash、isomorphic-fetch注释 Additional initializations 表明其承担共享模块的副作用初始化职责3.2 三类依赖关系产生的结果① vendor 提取与跨入口去重lodash、isomorphic-fetch同时被app、other-vendors使用react、react-dom同时被page1、react-vendors使用。webpack 在把模块归属到 chunk 时会把跨入口共享的第三方模块收敛到各自声明的 vendor 入口分块中业务入口里不再重复打包这些模块产物中app.js的模块数只有自身业务代码调用__webpack_require__(5)、(4)指向 other-vendors 分块内模块。② 加载顺序保证page1的代码只有在其全部前置依赖app、react-vendors进而传递到other-vendors与 runtime加载完毕后才会执行这正是后面产物代码中__webpack_require__.O(...)门控gate的由来。③ 链式/传递依赖Advanced 的核心app依赖other-vendorspage1依赖app——最终page1就传递性地要求other-vendors先于自己加载。产物中 page1 的启动门控数组写的是[app,react-vendors,other-vendors]即 webpack 已自动展开全部传递闭包无需在 page1 里重复书写other-vendors。此外lazy.js虽然静态引入lodash/prop-types但它们分别已在other-vendors与react-vendors分块中存在因此异步分块lazy_js中只装载./lazy.js自身公共部分由运行时去复用既有模块缓存模块缓存与__webpack_require__.e机制见下方产物剖析。3.3 源码级印证dependOn 如何进入 EntrypointdependOn从配置到依赖图的关键链路可从源码结构推断lib/EntryOptionPlugin.js 在entryOption阶段遍历每个 entry 名若 value 是函数则走DynamicEntryPlugin否则用EntryPlugin注册。lib/EntryOptionPlugin.js 的entryDescriptionToOptions把入口描述对象中的name/filename/runtime/layer/dependOn/baseUri/...组装成EntryOptions其中dependOn: desc.dependOn被原样透传。lib/Entrypoint.js 的Entrypoint内部持有this._dependOn new SortableSet()通过addDependOn/dependOn(entrypoint)管理入口间的依赖集合见 lib/Entrypoint.js。图构建阶段 lib/ChunkGraph.js 会依据Entrypoint.dependOn关系建立 chunk 间边注释中即写着// entryChunkA: dependOn entryChunkB从而约束初始 chunk 的加载顺序并计算 chunk 关系。因此dependOn不是简单的把代码并到一起而是建立了带优先级的入口间依赖图运行时才会据其生成门控加载逻辑。四、构建产物逐文件剖析dist/*.js示例在构建后产出 6 个文件见 README.md 的dist/小节本仓库中构建环境将 lodash/react 等第三方包解析为本地桩模块module.exports lodash之类的短实现便于离线复现模块划分规律与实际 npm 包一致。4.1 dist/runtime.js —— 唯一的 webpack 运行时runtime chunk 承载全部 bootstrap 代码模块缓存__webpack_module_cache__、__webpack_require__系列函数以及 chunk 加载用 runtime 模块chunk loaded__webpack_require__.O用于登记等某批 chunk 就绪后执行回调的门控。ensure chunk__webpack_require__.ePromise.all聚合各加载器。get javascript chunk filename__webpack_require__.uchunkId .js这也是lazy_js.js命名的来源。jsonp chunk loading__webpack_require__.f.j与全局self[webpackChunk]通过script标签异步加载分块并处理加载失败ChunkLoadError与去重installedChunks缓存。注意两处细节__webpack_require__.p dist/所有异步请求都拼上dist/前缀对应示例的输出目录。installedChunks初始含runtime: 0表示 runtime 自身永远已安装故不会被重复加载。4.2 dist/other-vendors.js —— 共享依赖分块lodash isomorphic-fetch该分块内容含模块3./other-vendors.js自身带 Additional initializations 副作用以及模块4lodash、5isomorphic-fetch。分块末尾的 runtime 段直接__webpack_exec__(3)执行入口模块——因为 vendor 入口没有前置依赖。4.3 dist/react-vendors.js —— React 全家桶分块含模块0react、1react-dom、2prop-typesruntime 段执行__webpack_exec__(0), __webpack_exec__(1), __webpack_exec__(2)一次性初始化三个库。4.4 dist/app.js —— 依赖门控的入口分块app.js的业务模块引用的lodash模块 4与isomorphic-fetch模块 5都不在本分块内对应位置留空/* 0 */, /* 1 */ ...而在other-vendors分块中。其启动段是理解dependOn的关键/******/ var __webpack_exec__ (moduleId) (__webpack_require__(moduleId)) /******/ __webpack_require__.O(0, [other-vendors], () (__webpack_exec__(6))); /******/ var __webpack_exports__ __webpack_require__.O();即app 入口的业务代码模块 6只有当 chunkother-vendors已加载完成才会执行这就是dependOn: [other-vendors]在产物中的直接体现——由 chunk-loaded runtime 保证 vendor 先就绪避免业务代码执行时访问到尚未注册的模块。4.5 dist/page1.js —— 传递依赖 动态懒加载的入口分块page1.js的启动门控数组进一步体现了依赖闭包展开/******/ __webpack_require__.O(0, [app,react-vendors,other-vendors], () (__webpack_exec__(7)));page1 配置只写了dependOn: [app, react-vendors]但产物的就绪条件同时包含other-vendors——因为app又依赖other-vendorswebpack 自动把依赖链展开成完整闭包并按序等待。业务代码中懒加载调用为__webpack_require__.e(/*! import() */ lazy_js).then(() (__webpack_require__(/*! ./lazy */ 8)));先__webpack_require__.e(lazy_js)通过 JSONP 拉取异步分块再执行其中的模块 8。4.6 dist/lazy_js.js —— 只含自身业务代码的异步分块由于lodash模块 4已在other-vendors、prop-types模块 2已在react-vendors中被实例化并缓存lazy_js分块仅装载./lazy.js自身的模块。这展示了dependOn方案与动态import()叠加时的收益懒加载分块不必重复携带大体积公共依赖因为page1的依赖链保证它们在更早的阶段就绪。五、stats 解读用 chunkRelations 验证依赖图配置中的stats.chunks/stats.chunkRelations会输出带关系符号的 chunk 列表见 README.md 的 Info 节。webpack 用尖括号描述关系A {B}A 的子 chunk 是 BB 由 A 派生/异步加载A {B}A 与 B 是兄弟共享同一运行时/被同一组入口共享此处即各入口 chunk 与runtime.js的关系A {B}A 的父 chunk 是 BB 依赖先于 A 加载。摘录几行关键输出Unoptimized 模式asset runtime.js 10.6 KiB [emitted] (name: runtime) asset other-vendors.js 2.11 KiB [emitted] (name: other-vendors) asset page1.js 1.86 KiB [emitted] (name: page1) asset app.js 1.41 KiB [emitted] (name: app) asset react-vendors.js 1.3 KiB [emitted] (name: react-vendors) asset lazy_js.js 1.1 KiB [emitted] Entrypoint app 1.41 KiB app.js Entrypoint page1 1.86 KiB page1.js Entrypoint react-vendors 11.9 KiB runtime.js 10.6 KiB react-vendors.js 1.3 KiB Entrypoint other-vendors 12.7 KiB runtime.js 10.6 KiB other-vendors.js 2.11 KiB chunk (runtime: runtime) app.js (app) 116 bytes {other-vendors} {runtime} {page1} [initial] [rendered] chunk (runtime: runtime) other-vendors.js (other-vendors) 210 bytes {runtime} {app} [initial] [rendered] chunk (runtime: runtime) page1.js (page1) 176 bytes {app} {react-vendors} {runtime} {lazy_js} [initial] [rendered] chunk (runtime: runtime) react-vendors.js (react-vendors) 87 bytes {runtime} {page1} [initial] [rendered]解读要点chunk app.js ... {other-vendors} {runtime} {page1}——app 的父 chunk 为 other-vendors 与 runtimeapp 又是 page1 的父 chunk。other-vendors行dependent modules 64 bytes [dependent] 2 modules——lodash、isomorphic-fetch是被业务入口共享、因此被标记为 dependent 的模块。vendor 入口react-vendors、other-vendors的 Entrypoint 都会列出runtime.js因为它们各自是入口点需要自带 runtime 引入而app/page1的 Entrypoint 不重复列 runtime.js其运行时就绪由dependOn依赖链先加载 vendor 入口保证。(runtime: runtime)前缀说明所有初始 chunk 共享同一个 runtime chunk对应runtimeChunk: singlelazy_js.js无 entry 名、只被{}从 page1 指向是纯异步 chunk。将上述统计映射到页面加载场景访问react-vendors页面时 HTML 注入runtime.js react-vendors.js访问 other-vendors 页面时注入runtime.js other-vendors.js访问 app 时注入runtime.js other-vendors.js app.js访问 page1 时则注入runtime.js react-vendors.js other-vendors.js app.js page1.js并可按需在触发懒加载后追加lazy_js.js。六、Unoptimized 与 Production 构建对比同一示例在非优化与生产模式下的产物体积形成鲜明对照数据取自 README.md 的 Info 两节产物文件UnoptimizedProductionminimizedruntime.js10.6 KiB2.38 KiBother-vendors.js2.11 KiB232 Bpage1.js1.86 KiB271 Bapp.js1.41 KiB193 Breact-vendors.js1.3 KiB197 Blazy_js.js1.1 KiB157 B生产模式下 runtime 由 10.6 KiB 压缩到 2.38 KiB各业务与 vendor chunk 也大幅瘦身。两次构建的 chunk 划分与依赖关系完全一致这正是配置里使用chunkIds: named的目的——模式间文件命名保持一致说明dependOn划分的 chunk 结构在开发/生产之间是稳定的。七、实战要点与边界结合源码归纳依赖必须指向真实存在的入口名dependOn数组里写的是entry的键如app、react-vendors不能指向不存在的入口或模块路径。适合显式 vendor 入口式拆分当你想完全掌控 vendor 边界例如other-vendors还需执行额外初始化代码时dependOn比自动化的SplitChunks.cacheGroups更直接可预期当依赖矩阵复杂、需要自动最小化共享分块时SplitChunks仍是更省心的方案。链式依赖会自动传递从 dist/page1.js 中[app,react-vendors,other-vendors]的门控数组可以看到无需在每层重复罗列所有祖先依赖webpack 会在 ChunkGraph 阶段基于 Entrypoint 的_dependOn集合计算传递闭包相关逻辑见 lib/ChunkGraph.js。共享模块只落一份被多个入口引用的模块会收敛到依赖链上游的 vendor 分块并以模块 id 引用方式被下游复用避免业务分块重复打包懒加载分块同样能复用已实例化的公共模块。与 runtimeChunk 的配合多入口 dependOn时通常配合runtimeChunk: single抽离公共运行时否则每个 vendor 入口都可能内联 runtime 造成冗余也可为入口单独指定runtime字段做更细粒度控制。文件名可预期性结合chunkIds: named让异步分块如lazy_js与入口分块都有稳定的可读文件名便于产物缓存策略与长期引用。如果只需要一层业务入口 → 共享 vendor的简单拆分可参考同仓库的简化版本 examples/code-splitting-depend-on-simple/webpack.config.js本文所述的 advanced 版本在其之上加入入口依赖另一个业务入口与懒加载复用上游公共模块两层进阶能力。对照官方 schemaschemas/WebpackOptions.json与入口解析实现lib/EntryOptionPlugin.js即可在自己的多页面工程中安全复刻这套共享 vendor 页面入口顺序加载的代码分割骨架。【免费下载链接】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),仅供参考
返回列表