1、什么是自动化构建?

就是将开发阶段的源代码自动的转化为生产环境可以运行的代码。我们将这一转换过程称为自动化构建工作流。自动化构建的作用就是脱离运行环境兼容带来的问题,使用提高效率的语法、规范和标准。
image.png

2、NPM Scripts是自动化构建最简单的方式

在package.json中添加scripts字段,他可以自动去执行node_modules中的命令

  1. {
  2. "name": "auto build",
  3. "version": "0.1.0",
  4. "main": "index.js",
  5. "license": "MIT",
  6. "scripts": {
  7. "build": "sass scss/main.scss css/style.css",
  8. "serve": "browser-sync .",
  9. },
  10. "dependencies": {
  11. }
  12. }

安装browser-sync,启动一个服务器

  1. npm i browser-sync
  2. npm run serve

但是这里直接启动服务器,sass文件并没有转化为css文件,所以要先执行npm build,在这里npm 提供了pre和post的钩子,这样preserve会在serve之前自动执行。

  1. {
  2. "name": "auto build",
  3. "version": "0.1.0",
  4. "main": "index.js",
  5. "license": "MIT",
  6. "scripts": {
  7. "build": "sass scss/main.scss css/style.css",
  8. "preserve": "npm run build",
  9. "serve": "browser-sync .",
  10. },
  11. "dependencies": {
  12. }
  13. }

在build命令行添加—watch参数

  1. {
  2. "name": "auto build",
  3. "version": "0.1.0",
  4. "main": "index.js",
  5. "license": "MIT",
  6. "scripts": {
  7. "build": "sass scss/main.scss css/style.css --watch",
  8. "preserve": "npm run build",
  9. "serve": "browser-sync .",
  10. },
  11. "dependencies": {
  12. }
  13. }

添加了“—watch”参数后,再执行npm run serve,程序会阻塞在build命令,不会往下执行。 这就需要借助npm-run-all同时执行多个任务。借助run-p命令,代码如下。这里运行npm run start就会同时执行build和serve命令。 并且修改sass文件,css文件同步自动修改。

  1. {
  2. "name": "auto build",
  3. "version": "0.1.0",
  4. "main": "index.js",
  5. "license": "MIT",
  6. "scripts": {
  7. "build": "sass scss/main.scss css/style.css --watch",
  8. "serve": "browser-sync .",
  9. "start":"run-p build serve"
  10. },
  11. "dependencies": {
  12. }
  13. }

在serve命令行添加—files \”css/*.css\”,这样css文件发生改变,浏览器会便自动更新。

  1. {
  2. "name": "auto build",
  3. "version": "0.1.0",
  4. "main": "index.js",
  5. "license": "MIT",
  6. "scripts": {
  7. "build": "sass scss/main.scss css/style.css --watch",
  8. "serve": "browser-sync . --files \"css/*.css\"",
  9. "start":"run-p build serve"
  10. },
  11. "dependencies": {
  12. }
  13. }

以上,我们通过npm scripts完成了简单的自动化构建工作流,他的工作流程就是在启动过后,同时启动了build和serve两个命令,其中build去监听sass文件的变化,并且编译成css,而browser-sync是启动一个web服务,当文件发生变化,自动刷新浏览器

npm scripts只能解决简单的自动化任务,对于复杂的构建任务,我们还是需要借助常用的构建工具。webpack是模块化打包工具,不在这个讨论范围之内。

3、常用的构建工具

image.png

Grunt

它是最早的构建工具,生态很完善,但是构建过程是基于临时文件的,所以构建速度慢。比如在完成sass的构建时,会先对sass文件做编译操作(从磁盘读取文件,完成后将结果写入一个临时文件),再自动添加一些自由属性的前缀(下一个插件在从磁盘读取文件。。。),最后压缩代码,在这每一步操作中,Grunt都会有磁盘读写操作。

Gulp

Gulp很好的解决了Grunt构建速度慢的缺点,它是基于内存实现的,对于文件的处理都是在内存中完成的。另外默认支持同时执行多个任务,这样大大提高了效率,生态也很完善,所以后来居上,目前是最受欢迎的前端构建工具。

FIS

百度推出的,相比较前两个,FIS将项目中一些典型的需求集成在内部,例如在FIS中可以很轻松的处理资源加载、模块化开发、代码部署,甚至是性能优化。

4、Gulp的基本使用

4.1、安装gulp并创建gulpfile.js文件

  1. yarn add gulp

4.2、创建task,通过exports导出成员的方式

  1. // gulp.js(gulp入口文件)
  2. exports.foo = ()=>{
  3. console.log('foo')
  4. }

执行gulp foo
image.png
任务会执行,但会出现报出一个错误—foo任务没有完成,是否忘记标记任务结束?
因为在最新的gulp中,取消了同步代码模式,约定每一个任务都是异步任务,当任务结束后,需要手动执行一个done函数,标识任务结束:

  1. exports.foo = done =>{
  2. console.log("foo task")
  3. done() //标识任务结束
  4. }

当导出的是default

  1. exports.default = done =>{
  2. console.log("default task")
  3. done() //标识任务结束
  4. }

直接执行gulp就会运行default任务

4.3、创建任务还可以通过gulp的task方法(不推荐)

  1. const gulp = require('gulp')
  2. gulp.task('build',done=>{
  3. console.log('build')
  4. done()
  5. })

4.4、组合任务

  1. const { series, parallel } = require('gulp')
  2. const task1 = done => {
  3. setTimeout(() => {
  4. console.log('task1 working~')
  5. done()
  6. }, 1000)
  7. }
  8. const task2 = done => {
  9. setTimeout(() => {
  10. console.log('task2 working~')
  11. done()
  12. }, 1000)
  13. }
  14. const task3 = done => {
  15. setTimeout(() => {
  16. console.log('task3 working~')
  17. done()
  18. }, 1000)
  19. }
  20. // 让多个任务按照顺序依次执行
  21. exports.foo = series(task1, task2, task3)
  22. // 让多个任务同时执行
  23. exports.bar = parallel(task1, task2, task3)

4.5、异步任务

  • 回调函数 ```javascript // callback

exports.callback = done =>{ console.log(‘cb task’) done() }

  1. 如果执行过程中出现错误,采取错误优先的原则,在回调函数中传入new Error(),这样后续任务就不会继续执行。
  2. ```javascript
  3. exports.callback_error = done =>{
  4. console.log('cb task')
  5. done(new Error('task failed'))
  6. }
  • Promise ```javascript exports.promise = ()=>{ console.log(‘promise task’) return Promise.resolve() }
  1. - **async **
  2. ```javascript
  3. const timeout = time=>{
  4. return new Promise((resolve,reject) => {
  5. setTimeout(resolve,time)
  6. })
  7. }
  8. exports.async = async ()=>{
  9. await timeout(1000)
  10. console.log('async tasks')
  11. }
  • stream
    1. exports.stream = done => {
    2. const readStream = fs.createReadStream('package.json')
    3. const writeStream = fs.createWriteStream('temp.text')
    4. readStream.pipe(writeStream)
    5. return readStream
    6. }

    4.6、gulp构建过程核心工作原理

    1. const {Transform} = require('stream')
    2. exports.default = ()=>{
    3. const readStream = fs.createReadStream('style.css')
    4. const writeStream = fs.createWriteStream('style.min.css')
    5. const transform = new Transform({
    6. transform:(chunk, encoding,callback) => {
    7. const input = chunk.toString()
    8. const output = input.replace(/\s+/g,'').replace(/\/\*.+?\*\//g,'')
    9. callback(null,output)
    10. }
    11. })
    12. readStream
    13. .pipe(transform)
    14. .pipe(writeStream)
    15. return readStream
    16. }
    在构建过程中有三个重要概念:
    image.png

    4.7、 gulp文件操作API之src和dest

    ```javascript const {src, dest} =require(‘gulp’) 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’)) }

  1. - **样式编译**
  2. ```javascript
  3. const {src,dest}= require('gulp')
  4. const sass =require('gulp-sass')(require('sass'))
  5. const style = ()=>{
  6. return src('src/assets/styles/*.scss',{base:'src'})
  7. .pipe(sass().on('error', sass.logError))
  8. .pipe(dest('dist'))
  9. }
  10. module.exports = {
  11. style
  12. }
  • 脚本编译 ```javascript const {src,dest}= require(‘gulp’) const babel = require(‘gulp-babel’)

const script = ()=>{ return src(‘src/assets/scripts/*.js’,{base:’src’}) .pipe(babel({presets: [‘@babel/preset-env’]})) .pipe(dest(‘dist’)) } module.exports = { script }

  1. 注意bable()需要传入presets的参数,bable是一个转换平台,具体的转码规则需要借助插件完成,@babel/presets-env是多个preset的集合 (es2015+),包含了我们在babel6中常用的es2015,es2016, es2017等最新的语法转化插件,可以根据配置的目标浏览器或者运行环境来自动将ES2015+的代码转换为es5
  2. - **页面模板编译**
  3. ```javascript
  4. const {src,dest}= require('gulp')
  5. const swig = require('gulp-swig')
  6. const data = {
  7. menus: [
  8. {
  9. name: 'Home',
  10. icon: 'aperture',
  11. link: 'index.html'
  12. },
  13. {
  14. name: 'Features',
  15. link: 'features.html'
  16. },
  17. {
  18. name: 'About',
  19. link: 'about.html'
  20. },
  21. {
  22. name: 'Contact',
  23. link: '#',
  24. children: [
  25. {
  26. name: 'Twitter',
  27. link: 'https://twitter.com/w_zce'
  28. },
  29. {
  30. name: 'About',
  31. link: 'https://weibo.com/zceme'
  32. },
  33. {
  34. name: 'divider'
  35. },
  36. {
  37. name: 'About',
  38. link: 'https://github.com/zce'
  39. }
  40. ]
  41. }
  42. ],
  43. pkg: require('./package.json'),
  44. date: new Date()
  45. }
  46. const page = ()=>{
  47. return src('src/**/*.html',{base:'src'})
  48. .pipe(swig({data}))
  49. .pipe(dest('dist'))
  50. }
  51. module.exports = {
  52. page
  53. }
  • 图片和字体压缩 ```javascript const imagemin = require(‘gulp-imagemin’)

const image = ()=>{ return src(‘src/assets/images/.{png,jpg,gif,svg,ico}’,{base:’src’}) .pipe(imagemin()) .pipe(dest(‘dist’)) } const font = ()=>{ return src(‘src/assets/fonts/*‘,{base:’src’}) .pipe(imagemin()) .pipe(dest(‘dist’)) } module.exports = { font, image }

  1. 图片压缩是无损压缩,只是删除了一些元数据的信息,svg类型的只是删除了空格。
  2. 以上任务可以通过parallel将任务组合在一起。
  3. ```javascript
  4. const {parallel}= require('gulp')
  5. const compile = parallel(style,script,page)
  6. module.exports = {
  7. compile
  8. }
  • 其他文件拷贝及文件清除
    1. const extra = ()=>{
    2. return src('public/**',{base:'public'})
    3. .pipe(dest('dist'))
    4. }
    5. module.exports = {
    6. extra
    7. }
    我们一般将src目录下的文件的编译组合成一个compile任务,将compile任务和其他文件的编译任务组成一个build任务,在build任务执行前先清除dist目录 ```javascript const del = require(‘del’)

const clean = ()=>{ return del([‘dist’]) }

const build = series(clean,parallel(compile,extra))

  1. - 自动加载插件
  2. ```javascript
  3. const loadPlugins = require('gulp-load-plugins')
  4. const plugins = loadPlugins()
  5. // plugins是一个对象,所有插件都是这个对象的属性。
  6. // 如
  7. plugins.babel
  • 开发服务器

为了提高开发体验,browser-sync提供了热更新服务。

  1. const browserSync = require('browser-sync').create();
  2. // 静态服务器
  3. const serve = ()=>{
  4. return browserSync.init({
  5. notify: false,
  6. port: 3000,
  7. open: true, // 是否自动打开浏览器
  8. server: {
  9. baseDir: "dist",
  10. }
  11. })
  12. }

使用gulp serve就可以启动服务。
对于页面中引入的css不是dist目录下的css,而是node_modules下的css,可以在配置项里面做映射:server 里面的routes参数

  1. const browserSync = require('browser-sync').create();
  2. // 静态服务器
  3. const serve = ()=>{
  4. return browserSync.init({
  5. server: {
  6. baseDir: "dist",
  7. routes: {
  8. '/node_modules': 'node_modules'
  9. }
  10. }
  11. })
  12. }

监听文件变化,自动刷新浏览器:files参数

  1. const browserSync = require('browser-sync').create();
  2. // 静态服务器
  3. const serve = ()=>{
  4. return browserSync.init({
  5. files'dist/**', // 单个文件
  6. server: {
  7. baseDir: "dist",
  8. routes: {
  9. '/node_modules': 'node_modules'
  10. }
  11. }
  12. })
  13. }

gulp的watch方法监听文件变化

  1. const browserSync = require('browser-sync').create();
  2. // 静态服务器
  3. const serve = ()=>{
  4. watch('src/assets/styles/*.scss',style)
  5. watch('src/assets/scripts/*.js',script)
  6. watch('src/**/*.html',page)
  7. watch('src/assets/images/*.{png,jpg,gif,svg,ico}',image)
  8. watch('src/assets/fonts/**',font)
  9. watch('public/**',extra)
  10. return browserSync.init({
  11. open:true,
  12. files: 'dist/**',
  13. server: {
  14. baseDir: "dist",
  15. routes: {
  16. '/node_modules': 'node_modules'
  17. }
  18. }
  19. })
  20. }

构建优化
在开发阶段,减少对图片和字体以及public目录下文件的压缩,可以提高效率。

  1. const browserSync = require('browser-sync').create();
  2. // 静态服务器
  3. const serve = ()=>{
  4. watch('src/assets/styles/*.scss',style)
  5. watch('src/assets/scripts/*.js',script)
  6. watch('src/**/*.html',page)
  7. // watch('src/assets/images/*.{png,jpg,gif,svg,ico}',image)
  8. // watch('src/assets/fonts/**',font)
  9. // watch('public/**',extra)
  10. return browserSync.init({
  11. open:true,
  12. files: 'dist/**',
  13. server: {
  14. baseDir: ["dist","src","public"],
  15. routes: {
  16. '/node_modules': 'node_modules'
  17. }
  18. }
  19. })
  20. }
  21. const compile = parallel(style,script,page)
  22. // 创建一个开发阶段的组合任务
  23. const develop = series(clean,compile,serve)
  24. // 上线前的组合任务
  25. const build = series(clean,parallel(compile,image,font,extra))
  26. module.exports = {
  27. develop,
  28. build
  29. }

文章内容输出来源:拉勾教育大前端高薪训练营;