Skip to content

🐙 基于 Webpack 的插件化工程构建工具,支持快速建设一套开箱即用的工程方案。

License

Notifications You must be signed in to change notification settings

ice-lab/build-scripts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

3a94a8e · Apr 2, 2024
Nov 22, 2022
Mar 25, 2022
Apr 2, 2024
Aug 12, 2020
Mar 25, 2022
Jan 26, 2022
Feb 8, 2022
Aug 12, 2020
Aug 12, 2020
Aug 12, 2020
Aug 12, 2020
Nov 22, 2022
Aug 12, 2020
Aug 12, 2020
Mar 25, 2022
Jul 6, 2023
Mar 25, 2022
Mar 25, 2022
Mar 25, 2022

Repository files navigation

2.x | 1.x | 0.x

build-scripts

NPM version NPM downloads

基于 Webpack 的插件化工程构建工具,支持快速建设一套开箱即用的工程方案。

目录

特性

  • 完善灵活的插件能力,帮助扩展不同工程构建的场景
  • 提供多构建任务机制,支持同时构建多份产物
  • 标准化构建&调试的完整流程,同时提供 Hook 能力进行定制
  • 已支持多种场景:
    • React 项目开发
    • Rax 项目开发
    • NPM 包开发
    • 天马模块开发

常见问题

NPM 包名是 build-scripts 还是 @alib/build-scripts

1.x 以及未来都以 build-scripts 为准,0.x 版本当时因为命名被占用只能使用 @alib/build-scripts 这个包名。

2.x 相比 1.x 有什么变化?

参考 版本升级 章节。

何时使用 build-scripts?

多个项目共享配置以及其他工程能力,同时支持插件扩展&修改配置。

使用 build-scripts 的项目如何修改工程配置?

build-scripts 核心是提供一套完善的工程插件设计,本身不耦合任何工程配置,也不具备任何工程调试构建能力,具体的工程配置都是由插件提供的,因此此类问题建议查阅下方对应场景的文档。

使用场景

基于 build-scripts 目前已支持多种场景,覆盖大多数的研发场景,当然你可以完全自定义一套工程能力。

React 项目开发

天马模块

私有方案

NPM 包开发

自定义工程

如果不想使用上述官方提供的解决方案,也可以基于 build-scripts 自定义完整的工程能力,具体请参考 example

方案设计

image

  • build-scripts 提供核心配置、插件机制、构建生命周期等能力
  • build-scripts 本身不在耦合具体构建工具的设计,具体实现由上层工具决定

能力介绍

build-scripts 2.0 本身不耦合任何构建工具,以下能力均以官方实现的 webpack-service 为基础

webpack-service 是集合 build-scripts 和 webpack 提供的基础构建服务,能力上对标 build-scripts 1.x 版本

配置文件

build-scripts 默认将 build.json 作为工程配置文件,运行 build-scripts 命令时会默认读取当前目录的 build.json 文件。

配置方式:

{
  "externals": {
    "react": "React"
  },
  "plugins": [
    "build-plugin-component",
    "./build.plugin.js"
  ]
}

build.json 中核心包括两部分内容:

  • 基础配置:比如示例的 externals 字段,默认情况下不支持任何字段,由基础插件通过 registerUserConfig API 注册扩展
  • 插件配置:二三方插件,以及针对单个项目通过本地插件实现 webpack config 的更改

除了 json 类型以外,build-scripts 也支持 ts 类型的配置文件:

// build.plugin.ts

export default {
  plugins: [],
}

配置插件

通过 build.json 中提供的 plugins 字段可配置插件列表,插件数组项每一项代表一个插件,build-scripts 将按顺序执行插件列表,插件配置形式如下:

{
  "plugins": [
    // 数组第一项为插件名,第二项为插件参数
    ["build-plugin-fusion", {
      "themePackage": "@icedesign/theme"
    }]
  ]
}

本地自定义配置

如果基础配置和已有插件都无法支持业务需求,可以通过本地插件自定义配置来实现,新建 build.plugin.js 文件作为一个自定义插件,然后写入以下代码:

module.exports = ({ context, onGetConfig }) => {
  // 这里面可以写哪些,具体请查看插件开发章节
  onGetConfig((config) => {
  });
}

最后在 build.json 里引入自定义插件即可:

{
  "plugins": [
    "build-plugin-app",
    "./build.plugin.js"
  ]
}

插件开发

通过命令创建一个插件 npm 包:

$ npm init npm-template <pluginName> build-plugin-template
$ cd <pluginName>

插件本质上是一个 Node.js 模块,入口如下:

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

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

插件 API

插件可以方便扩展和自定义工程能力,这一切都基于 build-scripts 提供的插件 API。

context

context 参数包含运行时的各种环境信息:

  • command 当前运行命令 start|build|test
  • commandArgs script 命令执行时接受到的参数
  • rootDir 项目根目录
  • originalUserConfig 用户在 build.json 中配置的原始内容
  • userConfig 用户配置,包含被 modifyUserConfig 修改后的结果
  • pkg 项目 package.json 的内容
module.exports = ({ context }) => {
  const { userConfig, command, webpack } = context;
  console.log('userConfig', userConfig);
  console.log('command', command);
};

onGetConfig

通过 onGetConfig 获取通过 registerTask 注册的配置,并对配置进行自定义修改:

// 场景一:修改所有 webpack 配置
module.exports = ({ onGetWebpackConfig }) => {
  onGetWebpackConfig((config) => {
    config.entry('src/index');
  });
}

// 场景二:多 webpack 任务情况下,修改指定任务配置
module.exports = ({onGetConfig, registerTask}) => {
  registerTask('web', webpackConfigWeb);
  registerTask('weex', webpackConfigWeex);

  onGetConfig('web'(config) => {
    config.entry('src/index');
  });

  onGetConfig('weex'(config) => {
    config.entry('src/app');
  });
}

onHook

通过 onHook 监听命令运行时事件,onHook 注册的函数执行完成后才会执行后续操作,可以用于在命令运行中途插入插件想做的操作:

module.exports = ({ onHook }) => {
  onHook('before.start.load', () => {
    // do something before dev
  });
  onHook('after.build.compile', stats => {
    // do something after build
  });
};

目前的命令执行生命周期如下:

start 命令:

生命周期 参数 调用时机
before.start.load { args: CommandArgs; webpackConfig: WebpackConfig[] } 获取 webpack 配置之前
before.start.run { args: CommandArgs; webpackConfig: WebpackConfig[] } webpack 执行构建之前
after.start.compile { url: string; stats: WebpackAssets; isFirstCompile: boolean } 编译结束,每次重新编译都会执行
before.start.devServer { url: string; devServer: WebpackDevServer } server 中间件加载后,webpack devServer 启动前
after.start.devServer { url: string; devServer: WebpackDevServer } webpack devServer 启动后

build 命令:

生命周期 参数 调用时机
before.build.load { args: CommandArgs; webpackConfig: WebpackConfig[] } 获取 webpack 配置之前
before.build.run { args: CommandArgs; webpackConfig: WebpackConfig[] } webpack 执行构建之前
after.build.compile { url: string; stats: WebpackAssets; isFirstCompile } 编译结束

test 命令:

生命周期 参数 调用时机
before.test.load { args: CommandArgs; webpackConfig: WebpackConfig[] } 获取 jest 配置之前
before.test.run { args: CommandArgs; config: JestConfig } jest 执行构建之前
after.test { result: JestResult } 测试结束

registerUserConfig

通过 registerUserConfig 注册 build.json 中的顶层配置字段,注册是可以进行用户字段校验,支持传入单个配置对象或者包含多个配置对象的数组。

方法生效的生命周期,在 registerTask 和 onGetConfig 之间。

配置对象字段如下:

  • name (string)

字段名称,唯一标识,多个插件无法注册相同的字段 保留字段:plugins

  • validation(string|function)

字段校验,支持 string 快速校验,string|boolean|number,也可以自定义函数,根据 return 值判断校验结果

  • ignoreTasks(string[])

配置忽略指定 webpack 任务

  • setConfig(function)

字段效果,具体作用到 webpack 配置上,接收参数:

  • config:通过 registerTask 注册的配置
  • value: build.json 中的字段值
  • context:与外部 context 相同,新增字段 taskName 表现当前正在修改的 task
module.exports = ({ registerUserConfig }) => {
  registerUserConfig({
    name: 'entry',
    // validation: 'string',
    validation: value => {
      return typeof value === 'string';
    },
    config: (config, value, context) => {
      config.mode(value);
    },
  });
};

registerTask

用于注册多 webpack 任务,比如 build-plugin-react-app 上已完整支持 React 链路开发,大部分情况下在默认 webpack 任务上拓展即可,无需额外注册.

// 注册的 config 必须是以 webpack-chain 形式组织
module.exports = ({ registerTask }) => {
  registerTask('web', webpackConfigWeb);
  registerTask('component', webpackConfigComponent);
};

cancelTask

用于取消已注册任务

module.exports = ({ cancelTask }) => {
  cancelTask('web');
};

hasRegistration

判断 build.json 中的顶层配置字段或者 cli 参数是否已经注册:

module.exports = ({ hasRegistration }) => {
  // 判断 build.json 顶层配置字段 entry 是否已配置
  const hasEntryRegistered = hasRegistration('entry');

  // 判断 cli --https 参数是否已被注册
  const hasHttpsRegistered = hasRegistration('https' 'cliOption');
  ...
}

modifyConfigRegistration

用于修改已注册用户配置的行为:

module.exports = ({ modifyConfigRegistration }) => {
  modifyConfigRegistration('name', configRegistration => {
    return {
      ...configRegistration,
      // 修正验证字段
      validation: 'string',
    };
  });
};

modifyUserConfig

通过 modifyUserConfig 可以修改通过 registerUserConfig 注册的基础配置,在插件中快速复用基础配置的处理逻辑:

module.exports = ({ modifyUserConfig }) => {
  modifyUserConfig(originConfig => {
    // 通过函数返回批量修改
    return { ...originConfig, define: { target: 'xxxx' } };
  });
};

通过指定具体修改的基础配置,快速完成配置的修改:

module.exports = ({ modifyUserConfig }) => {
  modifyUserConfig('entry', 'src/app');

  // 通过对象路径修改,比如修改对象 { outputAssetsPath: { js: 'js-dist'} } 可通过以下方式
  modifyUserConfig('outputAssetsPath.js', 'js');

  // 支持深合并,默认情况下 modifyUserConfig 将覆盖原有配置,通过配置参数支持配置的合并
  modifyUserConfig('outputAssetsPath', {
    js: 'js-output'
  }, { deepmerge: true });
};

API 执行的生命周期:所有插件对于修改配置函数将保存至 modifyConfigRegistration 中,在 runUserConfig 执行前完成对当前 userConfig 内容的修改

registerCliOption

注册各命令上支持的 cli 参数,比如 npm start --https 来开启 https:

module.exports = ({ registerCliOption }) => {
  registerCliOption({
    name: 'https', // 注册的 cli 参数名称,
    commands: ['start'], // 支持的命令,如果为空默认任何命令都将执行注册方法
    config: (config, value, context) => {
      // 对应命令链路上的需要执行的相关操作
    },
  });
};

注册函数执行周期,在 userConfig 相关注册函数执行之后。

modifyCliRegistration

用于修改已注册 cli 配置的行为:

module.exports = ({ modifyConfigRegistration }) => {
  modifyCliRegistration('https', cliRegistration => {
    return {
      ...cliRegistration,
      // 修正 commands 字段
      commands: ['start'],
    };
  });
};

getAllTask

用于获取所有注入任务的名称:

module.exports = ({ getAllTask }) => {
  const taskNames = getAllTask();
  // ['web', 'miniapp']
};

插件间通信

在一些业务场景下,插件间需要进行通信:

  1. 不同插件之间需要知道彼此的存在来确定是否执行相应的逻辑
  2. 多个插件共有的配置信息可以抽出来,在某个插件中进行配置
  3. 上层插件的执行,需要依赖基础插件提供的方法

基于上述的诉求,API 层面提供 setValuegetValue 来用于数据的存取,registerMethodapplyMethod 来解决方法的复用。

setValue

用来在 context 中注册变量,以供插件之间的通信。

module.exports = ({ setValue }) => {
  setValue('key', 123);
};

getValue

用来获取 context 中注册的变量。

module.exports = ({ getValue }) => {
  const value = getValue('key'); // 123
};

registerMethod

向工程核心注册相关方法,方便其他插件进行复用:

module.exports = ({ registerMethod }) => {
  // 注册方法
  registerMethod('pipeAppRouterBefore', content => {
    // 执行相关注册逻辑,可以返回相应的值
    return true;
  });
};

registerMethod 注册方式时,通过参数指定可以获取调用该方法的具体插件名:

module.exports = ({ registerMethod }) => {
  // 注册方法
  registerMethod('pipeAppRouterBefore', (pluginName) => (content) => {
    console.log('plugin name', pluginName);
    console.log('content', content);
    // 执行相关注册逻辑,可以返回相应的值
    return true;
  }, { pluginName: true });
};

applyMethod

调用其他插件的注册方法

module.exports = ({ applyMethod }) => {
  // 使用其他差价注册方法的方式,如果插件未注册,将返回一个 error 类型的错误
  // 类似 new Error(`apply unkown method ${name}`)
  const result = applyMethod('pipeAppRouterBefore', 'content');
};

版本升级

1.x -> 2.x

2.x 的核心变化:

  • 与 webpack 整体解耦,沉淀为插件开发服务
  • 修改与 webpack 耦合的相关 API

具体的 API 变化如下:

onGetWebpackConfig 移除

onGetWebpackConfig 变更为 onGetConfig,使用方式保持与之前不变,对于调用 onGetConfig 获取的 config 配置内容由具体的框架决定。 比如在 ICE PKG 下使用 API onGetConfig 获取的配置内容为基于 rollup 配置抽象的配置对象

registerTask 变化

registerTask 原先要求注册的任务配置必须是 webpack-chain 形式的配置,基于 build-scripts 2.0,其注册的内容由上层框架决定。 比如在 ICE PKG 下,任务配置为一个对象,详解具体配置项

registerUserConfig 变化

registerUserConfig 的参数 configWebpack 变更为 setConfigsetConfig 具体配置由上层框架决定:

module.exports = ({ registerUserConfig }) => {
  registerUserConfig({
    name: 'custom-key',
    validation: 'boolean' // 可选,支持类型有 string, number, array, object, boolean
    setConfig: (config) => {
      // config 内容由具体框架决定
    },
  });
};

registerCliOption 变化

registerCliOption 变化同 registerUserConfig

module.exports = ({ registerCliOption }) => {
  registerCliOption({
    name: 'custom-options',
    setConfig: (config) => {
      // config 内容由具体框架决定
    },
  });
};

0.x -> 1.x

1.x 核心变化:

  • 包名由 @alib/build-scripts 切换为 build-scripts
  • 不再依赖 webpack&jest&webpack-dev-server,建议由基础插件或项目自身依赖
  • 插件上下文 context 增加 originalUserConfig 字段,用于读取用户原始配置
  • userConfig 类型校验增强,支持 string | object | array 校验

除了前两点属于不兼容改动,其他能力都保持向前兼容。

自定义工程

在 package.json 中增加依赖:

{
  "devDependencies": {
+    "jest": "^26.4.2",
+    "webpack": "^4.27.1",
+    "webpack-dev-server": "^4.0.0",
-    "@alib/build-scripts": "^0.1.0",
+    "build-scripts": "^1.0.0",
  }
}

其中 jest 可按需判断是否需要安装,webpack 版本按需选择。修改完成后重装依赖然后重启即可。

build-scripts 暂时不支持直接从 1.x 升级为 2.x,2.x 的升级必须搭配上层 service 实现,比如 build-scripts 1.x + build-plugin-component 的组件开发模式将会由 ICE PKG 支持,ICE PKG 即是一个基于 build-scripts 2.x 实现的包开发方案

React 项目(icejs)

升级 icejs 2.0 即可。

Rax 项目(rax-app)

rax-app 3.8.0 以上已升级。

业务组件(build-plugin-component)

在 package.json 中升级依赖:

{
  "devDependencies": {
-    "@alib/build-scripts": "^0.1.0",
+    "build-scripts": "^1.0.0",
-    "build-plugin-component": "^1.0.0",
+    "build-plugin-component": "^1.6.5",
  }
}

build-plugin-component 从 1.6.5 开始同时兼容 build-scripts 0.x 和 1.x 两个版本

天马模块(@ali/build-plugin-pegasus-base)

待支持

License

MIT