diff --git a/docs/guide/develop/plugin-build.md b/docs/guide/develop/plugin-build.md index fe4828569c..be111a6fd4 100644 --- a/docs/guide/develop/plugin-build.md +++ b/docs/guide/develop/plugin-build.md @@ -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:插件自定义参数 }; @@ -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 注册的函数执行完成后才会执行后续操作,可以用于在命令运行中途插入插件想做的操作: @@ -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 需要通过以下方式调用: @@ -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` 下的一级页面列表: @@ -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'; +``` \ No newline at end of file diff --git a/docs/guide/develop/plugin-dev.md b/docs/guide/develop/plugin-dev.md index 083bd30ae0..fa64370577 100644 --- a/docs/guide/develop/plugin-dev.md +++ b/docs/guide/develop/plugin-dev.md @@ -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 +``` + ## 插件目录 通常情况下,插件通过 npm 包的形式分发,插件初始化目录如下: @@ -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` 位于同一目录下。 下面也会按照这两个纬度来分别介绍。 @@ -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 => { + 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 => { + 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 diff --git a/docs/guide/develop/plugin-runtime.md b/docs/guide/develop/plugin-runtime.md index 878fa13afb..9c0af8e375 100644 --- a/docs/guide/develop/plugin-runtime.md +++ b/docs/guide/develop/plugin-runtime.md @@ -6,16 +6,44 @@ order: 3 插件运行时能力通过 `src/runtime.ts` 定义,结构如下 ```javascript -export default ({ appConfig, addDOMRender, setRenderRouter, modifyRoutes }) => { +export default ({ appConfig, addDOMRender, setRenderRouter, modifyRoutes, ...rest }) => { const { loglevel } = appConfig; -} +}; ``` ## API ### appConfig -对应 `src/app.ts` 用户自定义的 appConfig +对应 `src/app.ts` 用户自定义的 appConfig。详细字段见 [应用入口](/docs/guide/basic/app)。 + +### buildConfig + +对应 `build.json` 用户自定义内容。仅包含 `router`、`store`、`ssr` 字段。 + +```javascript +export default ({ buildConfig }) => { + const { router, store, ssr } = buildConfig; +}; +``` + +### context + +应用运行上下文。包含`initialData` 和`pageInitialProps`,分别对应[应用级数据](/docs/guide/advance/ssr#应用级数据)和[页面级数据](/docs/guide/advance/ssr#页面级数据) 。 + +### setRenderRouter + +设置 renderRouter。 + +```javascript +export default ({ setRenderRouter }) => { + // renderRouter 入参为路由数组 + const renderRouter = (routes) => () => { + return
route
; + }; + setRenderRouter(renderRouter); +}; +``` ### addProvider @@ -23,13 +51,30 @@ export default ({ appConfig, addDOMRender, setRenderRouter, modifyRoutes }) => { ```js export default ({ addProvider }) => { - const StoreProvider = ({children}) => { + const StoreProvider = ({ children }) => { return {children}; } addProvider(StoreProvider); }; ``` +### addDOMRender + +自定义渲染。默认使用[react-dom](https://reactjs.org/docs/react-dom.html)。 + +```javascript +import ReactDOM from 'react-dom'; + +export default ({ addDOMRender }) => { + // App: React 组件 + // appMountNode: App 挂载点 + const DOMRender = ({ App, appMountNode }) => { + ReactDOM.render(, appMountNode); + }; + addDOMRender(DOMRender); +}; +``` + ### wrapperRouteComponent 为所有页面级组件做一层包裹: @@ -55,4 +100,21 @@ export default ({ wrapperRouteComponent }) => { return TitleWrapperedComponent; }); }; -``` \ No newline at end of file +``` + +### modifyRoutes + +动态修改路由。 + +```javascript +function modify (routes) { + return routes; +} + +export default ({ modifyRoutes }) => { + modifyRoutes(routes => { + const modifiedRoutes = modify(routes); // 修改路由 + return modifiedRoutes; + }); +}; +``` diff --git a/packages/create-ice/src/create.ts b/packages/create-ice/src/create.ts index 32cc30738e..811b96cceb 100644 --- a/packages/create-ice/src/create.ts +++ b/packages/create-ice/src/create.ts @@ -63,6 +63,9 @@ async function selectTemplate(): Promise { }, { npmName: '@alifd/fusion-design-pro-js', description: 'Fusion Design Pro JavaScript template.', + }, { + npmName: 'build-plugin-template', + description: 'ice.js plugin development template.' }]; const defaultTemplate = templates[0];