前端工程化整理(笔记)
一切以提高效率、降低成本、质量保证为目的的方案都是“工程化”
前端工程化,nodeJS厥功至伟,大部分工程化的东西都是用nodeJS开发的,无node无前端工程化。
大概分为:
- 脚手架工具
- 自动化构建系统
- 模块化打包
- 项目代码规范化
- 自动化部署
脚手架工具
常用脚手架
Yeoman
使用
1、在全局安装yeoman
$ npm install yo -g # or yarn global add yo
2、安装对应的generator(要生成哪种项目,就用哪种生成器这里是node)
$ npm install generator-node -g # or yarn global add generator-node
3、通过yo运行generator
$ cd path/to/project-dir
$ mkdir my-module
$ yo node
自定义一个Generator
|- generator/ 生成器
| |- app/ 默认生成器目录
| | |- index.js 默认生成器实现
| | |- templates 默认模板目录
index.js
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 () {
// Yeoman 自动在生成文件阶段调用此方法
// 通过模板方式写入文件到目标目录
// 模板文件路径
const tmpl = this.templatePath('bar.html')
// 输出目标路径
const output = this.destinationPath('bar.html')
// 模板数据上下文
const context = this.answers
this.fs.copyTpl(tmpl, output, context) //最后输出
}
}
发布到npm 命令:npm publish
Plop (快速创建组件得小脚手架工具)
使用
1、安装到项目中
$ npm install plop --dev
2、在项目根目录新建文件plopfile.js
// 此函数接收一个 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'
}
]
})
}
templateFile: 'plop-templates/component.hbs'
模板文件,在根目录创建的模板文件夹 /plop-templates/component.hbs
component.hbs
import React from 'react';
export default () => (
<div className="{{name}}">
<h1>{{name}} Component</h1>
</div>
)
脚手架原理
自动化构建
将开发出来的代码转换为生产代码,及自动化构建流,例如less->css、新语法转es5,兼容性等等
1、browesr-sync模块,起一个服务并且自动打开浏览器
“serve”: “browesr-sync” --files 监听文件变化,热更新
2、npm-run-all模块,同时实行多个命令
“start”: “run-p build serve” 同时执行build和serve
常用的自动化构建工具
Grunt、Gulp、FIS
Grunt
基本使用
1、 npm install grunt
2、创建 gruntfile.js
gruntfile.js
// Grunt 的入口文件
// 用于定义一些需要 Grunt 自动执行的任务
// 需要导出一个函数
// 此函数接收一个 grunt 的对象类型的形参
// grunt 对象中提供一些创建任务时会用到的 API
module.exports = grunt => {
//1、 grunt.initConfig() 用于为任务添加一些配置选项
grunt.initConfig({
// 键一般对应任务的名称
// 值可以是任意类型的数据
foo: {
bar: 'baz'
}
})
// --------------------------------
//2、 registerTask注册一个任务
grunt.registerTask('foo', () => {
// 任务中可以使用 grunt.config() 获取配置
console.log(grunt.config('foo'))
// 如果属性值是对象的话,config 中可以使用点的方式定位对象中属性的值
console.log(grunt.config('foo.bar'))
})
// ---------------------------------
//3、 grunt中执行异步任务使用this.async()回调函数
// 由于函数体中需要使用 this,所以这里不能使用箭头函数
grunt.registerTask('async-task', function () {
const done = this.async()
setTimeout(() => {
console.log('async task working~')
done()
}, 1000)
})
// ---------------------------------
//4、 任务函数执行过程中如果返回 false
// 则意味着此任务执行失败
grunt.registerTask('bad', () => {
console.log('bad task');
return false
})
// 异步函数中标记当前任务执行失败的方式是为回调函数指定一个 false 的实参
grunt.registerTask('bad-async', function () {
const done = this.async()
setTimeout(() => {
console.log('async task working~')
done(false)
}, 1000)
})
// -------------------------------
//5、 多任务配置
// 多目标模式,可以让任务根据配置形成多个子任务
// 配合initConfig使用,build下的每个键就是以一个任务与,除了option
grunt.initConfig({
build: {
options: {
msg: 'task options'
},
foo: {
options: {
msg: 'foo target options'
}
},
bar: '456'
}
})
grunt.registerMultiTask('build', function () {
console.log(this.options())
})
}
6、插件使用
1、下载对应插件,npm install grunt-contrib-clean
2、通过grunt.loadNpmTasks引入插件
3、通过initConfig配置插件的使用,启动命令对应插件clean
grunt-contrib-clean删除指定文件
module.exports = grunt => {
grunt.initConfig({
clean: {
temp: 'temp/**'
}
})
grunt.loadNpmTasks('grunt-contrib-clean')
}
7、常用的grunt插件
1、grunt-sass 编译sass
2、load-grunt-tasks 自动加载所有的 grunt 插件中的任务不必一个个loadNpmTasks();
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
}
3、grunt-babel @babel/core @babel/preset-env ES6转ES5编译兼容
module.exports = grunt => {
grunt.initConfig({
babel: {
options: {
sourceMap: true,
presets: ['@babel/preset-env']
},
main: {
files: {
'dist/js/app.js': 'src/js/app.js'
}
}
}
})
}
4、grunt-contrib-watch 监听文件变化
module.exports = grunt => {
grunt.initConfig({
watch: {
js: {
files: ['src/js/*.js'],
tasks: ['babel']
},
css: {
files: ['src/scss/*.scss'],
tasks: ['sass']
}
}
})
}
Gulp
基本使用基本与grunt相同
1、 npm install gulp
2、创建 gulpfile.js
// 创建任务
// gulp 的任务函数都是异步的
// 可以通过调用回调函数标识任务完成
exports.foo = done => {
console.log('foo task working~')
done() // 标识任务执行完成
}
// default 是默认任务
// 在运行是可以省略任务名参数
exports.default = done => {
console.log('default task working~')
done()
}
// v4.0 之前需要通过 gulp.task() 方法注册任务
const gulp = require('gulp')
gulp.task('bar', done => {
console.log('bar task working~')
done()
})
// 命令启动 gulp bar
gulp组合任务
const { series, parallel } = require('gulp')
const task1 = done => {
setTimeout(() => {
console.log('task1 working~')
done()
}, 1000)
}
const task2 = done => {
setTimeout(() => {
console.log('task2 working~')
done()
}, 1000)
}
const task3 = done => {
setTimeout(() => {
console.log('task3 working~')
done()
}, 1000)
}
// 让多个任务按照顺序依次执行(串行)
exports.foo = series(task1, task2, task3)
// 让多个任务同时执行(并行)
exports.bar = parallel(task1, task2, task3)
gulp中的异步任务
const fs = require('fs')
// gulp的任务函数本身都是异步的,通过调用done()完成
exports.callback = done => {
console.log('callback task')
done()
}
exports.callback_error = done => {
console.log('callback task')
done(new Error('task failed'))
}
// -------------------------------
// 也可使用Promise需要node8以上
exports.promise = () => {
console.log('promise task')
return Promise.resolve()
}
exports.promise_error = () => {
console.log('promise task')
return Promise.reject(new Error('task failed'))
}
// -----也可以是async/await-------------------------------
const timeout = time => {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
exports.async = async () => {
await timeout(1000)
console.log('async task')
}
// ------------------------------------
// 较为常用的是文件流的形式
exports.stream = () => {
const read = fs.createReadStream('yarn.lock')
const write = fs.createWriteStream('a.txt')
read.pipe(write)
return read
}
// 其实是调用了read中的done;
exports.stream = done => {
const read = fs.createReadStream('yarn.lock')
const write = fs.createWriteStream('a.txt')
read.pipe(write)
read.on('end', () => {
done()
})
}
gulp中的文件操作
gulp的内置插件:src和dest
// src读出文件流 dest写入文件流
const { src, dest } = require('gulp')
// 压缩css
const cleanCSS = require('gulp-clean-css')
// 重命名
const rename = require('gulp-rename')
exports.default = () => {
return src('src/*.css')
.pipe(cleanCSS())
.pipe(rename({ extname: '.min.css' }))
.pipe(dest('dist'))
}
gulp 自动化构建中常用的插件
1、gulp-load-plugins 自动加载所用到的插件
const loadPlugins = require(‘gulp-load-plugins’)
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
// sass编译 sass
plugins.sass({ outputStyle: 'expanded' })
// js新属性转为ES5 babel
plugins.babel({ presets: ['@babel/preset-env'] })
// 模板编译 swig
plugins.swig({ data, defaults: { cache: false } }
// 图片压缩 imagemin
plugins.imagemin()
2、热更新开发服务器 browser-sync插件
const { watch } = require('gulp')
const browserSync = require('browser-sync')
const bs = browserSync.create()
const style = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.pipe(plugins.sass({ outputStyle: 'expanded' }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true })) // 也可在这里触发服务器reload
}
const serve = () => {
// 监听文件修改
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/*.html', page)
//
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**'
], bs.reload)
// bs.reload重新加载,监听文件变化后可使用bs.reload
bs.init({
notify: false,
port: 2080,
// open: false, // 是否关闭热更新
// files: 'dist/**', // 监听文件变化更新浏览器监听目录
server: {
baseDir: ['temp', 'src', 'public'], // 网站根目录
routes: {
'/node_modules': 'node_modules'
}
}
})
}
3、构建优化拆封,生成与开发环境的配置
4、useref插件,处理文件引用目录问题,并打包儿多部分文件
// 打包后的文件处理,将构建注释转化
// 处理html文件中,js和css的引用
const useref = () => {
return src('temp/*.html', { base: 'temp' })
.pipe(plugins.useref({ searchPath: ['temp', '.'] }))
// html js css 文件压缩处理
// uglify js压缩、cleanCss css压缩、htmlmin html压缩
// gulp-if插件区分文件类型
.pipe(plugins.if(/\.js$/, plugins.uglify()))
.pipe(plugins.if(/\.css$/, plugins.cleanCss()))
.pipe(plugins.if(/\.html$/, plugins.htmlmin({
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
})))
.pipe(dest('dist'))
}
5、区分导出任务,并写入script中,用npm启动
6、封装自动化构建工作流,提取构建系统的公共部分,创建npm包儿发布
- 建立npm包儿,最后发布到npm仓库管理(可参考npm包开发流程)
- 导入专门的配置文件,可以传入参数
- 一般参数有:文件配置文件目录、环境目录等等
- 包装gulp-cli,创建/bin/umbal-page.js文件
umbal-page.js
#!/usr/bin/env node
process.argv.push('--cwd')
process.argv.push(process.cwd())
process.argv.push('--gulpfile')
process.argv.push(require.resolve('..'))
require('gulp/bin/gulp')