墙裂推荐plugin(pluginhybrid什么意思)
Webpack Plugin 对于一些前端同学来说,算得上是既陌生又熟悉的存在刚好前段时间我实现了一个Webpack Plugin: I18nExtractorWebpackPlugin,其目的是收集代码中所有的I1
Webpack Plugin 对于一些前端同学来说,算得上是既陌生又熟悉的存在刚好前段时间我实现了一个Webpack Plugin: I18nExtractorWebpackPlugin,其目的是收集代码中所有的I18n string ID 并生成js文件
基于该插件最终实现自动注册 React/Vue app 中引用到的 Mojo 侧所有 I18n string ID, 解决了之前一直需要靠SE手动维护 I18n ID 的痛点借此机会我正好进入了 Webpack Plugin 这个“黑盒”一探究竟,去了解它内部的实现机制,并在此与大家做个分享。
图片源自网络,侵删Webpack Plugin定义首先来看看 Webpack 官网对 Plugin 的官方解释:Webpack 插件是一个具有 apply 方法的 JavaScript 对象apply 方法会被 Webpack Compiler 调用,并且在整个编译生命周期都可以访问 Compiler 对象。
它向第三方开发者提供了 Webpack 引擎中完整的能力使用阶段式的构建回调,开发者可以在 Webpack 构建流程中引入自定义的行为emmm,这解释就真的很官方了,我好像看明白,又好像没明白….
图片源自网络,侵删翻译成白话大概就是:Webpack Plugin 可以允许开发者在 Webpack 构建整个项目的过程中插入我们想要实现的自定义行为,Webpack 在构建的不同阶段都会向用户提供不同的钩子函数(hook), 用户可以在这些 hooks 中实现想做的事情。
还是不太明白?别着急,再往下看看Webpack架构虽然 Webpack 很复杂,但是其内部实现却很优雅,是一个典型的可插拔架构,并且灵活可扩展近几年出来的一些构建工具,大多也都参考了 Webpack 的这种架构方式。
Webpack 对项目构建打包的过程本质上是由许多个内置的 Plugin 实现的,在不同的阶段由不同的 Plugin 执行相关的工作关键点在于 Webpack 需要实现一种事件流将所有 Plugin 串联起来,而实现这一切的核心在于其内部借助了 。
Tapable:Tapable 是一个事件订阅发布的库,Webpack 通过 Tapable 注册自定义事件,并在合适的时机触发执行其实 Tapable 的概念和 Vue/React 中的生命周期钩子函数有点类似,都是在不同的生命周期执行不同的自定义函数。
Tapable的使用也非常简单,只需三步:实例化钩子函数注册事件触发事件module.exports = I18nExtractorWebpackPlugin;const { SyncHook } = require(
"tapable"); //这是一个同步钩子//第一步:实例化钩子函数,可以在这里定义形参const syncHook = new SyncHook(["author"]);//第二步:注册事件1syncHook.tap(
"监听器1", (name) => { console.log("监听器1:", name);});//第二步:注册事件2syncHook.tap("监听器2", (name) => { console.
log("监听器2", name);});//第三步:触发事件syncHook.call("起来干饭了!");执行结果:监听器1 起来干饭了!监听器2 起来干饭了!这里只是举了一个 Tapable 的例子,有兴趣的同学可以移步以下链接进一步了解强大的 Tapable:
https://www.npmjs.com/package/tapableWebpack构建过程如果把 Webpack 构建过程比喻成大饭店完成一场宴席工作,Tapable 就是一张时间计划表,大致上分为三个阶段:
准备食材——打包开始前的准备工作制作菜肴——编译阶段摆盘上菜——编译完成后的阶段光有时间表还不行,还得有一个大管家来安排这一切,这种时候 Compiler 就该出场了:Compiler对象代表了一次webpack构建过程中
完整的生命周期,它提供了webpack所有的设置和上下文环境,而插件Plugin就能通过compiler对象访问到webpack环境中的所有信息Compiler 模块是 Webpack 的主要引擎,它通过 CLI 或者 Node API 传递的所有选项创建出一个 。
Compilation 实例它扩展(extends)自 Tapable 类,用来注册和调用插件大多数面向用户的插件会首先在 Compiler 上注册在这场浩大的宴席工作中,最重要的莫过于制作菜肴这个过程了,也就是 Webpack 对项目进行编译打包的过程,这时候就该要介绍一下我们的厨师。
Compilation:Compilation 代表了一次资源版本构建,提供当前模块资源,编译生成的资源以及变化的文件等重要信息Compilation 模块会被 Compiler 用来创建新的 Compilation。
对象(或新的 build 对象)Compilation 实例能够访问所有的模块和它们的依赖(大部分是循环依赖)它会对应用程序依赖图中的所有模块, 进行字面上的编译(literal compilation)。
在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)Compilation 本身也提供了很多颗粒度更细的 hook 给插件 Plugin 选择使用。
下面用一张图对上文进行总结:
可前往以下链接获得关于 Webpack Plugin hook 更详细的介绍:https://webpack.docschina.org/api/compiler-hooks/#additionalPass
说了这么多,肯定有人会说:
图片源自网络,侵删那么接下来,马上开始介绍如何实现 I18nExtractorWebpackPluginWebpack Plugin的实现方法i18n-extractor-webpack-plugin 。
需求分析:基于 Mojo 开发的 hybrid app(React/Vue), 需要收集所有从 Mojo 侧引用的 I18n keys 进行静态注册以 React-Bridge 为例, 其包含多个APP, 每个APP又可能依赖于多个组件,手动维护 I18n ID 是一件令人头秃的事情,因此我们需要实现一个插件自动收集 I18n keys。
首先,Webpack Plugin 其实就是一个包含 apply 方法的函数,Webpack 在打包过程中会执行插件的 apply 方法,并且会把 Compiler 作为参数传入:classI18nExtractorWebpackPlugin
{ apply(compiler){// console.log("this is a webpack plugin") }}这就是Plugin的雏形那么为什么是 apply 方法呢?。
其实原因并不深奥,在 Webpack 源码中,它调用的就是插件的 apply 方法关键在于:如何在 apply 方法中调用各种生命周期 hook 函数以满足插件需求以项目为例,在 React-bridge 项目中对于 I18n 的引用方法一般为以下形式,而我们最终需要的是将第一个参数,也就是 string ID 抓取出来。
i18n(123,"default string")// i18n(id, string)在 Webpack 构建过程中,可以将代码看成是 stream,也就是字符串流因此可以利用正则表达式匹配 I18n function pattern:。
this.functionPattern = /i18n\(\s*(\d+),\s*.*?\)/搞定如何匹配 pattern 后,接下来需要解决何时匹配这个问题要出场的就是compilation.hooks.optimizeChunks。
这个 hook 了optimizeChunks 会在 Compilation 生成 chunk 之后,但在 minification 之前调用,此时 chunk 还未经压缩优化,因此仍然能获取到 chunk 对应的源码。
在该 hook 中,可以通过之前的 functionPattern 匹配抓取 string ID在遍历 chunk 时,一般我们不会关心 node_modules 路径下的 third library,因此需要设置一个 module filter 过滤掉我们不关心的文件。
module filter 可以用正则表达式表示:this.moduleFilter = [ /((?:[^!?\s]+?)(?:\.js|\.jsx|\.ts|\.tsx))$/, /^((?!node_modules).)*$/,
];核心实现方法如下:apply(compiler){//compiler.hooks.compilation.tap 表示注册一个在compilation创建之后执行的hook函数,//当compilation创建完成之后webpack会调用这个hook并将compilation作为参数传入。
compiler.hooks.compilation.tap((compilation)=>{//compilation.hooks.optimizeChunks.tap 表示在对chunk进行优化阶段开始之前调用,
//此时的chunk由于还未被压缩优化,因此仍然可以获取到源码source code compilation.hooks.optimizeChunks.tap((chunks)=>{ chunks.forEach((chunk)=>{
compilation.chunkGraph .getChunkModules(chunk) .forEach((
module)=>{if(filterModule(module)){const source = module._source._value;while (this.functionPattern.exec(source)) !== null) {
//收集所有string ID } }
}) ... }) }) })}另外,在编译结束后我们需要将 string ID 以及匹配到的 I18n string 相关的信息进一步预处理后作为返回值暴露出去,
方便使用者基于插件做一些上层的逻辑处理,因此我们还需要使用到 compiler.hooks.done,该 hook 会在整个 Compilation 构建工作结束后被调用apply(compiler){。
...//ompiler.hooks.done 表示注册一个在 compilation完成后执行的操作事件 compiler.hooks.done.tap(()=>{ ... if(this.done){
this.done(results)//this.done是创建插件时传进来的一个callback函数 } }) }最后,为了增强插件的扩展性, 我们需要将 functionPattern、moduleFilter、done 等关键字以参数的形式暴露给插件使用者:。
classI18nExtractorWebpackPlugin { constructor(options) { options = options || {};this.functionPattern = options.functionPattern || /i18n\(\s*(\d+),\s*.*?\)/;
this.done = options.done || undefined;this.groupIndex = options.groupIndex || [1];this.moduleFilter = options.moduleFilter || [
/((?:[^!?\s]+?)(?:\.js|\.jsx|\.ts|\.tsx))$/, /^((?!node_modules).)*$/, ]; } apply(compiler){
// ... }}使用 Plugin 的过程也很简单,只需要在 webpack.config.js 中创建插件实例并传入配置参数:module.exports = { ...,
plugins:[ new I18nExtractorWebpackPlugin({ done: (json) => { rimraf.sync(path.resolve(__dirname,
dist)); generateLoaders(json, path.resolve(__dirname, path/)); } }) ]}Plugin 执行后,在指定路径下会生成
[bundleName]Loader.js 文件,该文件包含所有 React-bridge 项目中用到的 string ID,我们只需要在 Mojo 侧引入该 [bundleName]Loader.js
文件便可实现静态注册 string ID目前我们已经在 CI 中基于该插件,实现了一整套自动管理 React-bridge I18N string ID 的 workflow, 再也不用担心头秃啦
参考文献:https://juejin.cn/post/7170852747749621791#heading-2https://webpack.docschina.org/
免责声明:本站所有信息均搜集自互联网,并不代表本站观点,本站不对其真实合法性负责。如有信息侵犯了您的权益,请告知,本站将立刻处理。联系QQ:1640731186