工程化和脚手架入门
工程化概述
面临的问题
技术是为了解决问题而存在的
- 想要使用 ES6+ 新特性,但是兼容有问题
- 想要使用 Less/Sass/PostCSS 增强 CSS 的编程性,但是运行环境不能直接支持
- 想要使用模块化的方式提高项目的可维护性,但运行环境不能直接支持
- 部署上线前需要手动压缩代码及资源文件,部署过程需要手动上传代码到服务器
- 多人协作开发,无法硬性统一大家的代码风格,从仓库中 pull 回来的代码质量无法保证
- 部分功能开发时需要等待后端服务接口提前完成
工程化在项目中的表现
一切重复的工作都应该被自动化
创建项目
- 使用脚手架工具自动完成项目搭建(创建项目结构、特定类型文件)
编码
- 自动格式化代码、校验代码风格、借助编译工具在开发阶段就可以使用新特性
预览/测试
传统预览需要借助 Apache、Nginx 提供基础 Web 服务,让应用在上面运行
现在可以使用 Live Reloading/HMR 热更新方式,不用手动刷新
Web Server/Mock 解决后端服务未完成情况下提前开发具体业务功能
实际过程出现问题,我们想定位源代码位置可以借助 Source Map
提交
- 使用 Git Hooks 在代码提交前,整体进行检查(项目质量、风格)
- Lint-staged
- 持续集成
部署
- CI/CD
- 自动部署到服务器
工程化不等于工具
- Powered by Node.js
脚手架工具
脚手架本质作用:创建项目基础结构、提供项目规范和约定
- 相同的组织结构
- 相同的开发范式
- 相同的模块依赖
- 相同的工具配置
- 相同的基础代码
vue-cli
、create-react-app
、angular-cli
、yeoman
这些都是可以提供根据自己模板自定义的脚手架
Yeoman 基本使用
- 在全局安装 yo
yarn global add yo
npm install
- yeoman 必须搭配对应的 generator 使用
yarn global add generator-node
npm install -g generator-node
- 通过 yo 运行 generator
cd project-dir
mkdir my-module
yo node
Sub Generator
有时候并不需要生成全新项目,指示想给原有项目增加文件(README、babel)可以用
Sub Generator
bashyo node:cli # link 到全局范围 yarn link # 安装依赖 yarn
使用步骤
- 明确你的需求
- 找到合适的 Generator
- 全局范围安装找到的 Generator
- 通过 Yo 运行对应的 Generator
- 通过命令行交互填写选项
- 生成你所需要的项目结构
yo webapp
使用 webapp 生成器生成项目,它会依赖一些 C++ 模块,这些模块在安装时会下载二进制文件,这些模块并不会通过 npm 镜像去加速
Generator
Generator 基本结构
|- generators/ ............................生成器目录
| |- app/ ............................默认生成器目录
| | |- index.js.....................默认生成器实现
+| |- component/ ......................其他生成器目录
+| |- index.js.....................其他生成器实现
|-package.json ............................模块包配置文件
模块名必须是 generator-<name>
格式,不然无法找到
mkdir generator-sample
cd generator-sample
yarn init
yarn add yoman-generator
yarn link
yo sample
Yo 报错
nvm use v8...
Node 切换至 8 版本即可
Error sample
You don't seem to have a generator with the name “sample” installed.
But help is on the way:
You can see available generators via npm search yeoman-generator or via http://yeoman.io/generators/.
Install them with npm install generator-sample.
To see all your installed generators run yo --generators. Adding the --help option will also show subgenerators.
If yo cannot find the generator, run yo doctor to troubleshoot your system
code .
打开vscode,创建 generators/app/index
文件
- 此文件作为 Generator 的核心入口
- 需要导出一个继承自 Yeoman Generator 的类型
- Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法
- 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
writing() {
this.fs.write(
this.destinationPath('temp.txt'),
Math.random().toString()
)
}
}
使用模板文件
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
writing() {
// 模板文件路径
const tmpl = this.templatePath('bar.html')
// 输出目标路径
const output = this.destinationPath('bar.html')
// 模板数据上下文
const context = this.answers
this.fs.copyTpl(tmpl, output, context)
}
}
接收用户输入
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 => {
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)
}
}
根据模板创建文件
创建
generators/app/index.js
将要批量生成提前准备好的文件,拷贝模板到 template 目录
可能发生变化的地方替换成 ejs 模板标记
<%= name %>
html<title><%= name %></title>
如果想原封输出 ejs 模板标记,需要多加个
%
html<link rel="icon" href="<%%= BASE_URL %>favicon.ico">
遍历每一个路径,生成目标目录对应文件
jsconst Generator = require('yeoman-generator') module.exports = class extends Generator { 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 => { // item => 每个文件路径 this.fs.copyTpl( this.templatePath(item), this.destinationPath(item), this.answers ) }) } }
yarn link
到全局yo zce-vue
发布 Generator
echo node_modules .gitignore
git init
git add .
git commit -m 'feat: initial commit'
git remote add ...
git push -u origin master
yarn publish --registry=https://registry.yarnpkg.com
yarn logout
Plop
类似 sub-generator
,一般使用在已有项目中创建同样的文件
yarn add plop --dev
plop.setGenerator
参数1:生成器的名字
参数2:生成器的配置选项
description
:描述prompts
:命令行问题actions
:完成命令行交互后的动作
// 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'
},
{
type: 'add', // 代表添加文件
path: 'src/components/{{name}}/{{name}}.css',
templateFile: 'plop-templates/component.css.hbs'
},
{
type: 'add', // 代表添加文件
path: 'src/components/{{name}}/{{name}}.test.js',
templateFile: 'plop-templates/component.test.hbs'
}
]
})
}
配置好后,执行
yarn plop component
? yarn name Sidebar
√ ++ \src\components\Sidebar\Sidebar.js
√ ++ \src\components\Sidebar\Sidebar.css
√ ++ \src\components\Sidebar\Sidebar.test.js
- 将 plop 模块作为项目开发依赖安装
- 在项目根目录下创建一个 plopfile.js 文件
- 在 plopfile.js 文件中定义脚手架任务
- 编写用于生成特定类型文件的模板
- 通过 plop 提的 cli 运行脚手架任务
脚手架工作原理
在 package.json
中添加 bin
字段,指定 cli 入口
yarn link
生成软链接,之后执行sample-scaffolding
即可
{
"name": "sample-scaffolding",
"version": "0.1.0",
"main": "index.js",
"bin": "cli.js", // 添加bin 字段
"author": "zce <w@zce.me> (https://zce.me)",
"license": "MIT"
}
Node CLI 应用入口文件必须要有文件头
#!/usr/bin/env node
如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
具体就是通过
chmod 755 cli.js
实现修改
yarn add inquirer
脚手架的工作过程:
- 通过命令行交互询问用户问题
- 根据用户回答的结果生成文件
#!/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(answers => {
// 模板目录
const tmplDir = path.join(__dirname, 'templates')
// 目标目录
const destDir = process.cwd()
// 将模板下的文件全部转换到目标目录
fs.readdir(tmplDir, (err, files) => {
if (err) throw err
files.forEach(file => {
// 通过模板引擎渲染文件
ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => {
if (err) throw err
// 将结果写入目标文件路径
fs.writeFileSync(path.join(destDir, file), result)
})
})
})
})