开发脚手架与自动化构建工作流封装
去年6月24号开始工作,到今天刚好一周年了,纪念一下,分享最近学习的前端工程化笔记。
一、前端工程化
前端工程化是指遵循一定的标准和规范,通过工具去提高效率、降低成本的一种手段。
1. 前端开发中遇到的问题
- 想要使用ES6+新特性,但是兼容有问题
- 想要使用Less/Sass/PostCSS增强CSS的编程性,但是运行环境不能直接支持
- 想要使用模块化的方式提高项目的可维护性,但运行环境不能直接支持
- 部署上线前需要手动压缩代码及资源文件,部署过程需要手动上传代码到服务器
- 多人协同开发,无法硬性统一大家的代码风格,从仓库中pull回来的代码质量无法保证
2. 主要解决的问题
- 传统语言或语法的弊端
- 无法使用模块化/组件化
- 重复的机械式工作
- 代码风格统一、质量保证
- 依赖后端服务接口支持
- 整体依赖后端项目
3. 工程化表现
- 创建项目
- 创建项目结构
- 创建特定类型文件
- 编码
- 格式化代码
- 校验代码风格
- 编译/构建/打包
- 预览/测试
+ Web Server / Mock- Live Reloading / HMR
- Source Map
- 提交
- Git Hooks
- Lint-staged
- 持续集成
- 部署
- CI / CD
- 自动发布
4. 工程化不等于某个具体工具
工具并不是工程化的核心,工程化的核心是对项目的整体规划或架构,工具只是落地和实现工程化的一个手段
一些成熟的工程化集成:
- create-react-app
- vue-cli
- angular-cli
- gatsby-cli
上面的几个是某个项目的官方提供的集成化方案
5. 工程化与Node.js
工程化工具都是Node.js开发的
二、脚手架工具
脚手架的本质作用就是创建项目基础结构、提供项目规范和约定。
1. 脚手架工具的作用
因为在前端工程中,可能会有:
- 相同的组织结构
- 相同的开发范式
- 相同的模块依赖
- 相同的工具配置
- 相同的基础代码
脚手架就是解决上面问题的工具,通过创建项目骨架自动的执行工作。IDE创建项目的过程就是一个脚手架的工作流程。
由于前端技术选型比较多样,又没有一个统一的标准,所以前端脚手架不会集成在某一个IDE中,一般都是以一个独立的工具存在,相对会复杂一些。
2. 常用的脚手架工具
-
第一类脚手架是根据信息创建对应的项目基础结构,适用于自身所服务的框架的那个项目。
- create-react-app
- vue-cli
- Angular-cli
-
第二类是像Yeoman为代表的通用型脚手架工具,会根据模板生成通用的项目结构,这种脚手架工具很灵活,很容易扩展。
-
第三类以Plop为代表的脚手架工具,是在项目开发过程中,创建一些特定类型的组件,例如创建一个组件/模块所需要的文件,这些文件一般都是由特定结构组成的,有相同的结构。
3. 通用脚手架工具剖析
(1)Yeoman + Generator
Yeoman是最老牌、最强大、最通用的脚手架工具,是创建现代化应用的脚手架工具,不同于vue-cli,Yeoman更像是脚手架运行平台,我们可以通过Yeoman搭配不同的Generator去创建任何类型的项目,我们可以创建我们自己的Generator,从而去创建我们自己的前端脚手架。缺点是,在框架开发的项目中,Yeoman过于通用不够专注。
如果使用Yeoman:
- 在电脑上全局安装Yeoman:
yarn global add yo
- Yeoman要搭配相应的Generator创建任务,所以要安装Generator。例如是创建node项目,则安装generator-node:
yarn global add generator-node
- 创建一个空文件夹:
mkdir my-module
, 然后进入文件夹:cd my-module
- 通过Yeoman的yo命令安装刚才的生成器(去掉生成器名字前的generator-):
yo node
- 交互模式填写一些项目信息,会生成项目基础结构,并且生成一些项目文件,然后自动运行
npm install
安装一些项目依赖。
(2)SubGenerator
有时候我们可能不需要创建一个完成的项目结构,而是在已有项目的基础上,创建一些项目文件,如README.md,或者是创建一些特定类型的文件,如ESLint、Babel配置文件
- 运行SubGenerator的方式就是在原有Generator基础上加上:SubGenerator的名字,如:
yo node:cli
- 在使用SubGenerator前,要先去查看一下Generator之下有哪些SubGenerator
(3)Plop
Plop是一个小而美的脚手架工具,通常用于创建项目中特定类型文件的小工具,一般是把Plop集成到项目中,用来自动化创建同类型的项目文件。
如何使用Plop创建文件:
- 将plop模块作为项目开发依赖安装
- 在项目根目录下创建一个plopfile.js文件
- 在plopfile.js文件中定义脚手架任务
- 编写用于生成特定类型文件的模板
- 通过Plop提供的cli运行脚手架任务
4. 脚手架工作原理
脚手架的工作原理就是在启动脚手架之后,回自动地去询问一些预设问题,通过回答的结果结合一些模板文件,生成项目的结构。
使用NodeJS开发一个小型的脚手架工具:
-
用
yarn init
初始化一个空文件夹:sample-scaffolding
-
在
package.json
中添加bin
属性指定脚手架的命令入口文件为cli.js
{
"name": "sample-scaffolding",
"version": "1.0.0",
"main": "index.js",
"bin": "cli.js",
"license": "MIT",
"dependencies": {
"ejs": "^3.1.3",
"inquirer": "^7.1.0"
}
}
- 编写
cli.js
#!/usr/bin/env node
// Node CLI 应用入口文件必须要有这样的文件头
// 如果Linux 或者 Mac 系统下,还需要修改此文件权限为755: chmod 755 cli.js
// 脚手架工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件
const path = require('path')
const fs = require('fs')
const inquirer = require('inquirer') // 发起命令行交互询问
const ejs = require('ejs') // 模板引擎
inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Project name?'
}
]).then(answer => {
console.log(answer)
// 模板目录
const tempDir = path.join(__dirname, 'templates')
// 目标目录
const destDir = process.cwd()
// 将模板下的文件全部转换到目标目录
fs.readdir(tempDir, (err, files) => {
if (err) throw err
files.forEach(file => {
// 通过模板引擎渲染文件
ejs.renderFile(path.join(tempDir, file), answer, (err, result) => {
if(err) throw err
// 将结果写入到目标目录
fs.writeFileSync(path.join(destDir, file), result)
})
})
})
})
- 将该cli程序link到全局:
yarn link
- 然后再其他文件夹中执行:
sample-scaffolding
命令,就可以根据模板自动化创建文件了。
5. 自定义Generator开发脚手架
|-- generators/ ······生成器目录
| |-- app/ ······默认生成器目录
| |–templates ······模板文件夹
| |–foo.txx ······模板文件
| |–index.js ······默认生成器实现
| |–component/ ······其他生成器目录
| |–index.js ······其他生成器实现
|–package.json ······模块包配置文件
注意:Yeoman的生成器名称必须是
generator-<name>
,安装生成器的时候,就执行yo <name>
创建Generator生成器的步骤:
-
mkdir generator-sample
-
cd generator-sample
-
yarn init
-
yarn add yeoman-generator
-
创建文件:generators/app/index.jsx
// 此文件作为Generator的核心入口 // 需要导出一个集成字Yeoman Generator的类型 // Yeoman Generator在工作时会自动调用我们在此类型中定义的一些生命周期方法 // 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,比如文件写入 const Generator = require('yeoman-generator') module.exports = class extends Generator { prompting () { // Yeoman 在询问用户环节会自动调用次方法 // 在此方法中可以调用父类的prompt()方法发出对用户命令行询问 return this.prompt([ { type: 'input', name: 'name', message: 'Your project name', default: this.appname // appname为项目生成目录 } ]).then( answers => { // answers => {name: 'user input value'} this.answers = answers }) } writing () { // Yeoman 自动在生成文件阶段调用次方法 // 我们这里尝试往项目目录中写入文件 // this.fs.write( // this.destinationPath('temp.txt'), // Math.random().toString() // ) // 通过模板方法导入文件到目标目录 // 模板文件路径 const tmpl = this.templatePath('foo.txt') // 输出目标路径 const output = this.destinationPath('foo.txt') // 模板数组上下文 const context = { title: 'Hello', success: false} // const context = this.answers // 从命令行获取的参数 this.fs.copyTpl(tmpl, output, context) } }
-
templates/foo.txt作为模板文件
这是一个模板文件 内部可以使用EJS模板标记输出数据 例如:<%= title %> 其他的EJS语法也支持 <%if (success) {%> hello world <%}%>
-
执行
yarn link
, 此时这个模块就会作为全局模块被link到全局,别的项目可以直接使用它。 -
创建一个别的文件夹my-proj, 在这个文件夹中执行:
yo sample
-
发布到npmjs网站上:
yarn publish --registry=https://registry.yarnpkg.com
5. Plop
yarn add plop
plopfile.js
// Plop 入口文件,需要导入一个函数
// 此函数接受一个plop对象,用户创建生成器任务
module.exports = plop => {
plop.setGenerator('component', {
description: 'create a component',
prompts: [
{
type: 'input',
name: 'name',
message: 'component name',
default: 'MyComponent'
}
],
actions: [
{
type: 'add', // 代表添加文件
path: 'src/components/{
{name}}/{
{name}}.js',
templateFile: 'plop-templates/component.hbs'
},