前端工程化定义和主要解决的问题
由于现在前端可以做的范围越来越大,早已不是仅限至于网页。像 App、小程序、甚至桌面应用,都可以使用前端技术来开发。
所以原来通过前端写 Demo,后端套数据的模式早就无法支持。
前端工程化就是在这个背景下成为了受人重视的技术,并且是目前前端开发必备的技术之一。
技术是为了解决问题而存在的。前端工程化解决了很多问题。比如 ES6 新语法、Less/Sass/PostCss 等 CSS 工具、模块化,都是无法被运行环境直接支持的。但是使用前端工程化以后,就可以将它们转换为运行环境支持的代码。
还有一些部署项目时所需要做的操作,比如手动压缩代码及资源文件,手动将代码上传至服务器等。人来处理这些任务,很容易出现操作失误。让机器自动来完成这些任务更为合理。
多人协作时,代码风格,代码质量都需要得到保证。
开发过程中需要等待后端接口提前完成。
总结起来,前端工程化解决的问题主要有 6 个部分。
传统语言或语法的弊端。(ES6+语法、TypeScript)
无法使用模块化/组件化
重复的机械式工作(Build、Publish)
代码风格统一、质量保证(git、ESLint)
依赖后端服务接口支持(Mock)
整体依赖后端项目(DevServer)
工程化表现
一切以提高效率、降低成本、质量保证为目的的手段都属于「工程化」。
一切重复的工作都应该被自动化。
一个常规项目的环节有 5 步:
创建项目-编码-预览/测试-提交-部署
1.创建项目阶段:
借助脚手架工具自动创建项目目录结构和特定类型的文件。
2.编码阶段:
借助工具实现代码格式化、校验代码风格,还可以使用新的特性来编写源代码,通过编译/构建/打包生成运行环境支持的运行时代码。
3.预览/测试阶段
开发过程,需要 Mock 服务,需要使用基础的 Web 服务器来托管项目,比如 Nginx、Apatch 等。但这些服务都不提供热更新的体验,所以需要借助现代化的工具来提高开发体验。
在编译转换后,如果发生异常,需要定位源代码的位置。这是就需要使用 Source Map 技术来映射运行代码和源代码。
4.代码提交阶段
使用 git hooks 对项目整体进行检查,包括代码风格的检查和项目质量的检查,确保不会将有问题的代码提交到 git 仓库中,这样 git 仓库的代码永远都是可用的。除此以外,还可以对 git log 的格式进行限制,这样在代码分支合并和回滚时有很大的价值。
主要借助的工具有 Lint-staged 和持续集成等。
5.部署阶段
CI/CD、自动发布。
可以通过一行命令自动的将项目打包上传 ftp 服务器。
工程化不等于工具
现在有些工具非常强大,像 Webpack,以至于很多人认为前端工程化就是值 webpack。
实际上,工具不是工程化的核心,工程化的核心是对项目的整体规划和架构。工具只是实现这种规划和架构的一种手段。
一个项目的工程化,首先要规划出一个项目整体的工作流架构。
如文件组织结构(按作用分层、按业务分层)、代码开发范式(语法、规范、标准、语言,如 ES6+、TypeScript 等)、前后端分离方式(Ajax 和中间层)。
规划完工作流整体架构以后,再来考虑使用哪些工具来实现这套规划。
可以借鉴目前业界成熟的工程化方案中的思路,如 create-react-app、vue-cli、angluar-cli、gatsby-cli 等。
它们并不是一个简单的项目生成脚手架,而是工程化工具的集成。
工程化与 Node.js
目前前端的工程化很大程度上都归功于 Node.js。
Node.js 是继 Ajax 后有一次颠覆式的前端工业革命。
没有 Node.js 就没有今天的前端。
目前绝大多数工程化工具都是由 Node.js 开发的,所以前端工程化是由 Node.js 强力驱动的。
工程化是为了解决问题而存在的。
脚手架工具概要
脚手架工具是前端工程化的发起者。
自动帮助我们创建项目基础结构、提供项目规范和约定的工具。
在开发相同类型的项目时,会有很多相同的东西,包括:
组织结构
开发范式
模块依赖
工具配置
基础代码
...
这些相同的东西不需要人为创建,可以使用脚手架工具快速创建项目骨架。然后基于这个骨架来开发。
比如使用大型的 IDE,如 IDEA 和 Visual Studio 等来创建项目的过程就是一个脚手架的工作流程。
前端的脚手架和服务端项目、移动端项目不太一样。因为技术选型过于多样化,所以不会集中在某一个特定的 IDE 中,同时会在一个脚手架工具中。
常用的脚手架工具
React 项目 create-react-app
Vue 项目 vue-cli
Angular angular-cli
它们根据用户选择的信息创建对应的项目基础结构,不过它们只能服务于本身的框架,并不通用。
另一类是通用型脚手架工具,代表像 Yeoman。优点是灵活易扩展。
还有一类是在项目开发过程中会使用到的,比如 Plop。它可以创建某种特定类型的文件。比如某个模块和某个组件的基础代码。
Yeoman
yeoman 是最老牌、最强大、最通用的脚手架工具,基于 node.js 开发。
不同于常规的脚手架工具,yeoman 更像是一个脚手架的运行平台。
我们可以自己定义 Generator 来创建属于自己的项目脚手架。
Yeoman 的缺点就是它的优点,因为不够专注,所以在专注于某项框架的脚手架创建过程中不如 vue-cli 等工具。
基本使用
1.全局安装 yeoman。
npm i -g yo
安装完 yo 之后,可以查看版本号来确认安装是否成功。
yo --version
2.安装 Generator。
只安装 yo 是不够的,它只是一个 Generator 运行模块,还需要安装特定的 Generator。
比如一个 node 项目,就需要安装generator-node
。
3.通过 yo 命令运行 generator。
安装完成之后,就可以创建 node 项目了。
命令是 yo 加上 generator 的名字。
yo node
在创建过程中,yeoman 会提问一些问题,通过答案来创建不同的项目结构。
Sub Generator
每个 generator 都可以提供 sub generator,用于生成特定的文件或者基础代码。但也不是每一个 generator 都提供了 sub generator,具体可以去 generator 的官方文档去查看。
运行 sub generator 也比较简单,就是 yo 加上 generator,再加上:,sub generator 的名字。
比如在 node.js 项目中创建一个命令。
yo node:cli
Yeoman 使用步骤总结
大致上分为 6 步。
- 明确需求。
- 找到合适的 Generator。
- 全局安装 Generator。
- 通过 yo 运行对应的 Generator。
- 通过命令行交互填写选项。
- 生成所需要的项目结构。
自定义 Generator
因为官方的 generator 在某些场景下作用会非常局限,所以我们有必要自定义自己的 generator。
创建 Generator 模块
generator 本质上就是一个 npm 模块,只需要符合特定的结构即可。
generator 的名字必须是 generator-<name>
的格式。
generator 需要有一个生成器的基类。
整体的的操作步骤如下。
创建项目根目录。
mkdir generator-sample
初始化项目。
npm init -y
安装 yeoman-generator。
npm i yeoman-generator
按照项目结构要求,创建特定的文件夹。
入口文件是 generators/app/index.js
。
这个文件需要导出一个继承自 Yeoman Generator 的类。Yeoman Generator 在工作时会自动调用这个 class 中的生命周期函数。
在这个导出的类中,可以调用父类提供的工具方法实现一些具体的功能。
比如在 writing 生命周期函数中可以创建一些文件。
操作文件可以使用父类中提供的 this.fs 模块下的方法。这个 fs 和 nodejs 原生的 fs 不一样,是高度封装过的,所以功能更加强大。
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", success: false };
this.fs.copyTo(tmpl, output, context);
}
};
根据模板创建文件
如果一个项目所需要的文件过多,挨个文件创建会很麻烦,这时候使用模板的方式来创建文件更加方便。
在 generators/app 目录下创建 templates 文件夹,该文件夹下的文件都是模板文件。可以被 this.templatePath 方法获取到。
模板文件完全遵循 EJS 语法,即尖括号百分号的写法。
<%= name %>
最终使用 this.fs.copyTpl 方法将模板拷贝到目标目录下。
相对于手动创建每个文件,模板的方式大大提高了效率。
接收用户输入数据
模版中的动态数据需要用户来输入,一般会在命令行中发起询问。
promting 生命周期函数中可以调用父类的 prompt 方法来发起询问,这个询问返回的是一个异步函数。
const Generator = require("yeoman-generator");
module.exports = class extends (
Generator
) {
prompting() {
// prompt 的参数是一个数组,可以发起多个问题
// 每个问题都是以一个对象的形式体现
// type 是问题的类型,input 代表需要用户来输入,除此以外还有单选、多选等。
// name 是问题的 key
// message 是问题的提示
// default 是用户不做输入时的默认值
return this.prompt([
{
type: "input",
name: "name",
message: "Your project name",
default: this.appname,
},
]).then((answers) => {
// answers 会以一个对象的形式返回
// { name: 'user input value' }
// 将这个对象挂载到 this.answers
this.answers = answers;
});
}
writing() {
// 模版文件路径
const tmpl = this.templatePath("foo.txt");
// 输出目标路径
const output = this.destinationPath("foo.txt");
// 将上下文对象替换为用户输入对象
const context = this.answers;
this.fs.copyTpl(tmpl, output, context);
}
};
Vue Generator 案例
首先可以通过 vue-cli 生成一个基础的 vue 项目骨架,然后将这个骨架作为基础模板来使用。需要被变量替换的地方都使用 EJS 的语法替换掉。
在 writing 生命周期函数中通过遍历的方式将模板中所有文件都调用一遍 this.fs.copyTpl。
发布 Generator
generator 的发布流程和 npm 普通的包是一样的,通过 npm publish
命令去发布。
如果想要被 yeoman 官方收录,那么就需要在关键词中添加 yeoman-generator。
Plop
除了 yeoman 这类大型的脚手架工具外,还有很多小而美的脚手架工具,比如 Plop。
Plop 常用于在项目开发过程中创建某类类型的文件,优点类似于 Yeoman 中 sub generator 的概念。
应用场景
比如在 React 项目的开发过程中,开发一个组件需要创建 1 个目录,该目录下需要 3 个文件。
.
|____Footer
| |____Footer.tsx
| |____Footer.scss
| |____Footer.test.ts
如果我们手动的创建这些文件,并编写通用的代码,过程会非常繁琐,所以可以使用 Plop 来实现这个功能。
Plop 用法
首先将 plop 添加到项目中。
npm install --save-dev plop
在项目根目录下创建 plopfile.js 文件,它是 plop 工作所需要的入口文件。
一个简单的 plopfile.js 配置如下:
// 导出一个函数
// 该函数需要接收一个 plop 对象参数
module.exports = (plop) => {
// 调用 setGenerator 方法
// 第一个参数是命令名,第二个参数是配置对象
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}}.tsx",
// 模板的文件路径
templateFile: "plop-templates/component.hbs",
},
// 添加 scss 文件和 test 文件
{
type: "add", // 添加文件
// 使用 {{ }} 语法拿到上面询问的值
path: "src/components/{{name}}/{{name}}.scss",
// 模板的文件路径
templateFile: "plop-templates/component.scss.hbs",
},
{
type: "add", // 添加文件
// 使用 {{ }} 语法拿到上面询问的值
path: "src/components/{{name}}/{{name}}.test.ts",
// 模板的文件路径
templateFile: "plop-templates/component.test.ts.hbs",
},
],
});
};
上面这种 hbs 文件遵循 handlebars 的语法,具体参考:https://handlebarsjs.com/
模板一般存放在根目录下的 plop-templates 文件夹下。
最后就可以通过命令运行 plop 脚本。
npx plop component
总结一下,plop 的使用共有 5 步。
- 将 plop 模块作为项目开发依赖安装。
- 在项目根目录下创建一个 plopfile.js 文件。
- 在 plopfile.js 文件中定义脚手架任务。
- 编写用于生成特定类型文件的模板。
- 通过 Plop 提供的 CLI 运行脚手架任务。
脚手架的工作原理及简单 Demo 开发
脚手架的工作原理相对简单,大致上都是在启动时询问用户一些问题,通过得到的答案配合模板生成对应的文件。
开发脚手架的思路:
通过命令行交互询问客户问题。
根据用户回答的结果生成文件。
创建项目文件夹。
mkdir sample-scaffolding
初始化项目。
npm init -y
在 package.json 中添加 bin 字段,定义 cli 的入口文件。
{
"bin": "cli.js"
}
创建 cli.js。
touch cli.js
因为需要在控制台对用户发起询问,所以要借助一个 nodejs 的库,inquirer。
npm i --save-dev inquirer
除此之外,还需要借助模板引擎来给模板文件注入变量,所以需要借助 ejs。
npm i ejs
现在可以在 cli.js 中编写逻辑。
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const inquirer = require("inquirer");
const ejs = require("ejs");
// 执行命令后,直接发起询问。
inquirer
.prompt([
{
type: "input",
name: "name",
message: "Project name?",
},
])
.then(
// 返回值 anwsers 是一个对象
(anwsers) => {
// 模板目录
const tmplDir = path.join(__dirname, "templates");
// 目标目录
const destDir = process.cwd();
// 将模板目录下的所有模板文件通过模板引擎转换后复制到目标目录中
fs.readdir(tmplDir, (err, files) => {
if (err) throw err;
// 遍历所有模板文件
files.forEach((file) => {
// 通过 ejs 渲染文件
ejs.renderFile(path.join(tmplDir, file), anwsers, (err, result) => {
if (err) throw err;
// 将 ejs 渲染后的文件写入目标文件中
fs.writeFileSync(path.join(destDir, file), result);
});
});
});
}
);
第一行的 #!/usr/bin/env node
是告诉系统,该文件通过 node 来执行。
接下来创建模板文件夹。
mkdir templates
创建模板文件 index.html 和 style.css。
index.html 内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= name %></title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
hello, world!
</body>
</html>
style.css 内容:
body {
background-color: #ddffdd;
color: #060606;
}
将命令链接到全局。
npm link
到这里,简单的脚手架 demo 就开发完成了。
现在开始测试,到其他目录创建一个项目目录。
mkdir test-project
进入该目录,执行脚手架命令。
sample-scaffolding
此时命令行会出现询问,输入项目名,就可以看到生成后的文件了。
? Project name? hello