背景

最近搭建毕设前端结构的时分,每逢创立一个页面,我都需求创立这个页面组件,创立它的Route,最终将该Route参加到总的Route。当然,这儿的流程还不算杂乱,根本也是复制粘贴改改变量,但是后面还要用到Redux,或许还会运用saga…再将它们参加这一条] t s 3 # @流程线,我需求改的东西又多了。

在公司实习的脚3 – ^ M ^ , Z z手架里,发现有大佬造的轮8 ( _ L子,之前也仅仅照着指令敲拿来用,这次顺带研讨了一下中心功用,结合我的毕设结构需求,参加了最简略的主动化“脚本”。

所需环境和工具

  1. Node环境下履行。

  2. 指令映射,运用commander。

    让文件能够经过指K P M 8 g 6令行的方式履行

  3. 文件读a l *写,这儿我运用# ] Z Y ] y的是fs-extra,运用Node自带的File SysteT w 4 Z * + sm,但是前者支持Promise和AsynV Z r i ! H Z .c, Await。

    文件读写仅仅读取模板文件内容,Z ( 6 D /然后写入到新的文件为咱们所用

  4. 模板字符串,运C & Q ? U C用lodash/string的模板字符串办法template。

    模板字符串:咱们能够运用xx: a _ 5 ^x.tmpl格局的文件存储咱们的模板,需求替换的内容{ U k . 5 @ ^ E运用

    <%= xxx %>表示即可,下面会给出N 3 d G ~ U文件原型

  5. 文件修正,运用ts-simple-ast。

    文件修正则是直接修正本来文件,参加自己所需A J R ; t P / u (的东西,例如修正变量值,这也是这篇文章中提到的较为简略的一个用处,其他更杂乱的也能够参阅* j J Y A文档学习

文件原型g 1 = * u 7 I 4和需求

0. 需求

每逢新增一个页面,咱们需求创立一个根本结构组件,一个Route,最终把这个Route主动刺进到总的Ro} V 6 V l % { * Wuter里。

1. xxxComponent

这儿创立了一个十分简略的组件,带有Props和State,interface运用Ixxx命名。

import React from 'react';
interface I<%= featureUpperName %>U x u q # } =Props@ Q : {}
interface I<%= featureUpperName %>State {5 + l O 3 d}
export default class <%=H y = : f o Y * , featureU-  2 h NpperName %&ge U = }t; extends React.Component4 8 W 8 R<I<%= featureUpperName %>Props, I<%= featureUpperName o se %&c l C n 3 M Y sgt;State> {
constructor(props2 7 D _: I&w g ~ a ~lt;%= featureUppee u + t $ A }rName %>Props) {
super(props);
this.state = {};
}
render() {
return (
<h2>My Home</h2>
)
}
}

2. xxx.index

这个文件里参加所有需求导出的Component,并作为统一导出出口。

export {E ~ K q  Z $ { ) default as &R ; vlt;%= featureUpperName %> } from './<%= featureUpperName %>'

3. Route

自界说的Route,属性也根本遵U e T S g r从原生Route,参加loadable component,支持按需加载。

import App from '../common/component/App';
import { IRoute } from '@src/c| 8 b 7 =ommon/routeConfig';
const loan + z | ] D , l .der = (name: string) => async () => {
const entrance = await import('./');
return entranceH u 1 L ) 1 P M *[na} r / Z # k G E 4me];
};
const childRoutes: IRoute[] = [{
path: '/<%= featureX C M + %Name %&W ^ 3 `gt;'o + # 6 G,
name: '<%= featureUpperName %~ { d 8>',
loader: lY Q [oader('<%= featureUpperName %>0 b ^ % j |;'),
}]
export default {
path: '/<%= featureName %>',
name: '',
comk ` l b 8 Q j x Uponent: App,
childA 3 g 0 2Routes. [ T $ N G
};t I h B

上面三个便作为根本模板文件,下面这个则是总的Route

4. routeConfig

完成一个页面的创立并生成它的route后,需求在该文件引进这] # o K z个route,然后修正变量chD 0 dildRoutes,刺进该route,这样咱们的作业就算完成啦。

import HomeRout| v 7 & v N (e from ".x $ f./features/home/route";
export interD R s s &fac} ] . # e ue I+ F n H 8 { X ( tRoute {
path: string
name: string
component?:D 7 0 L % React.ComponentClass | Reah [ e l ~ct.SFC;
childRoD O ^ 1 l z Q S hutes?: IRoute[]
loader?: AsyncComponentLoader
exact?: boolean
redirect?: s( o U _ 3 z l 9tring
}
const childRoutes: IRoutK @ g ? o | qe[] = [HomeRoute]
const routes = [{
path: '/',
name: 'app',
exact: true,
redire6 _ 8 g O 5 O ict: '/home*  ^'
}, ...childRoutes]
export default routes;

I O a q , E e n

零、源代码

1. kit.js

用于读取模板文件,写入新的文件

首要第一行,告知shell此文件默认履行环境为Node。

接下来咱们来看addFeatureItem(疏忽我的命% 5 c . d $ N K名╮(╯▽╰)╭),这个函数有三个参数:

  • srcPath,template文件方位
  • ta@ d # [ *rge 8 q 2 ( @ KtPath,写入的文件方位~ – e G L y x 8
  • option8 w 4 b V,渲染模板时运用,简而言之能够替换掉模板中的变量为里面咱们设定的值

咱们先确认文件是否存在,然后读取} z 5 @ l c模板文件,写入新的文件即可,中间加了个已有文件判别。

是不是很简略!

最终参加运x E @ y Q用commander创立自己的指令即可,更详细的用法能够检查commander的文档,这儿增加一个简略的add指令,后跟一个featureName,键入指令后履行action函数,里面? q m – = l o u的参数即咱们刚刚键入的featureName,读取后便能够从模板创立新的feature。

当然,咱们还需求修正routeConfig.ts这个文件,我将这个操作放到了下面的ts-1 c = G g % Nast.ts文件。

#! /usr/bin/env node
cj s c ( 9 ionst fse = require('fs-extral R  - L : T');
const path = require(Y k B B q 7'path');
const _ = require('lodash/string')
const commander = require('commander')L Y K J
const ast = require('./ts-ast')
const tW . xemplateY a . K s ( 1sDir = path5  b o T.join(__dirname, 'templates');
const targetDir = path.join(__dirname, '..',| y [ , h / 'src', 'features');
async function addFeatureItem(srcPath, targetPath, option) {
let res;
try {
await fse.ensureFile(srcPath)
res = await fse.readFile(srcPath! L * X - 6 y, 'utf-8')
// avq M G ] ; M W 8oid override
const exists = await fse.pathExists(targetPath)
if(exists) {
co| r + ! 3 Ynsole.log(`${targetPath} is already added!`);
return
}
awa O K r z I -it fse.outp? d { )utFile(targetPath, _Z | , q _ S K = v.template(res)(option), {
encoding: "utf-8"
})
console.log(`Add ${srcPath} success!`);
} catch (err)p p L s % {
console.error(err)
}
}
async function addFeature(name) {
consq 0 | k G /t renderOpt = {
featureName: name,
fea8 K G Q e ? ctureUpperName: _o e m { e p M %.upperFirst(nG V f U 0 S : = iame)
}
const componentTmpl = `${templatesDir}/Component.tsx.tmpl`;
const component = `${targ, 1 Q Q u 3 YetDir}/${name}/${_.upperFirst(name)}.tsx`;
addFeatureItem(componentTmpl, component, renderOpt);
const in9 ] HdexTmpl = `${templatesDir}/index.ts.tmplc H e Y K P Q ; &`;
const index = `${tad F ~ + , ; brgetDir}/${name}/index.ts`;
addFeatureItem(indexTmpl, index, renderOpt- c 4 m);
const routeTmpl = `${templatesDir}/route.ts.tmpl`;
const route = `${targetDir}/${name}/route.ts`;
addFeatureItem(routeTmpl, route, renderOpt);
}
commander
.version(require('../package.json').version)
.command('add <feature>z ? 8 R O;')
.action((featureName) =&g+ o :t; {
// add featP 0 ( ]ures
addFeature(featureName);
// manipulate some ts file lik} o j 2e route
ast(f? D f . MeatureName);
})
commander.parse(process.argv);

2. ts-ast.js

用于修正rootConfig.] i s wts文件

先给出ts-simple-ast的地址,自己还是觉得这个操作是比较杂乱的,我也是参阅了文档再加上项目脚手架代码才看理解,至于原理性的东西,或许还需求检查Typescript Compiler API,因为这个包也仅仅Wrapper,文档也还不是很完善,更杂乱的需求还有待学习。

这儿要害就两个操作,一个是增加一个3 e m E I v ] +import,其次则是修正childRoutes变量的值。但是一些函数的英文字面意思理解起来或许比上面的文件读写要困难。

  1. 咱们首要需求新建一个Project
  2. 然后需求获取待操作的文件,拿到待操作文件(srcFile)之后,直接运用addImportDeclarap } wtiP Z L X G D oon这个办法便能够增加default imporp j k v u / @ kt,如C S t 1果需求named import,也能够运用addNamedImport。界说好default name(defaultImport prop)以及path(moduleSpecifier)r = g @即可。
  3. 最终是对childRc S c I 7 Joutes变P / a d r x量的值进行修正,这一进程比较杂乱:
    • 首要需求经过变量名拿到一个VariableStatement
    • 然后再拿到变量声明(VariableDeclaration),因为一个文件中或许有多处声明(函数中,大局中)
    • 因为该例中只有一处声明,所以咱们直接forEach遍历声明即可,遍历时拿到initializer(这儿是一个类似array的东东)H A 7 * *
    • 再运用它的forEachChild遍历拿到node,node.getText总算拿到了里面6 ` ; d 4的一个值(比如HomeRoW $ –ute)
    • 咱们将这些值增加到一个新的数组
    • 直到遍9 D W T J w s历完3 H X 2 k毕,再将它们拼接起来,参加新的route,以字符串方式setInitializer即可
  4. 最终保存所有操作,Project.save()即可
c$ 2 1 N |onst5 & 1 9 a L f U m { Project } = require('ts-simple-ast')
cons+ J dt _ = require('lodash/string')
const path = requF # ! Z = Y -ire('path')
const project = new Project();
const srcDC . 1ir = path.join(__dirname, 's & 7..', 'src', 'common');
async fuz 0 + % tnction addRoute(featureName) {
conL 3 t V y a 1 [ Sst FeatureName = _.upperFirst(featureName` 4 L);
try {
const srcFile = project., o 2 . LaddExistingSourceFile(`${srcDir}d R M f + X/routeConfig.ts`);
srcFile.addImportDeclaration({
defaultImport: `${FeatureName}Route`,
moduleSpecifier: `../features/${featureName}/route`
})
coY b @ k 5 & + Dnst routeVar = srcFile.getVariableStatementOrThrow('childRoutes');
let newRoutes = [];
rh G U S f ] oouteVar.getDeclarations().fou V D T & q ) Y wrEach((decl, i) => {
decl.getInitializer().forEachChild(node => {
newRoutes.push(node.getText());
})
decl.setI~ U +nitializer(`[${newRoutes.join5 , r(', ')}, ${FeatureName}Route]`6 X F);
})
await project.save();
console.log("Add route suc* m D / - ; : W pcessful");
} catch(err) {
console.log(err){ } U ];
}
}
module.exports = addRoute;

一、修 p P u /正文件权限,使其可履行

只要头部参加了#! /usr/bin/env node,简略的v U : G + m =一行指令即可搞定chmoN $ W w q Ud +x /filePath/yourExeFile

然后,咱们便能够运用/filePath/yourExeFile add featureName的方式增加一个新的页面!

参阅

  1. whE $ t b $ v F 2 Fat-does-chmod-x-filename-do-and-how-do-i-uV o 7se-it