006 使用 Umi 的微生成器快速助力业务交付

2,506 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

在项目中的最好的提效方案就是复用,比如将常用模块封装,给到其他页面复用,就是我们经常提到的组件(复制使用的情况)。将项目中的方案进行封装,给到不同的项目中使用,就是我们说到的脚手架。而这些好的组件或者脚手架,要快速的被其他人员使用,就要用到生成器。简单的理解就是使用脚本帮你复制了一份,像很多项目中经常会执行的新建项目就是生成器的一种能力体现。

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
剩余内置微生成器清单

后续我们会在介绍每一个能力的时候,使用到对应的微生成器。

组件生成器

src/components/ 目录下生成项目需要的组件。和页面生成器一样,组件生成器也有多种生成方式。

交互式生成:

$ umi g component
info  - @local
✔ Please input you component Name … foo
Write: src/components/Foo/index.ts
Write: src/components/Foo/Foo.tsx

直接生成:

$ umi g component bar
info  - @local
Write: src/components/Bar/index.ts
Write: src/components/Bar/Bar.tsx

嵌套生成:

$ umi g component group/subgroup/baz
info  - @local
Write: src/components/group/subgroup/Baz/index.ts
Write: src/components/group/subgroup/Baz/Baz.tsx

批量生成:

$ umi g component apple banana orange
info  - @local
Write: src/components/Apple/index.ts
Write: src/components/Apple/Apple.tsx
Write: src/components/Banana/index.ts
Write: src/components/Banana/Banana.tsx
Write: src/components/Orange/index.ts
Write: src/components/Orange/Orange.tsx

RouteAPI 生成器

生成 routeAPI 功能的模板文件。

交互式生成:

$umi g api
info  - @local
✔ please input your api name: … starwar/people
Write: api/starwar/people.ts

直接生成:

$ umi g api films
info  - @local
Write: api/films.ts

嵌套生成器:

$ umi g api planets/[id]
info  - @local
Write: api/planets/[id].ts

批量生成:

$ umi g api spaceships vehicles species
info  - @local
Write: api/spaceships.ts
Write: api/vehicles.ts
Write: api/species.ts

Mock 生成器

生成 Mock 功能的模板文件,mock 的具体实现参考文档

交互式生成:

$ umi g mock
info  - @local
✔ please input your mock file name … auth
Write: mock/auth.ts

直接生成:

$ umi g mock acl
info  - @local
Write: mock/acl.ts

嵌套生成:

$ umi g mock users/profile
info  - @local
Write: mock/users/profile.ts

Prettier 配置生成器

为项目生成 prettier 配置,命令执行后,umi 会生成推荐的 prettier 配置和安装相应的依赖。

$ umi g prettier
info  - @local
info  - Write package.json
info  - Write .prettierrc
info  - Write .prettierignore

Jest 配置生成器

为项目生成 jest 配置,命令执行后,umi 会生成 Jest 配置和安装相应的依赖。根据需要选择是否要使用 @testing-library/react 做 UI 测试。

$ umi g jest
info  - @local
✔ Will you use @testing-library/react for UI testing?! … yes
info  - Write package.json
info  - Write jest.config.ts

Tailwind CSS 配置生成器

为项目开启 Tailwind CSS 配置,命令执行后,umi 会生成 Tailwind CSS 和安装相应的的依赖。

$ umi g tailwindcss
info  - @local
info  - Write package.json
set config:tailwindcss on /Users/umi/playground/.umirc.ts
set config:plugins on /Users/umi/playground/.umirc.ts
info  - Update .umirc.ts
info  - Write tailwind.config.js
info  - Write tailwind.css

DvaJS 配置生成器

为项目开启 Dva 配置,命令执行后,umi 会生成 Dva

$ umi g dva
info  - @local
set config:dva on /Users/umi/umi-playground/.umirc.ts
set config:plugins on /Users/umi/umi-playground/.umirc.ts
info  - Update config file
info  - Write example model

自定义微生成器

当然官方提供的基本的微生成器,肯定无法满足我们特定的项目需求,我们可以通过简单的调用和约定来自定义微生成器。 比如官方提供的 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)这两点上,如果模版有修改或者问题有变更,我们也只需修改这两个地方,不用耗费大量的心力去处理当前复制和写入的是文件还是文件夹,目标文件夹是否为空等文件写入边界问题。

以上提供的只是基本的模块使用方式,后续的课程中我们会展示以上代码的完整场景和实现。

感谢阅读,今天教程仓库中没写一行代码,就不放源码归档链接了。今天更多的是微生成器的介绍,实际应用场景涉及整个最佳实践方案,我们会在后续的文章中一一体现。