自动化构建工具
自动化构建
自动化构建简介
一切重复工作本应自动化
- 自动化构建(自动化构建工作流):把开发阶段代码自动转换为生产环境运行的代码/程序;
- 作用:脱离运行环境兼容带来的问题,使用提高效率的语法、规范和标准
- 自动化构建工具,构建转换那些不被支持的【特性】(sass、ECMAScript next、模板引擎)
自动化构建初体验
NPM Scripts 可以在 NPM Scripts 中定义一些与项目开发过程有关的脚本命令便于后期开发使用
- 使用过程
- 在命令行执行
yarn init -y
生成 package.json 文件 - 在 package.json 文件添加 scripts 字段,该字段是一个对象,键是 scripts 名称,值是需要执行的命令;scripts 可以自动发现 node_modules 里面的命令,所以不用写完整的路径直接使用命令的名称即可
- 使用 npm/yarn 启动 scripts
--watch: scss
在工作是会监听文件的变化,scss 文件发生改变时会自动编译
- 在命令行执行
1 | "scripts": { |
- NPM Scripts 实现自动化构建工作流的最简单方式
使用 browser-sync 模块可以开启测试服务器运行项目,在 scripts 中添加命令"serve": "browser-sync ."
运行项目
--files "css/*.css"
参数可以让 browser-sync 在启动过后监听项目下文件的变化,browser-sync 会将文件内容自动同步到浏览器,更新浏览器界面
1 | "scripts": { |
在启动 serve 前让 build 工作可以借助 NPM Scripts 钩子机制定义一个 preserve,它可以在 serve 执行前去执行
1 | "scripts": { |
可以使用npm-run-all
模块同时执行多个任务
1 | "scripts": { |
常用的自动化构建工具
- Grunt 最早的构建系统;插件生态完善;工作过程基于临时文件实现所以构建速度较慢,如使用它完成 scss 文件构建,我们会先对 scss 文件进行编译工作,自动添加属性前缀,压缩代码,Grunt 每一步都有磁盘读写操作,处理环节越多,磁盘的读写越多;大型项目中速度会非常慢
- Gulp 可以解决 Grunt 中构建速度慢的问题,他基于内存实现的,对文件的读写都是基于内存完成的,相对与磁盘读写就快了;支持同时执行多个任务效率高;使用方式相对于 Grunt 更直观易懂;插件生态完善
- FIS 微内核;更像是一种捆绑套餐,把项目中典型的需求尽可能集成在内部
Grunt
Grunt 的基本使用
- 项目命令行输入
yarn init -y
生成 package.json 文件 - 安装 grunt
yarn add grunt
- 项目根目录创建 gruntfile.js 文件,该文件是 Grunt 入口文件,用于定义一些需要 Grunt 自动执行的任务,需要导出一个函数,此函数接收一个 grunt 的形参,内部提供一些创建任务时可以用到的 API
- 最后使用
yanr grunt taskName
执行任务
使用 grunt.registerTask() 方法注册一个任务
- 第一个参数为任务名称,如果任务名称为 default,该任务将作为默认任务,运行时不用指定任务名,Grunt 将自动调用 default
- 第二个参数
- 第二个参数是字符串时,该字符串将是任务的描述,命令行输入
yarn grunt --help
可以看到该信息; - 一般会使用 default 任务映射一些其他的任务,其他的任务将作为第二个参数,参数是数组,元素为任务名,当执行 default 任务时 Grunt 会依次执行数组中的任务;
- 当第二个参数为函数时,将制定任务函数,任务发生时自动执行函数
- 第二个参数是字符串时,该字符串将是任务的描述,命令行输入
- 异步任务:需要使用 this.async() 方法得到一个回调函数,在异步回调函数完成后调用这个函数标识任务已经完成
1 | // Grunt 的入口文件 |
Grunt 标记任务失败
在 grunt.registerTask() 任务函数 return false,在命令行执行该任务时将会提示执行失败,在任务列表中会导致后面的任务无法执行,在执行任务时添加 –force 命令,将会强制执行所有的任务 yarn grunt --force
,在异步任务中无法通过 return false 来标记失败,要给异步的回调函数指定 false 实参就可以标记失败了
1 | module.exports = (grunt) => { |
Grunt 的配置方法
- 通过 grunt.initConfig() 方法为任务添加一些配置项
- grunt.initConfig() 参数为一个对象,键一般对应任务的名,值可以是任意类型的数据
- 在 grunt.registerTask() 中可以通过 grunt.config() 获取配置,如果配置中的属性值是对象的话,config 可以使用点的方式定位对象中的属性值
1 | module.exports = (grunt) => { |
Grunt 多目标任务(子任务)
- grunt.registerMultiTask()
- 通过 grunt.registerMultiTask() 定义多目标
- 多目标模式,可以让任务根据配置形成多个子任务
- grunt.registerMultiTask() 第一个参数是任务的名字,第二个参数是任务执行过程要做的事情
- 多任务,要使用 grunt.initConfig()为任务配置目标
- 当运行任务时会去执行目标(子任务),如果要运行指定的目标可以使用“:目标名”,yarn grunt build:css
- 可以通过 this.target 拿到当前执行的目标名称,通过 this.data 拿到目标的配置数据
- 可以通过 this.options()方法拿到配置选项
- grunt.initConfig()
- 参数为对象,键名与任务名相同(registerMultiTask()方法的第一个参数),值必须为对象,对象中每个属性名就是目标名称
- 除了 options 键,其他都会作为目标,options 指定的信息会作为这个任务的配置选项
- 目标当中也可以添加 options,运行目标是可以获取相应的 options(目标 options 覆盖任务 options),如果目标没指定而任务指定了则可以获取任务的 options
1 | module.exports = (grunt) => { |
Grunt 插件的使用
- 通过 npm 安装
- 在 gruntfile.js 文件中通过 grunt.loadNpmTasks() 方法把插件中提供的任务加载进来,根据插件文档配置
- 在 grunt.initConfig() 方法中为任务添加配置选项
1 | // 删除文件的任务 |
Grunt 常用插件及总结
- grunt-sass
- 安装
yarn grunt-sass sass --dev
- 安装
1 | const sass = require("sass"); |
- load-grunt-tasks
自动加载所有的 grunt 插件- 安装
yarn add load-grunt-tasks --dev
- 安装
1 | const loadGruntTasks = require("load-grunt-tasks"); |
- babel
- 安装
yarn add grunt-babel @babel/core @babel/preset-env --dev
- 安装
1 | const sass = require("sass"); |
- grunt-contrib-watch 自动编译
- 安装
yarn add grunt-contrib-watch --dev
- 执行
yarn grunt watch
,当监听的文件发生变化的时候会执行相应的任务,但是刚开始的时候不会执行,可以使用 grunt.registerTask() 方法,执行yarn grunt
- 安装
1 | const sass = require("sass"); |
Gulp
Gulp 的基本使用
- 执行
yarn init -y
- 安装
yarn add gulp --dev
- 在根目录新建 gulpfile.js,此文件作为 gulp 入口文件
- 导出函数,导出的函数都会作为 gulp 任务
- gulp 的任务函数都是异步的,可以通过调用回调函数标识任务完成
- default 是默认任务,在运行是可以省略任务名参数
1 | // gulp入口文件 |
Gulp 的组合任务
series()
,parallel()
可以创建并行任务和串行任务
1 | const { series, parallel } = require("gulp"); |
Gulp 的异步任务
- 回调函数
- gulp 的任务为异步,异步函数无法知道是否执行完成,可以通过回调解决(done),任务执行完后调用回调函数通知 gulp 任务执行完成
- 回调函数叫做一种错误优先的回调函数,在执行过程中报错,阻止剩下任务执行可以通过给回调函数的第一个参数指定一个错误对象
1 | exports.callback_error = (done) => { |
- promise
- 使用 promise 通知任务执行完成
- 任务中返回一个 Promise 对象,如果 resolve 意味着任务结束了,不需要返回任何的值,如果 reject,gulp 会认为是一个失败任务结束后续任务的执行
1 | exports.promise = () => { |
- async await
1 | const timeout = (time) => { |
- stream
1 | // exports.stream = () => { |
Gulp 构建过程核心工作原理
- 工作过程:输入(读取流) => 加工(转换流) => 输出(写入流)
1 | const fs = require("fs"); |
Gulp 文件操作 API
- src:读取流,方法参数为文件路径,可以使用通配符匹配所以的文件
- dest:写入流
1 | const { src, dest } = require("gulp"); |
Gulp 案例 - 样式编译
1 | const { src, dest } = require("gulp"); |
Gulp 案例 - 脚本编译
安装 babel 依赖 yarn add babel @babel/core @babel/preset-env --dev
1 | const { src, dest } = require("gulp"); |
Gulp 案例 - 页面模板编译
安装 swig 模板引擎转换插件 yarn add gulp-swig --dev
1 | const swig = require("gulp-swig"); |
Gulp 案例 - 图片和字体文件转换
安装图片压缩转换插件 yarn add gulp-imagemin --dev
1 | const imagemin = require("gulp-imagemin"); |
Gulp 案例 - 其他文件及文件清除
打包是自动删除 dist 文件目录,安装插件 yarn add del --dev
,这个模块不是 gulp 的插件,只是可以在 gulp 中使用
1 | const del = require("del"); |
Gulp 案例 - 自动加载插件
安装插件 yarn add gulp-load-plugins --dev
1 | const loadPlugins = require("gulp-load-plugins"); |
Gulp 案例 - 开发服务器
热更新开发服务器
安装模块 yarn add browser-sync --dev
1 | const browserSync = require("browser-sync"); |
Gulp 案例 - 监视变化以及构建优化
使用 gulp 提供的 watch()方法,第一个参数是监听的文件,第二个参数是执行任务
1 | const { watch } = require("gulp"); |
Gulp 案例 - useref 文件引用处理
安装 yarn add gulp-useref --dev
把 HTML 文件中的构建注释去除、把构建注释中的内容合并到一个文件(比如上边提到的引用了 node_modules 目录中的文件)
1 | const useref = () => { |
Gulp 案例 - 文件压缩
对 html、js、css 压缩
安装插件 yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev
判断读取流的文件使用对应的压缩插件,安装 yarn add gulp-if --dev
1 | const useref = () => { |
Gulp 案例 - 重新规划构建过程
因为 useref 任务导致项目目录结构发生改变,所以构建时先把 html、css、js 文件放到一个临时目录中,开启的服务读取文件也是读取临时目录中的文件;在 useref 任务中把临时目录中的 html、css、js 取出压缩放进最终目录(dist)
1 | / 转换样式 |
完整构建
1 | const { src, dest, parallel, series, watch } = require("gulp"); |
Gulp 案例 - 补充
- 不需要全部任务都导出,只需要导出部分用到的就行
- 把任务写入到 package.json 文件中的 scripts 属性
1 | "scripts": { |
封装工作流 - 提取 gulpfile
- 把 gulp 构建文件封装打包为模块发布到 npm
- 如果模块还在本地没有发布到 npm 可以使用
yarn link
的方式把构建模块 link 到全局,然后在项目中yarn link zce-pages (脚手架名)
的方式安装进项目(相当于 yarn add xxx) - 如果有不应该提取到 gulp 构建模块的内容,应把它们抽离到项目配置文件(约定文件名),然后 gulp 构建模块读取该配置文件
- 在 gulp 构建模块中获取抽离的配置,process.cwd()返回当前命令行所在的工作目录
- 如果报 babel 相关的错误,如@babel/preset-env,因为 gulp 构建模块使用 babel 转换语法,@babel/preset-env 会查找根目录的 node_modules 目录中的@babel/preset-env 模块,所以就报错;可以在 gulp 构建模块 javascript 任务的 babel 修改为
{presets: [require('@babel/preset-env')]}
- 如果是通过 link 到全局的话,在构建项目时会报 gulp gulp-cli 相关的错误,是因为构建时在 node_modules 找不到 gulp 相关的命令,这时可以先在项目中安装依赖
yarn add gulp gulp-cli --dev
- 在入口文件 gulpfile.js 导入 gulp 构建模块
1 | module.exports = require("zce-pages"); |
封装工作流 - 抽象路径配置
封装的 gulp 构建模块中任务使用的路径应该可配置,使任务中的文件路径可根据开发者配置
模块中默认路径配置;在开发项目中也可以传入该配置,让封装的 gulp 构建模块灵活
1 | build: { |
封装工作流 - 包装 Gulp CLI
在 gulp 构建模块添加一个 cli,在 cli 里自动传递参数(参数:–gulpfile): yarn gulp build --gulpfile
,然后在内部调用 gulp 提供的可执行程序
- 在模块根目录新建目录 bin,在目录下新建 js 文件作为 cli 的入口,入口文件的文件头需要添加
#!/usr/bin/env node
- 在 package.json 文件中添加‘bin’字段,值为该入口文件路径
- 把 gulp 的调用和传入的参数放在该文件中
- 在 window 系统执行 gulp 构建命令,会去 node_modules 下的.bin 目录下的 gulp.cmd 文件,如下代码;在该文件中根据判断执行当前目录外面的 gulp-cli/bin/gulp.js,该文件就
require ('gulp-cli')()
,所以我们只需在入口文件中引入这个 gulp.js:require('gulp/bin/gulp')
1 | // %~dp0: 当前目录 |
- 接下来要指定 gulpfile 和 cmd 路径,命令行传递的产生可以通过
process.argv
获取,该属性返回一个数组 - 从中可以看出
gulp-cli
是通过process.argv
拿到参数的,可以在代码运行前 push 需要传递的参数
1 | // 入口文件 |
这时把该构建模块安装在项目,项目中就不在需要 gulpfile.js 文件
封装工作流 - 发布并使用模块
- 在发布 npm 时会把项目根目录的文件和 package.json 中的 files 字段中的目录发布到 npm 仓库,所以要在 files 字段添加一个 cli 入口目录 bin,然后
yarn publish --registry https ://registry.yarnpkg.com
推到 npm 仓库 - 在新项目中安装发布的模块,如果发布时间和安装时间间距短可能会安装到的是老版本,因为国能的镜像同步 npm 原地址需要时间,可以在淘宝镜像中搜索发布的模块,然后点击
SYNC
- 在项目中执行
yarn zce-pages build
就可以构建项目了,zce-pages
是发布到 npm 的模块名 - 也可以在项目的 package.json 文件中的 scripts 字段添加命令
1 | "scripts": { |