一、前言
脚手架可以快速生成项目模板,节约创建和配置项目时间,提升开发效率;比如,vue、angular和react都各自生成脚手架的CLI工具。最近在使用微前端的架构开发项目,以微前端应用为例,开发一个CLI工具来帮我们快速生成项目模板。
二、微前端CLI
1、准备模板
预先从网上找几个为前端框架模板,分别使用不同的分支来管理,并上传到github。后面会使用控制台询问的方式让用户输入命令,来从指定分支下载模板。
我找了几个测试模板并上传到我的github,地址:github.com/limzgiser/m…
2、相关插件
-
art-template
- 模板引擎
- 使用用户输入的变量替换模板文件中的占位符。
-
chalk
- node终端样式库
- 改变控制台输出样式
-
commander
- node.js命令行界面解决方案
-
download-git-repo
- node下载并提取一个git库(GitHub, GitLab, Bitbucket)
-
execa
调用shell和本地外部程序的javascript库,会启动子进程执行
-
fs-extra
- 扩展node fs
-
inquirer
- 用户与命令行交换工具
-
lodash
- js实用工具库,提供丰富工具函数
-
ora
- 优雅的终端转轮
主要会用到这些插件,可以到npm或github中搜索了解插件的使用。
3、获取用户输入参数
使用commander插件创建create命令,获取用户输入参数。需要用户在创建项目的时候,输入appType(创建主应用还是子应用)、frameType(应用使用什么框架实现)、install(是否自动安装项目依赖),默认不安装、pkg-took(如果安装依赖,是使用npm还是yarn安装)。
// index.ts
import { Command } from 'commander';
import create from "./actions/create";
const program = new Command('mfc');
program.version('0.0.1')
program
.usage('create <projectName> [options]')
.command('create <projectName>')
.description('创建项目')
.option('-a --appType [value]', '选择应用类型:主应用(main)|子(child)')
.option('-t --frameType [value]', '选择框架类型:vue|react|angular')
.option('-i --install', '是否自动安装依赖', false)
.option('-pt --pkg-tool [value]', 'npm or yarn?')
.action(create);
program.parse(process.argv)
action输入一个回调函数,我们可以在回调中拿到用户输入的参数。例如,在控制台中输入一个使用vue编写的主应用,且创建完成后自动使用yarn安装项目的依赖。
export default async function (projectName: string, options: CreateOptions) {
console.log('项目名称:', projectName);
console.log('输入参数:', options);
}
4、下载远程模板
根据用户输入的应用类型和框架类型参数,到远程仓库下载指定分支的项目模板。分支名是使用”应用类型+’_‘+框架类型“命名。用户在创建项目时,如果没有指定应用类型和框架类型参数就在控制台询问,让用户选择。
// 获取用户输入应用类型和框架类型参数
async function getTplParams(options: CreateOptions) {
let appType = '', frameType = '';
if (!options.appType) {
// 如果没有输入appType就询问用户,让用户选择
const answers = await inquirer.prompt([appTypeQues]);
appType = answers.appType;
} else {
appType = options.appType;
}
if (!options.frameType) {
// 如果没有输入项目名称就询问用户,让用户选择
const answers = await inquirer.prompt([frameTypeQues]);
frameType = answers.frameType;
} else {
frameType = options.frameType;
}
return {
appType,
frameType
}
}
// inquirers.ts
const appTypeQues = {
type: 'list',
name: 'appType',
choices: ['main', 'child'],
default: 'npm',
message: '选择应用类型,主应用(main)或子应用(child)!'
}
const frameTypeQues = {
type: 'list',
name: 'frameType',
choices: ['vue', 'react', 'angular'],
default: 'npm',
message: '选择应用框架!'
}
在获取应用类型和框架类型参数用,就可以根据参数拼接成分支名,从远程仓库下载项目模板。
let { appType, frameType } = await getTplParams(options);
let branch = appType + "_" + frameType;
try {
const spinner = ora(chalk.blue("初始化模版...")).start();
await downloadTemplate(
'direct:https://github.com/limzgiser/mfc-cli.git#' + branch,
projectName,
{ clone: true }
);
spinner.info('模版初始化成功');
} catch (error) {
}
5、处理模板
如何动态修改模板中的变量,例如:使用创建项目时候输入的项目名称替换package.json的name值。首先递归获取项目目录中的文件路径,并标识当前路径是否是文件夹。提去文件路径列表,然后遍历读取文件,使用模板引擎替换文件中的变量。最后将替换后的内容替换原内容。
// 递归读取项目目录,标识是否是文件夹
function recursiveDir(sourceDir: string) {
const res: FileItem[] = [];
function traverse(dir: string) {
readdirSync(dir).forEach((file: string) => {
const pathname = `${dir}/${file}`;
const isDir = statSync(pathname).isDirectory();
res.push({ file: pathname, isDir });
if (isDir) {
traverse(pathname);
}
})
}
traverse(sourceDir);
return res;
}
// 使用模板引擎绑定变量
function tplFile(projectName: string, files: Array<FileItem>) {
files.forEach(item => {
if (!item.file.includes('assets') && !item.file.includes('public') ) {
const content = template(process.cwd() + '/' + item.file, { projectName });
let dest = item.file;
if (dest.includes('.art')) {
unlinkSync(dest);
dest = dest.replace(/\.art/, '');
}
writeFileSync(dest, content);
}
});
}
6、安装依赖
如果用户创建项目时指定了自动安装依赖,就直接进入使用什么工具安装依赖,npm或yarn?如果用户没有指定自动安装依赖,就询问用户,是否安装依赖?选否就跳过安装,是,则询问用户使用npm还是yarn安装?如果选择yarn判断用户是否安装了yarn,如果没有,就提示用户先安装yarn。
spinner.info('模版初始化成功');
const cwd = './' + projectName;
if (options.install) {
installPkg(options.pkgTool, cwd);
} else {
const answers = await inquirer.prompt([
installQues,
{
...pkgToolQues,
when(currentAnswers) {
return currentAnswers.install && !options.pkgTool;
}
}
]);
if (answers.install) {
installPkg(answers.pkgTool || options.pkgTool, cwd);
} else {
console.log(chalk.green('项目创建成功'));
}
}
// 安装依赖包
async function installPkg(pkgTool: "npm" | "yarn", cwd: string) {
let tool = pkgTool;
if (!tool) {
const answers = await inquirer.prompt([pkgToolQues]);
tool = answers.pkgTool;
}
if (tool === 'yarn' && !hasYarn()) {
console.log(chalk.red('请先安装yarn'));
} else {
const spinner = ora(chalk.blue('正在安装依赖...')).start();
await exec(tool + ' install', { cwd });
spinner.succeed(chalk.green('项目创建成功'));
}
}
// 执行安装依赖
function exec(command: string, options: execa.Options) {
return new Promise((resolve, reject) => {
const subProcess = execa.command(command, options);
subProcess.stdout!.pipe(process.stdout);
subProcess.stdout!.on('close', resolve);
subProcess.stdout!.on('error', reject);
});
}
// 判断是否安装yarn
function hasYarn(): boolean {
try {
execa.commandSync('yarn -v', { stdio: 'ignore' });
return true;
} catch (error) {
return false;
}
}
7、创建子命令
可以通过子命令(add)来为已创建的项目,添加组件、指令等模板。下面是一个创建组件的命令。组件的模板就不需要到远程仓库里面下载了,可以定义在本地目录中。支持用户创建.vue和.tsx组件。
program.addCommand(childCommand(Command));
export default function (Command: CommandConstructor) {
const generate = new Command('add');
generate
.command('c <name>')
.description('添加一个组件')
.option('--tsx', 'Is tsx', false)
.action(addComponent);
return generate;
}
import template from "art-template";
import { join } from "path";
import { outputFileSync } from "fs-extra";
import { kebabCase } from "lodash";
import chalk from "chalk";
export default function (name: string, options: { tsx: boolean; }) {
let basePath = 'components';
let trueName = name;
const data = name.split('/');
if (data.length > 1) {
trueName = data.pop()!;
basePath = data.join('/');
}
let suffix = '.vue';
if (options.tsx) {
suffix = '.tsx';
}
try {
const content = template(
join(__dirname, '../../templates', 'component' + suffix),
{ name: trueName, rootCls: kebabCase(trueName) }
);
const dest = `src/${basePath}/${trueName}${suffix}`;
outputFileSync(dest, content);
console.log(chalk.green('创建成功>>', dest));
} catch (e) {
console.log(chalk.red('创建失败'));
throw e;
}
}
8、编译TS
使用gulp来编译项目。在根目录中创建gulp.ts文件:
import { src, dest, series } from 'gulp';
import del from 'del';
import gts from 'gulp-typescript';
const outputDir = 'dist';
on clean() {
return del(outputDir);
}
function script() {
return src('src/**/*.ts', { base: 'src' })
.pipe(gts.createProject('tsconfig.json')())
.pipe(dest(outputDir));
}
export default series(clean, script);
在package.json中添加script命令,执行编译ts项目。
"scripts": {
"build": "gulp"
},
npm run build
9、测试发布脚手架
package.json中添加bin。
"bin": {
"mfc": "index.js"
},
在根目录中创建index.js
#! /usr/bin/env node
require('./dist/index');
控制台执行脚本
cnpm link
测试脚手架
mfc create child-vue
10、发布脚手架
在package.json中指定发布包含的文件,files数组中定义的项,会上传到npm。
"files": [
"templates",
"dist",
"index.js",
"package.json",
"readme.md"
],
cli作为库被安装时,devDependencies中依赖不会被安装,所以,cli发布后使用的插件要明确为开发依赖。
npm login
npm publish
三、总结
详细可以参考【前端自动化高手训练营】,年前看的,一直没有实践。正好最近在使用微前端架构开发项目。借此场景写了个CLI工具,后续应该还会继续完善。
四、参考资源
【前端自动化高手训练营】www.bilibili.com/video/BV11K…
今天的文章微前端自动化实践分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/19114.html