ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Gutenberg 依赖提取 Webpack 插件(Dependency Extraction Webpack Plugin)深度实战指南

Gutenberg 依赖提取 Webpack 插件(Dependency Extraction Webpack Plugin)深度实战指南 Gutenberg 依赖提取 Webpack 插件Dependency Extraction Webpack Plugin深度实战指南【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg本指南围绕 Gutenberg 仓库中的wordpress/dependency-extraction-webpack-plugin展开系统讲解如何将打包产物中的 WordPress 共享依赖自动外部化externalize并生成声明依赖清单与版本号的.asset.php/.asset.json文件从而让前端 bundle 无缝复用 WordPress 站点已有的脚本与 Script Module。读完本文你将掌握该插件的安装配置、全部构造选项、脚本与模块两种模式的行为差异、源码级工作原理以及如何与wordpress/scripts的默认 webpack 配置协同工作。插件要解决的核心问题WordPress 后台以及整个 Gutenberg 生态共享大量脚本依赖例如react、react-dom、lodash、moment、jquery以及上百个wordpress/*包。如果在 webpack 打包时把这些依赖全部打进 bundle会造成重复加载同一份库白白浪费带宽与内存与 WordPress 官方脚本版本不一致引发潜在的 API 不兼容问题。传统做法是手工维护一份依赖清单包括脚本 handle 和版本号极易出错——版本一变页面就可能白屏。本插件的设计目标见 README恰好有两个外部化Externalize依赖把可作为 WordPress 站点共享脚本或模块的依赖从 bundle 中剔除运行时改为引用window上的全局变量或外部模块为每个入口生成资产文件asset file声明该入口依赖的 WordPress 脚本/模块 handle 列表并给出基于当前源码内容计算的唯一版本哈希。这两点合起来就实现了WordPress 风格的依赖共享bundle 瘦身且无需再手动维护依赖列表。安装与版本要求在项目中使用该插件前先将其作为开发依赖安装npm install wordpress/dependency-extraction-webpack-plugin --save-dev版本要求依据 package.jsonNode.js需要长期支持LTS版本当前仓库声明的 engines 为node 18.12.0、npm 8.19.2见 package.jsonwebpack5.0.0 或更高版本peerDependencies声明为webpack ^5.0.0见 package.json运行时依赖仅json2php用于把 JSON 数据序列化为 PHP 数组语法。不兼容旧版 webpack使用时请确认项目构建环境满足上述条件。基础用法接入 webpack 配置在webpack.config.js中把它当作普通 webpack 插件注册即可// webpack.config.js const DependencyExtractionWebpackPlugin require( wordpress/dependency-extraction-webpack-plugin ); module.exports { // …snip plugins: [ new DependencyExtractionWebpackPlugin() ], };重要限制不要创建多个插件实例插件内部通过全局状态跟踪已外部化的请求因此同一次编译中不允许存在多个实例否则可能产生意外结果见 lib/index.js 中基于实例的externalizedDeps集合以及 README 的明确提示。如果你正从wordpress/scripts的默认配置扩展 webpack 配置务必先移除默认实例再添加自己的实例const defaultConfig require( wordpress/scripts/config/webpack.config ); const webpackConfig { ...defaultConfig, plugins: [ ...defaultConfig.plugins.filter( ( plugin ) plugin.constructor.name ! DependencyExtractionWebpackPlugin ), new DependencyExtractionWebpackPlugin( { injectPolyfill: true, requestToExternal( request ) { /* My externals */ }, } ), ], };脚本模式Scripts的默认行为在普通脚本模式下每个入口entry point都会生成一个配套资产文件声明应当被入队的 WordPress 脚本依赖同时给出基于入口相关文件内容计算的唯一版本哈希——计算时会把提取出来的样式文件内容也纳入哈希实现见 lib/index.js。例如源文件// Source file entrypoint.js import { Component } from react;webpack 会输出output/entrypoint.js打包后的 JavaScript同时输出同名资产文件output/entrypoint.asset.php?php return array(dependencies array(react), version dd4c2dc50d046ed9d4c063a7ca95702f);资产文件命名规则资产文件名由产出的 JS 文件名推导而来。例如output.filename: bunny-plugin-[name].min.js时entrypoint入口会生成output/bunny-plugin-entrypoint.min.asset.php。从源码看lib/index.js未指定outputFilename时默认以 JS 文件名为基础把.m?js后缀替换为.asset.php脚本模式或.asset.jsonoutputFormat: json默认实现还通过 webpack 的compilation.getPath([file], ...)解析占位符保证与输出目录结构一致。默认处理的模块请求表以下请求默认会被外部化映射关系见 lib/util.jsRequestGlobalScript handlebabel/runtime/regeneratorregeneratorRuntimewp-polyfillwordpress/*wp[*]wp-*jqueryjQueryjquerylodash-eslodashlodashlodashlodashlodashmomentmomentmomentreact-domReactDOMreact-domreactReactreact此外源码还补充了若干 README 表格未列出的映射细节可供查阅 lib/util.jsreact-dom/client同样映射为全局ReactDOMhandle 为react-domreact/jsx-runtime、react/jsx-dev-runtime映射为全局ReactJSXRuntimehandle 为react-jsx-runtime包含react-refresh/runtime的请求映射为全局ReactRefreshRuntimehandle 为wp-react-refresh-runtimeReact Fast Refresh 场景wordpress/*命名空间下的请求会被转换为[ wp, camelCase(包名) ]形式的全局路径访问例如wordpress/api-fetch→[ wp, apiFetch ]wordpress/i18n→[ wp, i18n ]其 handle 为wp-{包名}camelCaseDash转换lib/util.js不采用 Lodash 式的激进大写数字后的字母不会大写因此a11y保持a11y、i18n保持i18n而api-fetch→apiFetch一批随 Gutenberg 一起打包的包wordpress/admin-ui、wordpress/dataviews、wordpress/fields、wordpress/grid、wordpress/icons、wordpress/interface、wordpress/kebab-case、wordpress/style-runtime、wordpress/ui、wordpress/undo-manager、wordpress/views等见BUNDLED_PACKAGES常量不会被外部化这些包会直接打进 bundle。与 webpackexternals的关系该插件的功能与 webpack 原生 externals 有重叠但有明确分工本插件的存在意义从编译结果中自动提取脚本 handle生成依赖清单省去手工维护。如果你不需要提取依赖清单直接用externals即可两者可以共存但可能冲突例如在 webpack 配置里加上{ externals: { wordpress/blob: wp.blob } }会让wordpress/blob对插件隐身从而不会出现在依赖清单中。底层实现它是如何接入 webpack 编译的了解原理有助于排查问题。插件在apply()中做两件事lib/index.js委托给ExternalsPlugin根据output.module决定使用import模块模式还是window脚本模式外部类型并把externalizeWpDeps作为外部解析函数而useModules的取值直接来自compiler.options.output?.module在两个processAssets阶段挂接任务PROCESS_ASSETS_STAGE_OPTIMIZE_COMPATIBILITY阶段扫描入口 chunk 的源码寻找/* wp:polyfill */魔法注释在压缩前处理压缩后注释可能丢失并把结果记录到资产信息wpMagicComments中lib/index.jsPROCESS_ASSETS_STAGE_ANALYSE阶段遍历入口 chunk 及其异步 chunk收集外部化依赖、计算内容哈希、生成并写入资产文件lib/index.js。externalizeWpDepslib/index.js的执行顺序是先调用用户传入的requestToExternalModule/requestToExternal若配置了其中模块模式支持布尔简写返回false视为不外部化返回true视为使用原请求名若未被处理且useDefaults开启则回落到默认转换函数成功外部化的请求被记录进this.externalizedDeps集合供后续生成依赖清单时识别。生成资产数据时lib/index.js插件还会在injectPolyfill或命中魔法注释时向静态依赖中加入wp-polyfill遍历模块图、递归跟踪入口的异步 chunkwebpack 可能把动态导入的外部模块拆到独立 chunk并区分静态依赖与动态依赖——动态依赖在模块模式下会被标记为{ id, import: dynamic }见hasStaticDependencyPathToRoot的静态依赖路径分析lib/index.js使用 webpack 的createHash对入口文件含被提取的样式文件内容计算哈希作为version模块模式下额外写入type: module当runtimeChunk未被禁用时还会写入handle字段compilation.name - JS文件名确保共享 runtime 文件被 WordPress 只注册一次lib/index.js。这些行为均有对应的测试夹具覆盖例如wordpress、wordpress-interactivity、dynamic-import、nested-dynamic-import、cyclic-dependency-graph、style-imports、style-cache-group、polyfill-magic-comment等目录见 test/fixtures。模块模式Script Modules的行为警告脚本模块支持目前仍被视为实验性功能。从插件的第 5 版起开始支持模块打包。要启用模块行为需要配合 webpack 的output.module选项插件会根据output.module自动调整行为产出适合 WordPress Module API 使用的资产文件。当前仓库版本的参考配置lib/index.js 读取output.moduleconst webpackConfig { ...defaultConfig, // 启用模块编译所必需的配置截至编写本文时 output: { module: true }, experiments: { outputModule: true }, plugins: [ ...defaultConfig.plugins.filter( ( plugin ) plugin.constructor.name ! DependencyExtractionWebpackPlugin ), new DependencyExtractionWebpackPlugin( { // 模块模式下应使用 requestToExternalModule requestToExternalModule( request ) { if ( request my-registered-module ) { return request; } }, } ), ], };注意webpack 对output.module的支持细节会随版本演进请以 webpack 官方文档 为准。模块模式下每个入口同样会生成一个资产文件声明应当被入队的 WordPress脚本模块依赖并附带版本哈希。例如// Source file entrypoint.js import { store, getContext } from wordpress/interactivity;产物output/entrypoint.asset.php?php return array(dependencies array(wordpress/interactivity), version dd4c2dc50d046ed9d4c063a7ca95702f);默认处理的脚本模块目前默认支持的外部脚本模块Requestwordpress/interactivitywordpress/interactivity当前是唯一可用的 WordPress 脚本模块。源码层面的默认处理lib/util.js比 README 表格更细致值得注意wordpress/interactivity返回外部类型为module ${ request }强制外部化为模块从而把该模块的 import提升为静态导入因为 Interactivity 目前不支持动态导入wordpress/interactivity-router和wordpress/a11y返回import ${ request }若某个请求属于 WordPress 脚本defaultRequestToExternal能识别但在模块上下文中被引用会直接抛错提示该脚本尚不支持在模块中使用。模块模式下同样兼容 webpackexternals但可能发生冲突与脚本模式完全一致。脚本模式与模块模式的差异小结维度脚本模式模块模式外部类型windowimport自定义函数requestToExternalrequestToExternalModule支持布尔简写handle 自定义requestToHandle无对应配置injectPolyfill可用不可用默认外部化大量脚本见请求表仅wordpress/interactivity等少数模块依赖条目handle 字符串数组模块 ID动态依赖带import: dynamic标记资产数据dependenciesversion额外含type: module构造选项详解插件构造函数接受一个对象例如module.exports { plugins: [ new DependencyExtractionWebpackPlugin( { injectPolyfill: true } ), ], };所有选项的默认值在构造函数中被合并初始化lib/index.js类型定义见 lib/types.d.ts。outputFormat类型string默认值php可选值php或json控制生成资产文件的格式。php格式通过json2php序列化为可require的 PHP 数组json格式输出标准 JSON。串行化逻辑见 lib/index.js。对应的测试夹具是 output-format-json。outputFilename类型string | function默认值null自定义生成的资产文件名接受与 webpackoutput.filename相同的值。函数形式时回调可拿到chunk、filename、contentHash等上下文见 lib/index.js。对应夹具option-output-filename字符串形式、function-output-filename配合 webpackoutput.filename函数形式。combineAssets类型boolean默认值false默认情况下每个入口各生成一个资产文件。置为true后所有入口的资产信息合并到输出目录下的单个assets.(json|php)文件中默认文件名由outputFormat决定。合并时以 JS 文件名为键组织数据见 lib/index.js。对应夹具combine-assets包含 file-a / file-b 两个入口。combinedOutputFile类型string默认值null仅在与combineAssets: true搭配时有效。自定义合并资产文件的输出路径可提供相对于输出目录的路径最终通过path.resolve与path.relative解析lib/index.js。useDefaults类型boolean默认值true传入useDefaults: false可禁用全部默认请求处理即上文请求表与wordpress/interactivity等默认映射都不再生效完全交由自定义函数处理。对应夹具no-default。injectPolyfill类型boolean默认值false强制在每个入口的依赖列表中加入wp-polyfill效果等同于在每个入口顶部写import wordpress/polyfill;。注意该选项不适用于脚本模块模式。与魔法注释机制的关系即使不开该选项只要源码中出现了/* wp:polyfill */注释插件也会自动加入wp-polyfill见 lib/index.js。相关夹具polyfill-magic-comment、polyfill-magic-comment-minified。externalizedReport类型boolean | string默认值false把实际被外部化的依赖以 JSON 数组形式输出为报告文件便于人工或自动化检查。提供字符串时作为文件名设为true时使用默认文件名externalized-dependencies.json常量定义于 lib/index.js。报告内容按字母排序lib/index.js。注意类型定义文件中该选项写作externalizedReportFiletypes.d.ts而实际构造函数读取的是externalizedReport使用时以 README 为准。requestToExternal类型function不适用于脚本模块模式模块模式请使用requestToExternalModule自定义模块请求到全局变量的映射。函数接收模块请求字符串返回表示全局变量的字符串也可以返回字符串数组来通过对象路径访问全局例如wp.i18n可表示为[ wp, i18n ]。配置的requestToExternal优先级高于默认处理未被处理的请求在useDefaults为true默认时回落到默认逻辑。/** * Externalize my-module * * param {string} request Requested module * * return {(string|undefined)} Script global */ function requestToExternal( request ) { // Handle imports like import myModule from my-module if ( request my-module ) { // Expect to find my-module as myModule in the global scope: return myModule; } } module.exports { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternal } ) ], };requestToExternalModule类型function仅适用于脚本模块模式自定义脚本模块请求到外部脚本模块 ID 的映射。函数接收模块请求字符串返回表示目标脚本模块的字符串通常情况下外部模块与源码模块同名。支持布尔简写返回true表示脚本模块 ID 与源码请求相同返回false表示不外部化见 lib/index.js。/** * Externalize my-module * * param {string} request Requested script module * * return {(string|boolean|undefined)} Script module ID */ function requestToExternalModule( request ) { // Handle imports like import myModule from my-module if ( request my-module ) { // Import should be of the form import { something } from myModule; in the final bundle. return myModule; } // If the script module ID in source is the same as the external script module, true can be returned. return request external-module-id-no-change-required; } module.exports { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule } ), ], };requestToHandle类型function不适用于脚本模块模式也没有对应的模块配置所有被插件外部化的模块都被视为 WordPress 脚本依赖并加入依赖列表。requestToHandle允许自定义出现在依赖列表中的脚本 handle。函数未返回字符串时handle 默认取请求名本身配置函数优先于默认处理。/** * Map my-module request to my-module-script-handle * * param {string} request Requested module * * return {(string|undefined)} Script global */ function requestToHandle( request ) { // Handle imports like import myModule from my-module if ( request my-module ) { // my-module depends on the script with the my-module-script-handle handle. return my-module-script-handle; } } module.exports { plugins: [ new DependencyExtractionWebpackPlugin( { requestToHandle } ) ], };requestToExternal与requestToHandle的配合这两个函数配合可以处理任意自定义模块requestToExternal是处理模块的必要条件它把模块请求映射为全局名称决定运行时如何取到该依赖requestToHandle把同一个模块请求映射为脚本 handle决定写入entrypoint.asset.php依赖列表中的字符串。换句话说前者解决运行时去哪找后者解决依赖声明里写什么。二者缺一不可——只配置 handle 而不配置 external模块仍会被打进 bundle只配置 external 而不配置 handle依赖列表里会出现未注册的 handle 字符串。WordPress 侧读取资产文件并入队构建完成后在 PHP 侧照常入队脚本动态读取资产文件中的依赖与版本即可$script_path path/to/script.js; $script_asset_path path/to/script.asset.php; $script_asset file_exists( $script_asset_path ) ? require( $script_asset_path ) : array( dependencies array(), version filemtime( $script_path ) ); $script_url plugins_url( $script_path, __FILE__ ); wp_enqueue_script( script, $script_url, $script_asset[dependencies], $script_asset[version] );模块模式Script Module API 仅在 WordPress 6.5 可用$module_path path/to/module.js; $module_asset_path path/to/module.asset.php; $module_asset file_exists( $module_asset_path ) ? require( $module_asset_path ) : array( dependencies array(), version filemtime( $module_path ) ); $module_url plugins_url( $module_path, __FILE__ ); wp_register_script_module( my-module, $module_url, $module_asset[dependencies], $module_asset[version] ); wp_enqueue_script_module( my-module );代码中file_exists分支是一个值得借鉴的健壮性兜底当资产文件缺失例如直接引用源码而未构建时退化为无依赖 文件修改时间作为版本避免白屏。与 wordpress/scripts 的集成在 Gutenberg 仓库自身的构建体系中该插件被wordpress/scripts默认配置使用见 packages/scripts/config/webpack.config.js// WP_NO_EXTERNALS global variable controls whether scripts assets get // generated, and the default externals set. ! process.env.WP_NO_EXTERNALS new DependencyExtractionWebpackPlugin(),这里揭示了一个对日常开发非常重要的环境变量约定默认情况下wordpress/scripts构建会启用该插件生成资产文件并应用默认外部化规则设置环境变量WP_NO_EXTERNALS可以整体禁用插件的默认实例不再生成资产文件、不使用默认 externals适合某些特殊调试或完全自控的场景。同时该文件还展示了模块模式的官方启用方式packages/scripts/config/webpack.config.js当检测到实验性模块标志时会生成第二份 webpack 配置包含experiments.outputModule: true、output.module: true、chunkFormat: module以及独立的new DependencyExtractionWebpackPlugin()实例最终导出[ scriptConfig, moduleConfig ]两份配置。因此在wordpress/scripts项目中若需自定义该插件务必参考上文移除默认实例的写法避免双实例冲突。源码级工作原理速览为便于深入阅读这里给出插件的核心代码路径全部位于 packages/dependency-extraction-webpack-plugin/lib/index.js构造L16-L49合并默认选项初始化externalizedDeps集合用于跨阶段跟踪外部化请求useModules初始为false。applyL140-L176根据output.module决定useModules挂载ExternalsPlugin外部类型import/window注册processAssets阶段的魔法注释扫描与资产生成。externalizeWpDepsL55-L99自定义函数优先 → 默认处理兜底 → 记录到externalizedDeps。mapRequestToDependencyL105-L124requestToHandle优先 →defaultRequestToHandle兜底 → 回退为请求名。addAssetsL233-L521输出 externalized 报告聚合入口 chunk含样式 chunk区分静态/动态依赖用createHash对文件内容含样式计算版本哈希写入dependencies、version模块模式还写type: module、必要时写handle按combineAssets决定逐入口输出还是合并输出。stringifyL130-L138php格式经json2php序列化json格式直接JSON.stringify。默认映射表实现在 lib/util.js并有专门的单元测试文件 test/util.js 对camelCaseDash、defaultRequestToExternal、defaultRequestToHandle的行为逐条验证例如camelCaseDash(a11y) a11y、defaultRequestToExternal(wordpress/i18n)返回[wp, i18n]。常见问题与排查建议依赖没有出现在清单里检查是否被 webpack 原生externals提前拦截会令插件看不见该模块或该模块属于BUNDLED_PACKAGESwordpress/dataviews、wordpress/ui等而按设计被直接打包。版本号总是变资产文件版本是基于入口文件与提取的样式内容计算的哈希样式或源码任何变动都会产生新哈希这是预期行为。模块模式下报错 Attempted to use WordPress script in a module说明某个仍是传统脚本的wordpress/*包被放进了模块 bundle需改用脚本模式或等待该包提供模块版本见 lib/util.js。injectPolyfill在模块模式下不生效该选项仅适用于脚本模式模块模式如需 polyfill 请用魔法注释或显式导入。不要多实例从wordpress/scripts配置扩展时先filter掉默认的DependencyExtractionWebpackPlugin。通过合理使用本插件Gutenberg 风格的 WordPress 插件与主题开发者可以彻底告别手工维护依赖清单让 webpack 与 WordPress 的脚本/模块共享机制自动、稳定地协同工作。【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表