概述
前端工程化主要解决的问题
- 传统语言或语法弊端(比如兼容ES6);
- 无法使用模块化/组件化;
- 重复的机械性工作;
- 代码风格统一、质量保证;
- 依赖后端服务接口支持;
- 整体依赖后端项目。
工程化表现
一切以提高效率、降低成本、质量保证为目的的手段都属于「工程化」。
一切重复的工作都应该被自动化。
- 创建项目:使用脚手架工具自动完成基础结构的搭建
- 创建项目结构
- 创建特定类型文件
- 编码
- 格式化代码
- 校验代码风格
- 编译/构建/打包
- 预览/测试
- Web Server/Mock
- Live Reloading/HMR
- Source Map
- 提交
- Git Hooks
- Lint-staged
- 持续集成
- 部署
- CI/CD
- 自动发布
脚手架工具开发
本质作用
创建项目基础结构、提供项目规范和约定。
常用的脚手架工具
React -> create-react-app
Vue.js -> vue-cli
Angular -> angular-cli
Yeoman
Plop
通用脚手架工具剖析
Yeoman
- 基础使用
- 在全局范围安装yo
npm install yo --global // yarn global add yo
- 安装对应的generator
npm install generator-node --global // yarn global add generator-node
- 通过yo运行generator
mkdir my-module
yo node
- sub generator
有时我们并不需要去创建完整的项目结构,只是需要在已有的项目基础上创建一些特定类型的文件,给已有的项目创建readme、eslint、babel等,这些文件都有一些基础代码,自己手动去配很容易配错,通过生成器帮我们自动生成以提高效率。
yo node:cli // generator-node 提供的一个sub generator
- 使用步骤总结
- 明确需求;
- 找到合适的Generator;
- 全局范围安装找到的Generator;
- 通过Yo运行对应的Generator;
- 通过命令行交互填写选项;
- 生成所需的项目结构。
Generator
创建Generator模块
yeoman的generator模块名称必须为generator-
。
步骤如下:
mkdir generator-sample
cd generator-sample
yarn init
yarn add yeoman-generator
code . // 用vscode打开该文件夹
在根目录创建generators > app > index.js
。
// index.js
// 此文件作为Generator的核心入口
// 需要导出一个继承自Yeoman Generator的类型
// Yeoman Generator在运行时会自动调用我们在此类型中定义的一些生命周期方法
// 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
writing() {
// Yeoman自动在生成文件阶段调用此方法
// 这里尝试往项目目录中写入文件
this.fs.write(
this.destinationPath('temp.txt'),
Math.random().toString()
)
}
}
将模块链接到全局范围,使之成为一个全局模块包:
yarn link
cd ..
mkdir my-proj
cd my-proj
yo sample // 通过yeoman运行这个生成器
根据模版创建文件
在app
文件夹下创建templates > foo.txt
:
// foo.txt
这个是一个模板文件
内部可以使用 EJS 模版标记输出数据
例如:<%= title %>
其他的 EJS 语法也支持
<%if (success) { %>
哈哈哈
<% }%>
// index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
writing() {
// 通过模板方式写入文件到目标目录
// 模板文件路径
const tmpl = this.templatePath('foo.txt')
// 输出目标路径
const output = this.destinationPath('foo.txt')
// 模板数据上下文
const context = { title: 'hello, world!', success: false }
this.fs.copyTpl(tmpl, output, context)
}
}
相对于手动创建每一个文件,模版的方式大大提高了效率,特别是在文件比较多比较复杂的情况下。
接收用户输入数据
// index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
prompting() {
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() {
const tmpl = this.templatePath('bar.html')
// 输出目标路径
const output = this.destinationPath('bar.html')
// 模板数据上下文
const context = this.answers
this.fs.copyTpl(tmpl, output, context)
}
}
Vue Generator案例
将项目结构拷贝至tamplates
文件夹,将内部的可变参数改为EJS标记。
const generator = require('yeoman-generator')
module.exports = class extends generator {
prompting() {
return this.prompt([
{
type: 'input',
name: 'name',
message: 'Your project name',
default: this.appname
}
]).then(answers => {
this.answers = answers
})
}
writing() {
// 把每一个文件都通过模板转换到目标路径
const templates = [
'.browserslistrc',
'.editorconfig',
'.env.development',
'.env.production',
'.eslintrc.js',
'.gitignore',
'babel.config.js',
'package.json',
'postcss.config.js',
'README.md',
'public/favicon.ico',
'public/index.html',
'src/App.vue',
'src/main.js',
'src/router.js',
'src/assets/logo.png',
'src/components/HelloWorld.vue',
'src/store/actions.js',
'src/store/getters.js',
'src/store/index.js',
'src/store/mutations.js',
'src/store/state.js',
'src/utils/request.js',
'src/views/About.vue',
'src/views/Home.vue'
]
templates.forEach(item => {
this.fs.copyTpl(
this.templatePath(item),
this.destinationPath(item),
this.answers
)
})
}
}
发布Generator
npm publish
/ yarn publish
Plop
小而美的脚手架工具。用于创建项目中特定类型文件的小工具。一般将Plop集成到项目中自动化地创建同类型的项目文件。
使用步骤
- 将plop模块作为项目开发依赖安装;
yarn add plop --dev
- 在项目根目录下创建plopfile.js文件;
- plopfile.js文件中定义脚手架任务:
// Plop 入口文件,需要导出一个函数
// 此函数接收一个 plop 对象,用于创建生成器任务
module.exports = plop => {
// setGenerator属性接收两个参数
// 1. 生成器名字
// 2. 生成器内部方法
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-template/component.hbs' // hbs 是 Handlebars.js 模板引擎
}
]
})
}
- 编写用于生成特定类型文件的模板;
- 通过Plop提供的CLI运行脚手架任务。
yarn plop component
开发一款脚手架
- 在
package.json
文件中添加bin
,用于指定CLI应用的入口文件;
- 编写cli.js:
// cli.js
#!/usr/bin/env node
// Node CLI 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改
// 文件系统
const fs = require('fs');
// path 模块提供了一些用于处理文件路径的小工具
const path = require('path');
// 交互式命令行工具
const inquirer = require("inquirer");
// 高效的嵌入式 JavaScript 模板引擎
const ejs = require('ejs');
// 脚手架的工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件
inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'project name'
}
]).then(answers => {
// 模板目录
const tmplDir = path.join(__dirname, 'templates')
// 目标目录
const destDir = process.cwd() // 返回 Node.js 进程的当前工作目录
// 将模板下的文件全部转换到目标目录
fs.readdir(tmplDir, (err, files) => {
if (err) throw err
files.forEach(file => {
// 通过模板引擎渲染文件
ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => {
// result => 输出渲染后的 HTML 字符串
if (err) throw err
// 将结果写入目标文件路径
fs.writeFileSync(path.join(destDir, file), result)
})
})
})
})
- 使用
yarn link
将模块链接到全局范围; - 在命令行中直接输入脚手架名称即可运行。