一切重复的工作都应该工程化。
创建项目
编码 格式化代码 校验代码风格
预览/测试 webServer / mock live Reloading /HMR source Map
提交 Git Hooks Lint-staged 持续限制
部署 CI/CD 自动部署
工程化
- 脚手架工具开发
- 自动化构建系统
- 模块化打包
- 项目代码规范优化
- 自动化部署
脚手架的本质作用
- 相同的组织结构
- 相同的开发范式
- 相同的工具配置
- 相同的基础代码
Yeoman 基本使用
yarn global add yo
// 先安装全局generator-node
yarn global add generator-node
// 创建文件夹
mkdir my-module
// 创建项目
yo node
```js
## sub Generator
```js
// 安装相应的脚手架
yo node:cli
// 安装yarn link 连接全局
yarn link
npm link // 我使用npm
使用
- 明确你的需求
- 找到合适的Generator
- 全局范围安装找到的Generator
- 通过yo运行的Generator
- 通过命令行交互填写选项
- 生成你所需要的项目结构
yo webapp
自定义Generator
mkdir generator-sample // 创建文件夹。 必须是generator-${name}结尾。
yarn init // 初始化项目
yarn add yeoman-generator // 提供一些工具函数。
/**
* 此文件作为Generator核心入口
* 需要导出一个继承自Yeoman Generator 的类型
* Yeoman Generator 在工作的时会自动调用我们在此
* 类型中定义的一些生命周期的方法
* 我们在这些方法可以通过父类提供的一些工具方法实现一些功能
* 例如文件写入
*/
const Generator = require('yeoman-generator');
module.exports = class extends Generator{
wrting() {
// Yeoman 自动
this.fs.write(
this.destinationPath('temp.txt'),
Math.random().toString()
)
}
}
// 使用yarn link在当前目录下执行。 使他成为一个全局模块包。
yarn link | npm link
// 新建一个文件夹
mkdir my-proj
// 接下来就可以使用yo sample 命令 sample>去的文件名字。
yo sample
当前文件就会创建一个txt文件
根据模版创建文件
如果我们需要更多的文件,这个时候就需要字符串模版了
现在generator-sample文件夹下app--> 新建一个模版文件 templates/foo.txt
foo.txt
// 这是一个模版文件
// 内部可以使用EJS模版标记输出数据
// 其他的EJS语法也支持
<% if (success) {%>
哈哈哈
<% } %>
generator-sample/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 ", success: true}
this.fs.copyTpl(tmpl, output, context)
}
}
接收用户输入数据
// Yeoman 在询问用户环节会自动调用此方法 // 在此方法中可以调用父类的Promise 方法发出对用户的命令询问
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(anewers => {
this.anewers = anewers;
})
}
writing () {
// 模版文件路径
const tmpl = this.templatePath('bar.html');
// 输出数据上下文
const output = this.destinationPath('bar.html')
// 模版数据上下文。
const context = this.anewers;
this.fs.copyTpl(tmpl, output, context);
}
}
vue generator 案例
// 新建文件夹
mkdir generator-yhb-vue
// 初始化项目
yarn init
// 安装yeoman-generator
code .
创建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 => {
// item => 每个文件路径
this.fs.copyTpl(
this.templatePath(item),
this.destinationPath(item),
this.answers
)
})
}
}
// 链接到全局
yarn link
// 在任意目录下:
yo ${name}
会生成你的项目。
发布npm包。
首先.过滤调node_modules
echo node_modules > .gitignore
初始化git
git init
跟踪所有文件
git add .
查看状态
git status
提交记录
git commit -m 'save'
将申请的git 地址跟本地项目链接
git remote add origin https: //github.coom/17698753015/generator-VUEtor.git
push 到master
git push -u origin master
npm publish// 一般有淘宝原
npm publish --registry=registry.npmjs.org/ // 指定地址发布。
创建脚手架
mkdir sample-scaffolding
yarn init -y
package.json
"bin": "cli.js"
// 新建cli.js
yarn add inquirer // 发起命令行询问
//进入: cli.js
// Node CLI 应用入口文件必须要这样的文件头
//如果是Linux 或者macOs系统下还需要修改此文件的都写权限为 755
// 返回文件上一级
// sudo chmod -R 755 文件名
// 具体就是通过chmod 755 cli.js 实现修改
// 脚手架的工作过程
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件
#! /usr/bin/env node
const inquirer = require('inquirer');
const fs = require("fs")
const path = require("path")
const ejs = require("ejs")
inquirer.prompt([
{
type: "input",
name: 'name',
message: 'Project name ?'
}
]).then(anwsers => {
// console.log(anwsers)
// 模版文件
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), anwsers, (err, result) => {
if (err) throw err
fs.writeFileSync(path.join(destDir, file), result)
})
})
})
})
// 项目的根目录新建模版
templates/index.html
自动化构建demo
mkdir saas
cd saas
yarn init -y
touch index.html
mkdir scss
cd scss
touch main.scss
yarn add sass --dev // 安装sass预编译插件
yarn add browser-sync --dev // 安装测试服务器
yarn add npm-run-all --dev // 同时执行多个命令
// package.json
{
"name": "saas",
"version": "1.0.0",
"main": "index.js",
"author": "Your name",
"license": "MIT",
"scripts": {
"build": "sass scss/main.scss style/main.css --watch", // 监听scss变化编译成css
"serve": "browser-sync . --files \"style/*.css\"", // 启动web server 监听css文变化
"start": "run-p build serve" // 同时执行以上两个命令, 按顺序执行
},
"devDependencies": {
"browser-sync": "^2.26.12",
"npm-run-all": "^4.1.5",
"sass": "^1.26.10"
}
}
前端自动化构建工具
grunt gulp FIS grunt: 磁盘读写操作。慢 gulp: 内存读写,速度快 FIS: 百度开发。套餐
grunt
yarn init -y
./node_modules/.bin/sass scss/main.scss style/main.css
grunt 的基本使用
mkdir grunt-sample
cd grunt-sample
yarn init -y
yarn add grunt --dev
// 创建gruntfile.js
code gruntfile.js
//打开编辑器。
code .
执行代码
// grunt 的入口文件
// 用于定义一些Grunt 自动执行的的任务
// 需要导出一个函数
// 此函数接收一个grunt形参。
// 内部提供一些创建任务时可以用到API
module.exports = grunt => {
// 第一个参数是任务名字自定义。 第二个参数可以是一个箭头函数
// 来执行这个任务的具体代码
// yarn grunt foo
grunt.registerTask('foo', () => {
console.log("hello")
})
// 第二个参数可以是字符串对任务的具体描述。
// yarn grunt --help 可以看到任务描述
// 三个参数是一个函数来执具体代码
// yarn grunt bar
grunt.registerTask('bar', '任务描述', () => {
console.log("任务描述")
})
// grunt 默认执行default 任务。 yarn grunt 不带任务名字
grunt.registerTask('default', 'default', () => {
console.log("default")
})
// 当默认任务是一个数组的里面可以是我们定义的其他任务。
// 里面任务会同步执行。
grunt.registerTask('default', ['foo', 'bar'])
// grunt 任务是默认同步任务的。如果直接使用异步代码
// 将不会执行
grunt.registerTask('async-test', function () {
setTimeout(() => {
console.log('async task working');
}, 1000)
})
// 当我们需要执行异步代码的时候。需使用this.async()
// 并且在异步任务执行一下。才会执行异步任务。
grunt.registerTask('async', function () {
const done = this.async();
setTimeout(() => {
console.log('async task working');
done()
}, 1000)
})
}
grunt 标记任务失败
const { set } = require("grunt")
module.exports = grunt => {
grunt.registerTask('foo1', () => {
console.log("1")
})
grunt.registerTask('foo2', () => {
console.log("2")
// 返回false 表明任务失败
return false;
})
grunt.registerTask('foo3', () => {
console.log("3")
})
// 异步返回false 需要在done执行的时候传过去
grunt.registerTask('foo4', function () {
const done = this.async()
setTimeout(() => {
console.log(55555);
done(false)
}, 1000)
})
// 直接执行的yarn grunt 的话会报错 如图1
// 使用yarn grunt --force 标记错误其他任务也能正常执行
grunt.registerTask('default', ['foo1', 'foo2', 'foo3'])
}
grunt 配置方法
module.exports = grunt => {
// 添加配置的方法
grunt.initConfig({
foo: 'bar',
foo01: {
bar: 123
}
})
grunt.registerTask('foo', () => {
// 可以这样获取配置的值
console.log(grunt.config('foo'))
// 可以这样.bar获取下一层。
console.log(grunt.config('foo01.bar'))
})
}
Grunt多目标任务配置
module.exports = grunt => {
grunt.initConfig({
// 健需要跟任务同名。
build: {
// 他类似build全局的选择配置
options: {
foo: 'bar' // 1
},
css: {
options: {
foo: 'baz' // 和1相同会覆盖1的值
}
},
js: '2'
}
})
// 多目标模式, 可以让任务根据配置形成多个子任务
// >> No "build" targets found.
// Warning: Task "build" failed. Use --force to continue.
// Aborted due to warnings.
// error Command failed with exit code 3.
// 需要配置 grunt.initConfig 配置不同的目标
grunt.registerMultiTask('build', function () {
console.log(this.options())
// 拿到initConfig配置的build的任务选项。
console.log(`target: ${this.target}, data: ${this.data}`)
console.log('build task')
})
//Running "build:css" (build) task
// build task
// Running "build:js" (build) task
// build task
// yarn grunt build:css
// yarn grunt build:js
}
grunt 插件使用。
yarn add grunt-contrib-clean --dev
module.exports = grunt => {
grunt.initConfig({
clean: {
// 执行yarn grunt clean app.js将被删除。
// * 删除该文件下所以文件
// ** 所有子目录和子目录下的文件
temp: 'temp/*.js'
}
})
// >> No "clean" targets found.
// Warning: Task "clean" failed. Use --force to continue.
grunt.loadNpmTasks('grunt-contrib-clean');
}
grunt 常用插件
// yarn add load-grunt-tasks --dev
// 监听grunt 所有安装的插件
const loadGruntTasks = require('load-grunt-tasks');
const sass = require('sass')
module.exports = grunt => {
grunt.initConfig({
// grunt-sass 是grunt 的插件 。需要运行在sass 环境里
// sass插件 yarn add grunt-sass sass --dev
sass: {
// 配置选项
options: {
// 源码
sourceMap: true,
// 加载sass
implementation: sass
},
// 配置
main: {
files: {
// 编译完成的地址: 和需要编译的地址
'dist/css/main.css': 'src/scss/main.scss'
}
}
},
// 安装babel yarn add grunt-babel @babel/core @babel/preset-env --dev
babel: {
options: {
// 源码
sourceMap: true,
// 执行babel 预装插件
presets: ['@babel/preset-env']
},
// 任务配置
main: {
files: {
// 编译完成的地址: 和需要编译的地址
'dist/js/app.js': 'src/js/app.js'
}
}
},
// 监听插件 yarn add grunt-contrib-watch --dev
watch: {
js: {
// 指定监听的文件
files: ['src/js/*.js'],
// 根据文件的变化执行对于应的任务
tasks: ['babel']
},
css: {
// 指定监听的文件
files: ['src/scss/*.scss'],
// 根据文件的变化执行对于应的任务
tasks: ['sass']
}
},
})
// 单个执行某一个插件。如果任务特别多的话我们需要写很多grunt.loadNpmTasks('grunt-sass')
// 类似的代码。load-grunt-tasks 这个插件可以执行我们安装的所有的grunt
// 插件。不需要我们引入。只需要配置它就可以执行。
// grunt.loadNpmTasks('grunt-sass')
loadGruntTasks(grunt) // 会自动加载所有的grunt插件里的任务
// 开启默认任务。当sass babel 执行完之后会执行watch会一直监听文件的变化。从而执行相应的任务
grunt.registerTask('default', ['sass', 'babel', 'watch'])
}
Gulp的基本使用
// gulp 最新版本取消来同步,都是异步执行
// 需要手调一个回调函数
// 推荐导出gulp 成员的方法。
// yarn gulp foo
exports.foo = done => {
console.log('foo task worhing~')
done() // 标示任务完成
}
// yarn gulp
exports.default = done => {
console.log('default---------')
done()
}
// gulp 4.0 以前
// yarn gulp bar
// 不推荐使用
const gulp = require('gulp');
gulp.task('bar', done => {
console.log("bar working");
done();
})
gulp 创建组合任务
const { series, parallel } = require('gulp');
const task1 = done => {
setTimeout(() => {
console.log('task1 woring');
done();
}, 1000)
}
const task2 = done => {
setTimeout(() => {
console.log('task2 woring');
done();
}, 1000)
}
const task3 = done => {
setTimeout(() => {
console.log('task3 woring');
done();
}, 1000)
}
// exports.${name}自定义任务名字
// series 任务组合方法。依次执行 串行
exports.foo = series(task1,task2,task3)
// parallel 任务组合方法。 并行执行
exports.bar = parallel(task1,task2,task3)
gulp 异步处理的几种方法
const fs = require('fs');
exports.callback = done => {
console.log('callback task ~')
done()
}
// 错误优先
exports.callback_error = done => {
console.log("callback task~");
done(new Error('task failed'))
}
exports.promise = () => {
console.log("Promise task~");
return Promise.resolve() // 成功返回的参数会被gulp忽略。
}
exports.promisee_error = () => {
console.log('promise task~');
// 调用promise.reject()
// 传给new Error('meessage');错误实例。
// 会结束后面代码的执行
return Promise.reject(new Error('task faileed~'))
}
const timeout = time => {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
exports.async = async () => {
await timeout(1000);
console.log('async task ~')
}
// exports.stream = () => {
// const readStream = fs.createReadStream('package.json');
// const writeStream = fs.createWriteStream('temp.txt');
// readStream.pipe(writeStream);
// return readStream;
// // console.log(23456)
// }
exports.stream = done => {
const readStream = fs.createReadStream('package.json');
const writeStream = fs.createWriteStream('temp.txt');
readStream.pipe(writeStream);
readStream.on('end', () => {
done()
})
// console.log(23456)
}
开发脚手架及封装自动化构建工作流
// 构建过程
// 复制 > 压缩 > 粘贴到一个文件里。
const fs = require('fs');
const { Transform } = require('stream')
exports.default = () => {
// 文件读取流
const readStream = fs.createReadStream('temp.css');
// 文件写入流
const writeStream = fs.createWriteStream('temp.min.css');
// 文件转换流。
const transform = new Transform({
transform: (chunk, encoding, callback) => {
// 和核心转换实现
// chunk => 读取流中读取到的内容(BUffer)
const input = chunk.toString();
const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '');
// callback 第一个参数传入错误对象 没有的话传如 - null;
callback(null, output);
}
})
// 把读取出来的文件导入写入流。
readStream
.pipe(transform)
.pipe(writeStream);
return readStream;
}
文件操作API + 插件的使用
// 文件操作API + 插件的使用
// src 读取流。
// yarn add gulp-clean-css 压缩css 转换流
// dest 写入流。
// yarn add gulp-rename --dev 重命名
const { src, dest } = require('gulp');
const cleanCss = require('gulp-clean-css');
const rename = require('gulp-rename');
exports.default = () => {
return src('src/*.css') // 返回执行结果告诉gulp任务已经结束
.pipe(cleanCss())
.pipe(rename({extname: '.min.css'}))
.pipe(dest('dist'))
}
开发脚手架及封装自动化构建工作流
// ## 开发脚手架及封装自动化构建工作流
// yarn add gulp --dev 安装gulp
// yarn add gulp-sass --dev 安装gulp-sass
// yarn add gulp-babel --dev 安装gulp-babel
// 需要安装babel 的核心模块
// yarn add @babel/core @babel/cli --dev
// yarn add @babel/core @babel/preset-env --dev
const { src, dest } = require('gulp');
const sass = require('gulp-sass');
const babel = require('gulp-babel');
const style = () => {
return src('src/assets/styles/*.scss', {base: 'src'})
.pipe(sass({outputStyle: 'expanded'}))
.pipe(dest('dist'))
}
const script = () => {
return src('src/assets/scripts/*.js', {base: 'src'})
.pipe(babel({presets: ['@babel/preset-env']}))
.pipe(dest('dist'))
}
// 导出需要执行的模块
// exports.style = () => {} 相同
module.exports = {
style,
script
}
gulp项目案例
// ## 开发脚手架及封装自动化构建工作流
// yarn add gulp --dev 安装gulp
// yarn add gulp-sass --dev 安装gulp-sass
// yarn add gulp-babel --dev 安装gulp-babel
// 需要安装babel 的核心模块
// yarn add @babel/core @babel/cli --dev
// yarn add @babel/core @babel/preset-env --dev
// yarn add gulp-swig --dev 转换模版文件
// yarn add gulp-imagemin --dev 图片压缩
// yarn add del --dev 删除指定文件
// yarn add browser-sync --dev // 启动本地服务器
// yarn add gulp-useref --dev 代码中引用node_modules 里面的css
// 打包的时候并没有打包进去。线上的时候并不能这样。所以使用gulp-useref 还可以合并代码。
// yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev // html js css 处理插件
// yarn add gulp-if // 判断插件
const { src, dest, parallel, series, watch } = require('gulp');
const del = require('del');
const bs = require('browser-sync').create();
// 自动只加载gulp, 所有安装gulp插件。gulp-load-plugins
const plugins = require('gulp-load-plugins')();
const data = {
menus: [
{
name: 'Home',
icon: 'aperture',
link: 'index.html'
},
{
name: 'Features',
link: 'features.html'
},
{
name: 'About',
link: 'about.html'
},
{
name: 'Contact',
link: '#',
children: [
{
name: 'Twitter',
link: 'https://twitter.com/w_zce'
},
{
name: 'About',
link: 'https://weibo.com/zceme'
},
{
name: 'divider'
},
{
name: 'About',
link: 'https://github.com/zce'
}
]
}
],
pkg: require('./package.json'),
date: new Date()
}
const clean = () => {
return del(['dist', 'temp'])
}
const style = () => {
return src('src/assets/styles/*.scss', {base: 'src'})
.pipe(plugins.sass({outputStyle: 'expanded'}))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const script = () => {
return src('src/assets/scripts/*.js', {base: 'src'})
.pipe(plugins.babel({presets: ['@babel/preset-env']}))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const page = () => {
// 匹配src下的所有html
return src('src/**/*.html', {base: 'src'})
// swig 有缓存。需要配置cache: false,否则效果不好。
.pipe(plugins.swig({ data, defaults: { cache: false } }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const image = () => {
// 匹配src下的所有html
return src('src/assets/images/**', {base: 'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
const font = () => {
// 匹配src下的所有html
return src('src/assets/fonts/**', {base: 'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
const extra = () => {
return src('public/**', {base: 'public'})
.pipe(dest('dist'))
}
const serve = () => {
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/*.html', page)
// 以下文件不需要时时监听。对他们并没有做任何处理。只需要引用他们即可。
// watch('src/assets/images/**', image)
// watch('src/assets/fonts/**', font)
// watch('public/**', extra)
// 当这些文件又变化的时候,执行bs.reload重新加载浏览器。
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**',
], bs.reload)
bs.init({
// 禁止右上角提示
notify: false,
// 设置端口号
open: false,
port: 2080,
// 可以在每个任务下执行bs.reload()
// files: 'dist/**',
server: {
// 启动服务的基准文件
// 从左到右一次执行。如果在dist找不到的文件回去src里找。以此类推。
baseDir: ['temp', 'src', 'public'],
// baseDir: 'temp',
// 发现有以/node_modules开头的路径。
// 就会在node_modules文件夹查找。
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const useref = () => {
return src('temp/**/*.html', {base: 'temp'})
.pipe(plugins.useref({
searchPath: ['temp', '.']
}))
.pipe(plugins.if(/\.js$/, plugins.uglify()))
.pipe(plugins.if(/\.css$/, plugins.cleanCss()))
.pipe(plugins.if(/\.html$/, plugins.htmlmin({
collapseWhitespace: true, // html里面的空格
minifyCSS: true, // html里面的css
minifyJS: true, // html里面的js
})))
.pipe(dest('dist'))
}
const comppile = parallel(style, script, page);
// 先执行串行任务。在执行并行任务。
const build = series(clean, parallel(series(comppile, useref), image, font, extra));
const develop = series(comppile, serve);
// 导出需要执行的模块
// exports.style = () => {} 相同
module.exports = {
build,
develop,
clean,
}
目录结构
git地址: github.com/17698753015…
自动化构建工作流
自动更新包: npm.taobao.org
- mkdir yhb-pages
- git init -y
- mkdir bin && touch yhb-pages.js
- mkdir lib && touch index.js
- 将写好的gulp项目。提取gulpfile
- 将gulpfile.js放入到lib/index.js里面 现在是空的
- 将gulp项目中的package.json的开发依赖放到yhb-pages项目的生产依赖上面。
- package.json里面加一个"bin": "biin/yhb-pages.js"
- 将gulp项目node-modules.
- 在yhb-pages项目安装所有依赖。
- yarn link 将当前项目命令链接到全局。
- 在gulp项目中执行 yarn lint 'yhb-pages'
- 在gulp项目中的gulpfile.js 写如下代码
module.exports = require('yhb-pages')
-
gulp项目中执行: yarn
-
gulp项目中执行: yarn build
-
yarn add gulp-cli --dev 里面有gulp命令
-
yarn build
-
临时安装yarn add gulp --dev
-
yarn build
在gulp项目中根目录新建一个pages.config.js
module.exports = {
data: {
menus: [
{
name: 'Home',
icon: 'aperture',
link: 'index.html'
},
{
name: 'Features',
link: 'features.html'
},
{
name: 'About',
link: 'about.html'
},
{
name: 'Contact',
link: '#',
children: [
{
name: 'Twitter',
link: 'https://twitter.com/w_zce'
},
{
name: 'About',
link: 'https://weibo.com/zceme'
},
{
name: 'divider'
},
{
name: 'About',
link: 'https://github.com/zce'
}
]
}
],
pkg: require('zce-pages/package'),
date: new Date()
}
}
- yhb-pages/lib/index.js