Skip to content

Commit

Permalink
docs: update plugin development (#3345)
Browse files Browse the repository at this point in the history
  • Loading branch information
LanceZhu authored Jul 16, 2020
1 parent 51808dd commit 2787cbf
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 16 deletions.
127 changes: 126 additions & 1 deletion docs/guide/develop/plugin-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ order: 2

插件工程能力通过 `src/index.ts` 定义,结构如下


```javascript
module.exports = ({ context, onGetWebpackConfig, log, onHook }, options) => {
module.exports = ({ context, onGetWebpackConfig, log, onHook, ...rest }, options) => {
// 第一项参数为插件 API 提供的能力
// options:插件自定义参数
};
Expand Down Expand Up @@ -42,6 +43,19 @@ module.exports = ({onGetWebpackConfig, registerTask}) => {
}
```

### onGetJestConfig

通过 `onGetJestConfig` 获取 jest 配置,可对配置进行自定义修改:

```javascript
module.exports = ({onGetJestConfig}) => {
onGetJestConfig((jestConfig) => {
const modifiedJestConfig = modify(jestConfig);
return modifiedJestConfig;
});
};
```

### onHook

通过 onHook 监听命令运行时事件,onHook 注册的函数执行完成后才会执行后续操作,可以用于在命令运行中途插入插件想做的操作:
Expand Down Expand Up @@ -95,6 +109,85 @@ log.verbose('debug');
log.error('exit');
```

### registerUserConfig

为用户配置文件 `build.json` 中添加自定义字段。

```javascript
module.exports = ({registerUserConfig}) => {
registerUserConfig({
name: 'custom-key',
validation: 'boolean' // 可选,支持类型有 string, number, array, object, boolean
});
};
```

### registerClioption

为命令行启动添加自定义参数。

```javascript
module.exports = ({registerClioption}) => {
registerCliOption({
name: 'custom-option', // 参数名
commands: ['start'], // 命令
configWebpack: (arg) => {} // 可选,arg 为命令行参数对应值
});
};
```

### registerMethod

注册自定义方法。通过 `applyMethod` 调用。

```javascript
module.exports = ({registerMethod}) => {
registerMethod(name, func); // name, func 分别为方法名和方法
};
```

### modifyUserConfig

修改用户配置文件。

```javascript
module.exports = ({modifyUserConfig}) => {
modifyUserConfig(key, value); // key, value 分别为用户配置文件键值对
};
```

### registerTask

添加 webpack 配置,配置为 webpack-chain 形式。

```javascript
module.exports = ({registerTask}) => {
registerTask(name, config); // name: Task名, config: webpack-chain 形式的配置
};
```

### getAllTask

获取所有 webpack 配置名称。

```javascript
module.exports = ({getAllTask}) => {
const alltasks = getAlltask();
};
```

### getAllPlugin

获取所有插件。

```javascript
module.exports = ({getAllPlugin}) => {
// 获取所有插件数组
// 类型:() => [{pluginPath, options, name}]
const plugins = getAllPlugin(); ,[]
}
```

## 扩展 API

除了以上由 build-scripts 内置支持的 API,我们还通过 icejs 对插件 API 做了扩展,扩展的 API 需要通过以下方式调用:
Expand Down Expand Up @@ -139,6 +232,31 @@ this.applyMethod('addPageExport', 'Home', { source: './models', 'store' })

`addPageExport` 对应

### addIceAppConfigTypes

向 appConfig 添加类型

```javascript
// 第一项参数对应 API 名称,第二项参数对应 API 参数。
//
// API 参数:
// source: 类型声明文件。./foo/types,对应 ICE_TEMP_DIR/foo/types。 ICE_TEMP_DIR,可通过 getValue('ICE_TEMP') 获得。注意:需先将对应类型文件移至 ICE_TEMP_DIR。
// specifier: 导出类型标识符,可选,默认值为 '*'。
// exportName: 添加至 appConfig 类型 IAppConfig 上的导出名。
//
// 结果为:
// // ICE_TEMP_DIR/types.ts
// import { Foo } from './foo/types';
// export interface IAppConfig {
// foo?: Foo
// }
applyMethod('addIceAppConfigTypes', { source: `./foo/types`, specifier: '{ Foo }', exportName: `foo?: Foo` });
```

### removeIceAppConfigTypes

`addIceAppConfigTypes` 对应

### getPages

获取 `src/pages` 下的一级页面列表:
Expand Down Expand Up @@ -231,3 +349,10 @@ module.exports = ({getValue}) => {
const projectType = getValue('PROJECT_TYPE'); // ts|js
const iceDirPath = getValue('ICE_TEMP'); // 对应 .ice 的路径
```

## 类型

接口类型通过以下方法引入:
```javascript
import { IPlugin } from '@alib/build-scripts';
```
136 changes: 126 additions & 10 deletions docs/guide/develop/plugin-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@ title: 插件开发指南
order: 1
---

icejs 基于工程构建工具 build-scripts 封装,因此在插件能力上也完整继承了 build-scrtips。除了通过插件定制工程能力以外,icejs 还为插件扩展了运行时定制的能力,这让插件拥有更多的想象空间。
icejs 基于工程构建工具 build-scripts 封装,因此在插件能力上也完整继承了 build-scripts。除了通过插件定制工程能力以外,icejs 还为插件扩展了运行时定制的能力,这让插件拥有更多的想象空间。

插件机制是 icejs 的核心之一,当前 icejs 的基础能力都是通过插件来实现。插件机制不但可以保证框架核心足够精简和稳定,还可以通过插件对运行时和编译时的能力进行封装复用,最终打造一个完整的生态。

## 快速开始

使用 [CLI](/docs/guide/start#使用%20CLI) 初始化项目。选择插件模板:

```shell
$ npm init ice <your-plugin-name>
```

## 插件目录

通常情况下,插件通过 npm 包的形式分发,插件初始化目录如下:
Expand All @@ -15,17 +23,17 @@ icejs 基于工程构建工具 build-scripts 封装,因此在插件能力上
.
├── src
│   ├── index.[t,j]s # 插件工程入口
│   └── module.[t,j]s # 插件编译时入口
├── lib/ # 编译目录
├── package.json
├── README.md
└── tsconfig.json
│   └── runtime.[t,j]s # 插件编译时入口
├── tests # 测试文件目录
├── package.json # npm 包配置
├── tsconfig.json # typescript 配置文件
└── README.md # 说明文档
```

这里以 ts 为例,实际上也可以通过 js 编写插件。插件核心有两个文件:
这里以 ts 为例,实际上也可以通过 js 编写插件。 ts 最终应编译为 js 以发布 npm 包。插件核心有两个文件:

1. `index.ts`:通常用于做一些工程相关的事情,比如更改 webpack 配置、构建结束后执行一些其他任务等
2. `runtime.ts`:实现一些运行时能力,比如 config/request 插件。注意: 旧版本的`module.ts`暂时兼容,但在未来不受支持。
1. `index.ts`:通常用于做一些工程相关的事情,比如更改 webpack 配置、构建结束后执行一些其他任务等。需保证该文件作为 npm 包入口。
2. `runtime.ts`:实现一些运行时能力,比如 config/request 插件。注意: 旧版本的`module.ts`暂时兼容,但在未来不受支持。需保证该文件与 `index.ts` 位于同一目录下。

下面也会按照这两个纬度来分别介绍。

Expand All @@ -41,6 +49,114 @@ icejs 基于工程构建工具 build-scripts 封装,因此在插件能力上

关于 `runtime.ts` 应该如何书写请参考下一个章节的文档 [通过插件定制运行时能力](/docs/guide/develop/plugin-runtime.md)

## 单元测试

使用 [Jest](https://github.com/facebook/jest) 进行单元测试。

### 插件开发示例

[`plugin-logger`](https://github.com/alibaba/ice/tree/master/packages/plugin-logger) 为例。该插件采用 typescript 编写,对工程能力及运行时能力均进行了修改。为框架提供了日志功能。

目录结构:

```json
.
├── README.md
├── template
│   └── index.ts // logger 功能实现
├── package.json
├── src
│   ├── index.ts // 工程能力实现
│   ├── runtime.ts // 运行时能力实现
│   └── types
│   └── index.ts // 类型声明文件
└── tsconfig.json
```

#### 类型和扩展

* 类型声明:

```typescript
// src/types/index
export interface ILogger {
level: string;
};
```

* 扩展 appConfig 类型

```typescript
// src/index.ts
import * as path from 'path';
import * as fse from 'fs-extra';
import { IPlugin } from '@alib/build-scripts';

const plugin: IPlugin = async ({ getValue, applyMethod }): Promise<void> => {
const exportName = 'logger';
const distPath = path.join(getValue('ICE_TEMP'), exportName);
await fse.copy(path.join(__dirname, './types'), path.join(distPath, 'types')); // 复制类型声明文件

// 挂载至 appConfig。 appConfig 对应类型为 IAppConfig
// source 为复制后的目录, specifier 为类型标识符,exportName 为 appConfig 类型名
// 得到以下结果
// import { ILogger } from './logger/types'
// export interface IAppConfig {
// logger?: ILogger;
// }
applyMethod('addIceAppConfigTypes', { source: `./${exportName}/types`, specifier: '{ ILogger }', exportName: `${exportName}?: ILogger` });
};
```

#### 工程化能力实现

* logger 功能实现

```typescript
// src/logger/index.ts
import * as logger from 'loglevel';

export default logger;
```

* 导出至 ice

```typescript
// src/index.ts
import * as path from 'path';
import * as fse from 'fs-extra';
import { IPlugin } from '@alib/build-scripts';

const plugin: IPlugin = async ({ getValue, applyMethod, onGetWebpackConfig }): Promise<void> => {
const exportName = 'logger';
const distPath = path.join(getValue('ICE_TEMP'), exportName);
await fse.copy(path.join(__dirname, `../${exportName}`), distPath);
// 导出 logger 功能
// 用户可通过 import { logger } from 'ice'; 使用
applyMethod('addIceExport', { source: `./${exportName}`, exportName });

onGetWebpackConfig((config) => {
// 为 logger 添加 webpack alias,供运行时能力调用
config.resolve.alias.set('$ice/logger', distPath);
});
};
```

#### 运行时能力实现

```typescript
import logger from '$ice/logger'; // $ice/logger 通过工程化能力设置 alias

const module = ({ appConfig }) => {
// 设置运行时 logger 等级
if (appConfig.logger && appConfig.logger.level) {
logger.setLevel(appConfig.logger.level);
}
};

export default module;
```

## 示例插件

官方插件代码:https://github.com/ice-lab/icejs/tree/master/packages
官方插件代码:https://github.com/alibaba/ice/tree/master/packages
Loading

0 comments on commit 2787cbf

Please sign in to comment.