大纲
😁 什么是脚手架
😁 企业级脚手架的原理
😁 命令行工具的实现思路
🏆 举个例子 create-react-app命令行工具
😁 企业级脚手架模版的设计
🏆 企业级脚手架模版的基本组成有哪些
🏆 举个例子 UmiJS
什么是脚手架
编程领域中的“脚手架(Scaffolding)”指的是能够快速搭建项目“骨架”的一类工具。例如大多数的React项目都有src目录,public目录,webpack配置文件,babel配置等等,而src目录中又通常包含components目录,reducers目录等等。每次在新建项目时,你不得不手动创建这些固定的文件目录,繁琐而累赘。脚手架的作用就是帮助你完成这些重复性的工作,包括一键生成主要的目录结构、安装依赖等等。create-react-app、UmiJS就是著名的脚手架工具。
企业级脚手架的原理
我们一定使用过,create-react-app或vue-cli这些官方的脚手架。回忆下使用方式:
create-react-app my-app
vue create my-app
在进入到my-app目录,从package.json文件中找到对应的命令,执行即可开发自己的项目了。
这里我们把整个流程抽象一下:
- 先通过npm安装了一个包(脚手架)
- 这个包里有一个可执行文件,可以在全局执行对应的命令。
- 执行命令,会将用户输入的信息捕获,并与用户交互。
-
命令最终会将一个配置好的模板,拷贝到用户本地目录下,我们就可以直接用它来开发项目。
逻辑如下:
脚手架原理.png
于是,可以把脚手架的实现分为2个主要部分:
- 命令行工具的开发;
- 不同场景脚手架模版的开发
命令行工具的实现思路
企业级脚手架命令行工具的开发,首先,我们要发布一个npm包。这样就用户就可以通过安装命令行工具的包选择相应的配置去github上拉取对应的模版代码。所以命令行工具的实现思路如下:
- 注册npm账户,用于后期发包
- 开发终端命令工具,进行命令解析,解析成功后执行相应测操作,比如create-react-app my-app,匹配到create-react-app命令后开始执行my-app工程的创建工作
- 命令识别成功之后,需要实现一个终端交互功能,来获取获取用户的输入信息,最终根据信息匹配出合适的模版
- 匹配到模版之后需要拷贝模板文件,从远程下载文件到本地->读取下载的内容->将文件中特殊标识提换成用户输入->生成对应的文件及目录->将原目录删除。
以上思路有一些非常好用的工具可以帮助我们开发。学会将多种工具组合使用来达成自己的目标,也是一种能力。这里列出开发脚手架基础的包
- commander:获取终端输入的参数
- inquirer:交互式命令行工具
- download-git-repo:下载模板
- chalk:美化控制台显示
- fs-extra:文件的操作,复制,粘贴,增加,删除,文件内容的新增,替换
实现代码如下:
1.终端命令工具命令实现:
// 四种模板。对应我git仓库四个仓库地址
const tempIndex = {
react: 'reactTemplate', // react 模板路径
vue: 'vueTemplate', // vue 模板路径
h5: 'h5Template', // h5模板路径
dva: 'dvaTemplate', // dva模板路径
};
let templateName; // 模板名称
let inputIndex; // 除了拷贝模板,还支持自定义模板路径下载,
const program = new commander.Command(packageJson.name)
.version('v' + packageJson.version, '-v, --version')
.arguments('<templateName>')
.option('-f, --force', 'force delete the exist director')
.option('-d, --directly', 'copy the not specified template')
.alias('cp')
.description('create-blue-cli react myProject')
.action(function (index,name) {
inputIndex = index;
// 允许目标项目名和要复制的模板类型名顺序颠倒
if (tempIndex[index] || tempIndex[name]) {
if (tempIndex[index]) {
templateName = tempIndex[index];
} else {
templateName = tempIndex[name];
}
}
if (program.directly) {
templateName = index;
}
});
program.parse(process.argv)
// 没有输入任何参数,报语法错误,并打印help
if (program.args.length === 0) {
console.log(chalk.red('syntax error'));
program.help()
}
if (templateName) {
inquire(templateName, program.force);
} else {
console.log(`the template ${inputIndex} you want download do not exist`);
}
- 终端交互功能的实现
const inquire = (templateName, force) => {
inquirer.prompt([{
type: 'list', // 开发环境
name: 'latype',
prefix: '****',
suffix: '****',
message: '请选择脚手架模版:',
choices: [
'react',
'vue',
'h5',
'dva'
],
default: 0
}]).then(answers => {
create(templateName, tempIndex[answers.latype], force);
});
}
- 拷贝模板文件:
async function create(temp, renameFile, force = false) {
tempName = temp;
forceDel = force;
const file = process.cwd().replace(/\\/g, '/') + '/';
try {
// 检测项目文件夹是否已存在, 若存在,抛出错误
const res = await fs.pathExists(file);
if (res) {
if (forceDel) {
console.log(green('force remove the exist directory'));
await fs.remove(file);
downloadByGit(renameFile, tempName);
} else {
// 抛出错误,并提示可以使用-f参数来强制删除已存在的项目
console.log(chalk.red('Error, In this directory, the project name already exsits !'));
console.log(chalk.green('you can use option -f to force delete the directory !'));
}
return;
}
// 若不存在,直接从git下载
downloadByGit(renameFile, tempName);
} catch (err) {
console.error(red(err));
}
}
关于chalk,这是一款颜色标记插件,将要打印的文字用不同的颜色标记出来,像下面这样:
image.png- git文件下载:
function downloadByGit(callback, template) {
console.log(green('start download'));
console.log(`git@github.com:closertb/${template}.git`);
const result = spawn(
'git',
['clone', `git@github.com:closertb/${template}.git`],
{ stdio: 'inherit' }
);
const error = result.error;
if (error) {
console.log(red(error));
return;
}
// 定义回调;
callback && callback();
}
主要就这4段代码,就实现了命令的解析,和从git源端拷贝模板到本地。
举个例子 create-react-app命令行工具
企业级脚手架架模版的设计
企业级脚手架的基本组成有哪些
前端脚手架.png举个例子 UmiJS
umi,中文可发音为乌米,是一个可插拔的企业级 react 应用框架,默认开启约定式路由、按需加载配置.
umi跟create-react-app有什么区别呢?大多数前端同学都试过用cra来搭react项目吧,每次总是要花很多时间来配置typescript/less/css modules,还有配套的react router/redux等,搭建一个基础项目就能花上好几天,而umi这个企业级的前端应用框架,则已经帮我们内置了很多前端开发常用的功能,让我们节省了很多时间去配置,做到真正的开箱即用。
执行npx @umijs/create-umi-app
初始化出来的react项目,天然就支持typecsript、less、css modules,并整合了antd、dva(阿里自研的数据流方案),提供国际化、权限、数据流、配置式路由等开发者常用的功能,能够节省大量的初始化项目时间。
抄个架构图
Umijs.png约定化的思想
在使用umi框架的时候,很容易就发现它很多东西都是约定式的。所谓约定式就是指,按照约定好的方式开发,就能达到某种效果,中间的过程由框架帮我们完成,特别适合我们这种懒懒的开发。
- 建一个 locales 目录,就拥有了国际化
- 建一个 models 目录,就拥有了数据流
- 建一个 mock 目录,就拥有了数据 mock
- 建一个 access.ts 文件,就拥有了权限策略
- ...
这看起来是非常黑盒非常酷的,用这种方式其实对于团队代码风格的统一是非常有好处的,直接在框架层面就约束了大家的目录组织模式,便于团队维护。
但是缺点也是挺明显的,灵活性不如配置式的高,因为只能按特定的模式来开发,如果原本约定的方式不满足业务需求,就需要额外开发umi插件来魔改原本的功能。而且约定式的开发是相对其他框架来说很特别的一点,对新上手的同学来说需要时间去通读官网文档了解约定式的规则。
约定式路由
约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过src/pages目录和文件及其命名分析出路由配置, 也就是让umi根据约定好的目录结构帮我们生成路由配置文件。
比如以下文件结构:
└── pages
├── index.tsx
└── users.tsx
└── setting
└── index.tsx
会得到以下路由配置,
[
{ exact: true, path: '/', component: '@/pages/index' },
{ exact: true, path: '/users', component: '@/pages/users' },
{ exact: true, path: '/setting', component: '@/pages/setting/index' }
]
需要注意的是,满足以下任意规则的文件不会被注册为路由,
- 以.或_开头的文件或目录
- 以d.ts结尾的类型定义文件
- 以test.ts、spec.ts、e2e.ts结尾的测试文件(适用于.js、.jsx和.tsx文件)
- components和component目录
- utils和util目录
- 不是.js、.jsx、.ts或.tsx文件
- 文件内容不包含 JSX 元素
动态路由
src/pages/users/[id].tsx 生成的对应path /users/:id
嵌套路由
└── pages
└── users
├── _layout.tsx
├── index.tsx
└── list.tsx
生成的对应path
[
{ exact: false, path: '/users', component: '@/pages/users/_layout',
routes: [
{ exact: true, path: '/users', component: '@/pages/users/index' },
{ exact: true, path: '/users/list', component: '@/pages/users/list' },
]
}
]
全局Layout
src/layouts/index.tsx
[
{ exact: false, path: '/', component: '@/layouts/index',
routes: [
{ exact: true, path: '/', component: '@/pages/index' },
{ exact: true, path: '/users', component: '@/pages/users' },
],
},
]
404路由
src/pages/404.tsx
生成的对应path
[
{ exact: true, path: '/', component: '@/pages/index' },
{ exact: true, path: '/users', component: '@/pages/users' },
{ component: '@/pages/404' },
]
项目结构
├── /dist/ # 项目输出目录
├── /mock/ # 数据mock
├── /public/ # 公共文件,编译时copy至dist目录
├── /src/ # 项目源码目录
│ ├── /components/ # UI组件及UI相关方法
│ ├── /layouts/ # 全局组件
│ │ └── index.js # 入口文件
│ ├── /models/ # 数据模型
│ ├── /pages/ # 页面组件
│ │ └── index.js # html模版
│ ├── /services/ # 数据接口
│ ├── /themes/ # 项目样式
│ │ ├── default.less # 全局样式
│ │ └── vars.less # 全局样式变量
│ ├── /utils/ # 工具函数
│ │ ├── config.js # 项目常规配置
│ │ ├── menu.js # 菜单及面包屑配置
│ │ ├── config.js # 项目常规配置
│ │ ├── request.js # 异步请求函数(axios)
│ │ └── theme.js # 项目需要在js中使用到样式变量
├── package.json # 项目信息
├── .eslintrc # Eslint配置
└── .umirc.js # umi配置
└── .umirc.mock.js # mock配置
└── .theme.config.js # 主题less编译配置
.umirc.js
alias: { // webpack 额外配置一些文件夹的快速访问
themes: resolve(__dirname, './src/themes'),
components: resolve(__dirname,"./src/components"),
utils: resolve(__dirname,"./src/utils"),
config: resolve(__dirname,"./src/utils/config"),
enums: resolve(__dirname,"./src/utils/enums"),
services: resolve(__dirname,"./src/services"),
models: resolve(__dirname,"./src/models"),
routes: resolve(__dirname,"./src/routes"),
}
约定式路由,建立文件夹就生产路由
类 next.js 的约定式路由,无需再维护一份冗余的路由配置,支持权限、动态路由、嵌套路由等等。
云端打包会把package.json ^ 开头的包都拉当下大版本最新的包安装来打包
、
网友评论