插件机制是如何工作的

266 阅读3分钟

前言

作为解决扩展性的普遍方案,很多工具都支持引入插件来应对个性化场景的需求。比如现在已经离不开的webpack, babel. 冉冉升起的vite. 还有我们伴随我们青春的老大哥jquery. 下面我将尝试用两条叙事线来说明,一条是CLI,另一条是使用CLI的项目。

1. 初始化一个极简CLI

初始化一个项目

npm init -y

简单装两个依赖,意思一下

npm i webpack minimist -D
// minimist这个库是解析命令行参数用的

在根目录创建这个CLI打包时要用的webpack的配置文件

// ./webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bandle.js'
  }
}

package.json里加入bin字段

// ./package.json
"bin":{
   "snowpack":"./bin/index.js"
},

创建bin文件夹,里面创建index.js, 我们开始写内容

#!/usr/bin/env node
// ./bin/.index.js
const { join } = require('path')
const webpack = require('webpack')
// 假设这个`CLI`用`snowpack.config.js`作为配置文件。
const snowpackConfig = 'snowpack.config.js'
// 读取webpack配置
const webpackConfig = require('../webpack.config.js')
// 命令和函数的映射
const __commands = {}
// 这个对象会传给用户传入的函数里
const api = {
  // 在插件作者传入的函数里会调用registeCommand注册命令
  registeCommand: (command, func) => {
    if(!__commands[command]){
      __commands[command] = func
    }
  }
}
...

先写到这里,我们切换故事线,切换之前回顾一下snowpack的目录结构。

.
|-snowpack
  |-bin
  |  |-index.js
  |-node_modules
     |...
  |-package-lock.json
  |-package.json

2. 创建一个要使用 "snowpack" 的项目

同样先npm init一下

npm init -y

在根目录创建一个index.js, 并随便写一写代码

// ./index.js
console.log('im test project')

在根目录创建一个snowpack的配置文件

// ./snowpack.config.js
const { clean } = require('./plugins.js')
module.exports = {
  plugins:{
    commands: [clean('cleaned')]
  }
}

再把plugins.js写一写

// ./plugins.js
module.exports = {
  clean: (str) => (api) => {
  // 这里接收api对象
    api.registeCommand('clean', () => {
      console.log('exec plugin', str)
    })
  }
}

最后看一下目录结构

.
|-testProject
  |-index.js
  |-package.json
  |-plugins.js
  |-snowpack.config.js

3. 补上 ./bin/index.js 剩余部分

第一节我们写到这里,通过第二节我们知道了snowpack.config.js的内容,和api对象会被传到哪里。

// ./bin/.index.js
const { join } = require('path')
const webpack = require('webpack')
// 假设这个`CLI`用`snowpack.config.js`作为配置文件。
const snowpackConfig = 'snowpack.config.js'
// 读取webpack配置
const webpackConfig = require('../webpack.config.js')
// 命令和函数的映射
const __commands = {}
// 这个对象会传给用户传入的函数里
const api = {
  // 在插件作者传入的函数里会调用registeCommand注册命令
  registeCommand: (command, func) => {
    if(!__commands[command]){
      __commands[command] = func
    }
  }
}
...

好,接着往下写

// ./bin/.index.js
...
// 封装一个执行webpack打包的函数
const runWebpackBuild = () => {
  webpack(webpackConfig, (err, status) => {
    if(err || status.hasErrors()){
      console.log('build failed')
    }
    console.log('build success')
  })
}
// 读取用户项目的配置文件
const readLocalOptions = () => {
  return new Promise((resolve, reject) => {
    const options = require(join(process.cwd(), snowpackConfig)) || {}
    // commands 就是用户传入的函数数组
    const { plugins: {commands = []} = {}} = options
    if(commands.length > 0){
      for(command of commands){
        // 调用用户传入的函数时把api暴露出去
        command(api)
      }
    }
    resolve(__commands)
  })
}
// 使用minimist把cli命令的参数提取出来
const minimist = require('minimist')
const args = minimist(process.argv.slice(2))
// 调用readLocalOptions,执行传入的命令参数
readLocalOptions().then((__commands)=>{
  const arg = args._[0]
  const func = __commands[arg]
  // 传入的参数如果存在就执行
  if(func){
    func()
  }else{ // 没有就走默认打包逻辑
    runWebpackBuild()
  }
})

3. 让我们康康效果

敲到这里就差不多了,先到snowpack目录下执行npm link

npm link

再到testProject目录npm link snowpack, 建立软连接

npm link snowpack

此时的目录

.
|-testProject
  |-node_modules
  |  |-.bin
  |  |-snowpack
  |-index.js
  |-package-lock.json
  |-package.json
  |-plugins.js
  |-snowpack.config.js

在命令行输入snowpack

xuxuefeng@xuxuefengdeMacBook-Pro-2 testProject % snowpack
build success

在命令行输入snowpack clean

xuxuefeng@xuxuefengdeMacBook-Pro-2 testProject % snowpack clean
exec plugin cleaned

嗯,运行正常

结语

从这个小例子,能大体知道插件是如何工作的。但真正的实现肯定要更加复杂,有时间可以读一读具体源码。