一切以提高效率、降低成本、质量保证为目的的手段都属于「工程化」
graph LR
创建项目-->编码
编码-->预览/测试
预览/测试-->提交
提交-->部署
部署-->编码
工程化的核心是对项目整体的规划和架构,工具只是落地规划和架构的一种手段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hYlfGtv4-1591460280651)(http://oss.ahh5.com/ahh5/md/202020200529221827.png)]
脚手架的本质就是自动的创建项目基础结构、提供项目规范和约定,脚手架工具可以快速的搭建特定项目的骨架。
根据提供信息自动创建对应的项目基础结构,但一般适用于自身所服务框架的项目
不针对于某一框架相对常用脚手架较为灵活。Yeoman可以搭配不同的generator创建不同的项目
缺点:过于通用不够专注
yarn global add yo
yarn global add generator-node
yo node
# 输入项目名字
# 输入项目名字
# 输入项目主页
# 输入作者
# 输入邮箱
# 输入主页
# 输入关键词
可以通过生成器的子集生成一些文件。例如eslint README
# 创建cli
yo node:cli
# 安装新增加的依赖
yarn
# 链接到全局
yarn link "yo-test-learn"
# 或者
npm link yo-test-learn
# 输入下面命令即可看到
yo-test-learn --help
基于Yeoman 搭建自己的脚手架
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-msZgVvsb-1591460280657)(http://oss.ahh5.com/ahh5/md/202020200530230529.png)]
提供多个 sub generator 需要在app同级目录下创建一个新的目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tjkNyYoP-1591460280660)(http://oss.ahh5.com/ahh5/md/202020200530230749.png)]
# 创建一个文件夹
mkidr generator-hello
# 进入目录
cd generator-hello
# 创建package.json
yarn init
# 安装 yeoman-generator 模块
yarn add yeoman-generator
创建 /generators/app/index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
// yeoman 自动在生成文件中调用此方法
writing() {
// 通过文件读写方式向目标目录写入文件
this.log('hello')
// this.destinationPath 目前目录路径
this.fs.write(
this.destinationPath('temp.txt'),
Math.random().toString()
)
}
}
通过 npm link 将模块安装到全局
找一个空目录运行 yo hello
.
└── temp.txt
在app目录下创建一个templates目录
这是一个模板文件,内部使用EJS模板标记输入数据
app/templates/foo.txt
<%= title %>
<% if(success){ %>
成功才会看到我
<% } %>
app/index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator{
// yeoman 自动在生成文件中调用此方法
writing(){
// 模板文件路径
const tmpl = this.templatePath('foo.txt')
// 输出目标路径
const output = this.destinationPath('foo.txt')
// 模板数据上下文
const context = {
title:"hello word",
success : true
}
// 输出模板文件
this.fs.copyTpl(tmpl,output,context)
}
}
完成目录结构
├── generators
│ └── app
│ ├── index.js
│ └── templates
│ └── foo.txt
├── package-lock.json
├── package.json
└── yarn.lock
找一个空目录运行 yo hello 即可看到输出的 foo.txt
内容如下
hello word
成功才会看到我
app/index.js 暴露出的类添加如下方法
prompting() {
// 在此方法可以调用父类的 prompt() 方法对用户命令行询问
return this.prompt([{
type: 'input',
name: 'name',
message: 'your project name',
default: this.appname //当前生成目录文件夹的名字
}])
.then(answers => {
// answers => { name : 'user input value' }
this.answers = answers
})
}
修改 writing
writing() {
// 模板文件路径
const tmpl = this.templatePath('foo.txt')
// 输出目标路径
const output = this.destinationPath('foo.txt')
// 模板数据上下文
const context = {
// 此处title 接收用户输入的值
title: this.answers.name,
success: true
}
// 输出模板文件
this.fs.copyTpl(tmpl, output, context)
}
执行 yo hello
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xWzvFQFE-1591460280662)(http://oss.ahh5.com/ahh5/md/202020200531112617.png)]
输出文件内容如下
My project
成功才会看到我
基础vue 构建我选择的还是cli,基于上面在做一些自己的配置就好了
# 创建文件夹
mkdir generator-zzy-vue
# 进入文件夹
cd generator-zzy-vue
# 初始化项目
yarn init
# 添加yeoman依赖
yarn add yeoman-generator
手动创建一些文件
基本结构如下
├── generators
│ └── app
│ ├── index.js
│ └── templates
├── package.json
└── yarn.lock
创建一个vue模板
vue create tmp-project
rm -rf node_modules
rm -rf .git
rm yarn.lock
目录结构如下
├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── README.md
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
└── src
├── App.vue
├── assets
│ └── logo.png
├── components
│ └── HelloWorld.vue
├── main.js
├── router
│ └── index.js
├── store
│ └── index.js
└── views
├── About.vue
└── Home.vue
修改README.md
# <%= name %>
修改 /public/index.html
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="favicon.ico">
<title><%= name %>title>
head>
<body>
<noscript>
<strong>We're sorry but <%= name %> doesn't work properly without JavaScript enabled. Please enable it to continue.strong>
noscript>
<div id="app">div>
body>
html>
复制到generators/app/templates中
app/index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
// yeoman 在询问用户环节会自动调用此方法
prompting() {
return this.prompt([{
type: "input",
name: 'name',
message: "Your project name",
default: this.appname
}])
.then(answers => {
this.answers = answers
})
}
// yeoman 自动在生成文件中调用此方法
writing() {
const templates = [".browserslistrc", ".editorconfig", ".eslintrc.js", ".gitignore", "README.md", "babel.config.js", "package.json", "public/favicon.ico", "public/index.html", "src/App.vue", "src/assets/logo.png", "src/components/HelloWorld.vue", "src/main.js", "src/router/index.js", "src/store/index.js", "src/views/About.vue", "src/views/Home.vue"]
templates.forEach(filePath => {
this.fs.copyTpl(
this.templatePath(filePath),
this.destinationPath(filePath),
this.answers
)
})
}
}
关于templates文件名获取, 可以通过node脚本实现
const fs = require('fs');
const path = require('path');
const _filePath = path.resolve('./templates');
const outFilePath = path.resolve('./arr');
let filePathArr = []
getFileRecursively(_filePath)
function getFileRecursively(filePath) {
const files = fs.readdirSync(filePath)
files.forEach(filename => {
const filedir = path.join(filePath, filename);
const stats = fs.statSync(filedir)
if (stats.isFile()) {
filedir.includes('.DS_Store') || filePathArr.push(filedir.replace(_filePath + '/', ''))
}
if (stats.isDirectory()) {
getFileRecursively(filedir);
}
})
}
fs.writeFileSync(outFilePath, JSON.stringify(filePathArr))
注意不要将此方法放入 app/index.js 而是通过该脚本获取并输出到一个新的文件中。后面在手动复制到app/index.js中。因为不能确定 yeoman 的执行环境,以及不需要用户每次执行都去重新递归遍历所有文件
一个小而美的脚手架工具,一般不会独立去使用,而是集成到项目之中区创建同类型的文件使用
自动化通过机器代替手工完成某些操作,
自动化构建就是将源代码自动化构建为生成代码或者程序。
Grunt
最早的自动化构建工具,但因为通过临时文件工作,所以工作效率较慢。
Gulp
使用频率较高,通过操作内存实现自动化构建,相对于Grunt速度快了很多且默认支持多任务
Fls
百度开源产品,继承项目常用自动化构建流程,例如资源加载、模块化开发、代码部署、性能优化,缺点不够灵活。
# 初始化项目
yarn init --yes
# 添加grunt模块
yarn add grunt --dev
根目录下创建gruntfile.js文件
module.exports = grunt => {
// 注册的任务 第一个参数为任务名称 第二个参数为任务描述(可以省略) 第三个是执行代码
grunt.registerTask('foo', '任务描述', () => {
console.log('hello word')
})
// 如果任务名称为default 那么yarn grunt 会默认执行
grunt.registerTask('default', () => {
console.log('grunt default task')
})
}
# 运行grunt 不加参数执行默认任务
yarn grunt
# 指定任务执行
yarn grunt foo
# 查看任务描述
yarn grunt --help
default实际工作中是执行多个task的默认任务例如
module.exports = grunt => {
grunt.registerTask('foo', 'foo任务描述', () => {
console.log('hello foo')
})
grunt.registerTask('bar', 'bar任务描述', () => {
console.log('hello bar')
})
grunt.registerTask('default', ['foo', 'bar'])
}
此时执行 yarn grunt 会执行 foo bar 两个任务
module.exports = grunt => {
grunt.registerTask('async-task', 'async-task任务描述', function() {
// 需要定义一个done 值为this.async() 标记此任务为异步任务
const done = this.async()
setTimeout(() => {
console.log('async-task workding')
// 异步任务执行结束后需要通过 done() 触发程序截止
done()
}, 1000)
})
}
在函数内部 return false
module.exports = grunt => {
grunt.registerTask('bad', 'bad任务描述', () => {
console.log('This is a bad task')
return false
})
}
如果在任务列表中,则后续任务不会执行
module.exports = grunt => {
grunt.registerTask('bad', 'bad任务描述', () => {
console.log('This is a bad task')
return false
})
grunt.registerTask('test', 'test任务描述', () => {
console.log('This is a test task')
return false
})
grunt.registerTask('default', ['bad', 'test'])
}
// 此时执行 yarn grunt 则会抛出 warnings 警告且 test不会执行
如果采用强制执行方式 --force 则所有的任务都会执行
yarn grunt --force
异步任务失败标记
module.exports = grunt => {
grunt.registerTask('async-task', 'async-task任务描述', function() {
const done = this.async()
setTimeout(() => {
console.log('async-task workding')
// done 传入一个实参 false
done(false)
}, 1000)
})
}
基本用法
module.exports = grunt => {
// 初始化config
grunt.initConfig({
foo: 'test'
})
grunt.registerTask('foo', 'foo任务描述', () => {
// 获取config foo的键值
const config_foo = grunt.config('foo')
console.log( `hello ${config_foo}` )
// 输出 hello test
})
}
通过配置多个目标, 执行多个目标
module.exports = grunt => {
grunt.initConfig({
// 定义build
build: {
// 参数配置不会作为目标执行
options: {
test: false
},
// 定义两个目标
css: '1',
js: '2'
}
})
// 通过 registerMultiTask 方法定义
grunt.registerMultiTask('build', function() {
// 拿出执行目标值与值
const {
target,
data,
} = this
// 获取所有配置
const { test } = this.options()
console.log( `build ${target} ${data} ` )
console.log(` test ${test} `)
})
}
安装一个文件清除的插件
yarn add grunt-contrib-clean -D
module.exports = grunt => {
grunt.initConfig({
// 定义clean
clean: {
// 定义一个目标 清除temp 下的所有文件
temp:'temp/**'
}
})
// 清除指定文件任务 需要多个目标
grunt.loadNpmTasks('grunt-contrib-clean')
}
编译sass
# 安装 grunt-sass 及 sass 模块
yarn add grunt-sass sass -D
// 引入sass 模块
const sass = require('sass')
module.exports = grunt => {
grunt.initConfig({
sass: {
// 定义配置文件
options:{
// 定义处理方法
implementation:sass,
// 开启sourceMap文件
sourceMap:true
},
// 定义个目标
main: {
// 定义文件
files:{
// 输出路径 输入路径
'dist/css/main.css':'src/scss/main.scss'
}
}
}
})
grunt.loadNpmTasks('grunt-sass')
}
npm grunt插件自动导出
# 安装插件
yarn add load-grunt-tasks -D
// 引入loadGruntTasks
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
// 将grunt传入loadGruntTasks 实现挂载所有的grunt模块
loadGruntTasks(grunt)
}
编译es6语法
yarn add grunt-babel @babel/core @babel/preset-env -D
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
grunt.initConfig({
babel: {
// 定义配置文件
options: {
presets:['@babel/preset-env'],
// 开启sourceMap文件
sourceMap: true
},
// 定义个目标
main: {
// 定义文件
files: {
// 输出路径 输入路径
'dist/js/main.js': 'src/js/main.js'
}
}
}
})
loadGruntTasks(grunt)
}
执行 yarn babel
修改自动更新
yarn add grunt-contrib-watch -D
const loadGruntTasks = require('load-grunt-tasks')
const sass = require('sass')
module.exports = grunt => {
grunt.initConfig({
sass: {
// 定义配置文件
options: {
// 定义处理方法
implementation: sass,
// 开启sourceMap文件
sourceMap: true
},
// 定义个目标
main: {
// 定义文件
files: {
// 输出路径 输入路径
'dist/css/main.css': 'src/scss/main.scss'
}
}
},
babel: {
// 定义配置文件
options: {
presets: ['@babel/preset-env'],
// 开启sourceMap文件
sourceMap: true
},
// 定义个目标
main: {
// 定义文件
files: {
// 输出路径 输入路径
'dist/js/main.js': 'src/js/main.js'
}
}
},
watch: {
js: {
files: ['src/js/*.js'],
tasks: ['babel']
},
css: {
files: ['src/scss/*.scss'],
tasks: ['sass']
}
},
})
loadGruntTasks(grunt)
grunt.registerTask('default', ['sass', 'babel','watch'])
}