一. 自动化构建
1. 简介
-
把开发时的源代码自动化的转化为生产环境代码
-
实现自动化构建
-
npm scripts
-
package.json
{ "name": "my-auto-build1", "version": "1.0.0", "description": "my-auto-build1", "main": "index.js", "scripts": { "build": "sass ./scss/main.scss ./css/style.css --watch", "serve": "browser-sync . --files ./scss/main.scss", "start": "run-p build serve" }, "author": "康小源", "license": "MIT", "dependencies": { "browser-sync": "^2.26.13", "npm-run-all": "^4.1.5", "sass": "^1.29.0" } }
-
-
2. 常用的自动化构建工具
-
grunt
-
最早的前端构建系统
-
基于临时文件实现
-
基本使用
-
初始化 package.json
-
安装 grunt,yarn add grunt --dev
/** * Grunt 的入口文件 * 用于定义一些需要 Grunt 自动执行的任务 * 需要导出一个函数 * 此函数接收一个 grunt 的形式参数,内部提供一些创建任务时可以用到的 api */ module.exports = grunt => { // 注册一个任务 // 第一个参数是任务的名字 // 第二个参数是执行任务时的程序 grunt.registerTask('foo', () => { console.log('hello grunt') }) // 第二个参数如果是字符串,将成为任务描述 grunt.registerTask('bar', '任务描述', () => { console.log('other task') }) // 如果任务名称为 default,这个任务将称为默认任务,运行时可以不加任务名称 // grunt.registerTask('default', () => { // console.log('default task') // }) // 映射其他任务,第二个参数传入一个数组,数组中指定任务 grunt.registerTask('default', ['foo', 'bar']) // grunt 对异步任务的支持 // 通过 this.async() 实现异步操作 grunt.registerTask('async-task', function() { const done = this.async() setTimeout(() => { console.log('async task') done() }, 2000) }) }
-
-
标记失败让任务
/** * Grunt 的入口文件 * 用于定义一些需要 Grunt 自动执行的任务 * 需要导出一个函数 * 此函数接收一个 grunt 的形式参数,内部提供一些创建任务时可以用到的 api */ module.exports = grunt => { // 标记任务失败 // 一个任务失败了,后续任务将不在被执行 // --force 可以跳过失败任务 grunt.registerTask('fail-task', () => { console.log('fail task') return false }) // 异步任务标记失败 // 给 done 方法一个 false 的实参来标记任务失败 grunt.registerTask('fail-async-task', function(){ const done = this.async() setTimeout(() => { console.log('fail async task') done(false) }, 2000) }) } -
grunt 配置方法
module.exports = grunt => { // grunt 的配置方法 // 此方法接收一个对象形式的参数 // 对象的属性名和任务名称保持一致 // 属性值可以是任意类型的数据 grunt.initConfig({ // foo: 'bar' foo: { bar: 123 } }) // grunt.config() 获取在initConfig 中定义的配置 grunt.registerTask('foo', () => { // console.log(grunt.config('foo')) // bar console.log(grunt.config('foo.bar')) // 123 }) } -
多目标任务
module.exports = grunt => { // 通过 initConfig 配置任务目标 // 目标必须是一个对象 // 运行指定目标 build:css // 在 initConfig 中所有的属性都是目标,除了 options 以外 grunt.initConfig({ build: { // 作为任务的配置选项 options: { foo: 'bar' }, css: '1', js: '2' } }) // 通过 grunt.resgisterMultiTask() 定义多目标任务 // 多目标任务需要配置不同的目标 // 接收两个参数 // 第一个参数:任务名字 // 第二个参数:任务逻辑 // 可以通过 this.target 拿到目标名称,通过 this.data 拿到目标数据 grunt.registerMultiTask('build', function () { console.log(`target: ${this.target}, data: ${this.data}`) }) } -
插件的使用
-
使用过程
-
通过 npm 安装插件
-
在 gruntfile 中载入插件
-
根据插件的配置文档使用插件
/** * grunt-contrib-clean * 用来清除项目中产生的临时文件 */ module.exports = grunt => { grunt.initConfig({ clean: { // temp: 'temp/app.js' temp: 'temp/*.txt' } }) // grunt.loadNpmTasks() 加载插件中提供的任务 grunt.loadNpmTasks('grunt-contrib-clean') }
-
-
-
常用的插件
-
安装 grunt-sass,yarn add grunt-sass sass --dev
-
安装 grunt-babel,yarn add grunt-babel @babel/core @babel/preset-env --dev
-
安装 load-grunt-tasks 模块,减少 grunt.loadNpmTasks() 的使用
-
安装 grunt-contrib-watch 模块,yarn add grunt-contrib-watch --dev,用于监视文件修改,自动编译
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'] } } }) loadGruntTasks(grunt) // 自动加载所有的 grunt 插件任务 grunt.registerTask('default', ['sass', 'babel', 'watch']) }
-
-
gulp
-
基于内存实现
-
基本使用
-
初始化 package.json,yarn init
-
安装 gulp, yarn add gulp --dev
-
gulp 以导出函数成员的方式定义任务
// gulp 的入口文件 exports.foo = done => { console.log('foo task') done() // 标识任务完成 } // 如果导出的任务为 default,会作为默认任务 exports.default = done => { console.log('default task') done() }
-
-
组合任务
-
series 串行
-
parallel 并行
const { series, parallel } = require('gulp') const task1 = done => { setTimeout(() => { console.log('task1') done() }, 1000) } const task2 = done => { setTimeout(() => { console.log('task2') done() }, 1000) } const task3 = done => { setTimeout(() => { console.log('task3') done() }, 1000) } // 串行任务 exports.foo = series(task1, task2, task3) // 并行任务 exports.bar = parallel(task1, task2, task3) -
异步任务
const fs = require('fs') // 通过回调标记任务结束 exports.callback = done => { console.log('callback') done() } // 错误任务 // gulp 为错误优先的方式 // gulp 任务一旦出错,则后续任务不会执行 exports.callback_error = done => { console.log('callback_error') done(new Error('task failed')) } // 返回 promise 标记任务结束 exports.promise = () => { console.log('promise task') // 不用给 resolve 传值,gulp 会忽略这个值 return Promise.resolve() } // 返回失败的 promise exports.promise_error = () => { console.log('promise_error task') return Promise.reject(new Error('task failed')) } // 定义异步函数 const timeout = time => { return new Promise(resolve => { setTimeout(resolve, time) }) } // 通过 async await 的方式标记任务结束 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 } -
构建过程核心工作原理
const fs = require('fs') const { Transform } = require('stream') exports.default = () => { // 文件读取流 const read = fs.createReadStream('normalize.css') // 文件写入流 const write = fs.createWriteStream('normalize.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, output) } }) // 把读取到的读取流写入到 normalize.min.css 中 read .pipe(transform) // 转换 .pipe(write) // 写入 return read } -
文件操作 API
const { src, dest } = require('gulp') const cleanCss = require('gulp-clean-css') const rename = require('gulp-rename') exports.default = () => { return src('./src/*.css') // 读取流 .pipe(cleanCss()) // 压缩 css .pipe(rename({ extname: '.min.css' })) .pipe(dest('dist')) // 写入流 } -
案例
-
安装模块
-
安装 gulp,yarn add gulp --dev
-
安装 gulp-sass,yarn add gulp-sass --dev
-
安装 gulp-babel,yarn add gulp-babel @babel/core @babel/preset-env --dev
-
安装 gulp-swig,yarn add gulp-swig --dev
-
安装 gulp-imagemin,yarn add gulp-imagemin --dev
-
安装 gulp-load-plugins,yarn add gulp-load-plugins --dev
-
安装 del,yarn add del --dev
-
安装 browser-sync,yarn add browser-sync --dev
-
安装 gulp-useref,yarn add gulp-useref --dev
-
安装 gulp-if,yarn add gulp-if --dev
-
安装 gulp-clean-css,yarn add gulp-clean-css --dev
-
安装 gulp-uglify,yarn add gulp-uglify --dev
-
安装 gulp-htmlmin,yarn add gulp-htmlmin --dev
const { src, dest, series, parallel, watch, tree } = require('gulp') // 清除文件插件 const del = require('del') // 自动加载插件 const loadPlugins = require('gulp-load-plugins') const plugins = loadPlugins() // 开发服务器 const browserSync = require('browser-sync') const bs = browserSync.create() // // scss 转换插件 // const sass = require('gulp-sass') // // es6 转换插件 // const babel = require('gulp-babel') // // 模板引擎转换插件 // const swig = require('gulp-swig') // // 图片转换插件 // const imagemin = require('gulp-imagemin') 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 = () => { // 指定 src 函数的选项参数 base,指定基准路径 return src('./src/assets/styles/*.scss', { base: 'src' }) // sass 会认为以下划线开头的样式文件是在主文件依赖的文件 // 指定参数 { outputStyle: expanded } 完全展开 .pipe(plugins.sass({ outputStyle: 'expanded' })) .pipe(dest('temp')) // 指定参数 { stream: true } 以流的方式推送至浏览器 .pipe(bs.reload({ stream: true })) } const script = () => { return src('./src/assets/scripts/*.js', { base: 'src' }) // 指定参数 { presets: ['@babel/preset-env'] } 转换插件 .pipe(plugins.babel({ presets: ['@babel/preset-env'] })) .pipe(dest('temp')) .pipe(bs.reload({ stream: true })) } const page = () => { return src('./src/*.html', { base: 'src' }) .pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板缓存导致页面不能刷新 .pipe(dest('temp')) .pipe(bs.reload({ stream: true })) } const image = () => { return src('./src/assets/images/**', { base: 'src' }) .pipe(plugins.imagemin()) .pipe(dest('dist')) } const font = () => { 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) // 通过 init 方法初始化开发服务器配置 bs.init({ // 关闭 browser-sync 启动时的小提示 nitify: false, // 设置端口 prot: 8080, // 自动打开浏览器 // open: false, // 配置 server // files: './dist/**', server: { // 指定网站根目录 baseDir: ['temp', 'src', 'public'], // 优先 baseDir 的配置 routes: { '/node_modules': './node_modules' } } }) } // useref 会自动处理 html 中的构建注释 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, // 压缩 style minifyCSS: true, // 压缩 script minifyJS: true }))) .pipe(dest('dist')) } const compile = parallel(style, script, page) const build = series(clean, parallel(series(compile, useref), image, font, extra)) const develop = series(compile, serve) module.exports = { clean, build, develop }
-
-
-
-
封装工作流
- 构建工作流 = gulpfile + gulp
-