聊聊插件

186 阅读3分钟

聊聊插件

内部程序提供给第三方一些扩展功能的方法,插入使用,具体就是第三方按照一定插件规范编写插件,如何接入到内部程序使用。下面详细针对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"]),
            ...
        })
        // ...其他逻辑
    }
}