在项目中的最好的提效方案就是复用,比如将常用模块封装,给到其他页面复用,就是我们经常提到的组件(复制使用的情况)。将项目中的方案进行封装,给到不同的项目中使用,就是我们说到的脚手架。而这些好的组件或者脚手架,要快速的被其他人员使用,就要用到生成器。简单的理解就是使用脚本帮你复制了一份,像很多项目中经常会执行的新建项目就是生成器的一种能力体现。
npx create-xxx appName 复制代码
Umi 中将生成器的目标定的更加细化,不是提供一整个脚手架,而是提供脚手架中的某一个方案,所以取名微生成器,其实灵感来源是 modern.js 的微生成器。
Umi 的微生成器有统一的命令入口调用:
umi generate (alias: umi g) [type] 复制代码
内置微生成器列表
页面生成器
比如快速生成一个初始的简单页面,可以使用一下方法方式。
交互式输入页面名称和文件生成方式:
$ umi g page ? What is the name of page? › mypage ? How dou you want page files to be created? › - Use arrow-keys. Return to submit. ❯ mypage/index.{tsx,less} mypage.{tsx,less} 复制代码
或者直接指明页面名称直接生成:
$ umi g page foo Write: src/pages/foo.tsx Write: src/pages/foo.less 复制代码
如果你的项目风格是使用目录方式,比如基于 Umi 构建的前端框架 alita 中,就只有目录下的 index 文件才会被识别成路由,所以就可以采用以目录方式生成页面,目录下为页面的组件和样式文件:
$ umi g page bar --dir Write: src/pages/bar/index.less Write: src/pages/bar/index.tsx 复制代码
批量生成多个页面:
$ umi g page page1 page2 a/nested/page3 info - @local Write: src/pages/page1.tsx Write: src/pages/page1.less Write: src/pages/page2.tsx Write: src/pages/page2.less Write: src/pages/a/nested/page3.tsx Write: src/pages/a/nested/page3.less 复制代码
剩余内置微生成器清单
自定义微生成器
当然官方提供的基本的微生成器,肯定无法满足我们特定的项目需求,我们可以通过简单的调用和约定来自定义微生成器。 比如官方提供的 g page
只生成了简单的页面。
import React from 'react'; import styles from './abc.less'; export default function Page() { return ( <div> <h1 className={styles.title}>Page abc</h1> </div> ); } 复制代码
而我们的项目中需要的初始化页面内容可能会更加复杂,要带有请求等页面模版:
import React from 'react'; import type { FC } from 'react'; import { useRequest } from 'umi'; import { query } from './service'; import styles from './index.less'; interface HomePageProps {} const HomePage: FC<HomePageProps> = () => { const { data } = useRequest(query); return <div className={styles.center}>Hello {data?.text}</div>; }; export default HomePage; 复制代码
以上模版中提到的能力和内容会在后续的文章中体现,这里仅仅作为一个展示
要自定义微生成器,只需要调用 generateFile
指定模版存放的路径,指定需要替换的模版中的变量,既可。
generateFile({ path: join(__dirname, '../../../templates/generate/page'), target: join(api.paths.absPagesPath, name), data: { color: randomColor(), name: lodash.upperFirst(name), }, }); 复制代码
generateFile
的目的是让开发人员在编写微生成器的时候,将精力更加聚焦于目标文件生成时的模版维护工作中。 不许花费而外的心力,去学习微生成器的功能开发。
比如我们生成上面的页面,用到的模版如下:
import React from 'react'; import type { FC } from 'react'; import { useRequest } from 'alita'; import { query } from './service'; import styles from './index.less'; interface {{{ name }}}PageProps {} const {{{ name }}}Page: FC<{{{ name }}}PageProps> = () => { const { data } = useRequest(query); return <div className={styles.center}>Hello {data?.text}</div>; }; export default {{{ name }}}Page; 复制代码
在为用户提供微生成器时,有一些数据需要用户提供给我们,会有一些交互式的问答功能需要实现,在微生成器中,我们只需要关注问题本身。
Umi 中提供了两种问答的手段,(底层是同一套方案)第一种就是在插件中使用的,配合上面提到的 generateFile
使用
import { prompts } from '@umijs/utils'; if (!name) { const response = await prompts({ type: 'text', name: 'name', message: 'What is the name of page?', }); name = response.name; } 复制代码
使用 Umi 的基础微生成器工具
第二种方式就是可以脱离 Umi 的声明周期使用的,是一个基础的生成模块,你可以将它用在任意的项目中,比如我们实现一个脚手架的生成器。
const appPrompts = [ { name: 'name', type: 'text', message: `What's the app name?`, default: name, }, { name: 'author', type: 'text', message: `What's your name?`, }, ]; const generator = new BaseGenerator({ path: join(__dirname, '..', 'templates', args.plugin ? 'plugin' : 'app'), target: name ? join(cwd, name) : cwd, data:{ version: require('../package').version, npmClient, registry, }, questions: appPrompts, }); await generator.run(); 复制代码
我们的关注点就只在模版文件(templates
)和问题(questions
)这两点上,如果模版有修改或者问题有变更,我们也只需修改这两个地方,不用耗费大量的心力去处理当前复制和写入的是文件还是文件夹,目标文件夹是否为空等文件写入边界问题。
以上提供的只是基本的模块使用方式,后续的课程中我们会展示以上代码的完整场景和实现。
感谢阅读,今天教程仓库中没写一行代码,就不放源码归档链接了。今天更多的是微生成器的介绍,实际应用场景涉及整个最佳实践方案,我们会在后续的文章中一一体现。