# 基于Plop快速生成模板文件
# 想法产生
使用umi开发时,新建一个组件就要手动创建index.tsx文件和index.scss文件,并且在tsx中还要编写一些模板代码,例如:
import React from 'react';
import styles from './index.scss';
interface IAvatar {}
const Avatar: React.FC<IAvatar> = () => {
return (
<div>{{name}}</div>
);
};
export default Avatar;
写过一点nest,接触到其cli,因此决定做一个快速生成文件的demo。
# 开始
# 项目搭建
新建文件夹,并在该文件夹路径下初始化npm信息:
npm init
# 设置eslint
npm i eslint -D
npx eslint --init
# below are choices about eslint prompts
To check syntax, find problems, and enforce code style
JavaScript modules (import/export)
None of these
Does your project use TypeScript >> yes
Node
Use a popular style guide
Airbnb
Javascript
可以在将配置改为typescript-eslint/recommended
:
// .eslintrc.js
module.exports = {
// ...
'extends': [
'plugin:@typescript-eslint/recommended',
],
// ...
}
# 设置typescript
安装依赖:
npm i -D typescript ts-node -D
ts-node是为了可以直接类似node index.js
这样直接运行ts文件。
初始化配置:
npx tsc --init
// tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "dist",
"rootDir": "src",
"importHelpers": true,
"strict": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["src/templates/**/*"]
}
配置文件需要配置编译后js的输出目录(outDir),为了编译后的运行js文件的路径查找与src中的ts一直,设置rootDir。
开启allowSyntheticDefaultImports
和esModuleInterop
,import导入包时,无需每次都从deafult
中取值。
为了让tsc
编译过程中能过包括一些环境声明,设置includes。
# 设置package.json中的script
"scripts": {
"start": "ts-node --files src/index.ts",
"build": "tsc -p tsconfig.json ",
"rebuild": "rm -r dist && npm run build",
"lint": "eslint --ext .ts --fix src"
}
ts-node需要--files
参数,否则不会包含声明文件。
tsc需要指明配置文件,否则不会包含声明文件。
# 新建templates文件夹
新建文件:templates/component/index.tsx
、template/scss/index.scss
。
其中index.tsx文件内容为:
import React from 'react';
import styles from './index.scss';
interface I{{capitalize name}} {}
const {{capitalize name}}: React.FC<I{{capitalize name}}> = () => {
return (
<div>{{name}}</div>
);
};
export default {{capitalize name}};
该文件将会被handlebars进行处理,并且会引入handlebars helpers中的string helper,对名字进行首字母大写。
上述文件内容了解简单的handlebars和helpers用法即可了解。plop库封装了对handlebars的使用方式,只需要指定模板文件路径和输出文件路径,以及设置helper即可。
# 使用设计
输入命令行create-umi -c avatar
,生成src/components/avatar/index.tsx
、src/components/avatar/index.scss
。
其中index.tsx内容为:
import React from 'react';
import styles from './index.scss';
interface IAvatar {
}
export const Avatar: FC<IAvatar> = () => {
return (
<div>
avatar
</div>
);
};
export default Avatar;
# 使用到的库
- chalk:输出terminal的字体带上指定颜色。
- handlebars:处理模板字符串。
- handlebars-helpers:增强处理模板的工具。
- minimist:处理命令行参数。
- node-plop:通过api的方式调用plop。
- plop:快速生成模板文件的库,集成了对话、模板复制等功能。
# 代码编写
// src/index.ts
#!/usr/bin/env node
import minimist from 'minimist';
import nodePlop from 'node-plop';
import { error } from './util';
import plopFile from './plopfile';
function start() {
const argv = minimist(process.argv.slice(2));
const { c, component, p, page } = argv;
const componentName = c || component;
const pageName = p || page;
if (!componentName && !pageName) {
return console.log(
error('请选择生成的类型(组件 or 页面)')
);
}
if (componentName && pageName) {
return console.log(
error('只能选择一种类型')
);
}
plopFile({
type: componentName ? 'component' : 'page',
name: componentName || pageName
})(nodePlop(''));
}
start();
文件第一行shebang,告诉系统使用何种程序执行文件。
start函数主要做的事情包括:
- 参数读取
- 判断参数合法性
- 调用plop执行文件复制
util中导入的使chalk的基本封装。
// src/plopfile.ts
import { NodePlopAPI } from 'plop';
import helpers from 'handlebars-helpers';
import { setHelper } from './plop/setHelper';
import { setGenerator } from './plop/setGenerator';
import { runActions } from './plop/runActions';
export interface IPlopfile {
type: 'component' | 'page',
name: string;
}
export default function ({ name, type }: IPlopfile) {
return (plop: NodePlopAPI): void => {
const stringHelpers = helpers.string();
setHelper({
plop,
helpers: stringHelpers,
helperName: 'capitalize'
});
const componentGenerator = setGenerator({
name,
plop,
description: '生成组件',
actions: [{
type,
ext: 'tsx'
}, {
type,
ext: 'scss'
}]
});
runActions({
generator: componentGenerator
});
};
}
该函数做的事情主要包括:
- 设置handlebars helper
- 设置plop generator
- generator中主要设置action,做的工作就是按照模板和命令行参数输出文件,这里没有用到对话。
- 该文件封装了设置generator的工作,外界只需要传入文件类型即可。
import { NodePlopAPI, ActionConfig } from 'plop';
import { getPath } from './getPath';
import { getTemplate } from './getTemplate';
interface IGenerate {
type: 'component' | 'page';
ext: 'tsx' | 'scss';
}
interface ISetGenerator {
name: string;
plop: NodePlopAPI;
description: string;
actions: IGenerate[];
}
export function setGenerator({
name,
plop,
description,
actions
}: ISetGenerator) {
return plop.setGenerator(name, {
prompts:[],
description,
actions: actions.map<ActionConfig>(({ type, ext }) => ({
type: 'add',
path: getPath({ type, name, ext }),
templateFile: getTemplate({ type, ext }),
data: {
name
}
}))
});
}
setGenerator做的事情就是根据plop api对action进行赋值。
getPath和getTemplate封装的操作就是路径映射:
- getPath:
src/components/avatar/index.xxx
- getTemplate:
../../templates/component/index.xxx
getPath的路径相对的是使用者当前的文件路径,例如umi项目根路径。
getTemplate的路径相对的是该工具的路径。
# 测试
# 设置package.json的bin
"bin": {
"create-umi": "dist/index.js"
}
# 构建
npm run build
执行构建命令后,文件会打包至dist文件夹中。
在该工具项目中执行npm link
。
在umi项目中执行npm link create-umi
,其中create-umi为该工具的名字,执行create-umi -c avatar
即可生成文件。
# 总结
- 环境搭建
- 进一步理解tsconfig.json
- npm link
- package.json bin
- node plop
- handlebars
# 后续
- jest测试
- 功能迭代