工程插件开发

ice-scripts 通过插件机制,扩展其项目编译和命令运行时的能力。便于常见构建需求和复用解决方案的共享。

插件本质上是一个 JS 模块(npm 包),约定插件初始化模版如下:

  1. module.exports = ({ context, chainWebpack, log, onHook }, options) => {
  2. // 第一项参数为插件 API 提供的能力
  3. // options:插件自定义参数
  4. };

插件方法会收到两个参数,第一个参数是插件提供的 API 接口和能力,推荐结构方式按需使用 API,第二个参数 options 是插件自定义的参数,由插件开发者决定其值。

插件 API

通过插件提供的 API,可以方便拓展和自定义能力。

context

通过 context 访问命令运行时的相关参数:

  • context.command:当前运行命令,值为 dev | build
  • context.commandArgs:cli 命令的 optios 参数,如执行 ice-scripts dev --https 则其内容为 { https: true }
  • context.rootDir:当前命令运行目录
  • context.userConfig:用户配置 ice.config.js 中的配置(与默认配置会进行 deepMerge)
  • context.pkg:命令运行项目的 package.json 信息
  1. module.exports = ({ context }) => {
  2. const { userConfig, command } = context;
  3. if (userConfig.entry) {
  4. console.log('config entry :', userConfig.entry);
  5. }
  6. if (command === 'dev') {
  7. console.log('do someting');
  8. }
  9. };

chainWebpack

通过 chainWepack 对 webpack 配置进行自定义

  1. module.exports = ({ chainWepack }) => {
  2. chainWebpack(config => {
  3. // 通过 webpack-chain 修改 webpack 配置
  4. config.devServer.hot('dist');
  5. config.output.path('dist');
  6. })
  7. }

更多 webpack-chain 用法,参考 自定义 webpack 配置

log

通过 log 向插件提供统一的日志输出方法

  1. module.exports = ({ log }) => {
  2. log.verbose('verbose');
  3. log.info('info');
  4. log.error('error');
  5. log.warn('warn');
  6. }

onHook

通过 onHook 监听命令运行事件。

目前支持监听四种事件:

  • beforeDev:执行dev命令脚本前
  • afterDev:完成dev命令脚本后
  • beforeBuild:执行build命令脚本前
  • afterBuild:完成build命令脚本后
  1. module.exports = ({ onHook }) => {
  2. onHook('beforeDev', () => {
  3. // some code before dev
  4. });
  5. onHook('afterDev', (stats) => {
  6. // 通过stats可以判断webpack是否执行成功
  7. });
  8. }

插件开发示例

下面是 ice-scrpts-plugin-css-assets-local 插件的代码示例

  1. const ExtractCssAssetsWebpackPlugin = require('extract-css-assets-webpack-plugin');
  2. module.exports = ({ context, log, chainWebpack }, options) => {
  3. const { command } = context;
  4. // 仅在运行 ice-scripts build 的时候执行
  5. if (command === 'build') {
  6. log.info('离线化构建项目,自动下载网络资源,请耐心等待');
  7. chainWebpack((config) => {
  8. // 添加 extract-css-assets-webpack-plugin 插件
  9. config.plugin('ExtractCssAssetsWebpackPlugin')
  10. .use(ExtractCssAssetsWebpackPlugin, [{
  11. outputPath: options.outputPath || 'assets',
  12. relativeCssPath: options.relativeCssPath || '../',
  13. }]);
  14. });
  15. }
  16. };

修改 webpack 配置

大部分情况下插件都是在修改 webpack 配置,此处收集一些修改 webpack 的常见场景。

新增 webpack loader

  1. // ice.config.js
  2. module.exports = {
  3. chainWebpack: (config) => {
  4. config.module
  5. .rule('new-rule')
  6. .test(/\.scss$/)
  7. .use('sass-loader')
  8. .loader('sass-loader');
  9. }
  10. }

修改已有 webpack loader

内置 webpack loader 请参考 loader 规则命名

  1. // ice.config.js
  2. module.exports = {
  3. chainWebpack: (config) => {
  4. config.module
  5. .rule('scss') // ice-scripts 中已定义这条规则
  6. .use('sass-loader')
  7. .loader('sass-loader')
  8. .tap(options => {
  9. // 根据需求修改 options 中的配置
  10. return options;
  11. });
  12. }
  13. }

新增 webpack 插件

  1. // ice.config.js
  2. const WebpackPluginImport = require('webpack-plugin-import');
  3. module.exports = {
  4. chainWebpack: (config) => {
  5. config
  6. // 定义插件名称
  7. .plugin('WebpackPluginImport')
  8. // 第一项为具体插件,第二项为插件参数
  9. .use(WebpackPluginImport, [[
  10. {
  11. libraryName: /@ali\/ice-.*/,
  12. stylePath: 'style.js',
  13. },
  14. ]]);
  15. }
  16. }

修改已有 webpack 插件

内置 webpack plugin 请参考 webpack 插件命名

  1. // ice.config.js
  2. module.exports = {
  3. chainWebpack: (config) => {
  4. config
  5. .plugin('WebpackPluginImport')
  6. .tap(args => {
  7. // 根据需求返回 WebpackPluginImport 插件构造函数的参数
  8. return [];
  9. });
  10. }
  11. }

修改指定命令的 webpack 配置

  1. // ice.config.js
  2. module.exports = {
  3. chainWebpack: (config, { command }) => {
  4. // 执行 ice-scripts dev 命令时
  5. if (command === 'dev') {
  6. config.devServer.historyApiFallback(true);
  7. }
  8. // 执行 ice-scripts build 命令时
  9. if (command === 'build') {
  10. config.optimization.minimize(true);
  11. }
  12. }
  13. }

配置 splitChunks

通过配置 splitChunks 实现第三方库分离:

  1. module.exports = {
  2. chainWebpack: (config, { command }) => {
  3. config.optimization.splitChunks({ cacheGroups: {
  4. vendor: {
  5. test: /[\\/]node_modules[\\/]/, // 匹配 node_modules 目录
  6. name: 'vendor',
  7. chunks: 'all',
  8. minChunks: 1,
  9. },
  10. }});
  11. }
  12. }

修改 UglifyJsPlugin

ice-scripts 内置了 uglifyjs-webpack-plugin 的实践配置,可以通过定制能力修改默认配置:

  1. module.exports = {
  2. chainWebpack: (config, { command }) => {
  3. // 仅在 build 构建下会存在 UglifyJsPlugin
  4. if (command === 'build') {
  5. // 生成 soruce-map 进行调试
  6. config.devtool('source-map');
  7. config.optimization
  8. .minimizer('UglifyJsPlugin')
  9. .tap(([options]) => [
  10. {
  11. ...options,
  12. sourceMap: true,
  13. },
  14. ]);
  15. }
  16. }
  17. }

修改 babel 配置

ice-scripts 的 babel 配置同样可以通过 webpack-chain 方式进行修改:

  1. module.exports = {
  2. chainWebpack: (config, { command }) => {
  3. // 内置 jsx 和 tsx 规则均会使用到 babel 配置
  4. ['jsx', 'tsx'].forEach((rule) => {
  5. config.module
  6. .rule(rule)
  7. .use('babel-loader')
  8. .tap((options) => {
  9. // 添加一条 babel plugin,同理可添加 presets
  10. options.plugins.push(require.resolve('babel-plugin-transform-jsx-list'));
  11. // 修改 babel preset 配置,同理可修改 plugins
  12. options.presets = options.presets.map((preset) => {
  13. if (Array.isArray(preset)) {
  14. const [modulePath, presetOptions] = preset;
  15. // 判断指定配置
  16. if (modulePath.indexOf('preset-env') > -1) {
  17. return [
  18. modulePath,
  19. // 自定义新的 options
  20. { ...presetOptions, modules: false },
  21. ];
  22. }
  23. }
  24. return preset;
  25. });
  26. return options;
  27. });
  28. });
  29. }
  30. }

查看所有 webpack 配置

  1. // ice.config.js
  2. const Config = require('webpack-chain');
  3. module.exports = {
  4. chainWebpack: (config) => {
  5. // 通过 Config.toString 的方法输出当前 config 配置
  6. console.log(Config.toString(config));
  7. }
  8. }

查看 rule 配置

  1. // ice.config.js
  2. const Config = require('webpack-chain');
  3. module.exports = {
  4. chainWebpack: (config) => {
  5. // 输出所有定义的 rule 配置
  6. console.log(Config.toString(config.module.toConfig().rules));
  7. // 输出指定 rule 的配置
  8. const ruleName = 'scss';
  9. console.log(Config.toString(
  10. config.module.rule(ruleName).toConfig(),
  11. ));
  12. }
  13. }

查看 plugin 配置

  1. // ice.config.js
  2. const Config = require('webpack-chain');
  3. module.exports = {
  4. chainWebpack: (config) => {
  5. // 输出所有定义的 plugin 配置
  6. console.log(Config.toString(config.toConfig().plugins));
  7. // 输出指定 rule 的配置
  8. const pluginName = 'WebpackPluginImport';
  9. console.log(Config.toString(
  10. config.plugins.get(pluginName).toConfig(),
  11. ));
  12. }
  13. }