实现构建工具之插件

衔接上文,既然添加了loader,那么就干脆朝着webpack方向完善其他主要功能,此文主要是提供Plugin实现部分

参考webpack官网,发现其对插件的解释为:

plugins 选项用于以各种方式自定义 webpack 构建过程

可以理解为插件可以参与整个 webpack 构建过程,那么由此可以推断如下几点:

  1. 可自定义
  2. webpack构建过程中会有多个生命周期埋点
  3. plugin在对应生命周期处执行

再深入了解,我们发现自定义插件有如下规则:

  1. 插件为实例对象
  2. 插件对象需要包含apply方法,即定义该实例的类需要绑定apply方法
  3. apply方法传入webpack构建上下文
  4. 可以在对应构建上下文提供的周期函数钩子内执行我们需要的操作

思考

  1. 启动时生成构建上下文
  2. 构建过程埋点
  3. 注册插件,执行插件实例的apply方法,在构建上下文提供的生命周期中执行内容

实现

构建上下文,应包含配置的入口,出口,loaders等,此处只写插件相关

const ctx = {
    beforeRun: {
        pluginMap: {},
        tap(pluginName, callback) {
            this.pluginMap[pluginName] = callback
        },
    },
    run: {
        pluginMap: {},
        tap(pluginName, callback) {
            this.pluginMap[pluginName] = callback
        },
    },
    beforeCompile: {
        pluginMap: {},
        tap(pluginName, callback) {
            this.pluginMap[pluginName] = callback
        },
    },
    compile: {
        pluginMap: {},
        tap(pluginName, callback) {
            this.pluginMap[pluginName] = callback
        },
    },
    make: {
        pluginMap: {},
        tap(pluginName, callback) {
            this.pluginMap[pluginName] = callback
        },
    },
    seal: {
        pluginMap: {},
        tap(pluginName, callback) {
            this.pluginMap[pluginName] = callback
        },
    },
}

埋点函数

// 生命周期事件埋点
const execHook = (hook) => {
    //批量执行该此生命周期注册的函数列表
    Object.values(ctx[hook].pluginMap).forEach(pluginFunc => {
        pluginFunc()
    })
}

browserify.js

const fs = require("fs");
const { resolve } = require("path");
const { useLoader } = require("./loader.js");
const { registerPlugin, execHook } = require("./plugin")

//注册插件
registerPlugin()

execHook('beforeRun')
//开始初始化参数
const moduleFuncCache = [];
let curIndex = 0;

//记录path和数组对应资源数组位置
const pathIndexMap = {};
const codeSplicing = (path) => {
  // 获取绝对路径
  const wholePath = resolve(path);
  if (pathIndexMap[wholePath] !== undefined) return;

  moduleFuncCache.push(`
    function(){
      ${useLoader(wholePath, codeSplicing, pathIndexMap)}
    }
  `);
  pathIndexMap[wholePath] = curIndex++;
};

const getCode = () => {
  // eval方式转函数
  return `
        // 自执行函数,避免全局污染
        (function(){
            const moduleCache = []
            const _require = function(index){
                // 第一次引用该模块则执行,后续从缓存中取
                if(!moduleCache[index]) moduleCache[index] = formatModuleFuncCache[index]()
                return moduleCache[index]
            }

            //数组收集,省去了json序列化的过程,从而省去转码的过程
            const formatModuleFuncCache = [${moduleFuncCache}]

            //执行入口文件代码
            formatModuleFuncCache[${moduleFuncCache.length - 1}]()
        })()
    `;
};

//主函数,传入文件路径,返回最终打包完成的代码块
const browserify = (path) => {
  execHook('run')
  // 为每个require的模块拼接代码,为其提供module实例,并返回module.exports
  codeSplicing(path);

  // 阻止代码,使其能解析代码cache对象,并依照引入顺序来执行代码块
  execHook('beforeCompile')
  const code = getCode();
  execHook('compile')
  return code
};

// 执行命令行传入打包源文件 node ./browserify.js index.js,此时path即index.js
const [path] = process.argv.splice(2);
// 写目标文件;
execHook('make')
try {
  fs.mkdirSync("./dist");
} catch (error) { }
fs.writeFileSync("./dist/chunk.js", browserify(path));
execHook('seal')

插件列表

class ConsoleLogOnBuildPlugin {
    apply(compiler) {
        const start = Date.now()

        compiler.run.tap('ConsoleLogOnBuildPlugin', (compilation) => {
            console.log(`${moment(start).format("YYYY-MM-DD HH:mm:ss")}: mini-webpack 构建正在启动!`);
        });

        compiler.seal.tap('ConsoleLogOnBuildPlugin', (compilation) => {
            console.log(`${moment().format("YYYY-MM-DD HH:mm:ss")}: mini-webpack 构建完成!`);
            console.log(`构建时长: ${Date.now() - start}`);
        });
    }
}

const pluginList = [
    new ConsoleLogOnBuildPlugin()
]

测试效果:

2022-02-14 18:09:07: mini-webpack 构建正在启动!
2022-02-14 18:09:10: mini-webpack 构建完成!
构建时长: 3443

总结

插件通过构建工具的埋点,以及构建上下文,可以执行各种自定义功能!

项目地址:https://github.com/a793816354/myBrowserify/tree/mini-webpack

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 2019.3.38 比较两个文件 英文 detect: 检测2019.3.27 如何找到第一个bug出现的comm...
    饶家俊阅读 2,633评论 0 1
  • 前言 replugin-plugin-gradle 是 RePlugin 插件框架中提供给replugin插件用的...
    osan阅读 7,129评论 8 33
  • 081.什么是 Polyfill ? Polyfill 指的是用于实现浏览器并不支持的原生 API 的代码。 比如...
    造了个轮子阅读 318评论 0 0
  • 响应式布局的理解 响应式开发目的是一套代码可以在多种终端运行,适应不同屏幕的大小,其原理是运用媒体查询,在不同屏幕...
    懒猫_6500阅读 801评论 0 0
  • 基本功考察 1.关于Html 1、html语义化标签的理解、结构化的理解;能否写出简洁的html结构;SEO优化。...
    韩娜爱吃辣_前端程序媛阅读 1,485评论 0 1