聊聊插件
内部程序提供给第三方一些扩展功能的方法,插入使用,具体就是第三方按照一定插件规范编写插件,如何接入到内部程序使用。下面详细针对vue的插件,webpack插件进行简要介绍。
vue插件
vue插件自包含代码,通常是向vue添加全局级功能,比如全局组件,全局指令,全局属性等。可以是自带公开install()方法的object,或者自身就是一个function
如何使用一个插件
- 使用use注入插件,use可以链式调用,依次注入多个插件。
- 基本比如store, router, element组件库等都可以作为插件注入;
import { createApp } from "vue";
createApp(App).use(store).use(element).mount("#app");
如何实现一个插件
// plugins/i18n.js
export default {
install: (app, options) => {
app.config.globalProperties.$translate = key => {
return key.split('.').reduce((o, i) => {
if (o) return o[i]
}, options)
}
}
}
// 使用
import { getCurrentInstance } from "vue";
const title = computed(() => {
const instance: any = getCurrentInstance();
return instance.appContext.app.config.globalProperties.$translate(
"page.title"
);
});
vue是如何支持插件的运行
- 在vue中,定义了initUse,并调用默认把vue实例传进去;
- 在use中,判断当前plugin是否存在,存在就直接返回;如果没有就调用plugin中的install方法,分别对plugin.install是一个函数或者plugin本身就是一个函数进行调用处理。
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
initUse(Vue)
webpack插件
webpack插件,是webpack向开发者提供扩展webpack构建流程中的自定义行为能力。 常见的plugin有html-webpack-plugin,mini-css-extract-plugin...
如何使用一个插件
在配置文件中,引入插件,并实例化,可以传入一些配置选项。
const HelloWorldPlugin = require('hello-world');
const webpackConfig = {
// ... 这里是其他配置 ...
plugins: [
new HelloWorldPlugin({options: true})
]
};
如何实现一个插件
- webpack插件就是一个构造函数,但是该函数原型对象(prototype)上定义了apply方法;
- 在apply方法里面,指定一个绑定到webpack自身的事件钩子;这些钩子的核心是tapable(类似发布订阅),常用的事件钩子有: compilation(文件编译后,但未生成); exit(生成资源到output目录之前); end(生成资源到output目录之后);
- 操作webpack内部实例特定(compilation)的数据
- 功能完成后调用webpack提供的回调(callback),将控制器交给webpack;
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, ((compilation, callback) => {
console.log("webpack 构建过程开始!");
callback();
});
}
}
compiler
是webpack的主要引擎,webpack初始化compiler,然后调用其run,完成编译,全局就一个实例;
compilation
- 重新编译就会由compiler创建生成一个新的compilation实例;
webpack是如何支持插件的运行
- 首先在createCompiler时,会实例化Compiler;
- 然后遍历所有的options.plugins插件列表。如果是插件是函数,直接执行;如果是实例对象,调用apply执行,将compiler传入;
- 最后再触发hooks上,environment,afterEnvironment,initialize登钩子执行。
const createCompiler = rawOptions => {
const options = getNormalizedWebpackOptions(rawOptions);
applyWebpackOptionsBaseDefaults(options);
// 实例化compiler;传入配置;
const compiler = new Compiler(options.context, options);
// 遍历插件列表,执行
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
applyWebpackOptionsDefaults(options);
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
compiler.hooks.initialize.call();
return compiler;
};
compiler钩子原理
webpack采用Tabable,来进行整个编译构建阶段,各个钩子的注册和触发。 在compiler类中,在hooks下面定义了许多钩子;
const { SyncHook, SyncBailHook, AsyncParallelHook, AsyncSeriesHook } = require("tapable");
class Compiler {
constructor(context, options) {
this.hooks = Object.freeze({
initialize: new SyncHook([]),
shouldEmit: new SyncBailHook(["compilation"]),
done: new AsyncSeriesHook(["stats"]),
afterDone: new SyncHook(["stats"]),
additionalPass: new AsyncSeriesHook([]),
beforeRun: new AsyncSeriesHook(["compiler"]),
run: new AsyncSeriesHook(["compiler"]),
emit: new AsyncSeriesHook(["compilation"]),
...
})
// ...其他逻辑
}
}