2-工程化-自动化构建(02-01-02)

133 阅读12分钟

工程化

自动化构建

scss 处理

npm install sass

自动化构建

  • 一些代码需要编译(为css,js),保证浏览器的兼容性
  • 将less或sass转化为css
  • es6+的新语法转成es5
  • 有些代码需要压缩(css,js,html,图片等)
  • 代码在上线前解决这些问题的过程就叫构建

NPM script

{
  "name": "my-web-app",
  "version": "0.1.0",
  "main": "index.js",
  "author": "zce <w@zce.me> (https://zce.me)",
  "license": "MIT",
  "scripts": {
    "build": "sass scss/main.scss css/style.css --watch",
    "serve": "browser-sync . --files \"css/*.css\"",
    "start": "run-p build serve"
  },
  "devDependencies": {
    "browser-sync": "^2.26.7",
    "npm-run-all": "^4.1.5",
    "sass": "^1.22.10"
  }
}
  • 实现自动化最简单的方式
  • npm允许在package.json中,使用scripts字段自定义脚本命令
  • 自定义脚本命令需要两步
    1. 声明命令 2.执行命令
  • npm script任务执行的方式
    1. 并行:任务之间没有先后顺序,同时执行可以提高效率,书写:任务1 & 任务2
    2. 串行:人物之间有先后顺序,先执行一个再执行下一个,书写:任务1 && 任务2
    3. “&”在windows下不起作用
  • 需要安装 npm-run-all -D
    1. 执行命令npm-run-all -s 脚本1 脚本2 ,串行执行,简写,run-s
    2. 执行命令npm-run-all -p 脚本1 脚本2 ,并行执行,简写run-p
    3. 控制台就输入 npm run 名称 就可以执行自定义的脚本操作
  1. scripts里面不需要指定完整的路径,可以自动发现node-modules里面的命令,只需要写命令就可以了。
  2. --watch会自动编译
  3. --files会自动监听文件的改变然后更新到页面上、
  4. run-p会同时启动
"start": "run-p build serve" // 并行执行
npm run start

常用的自动化构架工具

zhuanlan.zhihu.com/p/403172420

Gulp、Grunt、FIS

Grunt:

最早的前端构建系统、Grunt的插件可以完成任何想要完成的事情,但是由于它是基于临时文件实现的,所以构建速度相对较慢,每一步都会有磁盘读写操作,将每一个环节的结果都会写入一个临时的磁盘进行存储,然后读取文件进行下一步,进而造成它相对较慢。环节越多文件读写次数越多,构建速度会特别的慢。

如sass:编译 ->添加属性前缀->压缩代码,每一步都会有读写操作

Gulp:

解决了构架速度非常慢的问题,是基于内存实现的。文件的处理环节都是在内存中完成的。默认支持同时执行多个任务,使用方式更加直观易懂,插件生态非常完善,目前更受欢迎,应该是市面上最流行的前端构架系统。

FIS:

百度前端团队推出来的。更像是捆绑套餐,把典型的需求集成到了内部,如资源加载、模块化开发、代码部署、性能优化等,大而全。

总结:初学者:FIS;灵活多变:Gulp、Grunt;新手需要规范、老手需要自由哈哈~

webpack算是模块打包工具,不算真正意义上的自动化构建工具。

Grunt:

1. 基本使用

npm init -y 
yarn init --yes  //生成package.json

// mac 安装grunt
/usr/bin/sudo npm install -g grunt-cli
grunt --version

yarn add grunt      //添加grunt模块

新建 gruntfile.js   
//根目录下添加gruntfile.js文件,作为grunt的入口文件,
//用于定义一些自动执行任务
//需要导出一个函数,此函数接收一个grunt的形参,
//grunt 对象中提供一些创建任务时会用到的 API

// mac 运行
grunt foo

gruntfile.js文件:

module.exports = grunt => {

  //grunt.registerTask用于添加任务,运行命令就是yarn grunt foo
  grunt.registerTask('foo', () => {
    console.log('foo')
  })

  grunt.registerTask('bar', () => {
    console.log('bar')
  })

  // grunt.registerTask('default', () => {
  //   console.log('grunt默认任务')//运行的时候不需要指定任务名称 直接yarn grunt
  // })

  //用default去映射其他的任务.即定义默认执行任务
  grunt.registerTask('default',['foo','bar'])

  grunt.registerTask('async-task',() =>{
    setTimeout(()=>{
      console.log('async task working~')
    },1000)
  }) //异步任务的该方法不支持

  // 异步必须使用this的async方法得到一个回调函数,
  // 在异步操作完成过后去调用这个回调函数,标识任务完成
  grunt.registerTask('async-task',function(){
    const done = this.async()
    setTimeout(()=>{
      console.log('async task working~')
      done()
    },1000)
  })
}

2. 标记任务失败

若构建任务中发生逻辑代码错误,此时就可以将任务标记为失败任务。

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'])

  // 异步函数中无法用return false标记任务失败
  // 异步函数中标记当前任务执行失败的方式是为回调函数指定一个 false 的实参
  grunt.registerTask('bad-async', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('async task working~')
      done(false)
    }, 1000)
  })
}

3. 配置选项方法

grunt提供了用于添加配置选项的API:grunt.initConfig

用配置压缩文件的路径作为实例:

module.exports = grunt => {
  // grunt.initConfig() 用于为任务添加一些配置选项
  grunt.initConfig({
    // 键一般对应任务的名称
    // 值可以是任意类型的数据
    foo: {
      bar: 'baz'
    }
  })

  grunt.registerTask('foo', () => {
    // 任务中可以使用 grunt.config() 获取配置
    console.log(grunt.config('foo'))    //{ bar: 'baz'}
    // 如果属性值是对象的话,config 中可以使用点的方式定位对象中属性的值
    console.log(grunt.config('foo.bar'))  //bar
  })
}

4. 多目标任务

支持多目标模式的任务,理解为子任务的概念。使用API:grunt.registerMultiTask

module.exports = grunt => {
  // 多目标模式,可以让任务根据配置形成多个子任务

  // grunt.initConfig({
  //   build: {
  //     foo: 100,
  //     bar: '456'
  //   }
  // })

  // grunt.registerMultiTask('build', function () {
  //   console.log(`task: build, target: ${this.target}, data: ${this.data}`)
  // })

  //除了options之外,每一个build里面的东西都会成为一个子任务,build是总体的任务名
  grunt.initConfig({
    build: {
      options: {
        msg: 'task options'
      },
      foo: {
        options: {
          msg: 'foo target options'
        }
      },
      bar: '456'
    }
  })

  grunt.registerMultiTask('build', function () {
    console.log(this.options())
  })
}

5、插件使用

插件机制是grunt的核心,很多构建任务都是通用的,类似于压缩代码,所以社区就会有很多通用插件。

流程:

  1. npm安装
  2. gruntfile.js去载入:grunt.loadNpmTasks
  3. 根据插件文档去完成相关配置选项
grunt-contrib-clean:清除项目开发中的临时文件

module.exports = grunt => {
  grunt.initConfig({
    clean: {
      temp: 'temp/**' //删除文件的文件路径
    }
  })
  
  grunt.loadNpmTasks('grunt-contrib-clean') //多目标任务
}

6、常用插件及总结

grunt-sass(npm的模块非官方)

yarn add grunt-sass sass --dev

const sass = require('sass')

module.exports = grunt => {
  grunt.initConfig({
    sass: {
      options: {
        sourceMap: true,
        implementation: sass
      },
      main: {
        files: {
          'dist/css/main.css': 'src/scss/main.scss'
        }
      }
    }
  })

  grunt.loadNpmTasks('grunt-sass') //多目标任务
}

Gulp:

zhuanlan.zhihu.com/p/403489287

1. 基本使用

核心特点:高效、易用、

使用流程:添加开发依赖、根目录增加一个gulpfile.js的文件,编写自动执行的构建任务,运行

npm init -y
yarn init --yes

/usr/bin/sudo npm install gulp -g
yarn add gulp --dev  //会自动安装gulp-cli,让我们后续可以使用gulp命令

gulp -v

创建 gulpfile.js //创建gulp入口文件

gulp foo
gulp

gulpfile.js文件:

// 导出的函数都会作为 gulp 任务
// exports.foo = () => {
//   console.log('foo task working~')
// }

// 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()
})
注意:glup最新取消了同步任务模式,全都是异步任务

2、组合任务

并行任务、串行任务。

例子:编译css和编译js的任务可以用并行parallel、部署的编译需要有顺序可以用串行series

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)

3、异步任务

调用异步任务,一般都是没有办法明确调用完成的,都是在函数内部通过回调或者事件的方式通知外部完成。

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'))
}
-------
//promise回调
exports.promise = () => {
  console.log('promise task')
  return Promise.resolve()
}

//promise失败回调
exports.promise_error = () => {
  console.log('promise task')
  return Promise.reject(new Error('task failed'))
}
-------
const timeout = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}

//只受限于node环境,可运行async即可
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
}

// exports.stream = done => {
//   const read = fs.createReadStream('yarn.lock')
//   const write = fs.createWriteStream('a.txt')
//   read.pipe(write)
//   read.on('end', () => {
//     done()
//   })
// }

4、构建过程核心工作原理

gulp的官方描述:The streaming build system

大多数构建过程就是将文件读出来,做一个转化最后形成到另外一个位置,其实就是把手工的过程自动化。

读取文件->压缩文件->写入文件

核心概念:读取流、转换流、写入流

手动实现一个流程示例:

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({
    // 核心转换过程
    //chunk:文件流读取读取到的内容(Buffer)
    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) // 写入
}

5、文件操作API

gulp专门提供了底层读取流和写入流的API,相对于Node.js的API,gulp的API更强大也更容易使用。相对于转换流都是使用独立的插件来提供。

gulp构建任务的过程是先通过gulp提供的src方法创建读取流,再借助插件提供的转化流加工文件,最后通过gulp提供的dist方法创建写入流,写入目标文件。

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'))
}

gulp案例 样式编译

  1. 安装gulp
// 会自动安装gulp-cli,让我们后续可以使用gulp命令
/usr/bin/sudo npm install gulp -g
// 查看版本
gulp -v
  1. 创建gulpfile.js,gulp入口文件,增加构建任务
  2. 样式处理
const { src, dest } = require('gulp')
const style = () => {
  return src('src/assets/styles/*.scss',
     { base: 'src' }) // 保证文件的层级
    .pipe(dest('dist'))
}

module.exports = {
  style
}

添加gulp-sass依赖

npm install gulp-sass --dev
const { src, dest } = require('gulp')
const sass = require('gulp-sass')
const style = () => {
  return src('src/assets/styles/*.scss',
     { base: 'src' }) 
     // 保证文件的层级
    .pipe(sass({ outputStyle: 'expanded' }))     
    // 不会处理_开始的scss
    // 后面的属性代表扩展开{} 各占一行
    .pipe(dest('dist'))
}

module.exports = {
  style
}
  1. js处理
npm install @babel/core@babel/preset-env --dev

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. 页面模板编译
npm install gulp-swig --dev

// src/**/*.html
// 可以从外部引入
const data = {
   pkg: 'aaa'
}
const swig = require('gulp-swig')
const page = () => {
  return src('src/*.html', { base: 'src' })
    .pipe(swig({ data, defaults: { cache: false } }))
    // 防止模板缓存导致页面不能及时更新
    .pipe(dest('dist'))
}
module.exports = {
  page
}
  1. 组合任务
const { compile } = require('gulp')
const compile = parallel(style, script, page)
module.exports = {
  compile
}
  1. 图片和字体文件转换
npm install gulp-imagemin --dev

const { imagemin } = require('gulp-imagemin')
const image = () => {
  return src('src/assets/images/**', { base: 'src' })
    .pipe(imagemin())
    .pipe(dest('dist'))
}
const font = () => {
  return src('src/assets/fonts/**', { base: 'src' })
    .pipe(imagemin())
    .pipe(dest('dist'))
}
module.exports = {
  image,
  font
}
  1. 其他文件和清除
const extra = () => {
  return src('public/**', { base: 'public' })
    .pipe(dest('dist'))
}
// 上线之前执行的任务
const build =  parallel(compile, image, font, extra)

module.exports = {
  build
}
npm install del --dev

const { src, dest, parallel, series } = require('gulp')
const del = require('del')

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

const compile = parallel(style, script, page)
// 上线之前执行的任务
const build =  series(
                    clean, 
                    parallel(
                        compile, image, font, extra
                        )
                    )

module.exports = {
  build
}
  1. 自动加载插件
npm install gulp-load-plugins --dev

const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()

const style = () => {
  return src('src/assets/styles/*.scss', { base: 'src' })
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('dist'))
    .pipe(bs.reload({ stream: true }))
}

const script = () => {
  return src('src/assets/scripts/*.js', { base: 'src' })
    .pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
    .pipe(dest('dist'))
    .pipe(bs.reload({ stream: true }))
}

const page = () => {
  return src('src/*.html', { base: 'src' })
    .pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
    .pipe(dest('dist'))
    .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'))
}
  1. 开发服务器

支持热更新

npm install browser-sync

// 创建一个服务器
const browserSync = require('browser-sync')
const bs = browserSync.create()

const serve = () => {
  bs.init({
    // 关掉重启后的提示
    notify: false,
    // 启动端口号 默认3000
    port: 2080,
    // dist下面文件被监听,发生变化后页面跟着变化
    files: 'dist/**',
    server: {
        baseDir: 'dist',
        // 临时处理第三方引用文件 优先级高
        routes: {
          '/node_modules': 'node_modules'
        }
    }
  })
}
module.exports = {
  serve
}
  1. 监视变化以及构建优化
const { src, dest, parallel, series, watch } = require('gulp')

const serve = () => {
  watch('src/assets/styles/*.scss', style)
  watch('src/assets/scripts/*.js', script)
  watch('src/*.html', page)
  // 开发阶段没必要监听images\fonts
  // watch('src/assets/images/**', image)
  // watch('src/assets/fonts/**', font)
  // watch('public/**', extra)
  watch([
    'src/assets/images/**',
    'src/assets/fonts/**',
    'public/**'
  ], bs.reload)

  bs.init({
    notify: false,
    port: 2080,
    // open: false,
    // files: 'dist/**',
    server: {
      // html\js\css是dist,图片是src和public
      baseDir: ['dist', 'src', 'public'],
      routes: {
        '/node_modules': 'node_modules'
      }
    }
  })
}
const compile = parallel(style, script, page)
const develop = series(compile, serve)
// 上线之前执行的任务
const build =  series(
  clean,
  parallel(
    series(compile, useref),
    image,
    font,
    extra
  )
)
module.exports = {
  build,
  compile,
  develop
}

  1. useref文件引用处理、压缩
npm install gulp-useref --dev

const useref = () => {
  return src('dist/*.html', { base: 'dist' })
    .pipe(plugins.useref({ searchPath: ['dist', '.'] }))
    .pipe(dest('dist'))
}
module.exports = {
  useref
}

npm install gulp-htmlmin gulp-uglify gulp-clean-css --dev

const useref = () => {
  return src('dist/*.html', { base: 'dist' })
    .pipe(plugins.useref({ searchPath: ['dist', '.'] }))
    // html js css
    .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'))
}
// 清除 HTML 注释
removeComments: true,
// 省略空白
collapseWhitespace: true, 
// 省略所有标签内的布尔属性
collapseBooleanAttributes: true// 清除所有标签内值为空的属性
removeEmptyAttributes: true,
// 清除 <script> 标签内的 type 属性
removeScriptTypeAttributes: true, 
// 清除 <style> 和 <link> 标签内的 type 属性
removeStyleLinkTypeAttributes: true, 
// 压缩 html 页面内的 js 代码
minifyJS: true,
// 压缩 html 页面内的 css 代码
minifyCSS: true   

module.exports = {
  useref
}

module.exports = {
  clean,
  build,
  develop
}

// package.json
"scripts": {
    "clean": "gulp clean",
    "build": "gulp build",
    "develop": "gulp develop"
  },

多个项目的自动化构建工作流

www.jianshu.com/p/aaa7db89a…

yarn link

cd npm-link-module
npm link

cd npm-link-example
npm link npm-link-module

gulpfile.js中写入
require('npm-link-module')
// process.cwd()返回的是当前Node.js进程执行时的工作目录
const cwd = process.cwd()
"bin": "bin/zce-pages.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')


"files": [
    "lib",
    "bin"
  ],
  
 npm publish