主要解决的问题
- 传统语言或语法的弊端
- 无法使用模块化 / 组件化
- 重复的机械工作
- 代码风格统一/质量保证
- 依赖后端服务接口支持
- 整体依赖后端项目
工程化表现
- 一切以提高效率、降低成本、质量保证为目的的手段都属于[工程化]
- 一切重复的工作都应该被自动化
脚手架工具
常用的脚手架工具
- React.js 项目 ----> create-react-app
- Vue.js 项目 ----> vue-cli
- Angular 项目 ----> angular-cli
- Yeoman
- Plop
Yeoman
- The web’s scaffloding tool for modern webapps
Yeoman - 基本使用
- 在全局范围安装 yo
$ npm install yo -- global # or yarn global add yo
- 安装对应的 generator
$ npm install generator-node --global # or yarn global add generator-node
- 通过 yo 运行 generator
$ cd path/to/project-dir $ mkdir my-module $ yo node
yeoman - Sub Generator
yo node:cli
yarn link //全局使用
yarn
my-module --help
常规使用步骤
- 明确你的需求
- 找到合适的 Generator
- 全局范围安装找到的 Generator
- 通过 yo 运行对应的 Generator
- 通过命令行交互填写选项
- 生成你所需要的项目结构
自定义 Generator
- 基于 Yeoman 搭建自己的脚手架
创建 Generator模块
- Generator 本质上就是一个 NPM 模块
- 名字必须为:generator-< name >
根据模板创建文件
- 相对于手动创建每一个文件,模板的方式大大提高了效率
接收用户输入数据
prompting () {
// Yeoman 在询问用户环节会自动调用此方法
// 在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问
return this.prompt([
{
type: 'input',
name: 'name',
message: 'Your project name',
default: this.appname // appname 为项目生成目录名称
}
])
.then(answers => {
// answers => { name: 'user input value' }
this.answers = answers
})
}
发布 Generator
Plop
- 一个小而美的脚手架工具
Plop 的具体使用
- 将 plop 模块作为项目开发依赖安装
- 在项目根目录下创建一个 plopfile.js 文件
- 在 plopfile.js 文件中定义脚手架任务
- 编写用于生成特定类型文件的模板
- 通过 Plop 提供的 CLI 运行脚手架任务
// 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' } ] }) }
自动化构建
npm scripts : 实现自动化构建工作流的最简单方式
常用的自动化构建工具
- Grunt 构建速度较慢,磁盘读写操作
- Gulp 解决了Grunt 构建速度慢问题
- FIS 百度前端推出 – 大而全
Grunt
-
基本使用
yarn init yarn add grunt
-
创建 gruntfile.js grunt 的入口函数
// Grunt 的入口文件 // 用于定义一些需要 Grunt 自动执行的任务 // 需要导出一个函数 // 此函数接收一个 grunt 的对象类型的形参 // grunt 对象中提供一些创建任务时会用到的 API module.exports = grunt => { grunt.registerTask('foo', 'a sample task', () => { console.log('hello grunt') }) grunt.registerTask('bar', () => { console.log('other task') }) // // default 是默认任务名称 // // 通过 grunt 执行时可以省略 // grunt.registerTask('default', () => { // console.log('default task') // }) // 第二个参数可以指定此任务的映射任务, // 这样执行 default 就相当于执行对应的任务 // 这里映射的任务会按顺序依次执行,不会同步执行 grunt.registerTask('default', ['foo', 'bar']) // 也可以在任务函数中执行其他任务 grunt.registerTask('run-other', () => { // foo 和 bar 会在当前任务执行完成过后自动依次执行 grunt.task.run('foo', 'bar') console.log('current task runing~') }) // 默认 grunt 采用同步模式编码 // 如果需要异步可以使用 this.async() 方法创建回调函数 // grunt.registerTask('async-task', () => { // setTimeout(() => { // console.log('async task working~') // }, 1000) // }) // 由于函数体中需要使用 this,所以这里不能使用箭头函数 grunt.registerTask('async-task', function () { const done = this.async() setTimeout(() => { console.log('async task working~') done() }, 1000) }) }
-
标记任务失败
module.exports = grunt => { // 任务函数执行过程中如果返回 false // 则意味着此任务执行失败 grunt.registerTask('bad', () => { console.log('bad working~') return false }) grunt.registerTask('foo', () => { console.log('foo working~') }) grunt.registerTask('bar', () => { console.log('bar working~') }) // 如果一个任务列表中的某个任务执行失败 // 则后续任务默认不会运行 // 除非 grunt 运行时指定 --force 参数强制执行 grunt.registerTask('default', ['foo', 'bad', 'bar']) // 异步函数中标记当前任务执行失败的方式是为回调函数指定一个 false 的实参 grunt.registerTask('bad-async', function () { const done = this.async() setTimeout(() => { console.log('async task working~') done(false) }, 1000) }) }
-
Grunt 配置选项方法
module.exports = grunt => { // grunt.initConfig() 用于为任务添加一些配置选项 grunt.initConfig({ // 键一般对应任务的名称 // 值可以是任意类型的数据 foo: { bar: 'baz' } }) grunt.registerTask('foo', () => { // 任务中可以使用 grunt.config() 获取配置 console.log(grunt.config('foo')) // 如果属性值是对象的话,config 中可以使用点的方式定位对象中属性的值 console.log(grunt.config('foo.bar')) }) }
-
Grunt 多目标任务
module.exports = grunt => { // 多目标模式,可以让任务根据配置形成多个子任务 // grunt.initConfig({ // build: { // foo: 100, // bar: '456' // } // }) // grunt.registerMultiTask('build', function () { // console.log(`task: build, target: ${this.target}, data: ${this.data}`) // }) grunt.initConfig({ build: { options: { msg: 'task options' }, foo: { options: { msg: 'foo target options' } }, bar: '456' } }) grunt.registerMultiTask('build', function () { console.log(this.options()) }) }
-
Grunt 插件的使用
- npm安装插件
- 使用 grunt.loadNpmTasks 加载插件
- grunt.initConfig 配置插件选项
module.exports = grunt => { grunt.initConfig({ clean: { temp: 'temp/**' } }) grunt.loadNpmTasks('grunt-contrib-clean') }
-
Grunt常用插件
const sass = require('sass') const loadGruntTasks = require('load-grunt-tasks') module.exports = grunt => { grunt.initConfig({ sass: { options: { sourceMap: true, implementation: sass }, main: { files: { 'dist/css/main.css': 'src/scss/main.scss' } } }, babel: { options: { sourceMap: true, presets: ['@babel/preset-env'] }, main: { files: { 'dist/js/app.js': 'src/js/app.js' } } }, watch: { js: { files: ['src/js/*.js'], tasks: ['babel'] }, css: { files: ['src/scss/*.scss'], tasks: ['sass'] } } }) // grunt.loadNpmTasks('grunt-sass') loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务 grunt.registerTask('default', ['sass', 'babel', 'watch']) }
Grunt常用插件总结:
-
css处理
安装 scss处理: grunt-sass sass
压缩:grunt-contrib-cssminsass: { options: { implementation: sass }, dist: { files: [{ expand: true, cwd: 'src/assets/styles', src: ['*.scss'], dest: 'dist/assets/styles', ext: '.css' }] } },
-
js处理
安装 Es6处理: @babel/core @babel/preset-env grunt-babel
压缩:grunt-contrib-uglifybabel:{ options: { // sourceMap: true, presets: ['@babel/preset-env'] }, dist: { files:[{ expand: true, cwd: 'src/assets/scripts', src: ['*.js'], dest: 'dist/assets/scripts' }] } },
-
HTML处理 swig 模板
安装编译插件: grunt-swigtemplates
压缩: grunt-contrib-htmlminswigtemplates: { options: { defaultContext: data, templatesDir: 'src' }, production: { dest: 'dist', src: ['src/*.html'] } },
-
图片压缩
安装插件: grunt-contrib-imageminimagemin: { dist: { options: { optimizationLevel: 3 //定义 PNG 图片优化水平 }, files: [{ expand: true, cwd: 'src/assets/images', src: ['**'], dest: 'dist/assets/images' }, { expand: true, cwd: 'src/assets/fonts', src: ['**'], dest: 'dist/assets/fonts' }] } },
-
拷贝文件
安装插件: grunt-contrib-copycopy: { main: { files: [{ expand: true, cwd: 'public', src: ['**'], dest: 'dist', filter: 'isFile', }], }, },
-
删除文件
安装插件: grunt-contrib-cleanclean: { build: ['dist'] },
-
启动开发服务器
安装插件: grunt-browser-syncbrowserSync: { bsFiles: { src : ['dist', 'src', 'public'] }, options: { watchTask: true, server: { baseDir: ['dist', 'src', 'public'], routes: { '/node_modules': 'node_modules' } } } },
-
监听文件的变化
安装插件: grunt-contrib-watchwatch: { js: { files: ['src/assets/scripts/*.js'], tasks: ['babel'] }, css: { files: ['src/assets/styles/*.scss'], tasks: ['sass'] } },
-
同是执行多个任务
安装插件: grunt-concurrent
concurrent: { target: { tasks: ['browserSync', 'watch'], options: { logConcurrentOutput: true } } },
-
处理页面中引用模块 URL 路径问题
安装插件: grunt-useref grunt-css grunt-contrib-uglify grunt-contrib-concatuseref: { html: 'dist/*.html', temp: 'dist' },
Gulp
-
基本使用
yarn init yarn add gulp --dev
-
创建 gulpfile.js gulp的入口函数
exports.foo = done => { console.log("foo ....") done() }
-
创建组合任务
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)
构建过程核心工作原理
- 输入 - 读取流
- 加工 - 转换流
- 输出 - 写入流
const fs = require('fs') const { Transform } = require('stream') exports.default = () => { // 文件读取流 const readStream = fs.createReadStream('normalize.css') // 文件写入流 const writeStream = fs.createWriteStream('normalize.min.css') // 文件转换流 const transformStream = new Transform({ // 核心转换过程 transform: (chunk, encoding, callback) => { const input = chunk.toString() const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '') callback(null, output) } }) return readStream .pipe(transformStream) // 转换 .pipe(writeStream) // 写入 }
Gulp 文件操作 API + 插件的使用
```
// src 读取流 dest 写入流
const { src, dest } = require('gulp')
const cleanCSS = require('gulp-clean-css')
const rename = require('gulp-rename')
.pipe(rename({ extname: '.min.css' }))
exports.default = () => {
return src('src/*.css')
.pipe(cleanCSS())
.pipe(dest('dist'))
}
```
gulp常用的插件
- gulp-sass 将sass文件转换为 css
- gulp-babel @babel/core @babel/preset-env 转换es6新特性
- gulp-swig 转换HTML swig 模板
- gulp-imagemin 压缩img
- del 清除文件
- gulp-load-plugins 自动加载 gulp 插件
- browser-sync 启动一个开发服务器
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.init({ notify: false, port: 2080, // open: false, files: 'dist/**', // 监听 dist 文件变化刷新 server: { baseDir: ['dist', 'src', 'public'], routes: { '/node_modules': 'node_modules' } } }) }
- gulp-useref 根据构建注释构建css,js 路径
- gulp-htmlmin 压缩 HTML 文件
- gulp-uglify 压缩 JS 文件
- gulp-clean-css 压缩 CSS 文件
- gulp-if 判断文件类型
const useref = () => { return src('dist/*.html',{base: 'dist'}) .pipe(plugins.useref({searchPath: ['dist', '.']})) .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('release')) }
封装自动化构建工作流
-
创建一个模块,将模块发布npm,新项目使用npm
-
将gulpfile 中的代码替换到入口文件
-
将 gulpfile 完成项目中的 package.json 依赖拷入新 package.json 项目依赖(dependencies)
-
将模块 yarn link 到全局
-
在项目中使用link的模块
-
提取公共配置文件
const cwd = process.cwd() //命令行工作 let config = { // default config build: { src: 'src', dist: 'dist', temp: 'temp', public: 'public', paths: { styles: 'assets/styles/*.scss', scripts: 'assets/scripts/*.js', pages: '*.html', images: 'assets/images/**', fonts: 'assets/fonts/**' } } } try { const loadConfig = require(`${cwd}/pages.config.js`) config = Object.assign({}, config, loadConfig) } catch (e) {}
-
包装 Gulp CLI
创建 bin --> 项目名.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')
-
发布
在package.json files 中增加 “bin”
yarn publish -
同步时间差问题:
npm.taobao.org 中找到模块 点击 SYNC
FIS
- 基本使用
yarn global add fis3
- 创建 fis-conf.js
fis.match('*.{js,scss,png}', { release: '/assets/$0' }) fis.match('**/*.scss', { rExt: '.css', parser: fis.plugin('node-sass'), optimizer: fis.plugin('clean-css') }) fis.match('**/*.js', { parser: fis.plugin('babel-6.x'), optimizer: fis.plugin('uglify-js') })