通过插件定制工程能力
插件工程能力通过 src/index.ts
定义,结构如下
module.exports = ({ context, onGetWebpackConfig, log, onHook, ...rest }, options) => {
// 第一项参数为插件 API 提供的能力
// options:插件自定义参数
};
该方法会接收两个参数,第一个参数是插件提供的 API 接口,推荐按照解构方式使用,第二个参数 options
是插件自定义的参数,由插件开发者决定提供哪些选项给用户配置。
插件 API
通过插件提供的 API,可以方便拓展和自定义能力。
context
包含运行时的各种环境信息:
command
当前运行命令,start/build/testcommandArgs
script 命令执行时接受到的参数rootDir
项目根目录userConfig
用户在 build.json 中配置的内容pkg
项目 package.json 中的内容
onGetWebpackConfig
通过 onGetWebpackConfig
获取 webpack-chain 形式的配置,并对配置进行自定义修改:
module.exports = ({onGetWebpackConfig, registerTask}) => {
registerTask('default', webpackConfig);
onGetWebpackConfig((config) => {
config.entry('xxx');
});
}
onGetJestConfig
通过 onGetJestConfig
获取 jest 配置,可对配置进行自定义修改:
module.exports = ({onGetJestConfig}) => {
onGetJestConfig((jestConfig) => {
const modifiedJestConfig = modify(jestConfig);
return modifiedJestConfig;
});
};
onHook
通过 onHook 监听命令运行时事件,onHook 注册的函数执行完成后才会执行后续操作,可以用于在命令运行中途插入插件想做的操作:
module.exports = ({ onHook }) => {
onHook('before.build.load', () => {
// do something before dev
});
onHook('after.build.compile', (stats) => {
// do something after build
});
}
目前支持的命令执行生命周期如下:
start 命令
生命周期 | 参数 | 调用时机 |
---|---|---|
before.start.load | { args: CommandArgs, // 启动参数 webpackConfig: ConfigInfo[] } | 配置转化之前 |
before.start.run | { args: CommandArgs, // 启动参数 config: WebpackConfig[] } | 构建执行之前 |
after.start.compile | { url: string // serverUrl, isFirstCompile: boolean, stats: WebpackAssets // Vite 模式下不存在 stats } | 编译结束,每次重新编译都会执行 |
before.start.devServer | { url: string // serverUrl, devServer: WebpackDevServer | viteServer } |
after.start.devServer | { url: string // serverUrl, devServer: WebpackDevServer | viteServer, err: Error } | dev server 启动后,仅支持 Webpack 构建模式 |
build 命令
生命周期 | 参数 | 调用时机 |
---|---|---|
before.build.load | { args: CommandArgs, // 启动参数 webpackConfig: ConfigInfo[] } | 配置转化之前 |
before.build.run | { args: CommandArgs, // 启动参数 config: WebpackConfig[] } | 构建执行之前 |
after.build.compile | { err: Error, stats: WebpackAssets // Vite 模式下不存在 stats } | 构建结束 |
test 命令
生命周期 | 参数 | 调用时机 |
---|---|---|
before.test.load | { args: array // 启动参数 } | 获取 jest 配置之前 |
before.test.run | - | jest 执行之前 |
after.test | { result // jest执行结果 } | 测试命令执行结束 |
log
统一的 log 工具,底层使用 npmlog
,便于生成统一格式的 log。
log.info('start');
log.verbose('debug');
log.error('exit');
registerUserConfig
为用户配置文件 build.json
中添加自定义字段。
module.exports = ({registerUserConfig}) => {
registerUserConfig({
name: 'custom-key',
validation: 'boolean' // 可选,支持类型有 string, number, array, object, boolean
});
};
registerClioption
为命令行启动添加自定义参数。
module.exports = ({registerClioption}) => {
registerCliOption({
name: 'custom-option', // 参数名
commands: ['start'], // 命令
configWebpack: (arg) => {} // 可选,arg 为命令行参数对应值
});
};
registerMethod
注册自定义方法。通过 applyMethod
调用。
module.exports = ({registerMethod}) => {
registerMethod(name, func); // name, func 分别为方法名和方法
};
modifyUserConfig
修改用户配置文件。
module.exports = ({modifyUserConfig}) => {
modifyUserConfig(key, value); // key, value 分别为用户配置文件键值对
};
registerTask
添加 Webpack 配置,配置为 webpack-chain 形式。
module.exports = ({registerTask}) => {
registerTask(name, config); // name: Task名, config: webpack-chain 形式的配置
};
getAllTask
获取所有 Webpack 配置名称。
module.exports = ({getAllTask}) => {
const alltasks = getAlltask();
};
getAllPlugin
获取所有插件。
module.exports = ({getAllPlugin}) => {
// 获取所有插件数组
// 类型:() => [{pluginPath, options, name}]
const plugins = getAllPlugin(); ,[]
}
扩展 API
除了以上由 build-scripts 内置支持的 API,我们还通过 icejs 对插件 API 做了扩展,扩展的 API 需要通过以下方式调用:
module.exports = ({ applyMethod }) => {
// 第一个参数对应 API 名称,第二个参数对应 API 参数
applyMethod('addIceExport', { source: `./config`, exportName });
}
addPluginTemplate
为运行时目录中的模版生成提供统一的渲染服务(目录):
// 默认渲染目录由插件名称决定
applyMethod('addPluginTemplate', path.join(__dirname, '../template'));
// 指定模版渲染目录
const renderData = {}; // 可选,ejs 额外渲染参数
applyMethod('addPluginTemplate', {
template: path.join(__dirname, '../src/types'),
targetDir: 'router/types',
}, renderData);
addRenderFile
向运行时目录添加提供文件的渲染服务(文件):
const sourceFile = '../template/index.tsx.ejs');
const targetFile = path.join(runtimeDir, 'source/index.tsx');
const renderData = {}; // 可选,ejs 额外渲染参数
applyMethod('addRenderFile', path.join(__dirname, sourceFile, targetFile, renderData);
addExport
向 ice
里注册模块,实现 import { foo } from 'ice';
的能力:
// API 参数
// source: 指定导出模块引入的文件目录
// exportName: 从 ice 中导出的模块名称。
// specifier: 从 source 中默认导出方式,可选,默认值为 default export,如果为 named export 需要额外设置
//
// 实现 import { request } from 'ice'; request 由插件的 `./request/request` 文件实现
applyMethod('addExport', { source: './request/request', exportName: 'request' })
addAppConfigTypes
向 appConfig 添加类型
// 第一项参数对应 API 名称,第二项参数对应 API 参数。
//
// API 参数:
// source: 类型声明文件。./foo/types,对应 ICE_TEMP_DIR/foo/types。 ICE_TEMP_DIR,可通过 getValue('TEMP_PATH') 获得。注意:需先将对应类型文件移至 ICE_TEMP_DIR。
// specifier: 导出类型标识符,可选,默认值为 '*'。
// exportName: 添加至 appConfig 类型 IAppConfig 上的导出名。
//
// 结果为:
// // ICE_TEMP_DIR/types.ts
// import { Foo } from './foo/types';
// export interface IAppConfig {
// foo?: Foo
// }
applyMethod('addAppConfigTypes', { source: `./foo/types`, specifier: '{ Foo }', exportName: `foo?: Foo` });
getPages
获取 src/pages
下的一级页面列表:
// ['Home', 'About']
const pages = this.applyMethod('getPages', this.rootDir);
addDisableRuntimePlugin
禁用插件的运行时能力
// 禁用内置的 request 的运行时能力
applyMethod('addDisableRuntimePlugin', 'build-plugin-ice-request');
watchFileChange
监听 src
下的文件变化:
applyMethod('watchFileChange', 'src/config.*', async (event: string) => {
if (event === 'unlink' || event === 'add') {
// do something
}
});
插件参数
用户可以在 build.json
中指定插件参数:
{
"plugins": [
["build-plugin-foo", {
"type": "bar"
}]
]
}
那么在 build-plugin-foo 里就可以获取到这个参数:
module.exports = ({ context, log }, options) => {
const { type } = options;
log.info(type); // => bar
}
插件通信
插件间需要进行通信的场景诉求:
- 不同插件之间需要知道彼此的存在来确定是否执行相应的逻辑
- 多个插件共有的配置信息可以抽出来,在某个插件中进行配置
使用 setValue
和 getValue
两个API来实现,分别用于数据的存取。
setValue
类型:(key: string | number, value: any) => void
,示例:
// build-plugin-test
module.exports = ({ setValue }) => {
setValue('key', 123);
}
getValue
类型:(key: string | number) => any
,示例:
module.exports = ({getValue}) => {
const value = getValue('key'); // 123
}
同时在 icejs 中也内置了几个变量:
const projectType = getValue('PROJECT_TYPE'); // ts|js
const iceDirPath = getValue('TEMP_PATH'); // 对应 .ice 的路径
类型
接口类型通过以下方法引入:
import { IPlugin } from 'build-scripts';