读书,学会和伟大的灵魂沟通;学会独处,学会独立思考;学会坚韧,也学会谦卑
一、工程化
定义: 一切以提高效率、降低成本、质量保证为目的的手段都属于 工程化
1. 主要解决的问题:
- 想要使用ES6+新特性,但是兼容有问题 (传统语言或语法的弊端)
- 想要使用Less/Sass/PostCSS增强CSS的编程性,但是运行环境不能直接支持想要使用模块化的方式提高项目的可维护性,但运行环境不能直接支持 (无法使用模块化、组件化)
- 部署上线前需要手动压缩代码及资源文件,部署过程需要手动上传代码到服务器 (重复的机械式工作)
- 多人协作开发,无法硬性统一大家的代码风格,从仓库pull回来的代码质量无法保证 (代码风格不统一,质量无法保证)
- 部分功能开发时需要等待后端服务接口提前完成
- 整体依赖后端项目
2. 工程化的表现:
- 创建项目 (使用脚手架完成基础工具的搭建)
- 编码 (代码格式化、代码风格校验,编译工具可以将使用的新特性自动编译可运行的语言)
- 预览/测试 (Web Server/Mock前端服务器、Live Reloading/HMR热更新、Source Map定位源代码位置)
- 提交 (Git Hooks代码整体质量检查、Lint-staged代码风格检查,持续集成)
- 部署 (CI/CD、自动发布)
二、脚手架工具
1. 概述
作用: 创建项目基础结构、提供项目规范和约定(相同的组织结构、开发范式、模块依赖、工具配置,更有一些基础代码都相同)
2. 常用的脚手架工具
脚手架工作过程:
- 通过命令行交互询问用户问题 (
node 中发起交互命令我们使用inquirer 模块) - 根据用户回答的结果生成文件 (入口文件中进行逻辑实现)
常用的脚手架工具:
React 项目 - - - create-react-app Vue 项目 - - - vue-cli Angular 项目 - - - angular-cli Yeoman 通用脚手架工具Plop 同于创建特定类型的文件
3. Yeoman
⑴. 概述
一个通用的脚手架工具
- 优势: 更像脚手架的运行平台,Yeoman搭配不同的generator可以创建任何类型的项目,我们可以根据自己的generator定制自己的前端脚手架
- 劣势: 优点即缺点,过于通用不够专注
⑵. 基础使用
安装:
node -v
npm install -g yo
npm install -g generator-node
使用:
$ yo node
? ==========================================================================
We're constantly looking for ways to make yo better!
May we anonymously report usage statistics to improve the tool over time?
More info: https://github.com/yeoman/insight & http://yeoman.io
========================================================================== (Y/n)
? Module Name (code)
$ my_modules
? The name above already exists on npm, choose another? (Y/n)
$ n
? Description
$ awesome node module
? Project homepage url
$ https://gitee.com/...
? Author s Name ()
$ yuan
? Author s Email ()
$ ...@163.com
? Author s Homepage
$ https://gitee.com/...
? Package keywords (comma to split)
$ modules, node
? Send coverage reports to coveralls (Y/n)
$ n
? Enter Node versions (comma separated)
$ (不输入时支持全部版本)
? GitHub username or organization ()
$ yuan
Apache 2.0
? MIT
Mozilla Public License 2.0
BSD 2-Clause (FreeBSD) License
BSD 3-Clause (NewBSD) License
Internet Systems Consortium (ISC) License
GNU AGPL 3.0
(Move up and down to reveal more choices)
⑶. sub generator
有时候我们并不需要创建完整的项目结构,只需要在原有项目的基础上创建一些特定的文件,例如在项目中添加yeoman,比如在项目中添加eslint,babel配置文件…;就可以通过生成器帮我们实现
使用:
yo node:cli
? Overwrite package.json? (ynaxdH)
$ Y
$ npm link(博主这里使用 npm 未成功)
$ yarn link(npm install yarn 安装)
$ my_modules --help
⑷. yeoman 使用步骤
- 明确需求
- 找到合适的 Generator
- 全局范围安装找到的
Generator - 通过Yo运行对应的
Generator - 通过命令行交互填写选项
- 生成你所需要的项目结构
4. 自定义 Generator
⑴. 概述
基于Yeoman搭建自己的脚手架
基础结构:
generators/ ... 生成器目录
| app/ ... 默认生成器目录
| | index.js ... 默认生成器实现
+| component/ ... 如果有sub generator写这个目录下面
+| index.js ... 其他生成器实现
package.json ... 模块包配置文件
名称规范: 必须是 generator-<name> 的格式
⑵. 使用
mkdir generator-sample
cd generator-sample
npm install yeoman-generator
创建 index.js 文件 - generator/app/index.js:
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
writing () {
this.fs.write(
this.destinationPath('temp.txt'),
Math.random().toString()
)
}
}
链接到全局:
npm link
其他项目使用:
mkdir myjob
cd myjob
yo sample
⑶. 根据模板创建文件
相对于上面手动创建每一个文件,模板方式效率更高
创建 foo.txt 文件 - generator/app/templates:
这是一个模板文件
内部可以使用 EJS 模板标记输出数据
例如: <%= title %>
<% if (success) {%>
歌尽花落
<% }%>
编辑 index.js 文件 - generator/app/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 xm~', success: true}
this.fs.copyTpl(tmpl, output, context)
}
}
其他项目使用:
cd myjob
yo sample
⑷. 动态接收用户输入数据
创建 test.html 文件 - generator/app/templates:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= name%></title>
</head>
<body>
<h1><%= title%></h1>
</body>
</html>
编辑 index.js 文件 - generator/app/index.js:
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
prompting() {
return this.prompt([
{
type: 'input',
name: 'name',
message: 'your project name is :',
default: this.appname
},
{
type: 'input',
name: 'title',
message: 'your title is :',
default: '目录'
},
])
.then(answers => {
this.answers = answers
})
}
writing () {
const tmpl = this.templatePath('test.html')
const output = this.destinationPath('test.html')
const context = { name: this.answers.name, title: this.answers.title}
this.fs.copyTpl(tmpl, output, context)
}
}
其他项目使用:
cd myjob
yo sample
> ? your project name is : test myjob
> ? your title is : session1
5. Vue Generator 案例
⑴. 创建项目
mkdir generator-vue
cd generator-vue
npm init
npm install yeoman-generator
⑵. 添加项目模板
创建 templates 文件夹 - generator/app/templates:
将Vue 项目(自己创建的)的目录直接拷贝进来
对项目名称替换为模板标记:
my-vue-project => <%= name %>
⑶. inidex.js
编辑 index.js 文件 - generator/app/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
}
])
.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
)
})
}
}
⑷. inidex.js
npm link
mkdir my-proj
cd my proj
yo generator-vue
? Your project name (my proj)
$ 直接回车(之前设置的用户需要填写项目名称)
⑷. 发布 Generator
Generator实际是一个npm模块,那么发布generator就是发布npm模块,我们需要通过npm publish命令发布成一个公开的模块
创建本地仓库: 创建 .gitignore (generator-vue/node_module)文件,用于管理需要被忽略写入的文件
git init
git status
git add .
git commit -m 'init project'
...
// 绑定云端仓库
git remote add origin <仓库ssh地址>
git push -u origin master
npm publish
6. Plop
主要用于创建项目中特定文件类型的小工具,类似于Yeoman中的sub generator,一般不会独立使用。 一般会把Plop集成到项目中,用来自动化的创建同类型的项目文件
使用步骤:
1. 安装 plop
npm init -y
npm install -g plop
2. 模板文件
根目录下创建 plop-templates 文件夹,里面创建三个模板文件:
component.css.hbs:
.{{name}} {
}
component.hbs:
import React from 'react';
export default () => (
<div className="{{ name }}">
<h1>{{name}} Component</h1>
</div>
)
component.test.hbs:
import React from 'react';
import ReactDOM from 'react-dom';
import {{name}} from './{{name}}';
it('renders widthout crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<{{name}} />, div);
ReactDOM.unmountComponentAtNode(div);
});
3. 入口文件
在根目录下创建一个plopfile.js 的文件,这个文件是Plop的入口文件,需要导出一个函数,这个函数接收一个plop对象,用于创建生成器任务
module.exports = plop => {
plop.setGenerator('compontent', {
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'
}
]
})
}
- plop.setGenerator:设置一个生成器,第一个参数是项目名称,第二个函数是对象,对应设置选项
- 配置项内容:
- description:描述
- prompts:值是数组,命令行交互问题,一个问题对应一个对象
- actions:值是数组,完成命令行交互过后完成的一些动作,一个对象一个动作
4. 运行
在 package.json 中添加
"scripts": {
"plop": "plop"
}
npm run plop
? component name Header
7. 手撕一个简易的脚手架工具
1. 创建项目
mkdir sample-scaffold
cd sample-scaffold
npm init
2. 入口文件搭建
编辑 package.json 文件:
{
...
// 项目入口文件
"bin": "cli.js",
}
根目录创建 cli.js 文件:
console.log('cli working!')
链接到全局:
npm link
sample-scaffold
3. 安装依赖
npm install inquirer
npm install ejs
4. 模板创建
根目录下创建 templates 文件夹,里面创建两个模板文件:
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>
</head>
<body>
</body>
</html>
style.css:
body{
margin: 0;
background-color: bisque;
}
编辑 cli.js 文件:
const inquirer = require('inquirer')
const path = require('path')
const fs = require('fs')
const ejs = require('ejs')
inquirer.prompt([
{
type:'input',
name:'name',
message: 'Project name?'
}
])
.then(answers => {
console.log(answers)
const tmplDir = path.join(__dirname, 'templates')
const destDir = process.cwd()
fs.readdir(tmplDir, (err, files) => {
if(err) throw err
files.forEach(file => {
console.log(file)
ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => {
if(err) throw err
console.log(result)
fs.writeFileSync(path.join(destDir, file), result)
})
})
})
})
5. 运行
mkdir demo
cd demo
sample-scaffold
> ? Project name? myProject
|