浅谈前端自动化构建 -- Grunt、Gulp、FIS
前言
笔记来源:拉勾教育 大前端高薪训练营
阅读建议:内容较多,建议通过左侧导航栏进行阅读
前端自动化构建
基本介绍
一切重复工作本应自动化。将开发中的源代码,自动化的转换成生产环境中可以运行的程序,转换过程称为 自动化构建工作流。
作用:
1,脱离运行环境兼容带来的问题
2,使用提高效率的语法、规范和标准
3、构建转换那些不被支持的特性
自动化构建初体验
-
1,安装 sass 模块,并将其作为开发依赖进行安装
$ yarn add sass --dev # or npm install sass --save-dev
-
2,使用命令将 sass文件转换为 css文件
$ .\node_modules\.bin\sass sass/main.scss css/style.css
通过上面的命令,可以看到转换比较繁琐,下面我们来简化一下。
-
3,使用 NPM Scripts,包装构建命令,实现自动化构建工作流的最简方式
然后,在命令行界面使用包装后的命令,将 sass 文件转换为 css 文件$ yarn bulid # or npm run build
注意
使用yarn运行命令时,中间的 run 可以省略,而 npm 不可以
-
4,安装 browser-sync 模块,用于启动一个测试服务器,使其运行我们的项目
$ yarn add browser-sync --dev # or npm install browser-sync --save-dev
然后,使用 NPM Scripts,进行包装命令,如下图所示:
接着,在命令行界面使用包装后的命令,启动测试服务器$ yarn serve # or npm run serve
此时,process会自动启动一个web服务器,并且唤起浏览器,运行我们的网页。
运行结果,如下图所示:
然后,为了使我们在启动服务之前,先执行sass文件的转换命令,我们可以添加一个preserve命令,用于在启动服务之前,先执行转换命令
其次,要想实现 sass 文件在改变时,就去转换成css 文件,我们需要对其进行监听。代码示例,如下图所示:
然而,这种在启动服务时,会一直等待文件的变化,造成阻塞,导致后面的 browser-sync 无法执行。阻塞效果,如下图所示:
此时,我们就需要借助一个 npm-run-all 模块,去解决上述问题。 -
5,安装 npm-run-all 模块,使其可以一次执行多个命令,即合并命令
$ yarn add npm-run-all --dev # or npm install npm-run-all --save-dev
package.json 中添加包装命令,如下图所示:
此时,当我们修改sass文件时,会自动实现编译转换,如下图所示:
最后,我们再去 browser-sync 后面添加一个 --files 参数,这个参数可以让 browser-sync 在启动过后,去监听项目下的一些文件的变化,一旦当文件发生变化过后,browser-sync 会自动将这些文件的变化同步到浏览器,从而更新浏览器的界面。
自动化构建工具
Grunt
基本介绍
最早的构建系统,插件系统很完善,工作过程是基于临时文件去实现的,需要利用磁盘去读写每一个文件,所以构建速度较慢。
基本使用
准备工作
-
1,首先创建项目文件夹,并初始化 package.json 包管理文件,在这里我们使用快速创建
$ mkdir project-name $ cd project-name $ yarn init --yes # or npm init -y
-
2,安装 grunt 模块
$ yarn add grunt --dev # or npm install grunt --save-dev
-
3,使用命令添加一个 grunt 的 入口文件 gruntfile.js ,并进行编码
$ code gruntfile.js
gruntfile.js 是Grunt 的入口文件,用于定义一些需要 Grunt 自动执行的任务,并且需要导出一个函数,此函数接收一个 grunt 的形参,内部提供一些创建任务时可以用到的 API。
API
grunt.registerTask()
grunt.registerTask() 方法用来注册普通任务,他有几种不同的传参方式。
一、传参方式
-
1,接收两个参数时,第一个参数为指定任务的名字;第二个参数为指定任务函数,即当任务发生时,所要去执行的一系列操作。
代码示例如下:
// Grunt 的入口文件 module.exports = grunt => { grunt.registerTask('foo', () => { console.log("hello"); }) }
-
2、接收三个参数时,第一个参数为指定任务的名字;第二个参数为字符串时,即为任务描述;第三个参数为指定任务函数,即当任务发生时,去执行的函数。
代码示例如下:
module.exports = grunt => { grunt.registerTask('bar', '任务描述', () => { console.log('other task-') }) }
上述 bar 任务传入了任务描述,则此描述信息会在 grunt 的帮助信息中显示出来,执行如下命令查看:
$ yarn grunt --help
结果展示,如下图所示:
-
3,接收两个参数,第一个参数为指定任务的名字;第二个参数为数组,即为任务列表。
代码示例如下:
// Grunt 的入口文件 module.exports = grunt => { grunt.registerTask('foo', () => { console.log("hello"); }) grunt.registerTask('bar', '任务描述', () => { console.log('other task-'); }) grunt.registerTask('tasklist', ['foo', 'bar']) }
此时,当运行 tasklist 任务时,grunt 将会依次运行任务列表中的 foo 任务 和 bar 任务。
二、具体使用
-
1,使用命令,运行注册的任务
$ yarn grunt foo # yarn grunt 任务名称
在 grunt 的任务列表中,存在一个默认运行任务,即任务名称为 default。
代码示例如下:
module.exports = grunt => { grunt.registerTask('default', () => { console.log('default task-'); }) }
当任务名称为 default 时,使用grunt运行任务时,可以将任务名称省略,默认运行 default 任务
$ yarn grunt
运行结果,如下图所示:
然而,一般我们会使用 default 去映射一些其他的任务,即registerTask() 方法的第二个参数,需要传入一个 taskList 数组,当执行 default 时,grunt 将会依次执行数组中的任务。代码示例如下:
// Grunt 的入口文件 module.exports = grunt => { grunt.registerTask('foo', () => { console.log("hello"); }) grunt.registerTask('bar', '任务描述', () => { console.log('other task-'); }) grunt.registerTask('default', ['foo', 'bar']) }
-
2,Grunt 是否异步任务支持,使用 setTimeout 进行异步任务的模拟操作。
代码示例如下:
module.exports = grunt => { grunt.registerTask('async-task', () => { setTimeout(() => { console.log('async task working-'); }, 1000) }) }
运行结果,如下图所示:
通过上面的结果,我们可以看到,使用 grunt 运行异步任务时,并没有打印出异步任务中 “async task working-”,这是因为 grunt 默认支持 同步模式。 那么,如何解决 grunt 对异步操作的不支持问题呢?代码示例如下:
// Grunt 的入口文件 module.exports = grunt => { grunt.registerTask('async-task', function () { // 在异步操作完成后,再去调用这个回调函数 const done = this.async() setTimeout(() => { console.log('async task working-'); // 标识这个任务已经被完成,告知grunt这是一个异步任务 // 使之等待 done 的执行, // 直到done()被执行,grunt 才会结束这个任务的执行 done() }, 1000) }) }
通过使用 this.async() 方法,去告知 grunt 这是一个异步任务,让他就去等待这个任务结束以后,再进行任务的结束。
-
3,Grunt 如何将任务标记成失败任务?
代码示例如下:
module.exports = grunt => { grunt.registerTask('bad', () => { console.log('bad workding-'); return false // 返回false,则表示任务失败 }) }
可以看到,grunt 是通过返回 false 的方法,将任务标记为失败任务的。
运行结果,如下图所示:
如果一个任务列表中存在失败任务,那么他后面的任务还会不会运行呢?代码示例如下:
module.exports = grunt => { grunt.registerTask('foo', () => { console.log('foo task-'); }) grunt.registerTask('bar', () => { console.log('bar task-'); }) grunt.registerTask('default', ['foo', 'bad', 'bar']) }
运行结果,如下图所示:
可以看到,bar 任务没有被执行。即,如果任务列表中存在失败任务,那么只运行单独的 yarn grunt 命令,则会导致失败任务后面的任务不会被执行。那么,我们该如何使失败任务后面的任务继续运行呢?
此时,我们需要采用强制执行的命令,以便后面的任务可以继续运行,即:
$ yarn grunt --force
运行结果,如下图所示:
可以看到,bar 任务被强制运行了。在上面所说的都是同步任务,那么异步任务又该如何标记为失败任务呢?
代码示例如下:
module.exports = grunt => { // 异步任务如何标记为失败任务 grunt.registerTask('bad-async', function () { const done = this.async() setTimeout(() => { console.log('bad async'); done(false) // 传入一个false实参,即标记为这是一个失败的任务 }, 1000); }) }
可以看到,异步任务就是在使用this.async()方法的时候,向里面传入一个false即可。
运行结果,如下图所示:
grunt.initConfig()
grunt.initConfig()是用来添加一些配置选项的API,接收一个 { } 对象形式的参数,对象的属性名(键),一般与任务名保持一致; 值可以是任意类型。
-
1,配置选项
代码示例如下:
module.exports = grunt => { grunt.initConfig({ str: 'string', obj: { property: 123 } }) }
-
2,获取配置数据
通过 grunt.config() 方法,获取对应属性的值,传入的参数是设置的属性名。
代码示例如下:
module.exports = grunt => { grunt.initConfig({ str: 'string', obj: { property: 123 } }) grunt.registerTask('str', () => { console.log(grunt.config('str')); // string }) grunt.registerTask('obj', () => { console.log(grunt.config('obj')); // { property: 123 } console.log(grunt.config('obj.property')); // 123 }) grunt.registerTask('tasklist', ['str', 'obj']) }
运行结果,如下图所示:
grunt.registerMultiTask()
grunt.registerMultiTask()采用多目标模式,可以让任务根据配置形成多个子任务。
该方法接收两个参数,第一个参数为指定任务的名称;第二个参数为指定任务的函数,当任务运行过程中,所要去执行的操作,由于我们会用到this,因此不建议使用箭头函数。
-
1, 注册任务,并运行任务
代码示例如下:
module.exports = grunt => { grunt.registerMultiTask('build', function () { console.log('bulid task working-') }) }
运行结果,如下图所示:
可以看到,提示没有找到 build 任务的目标,也可以理解为没有找到子任务。这是因为多任务模式,需要在配置选项中添加任务目标的配置。 -
2,配置任务目标
代码示例如下:
module.exports = grunt => { // 多目标模式,可以让任务根据配置形成多个子任务 grunt.initConfig({ build: { // 属性名和任务名保持一致,属性值只能为对象形式 css: {}, // 每一个属性代表一个目标,属性名即为目标名 js: '2' } }) grunt.registerMultiTask('build', function () { console.log('bulid task working-') }) }
运行结果,如下图所示:
可以看到,运行了两个目标任务,一个为 css任务,一个为 js任务,即运行多目标。那么,如何运行某一个具体的目标任务呢?根据上面的提示,可以执行如下命令:
$ yarn grunt build:js # yarn grunt taskname:target-attr-name
运行结果,如下图所示:
-
3,获取运行目标数据
通过 this.target 获取运行目标任务,通过 this.data 获取配置数据
代码示例如下:
module.exports = grunt => { // 多目标模式,可以让任务根据配置形成多个子任务 grunt.initConfig({ build: { // 属性名和任务名保持一致,属性值只能为对象形式 css: { }, // 每一个属性代表一个目标,属性名即为目标名 js: '2' } }) grunt.registerMultiTask('build', function () { // 通过this.target 获取运行目标 console.log(`target: ${this.target}`) // 通过this.data 获取配置数据 console.log(`data: ${this.data}`) }) }
运行结果,如下图所示:
-
4,options 配置选项
在任务的配置属性中,添加 options 属性,此时,options不可以看做是目标,它是作为任务的配置选项出现的。可以通过 this.options() 获取任务的配置选项
代码示例如下:
module.exports = grunt => { // 多目标模式,可以让任务根据配置形成多个子任务 grunt.initConfig({ build: { // 属性名和任务名保持一致,属性值只能为对象形式 options: { foo: 'bar', count: 1 }, css: { }, js: '2' } }) grunt.registerMultiTask('build', function () { // 获取任务的配置选项 console.log(this.options()); // 通过this.target 获取运行目标 console.log(`target: ${this.target}`) // 通过this.data 获取配置数据 console.log(`data: ${this.data}`) }) }
运行结果,如下图所示:
注意如果目标中也存在配置选项,那么目标中的配置选项会覆盖任务中相同属性名的配置选项
代码示例如下:
module.exports = grunt => { // 多目标模式,可以让任务根据配置形成多个子任务 grunt.initConfig({ build: { // 属性名和任务名保持一致,属性值只能为对象形式 options: { foo: 'bar', count: 111 }, css: { options: { foo: 'baz' // 会覆盖任务配置选项中的 foo 属性 } }, js: '2' } }) grunt.registerMultiTask('build', function () { // 获取任务的配置选项 console.log(this.options()); // 通过this.target 获取运行目标 console.log(`target: ${this.target}`) // 通过this.data 获取配置数据 console.log(`data: ${this.data}`) }) }
运行结果,如下图所示:
grunt.loadNpmTasks()
grunt.loadNpmTasks() 方法,用来加载插件中提供的任务,它接收一个参数,参数为插件名称
- 语法
grunt.loadNpmTasks('grunt-contrib-pluginname')
Plugins
- grunt 插件,常用命名方式
grunt-contrib-pluginname
- 使用 grunt 运行插件中的任务时,完整插件名称后面的 pluginname,其实就是其提供的任务名称。
$ yarn grunt pluginname # or npm run grunt pluginname
grunt-contrib-clean
grunt-contrib-clean 插件,用来清除在项目开发过程中产生的临时文件。
-
1,安装插件模块
$ yarn add grunt-contrib-clean # or npm install grunt-contrib-clean
-
2,基本使用
该插件中所提供的任务,是一种多目标任务,因此需要在 initConfig() 中进行配置。
代码示例如下:
module.exports = grunt => { grunt.initConfig({ clean: { temp: 'temp/app.js', // 所要清除的文件的具体路径 allTxt: 'temp/*.txt', // 使用通配符*,删除所有txt文件 allFiles: 'temp/**' // 使用**的形式,删除temp整个文件夹 } }) grunt.loadNpmTasks('grunt-contrib-clean') }
运行结果,如下图所示:
通过对比可以看到,通过 grunt-contrib-clean 插件 可以清除一些临时文件,如上面的 temp目录。
grunt-sass
grunt-sass 插件是一个npm的模块,它在内部通过npm依赖sass,它需要一个npm提供sass模块进行支持,因此两个模块都需要安装。
-
1, 安装插件模块
$ yarn add grunt-sass sass --dev # or npm install grunt-sass sass --save-dev
-
2,基本使用
该插件中所提供的任务,是一种多目标任务,因此需要在 initConfig() 中进行配置。
代码示例如下:
module.exports = grunt => { grunt.initConfig({ sass: { // 配置目标 main: { // main目标中需要指定sass中的输入文件,以及最终输出的css的文件路径 files: { // 属性名(键),需要输出的css的路径 // 属性值,需要输入的sass文件的源路径 'dist/css/main.css': 'src/scss/main.scss' } } } }) grunt.loadNpmTasks('grunt-sass') }
运行结果,如下图所示:
可以看到,会提示没有去指定一个 implementation 的选项,这是因为 grunt-sass 需要使用implementation 去指定grunt-sass中使用哪一个模块去处理sass的编译。代码示例如下:
const sass = require('sass') module.exports = grunt => { grunt.initConfig({ sass: { // 配置目标 options: { sourceMap: true, // 编译时,生成对应的sourceMap文件 implementation: sass // 指定grunt-sass中使用哪一个模块去处理sass的编译 }, main: { // main目标中需要指定sass中的输入文件,以及最终输出的css的文件路径 files: { 'dist/css/main.css': 'src/scss/main.scss' } } } }) grunt.loadNpmTasks('grunt-sass') }
运行结果,如下图所示:
通过对比可以看到,通过 grunt-sass 插件 可以将 sass 文件编译成 css 文件,实现预编译。
load-grunt-tasks
load-grunt-tasks 模块,用于减少loadNpmTasks() 方法的使用。
-
1,安装模块
$ yarn add load-grunt-tasks --dev # or npm install load-grunt-tasks -save-dev
-
2,基本使用
代码示例如下:
const loadGruntTasks = require('load-grunt-tasks') module.exports = grunt => { loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务 }
grunt-babel
grunt-babel 插件,用来编译 ES6 语法。它需要使用 Babel 的核心模块 @babel/core,以及 Babel 的预设@babel/preset-env。
-
1,安装插件模块
$ yarn add grunt-babel @babel/core @babel/preset-env --dev
-
2,基本使用
代码示例如下:
const loadGruntTasks = require('load-grunt-tasks') module.exports = grunt => { grunt.initConfig({ babel: { options: { sourceMap: true, // 指定转换所有ECMAScript中的特性 presets: ['@babel/preset-env'] }, main: { files: { 'dist/js/app.js': 'src/js/app.js' } } } }) loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务 }
运行 yarn grunt babel 结果,如下图所示:
通过对比可以看到,通过 grunt-babel 插件 可以将 ES6 语法 编译成 ES5 等语法,实现兼容。
grunt-contrib-watch
grunt-contrib-watch 插件,是指当文件发生改变时,可以实现自动跟踪编译。
-
1,安装插件模块
$ yarn add grunt-contrib-watch --dev # or npm install grunt-contrib-watch -save-dev
-
2,基本使用
代码示例如下:
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: { // .scss 就是sass的新扩展名 files: ['src/scss/*.scss'], tasks: ['sass'] } } }) loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务 // 使用映射,确保在启动时,运行各种编译任务,然后再启动监听 grunt.registerTask('default', ['sass', 'babel', 'watch']) }
-
3,运行命令
$ yarn grunt
Gulp
基本介绍
基本作用
很好的解决了 Grunt 当中构建速度非常慢的问题,它是基于内存去实现的,它对于文件的处理环节都是在内存中完成的,相对于 Grunt 的磁盘读写,速度就快了很多。
另外,默认支持同时处理多个任务,因此,效率比较高。相对于 Grunt ,比较直观易懂,插件生态也同样相对完善,称为最流行的前端构建系统。
工作原理
下面将介绍 gulp 构建过程的核心工作原理。
-
定义
The streaming build system (基于流的构建系统)
-
工作流程
具体步骤通过读取流将需要转换的文件进行读取,然后通过转换流的转换逻辑将其转换成我们想要的结果,再通过写入流写入到指定的文件位置。
-
代码操作
代码示例如下:
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) => { // transform 是指转换流核心转换过程实现 // chunk => 获取文件读取流中读取到的文件内容(Buffer)--> 结果:字节数组 const input = chunk.toString() // toString()转换,拿到文件的文本内容 const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '') callback(null, output) // 错误优先回调函数,将output返回出去,传入说明成功 } }) // 把读取出来的文件流导入到写入文件流 read. pipe(transform) // 将文件读取流转换成文件转换流 pipe(write) // 将文件转换流写入到文件写入流中 return read }
异步任务
下面将阐述 gulp 中处理异步任务的三种方式。
回调函数
回调函数是最常用的一种方式。
-
1,成功回调函数
代码示例如下:
exports.callback = done => { console.log('callback task~'); done() // done 是一个函数,用来标识任务完成 }
-
2,错误回调函数,优先原则,即后面的任务不会执行
代码示例如下:
exports.callback_error = done => { console.log('callback task~'); done(new Error('task falied!')) }
Promise
-
1,成功异步任务
代码示例如下:
exports.promise = () => { console.log('promise task~') return Promise.resolve() // 成功,意味着任务结束 }
-
2,失败异步任务
代码示例如下:
exports.promise_error = () => { console.log('promise task~') return Promise.reject(new Error('task failed')) // 失败,任务结束 }
async / await
async await 是 Promise 的语法糖,因此需要返回一个 Promise对象,node版本要在 8 以上。
-
1,成功异步任务
代码示例如下:
const timeout = time => { return new Promise(resolve => { setTimeout(resolve, time) }) } exports.async = async () => { await timeout(1000) console.log('async task~'); }
-
2,失败异步任务
代码示例如下:
exports.async_error = async () => { await new Promise((resolve, reject) => { reject(new Error('task failed')) }) }
基本使用
准备工作
-
1,首先创建项目文件夹,并初始化 package.json 包管理文件,在这里我们使用快速创建
$ mkdir project-name $ cd project-name $ yarn init --yes # or npm init -y
-
2,安装 gulp 模块,同时会安装一个 Gulp CLI 的命令
$ yarn add gulp --dev # or npm install gulp --save-dev
-
3,使用命令添加一个 gulp 的 入口文件 gulpfile.js ,并进行编码
$ code gulpfile.js
gulpfile.js 是 Gulp 的入口文件,用来定义一些 gulp 执行的构建任务。因为这个文件运行在 nodeJs 环境中,所以可以使用commonJs规范进行代码的编写。
gulp 初体验
-
1,定义构建任务的方式,就是通过 exports 导出函数成员的方式进行定义
代码示例如下:
// gulp 的入口文件 exports.foo = () => { console.log('foo task working~'); }
运行结果,如下图所示:
可以看到,提示说这个任务没有完成,这是因为最新的 gulp 中取消了同步代码模式,约定每一个任务都必须是一个异步任务。当任务执行完成后,需要通过调用回调函数,或者其它的一些方式去标记这个任务已经完成。 -
2, 使用回调函数的形式,标识任务完成
代码示例如下:
// Gulp 的入口文件 exports.foo = done => { // done 是个函数 console.log('foo task working~'); done() }
运行结果,如下图所示:
可以看到,提示已经完成 foo 任务。 -
3,与 grunt 类似,gulp 的默认任务 也是default 任务
代码示例如下:
// Gulp 的默认任务 exports.default = done => { console.log('default task working~'); done() }
运行结果,如下图所示:
可以看到,使用 yarn gulp 命令,默认运行 default 任务。 -
4,gulp 4.0 以前,需要使用Gulp中的一个 gulp 的 tasks() 方法去注册任务。
代码示例如下:
const gulp = require('gulp') gulp.task('bar', done => { // gulp 4.0 以后保留了这个API , 但是不推荐使用 console.log('bar working~'); done() })
Gulp API
series 和 parallel
创建组合任务时,可以使用 series 实现串行任务,使用 parallel 实现并行任务,使用这两种方法对实际创建构建工作流很有用。
-
1,定义三个未被导出的成员函数,即私有的任务,他们无法通过 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) }
-
2,通过 gulp 提供的 series方法 将其包装成串行任务,进行任务的运行
代码示例如下:
const { series } = require('gulp') // series 是一个函数,每一个参数都可以是一个任务 exports.foo = series(task1, task2, task3)
运行结果,如下图所示:
可以看到,依次执行 task1 任务、task2任务、task3任务。串行任务,一般用在项目部署上,即需要先执行编译的任务,再进行部署。
-
3,通过 gulp 提供的 parallel 方法 将其包装成并行任务,进行任务的运行
代码示例如下:
// parallel 是一个函数,每一个参数都可以是一个任务 exports.bar = parallel(task1, task2, task3)
运行结果,如下图所示:
可以看到,task1任务、task2任务、task3任务同时启动,最后再分别完成任务列表中的每一个任务。并行任务,一般用于同时开启编译 js 和 css 文件时。
src 和 dest
gulp 文件操作 API,主要为 src 和 dest 。其中 src 创建读取流,参数为需要读取的目标源文件;dest 创建写入流,参数为写入的目标文件目录。
-
转换单一文件
代码示例如下:
const { src, dest } = require('gulp') // 导入 gulp 提供的文件操作API:src 和 dest exports.default = () => { return src('src/normalize.css') .pipe(dest('dist/normalize.min.css')) // 写入目标目录 }
-
使用通配符* 转换多个文件
代码示例如下:
const { src, dest } = require('gulp') exports.default = () => { return src('src/*.css') .pipe(dest('dist')) // 写入目标目录 }
watch
watch 方法,会自动监视一个文件路径的通配符,根据这些文件的变化,来决定是否要重新执行某一个任务。
-
具体操作
代码示例如下:
const { src, dest, parallel, series, watch } = require('gulp') // 文件清除,不是 gulp 的插件 const del = require('del') // 开发服务器 const browserSync = require('browser-sync') // 自动加载全部插件 const loadPlugins = require('gulp-load-plugins') // // 返回一个 plugins的对象 const plugins = loadPlugins() // { babel: [Getter], imagemin: [Getter], sass: [Getter], swig: [Getter] } // browserSync() 提供一个方法,用于创建服务器 const bs = browserSync.create() // 页面中存在的动态数据 const data = { menus: [ { name: 'Home', icon: 'aperture', link: 'index.html' } ], pkg: require('./package.json'), date: new Date() } // 清除文件 const clean = () => { return del('dist') } // 样式编译 const style = () => { // 通过添加配置选项base属性,设置转换的基准路径,这样就会把src后面的一系列路径保留下来 return src('src/assets/styles/*.scss', { base: 'src' }) // outputStyle 属性,指定转换后的css文件按照完全展开的格式进行生成 .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' }) // 使用plugins.swig模板引擎时,会将一些数据使用 {{}} 形式,进行动态加载 // 因此,需要将所加载的数据,添加到配置选项中 .pipe(plugins.swig({ data })) .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 = () => { // 字体文件可以直接拷贝进目标文件,但存在 .svg文件,因此可以使用plugins.imagemin() 进行转换 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 方法接收两个参数 * 第一个参数globs,即监听文件的路径,可以使用通配符 * 第二个参数,即需要执行的任务,一般是设置编译的任务 */ 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) // 文件变化时,自动更新浏览器 watch([ 'src/assets/images/**', 'src/assets/fonts/**', 'public/**' ], bs.reload) // 初始化web服务器的核心配置 bs.init({ notify: false, // 禁止弹出 “是否连接browser-sync” 提示 port: 2080, // 设置启动端口号,默认 3000 // open: false, // 设置是否在启动服务器时,自动打开浏览器 // files: 'dist/**',// 设置哪些文件被监听,使其改变时自动更新浏览器,使用watch时,此属性可以省略, server: { // 减少构建过程,使图片、字体、其他文件使用'src', 'public'中的 // 当在dist目录下找不到文件时,会依次往下查找 baseDir: ['dist', 'src', 'public'], // 指定网站的根目录 routes: { // 将某一个路径(key)指定为另一个路径(value) 优先于 baseDir '/node_modules': 'node_modules' } } }) } // 创建并行任务,完成 src目录下面需要编译的文件 const compile = parallel(style, script, page) // 上线之前执行的任务 const build = series(clean, parallel(compile, image, font, extra)) // 开发阶段 const develop = series(clean, compile, serve) module.exports = { build, // 生产打包 develop // 开发 }
Gulp Plugins
每一个插件模块,都会返回一个函数。
gulp-sass
gulp-sass 模块 用来将 sass 样式文件转换成 css 样式文件,同时 gulp-sass 模块 需要npm提供sass模块进行支持,因此两个模块都需要安装。
-
1,安装插件模块
$ yarn add gulp-sass sass --dev # or npm install gulp-sass sass --save-dev
-
2,将sass文件转换成 css文件
代码示例如下:
const { src, dest } = require('gulp') // 使用sass依赖将sass 文件转换为 css const sass = require('gulp-sass') const style = () => { // 通过添加配置选项base属性,设置转换的基准路径,这样就会把src后面的一系列路径保留下来 return src('src/assets/styles/*.scss', { base: 'src' }) // outputStyle 属性,指定转换后的css文件按照完全展开的格式进行生成 .pipe(sass({ outputStyle: 'expanded' })) .pipe(dest('dist')) } module.exports = { style }
运行结果,如下图所示:
可以看到,在我们生成的 dist 目录下,并没有_icons.css 和 _variables.css,这是因为 sass 模块在工作过程中,会默认将以 _ (下划线) 开头的文件认为是在主文件中依赖的一些文件,不会被转换,会被忽略掉。
gulp-babel
gulp-babel 模块,用来将 ES6 语法转换成 ES5 语法,但是 gulp-babel 模块只是唤醒 @babel/core 模块中的转换过程,并不会自动的去调用 @babel/core模块中的转换方法,因此,需要同时安装 @babel/core模块。并且,若需要将ECMAScript 所有的新特性进行转换时,还需要安装 @babel/preset-env 模块。
-
1,安装插件模块
$ yarn add gulp-babel @babel/core @babel/preset-env --dev
-
2,将 ES6 语法编译为 ES5 语法
代码示例如下:
const { src, dest } = require('gulp') // 使用Babel 依赖将 ES6 语法编译为 ES5以下语法 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 }
gulp-swig
gulp-swig 模块,用来将使用 swig 模板引擎的 html文件转换成正常的 html文件。
-
1,安装插件模块
$ yarn add gulp-swig --dev # or npm install gulp-swig --save-dev
-
2,转换 swig 模板引擎
代码示例如下:
const { src, dest, parallel } = require('gulp') // 将使用 swig 模板引擎的 html文件转换成正常的 html文件 const swig = require('gulp-swig') // 页面中存在的动态数据 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 page = () => { return src('src/*.html', { base: 'src' }) // 使用swig模板引擎时,会将一些数据使用 {{}} 形式,进行动态加载 // 因此,需要将所加载的数据,添加到配置选项中 .pipe(swig({ data })) .pipe(dest('dist')) } // 创建并行任务 const compile = parallel(page) module.exports = { compile }
gulp-imagemin
gulp-imagemin 模块,用来对图片进行压缩后,转换到目标目录
-
1,安装插件模块
$ yarn add gulp-imagemin --dev # or npm install gulp-imagemin --save-dev
-
2,将图片压缩后编译
代码示例如下:
const { src, dest, parallel } = require('gulp') // 将图片进行压缩后转换 const imagemin = require('gulp-imagemin') // 图片压缩编译 const image = () => { return src('src/assets/images/**', { base: 'src' }) .pipe(imagemin()) .pipe(dest('dist')) } // 字体文件编译 const font = () => { // 字体文件可以直接拷贝进目标文件,但存在 .svg文件,因此可以使用imagemin() 进行转换 return src('src/assets/fonts/**', { base: 'src' }) .pipe(imagemin()) .pipe(dest('dist')) } // 创建并行任务,完成 src目录下面需要编译的文件 const compile = parallel(style, script, page, image, font) module.exports = { compile }
gulp-rename
-
1,安装插件模块
$ yarn add gulp-rename --dev # or npm install gulp-rename --save-dev
-
2,指定转换文件的扩展名
代码示例如下:
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' })) // extname 属性,用于指定重命名的扩展名 .pipe(dest('dist')) // 写入目标目录 }
gulp-load-plugins
gulp-load-plugins 模块,可以实现自动的加载全部的 plugins,减少 require 的使用。
-
1,安装插件模块
$ yarn add gulp-load-plugins --dev # or npm install gulp-load-plugins --save-dev
-
2,语法
代码示例如下:
// 自动加载全部插件 const loadPlugins = require('gulp-load-plugins') // 返回一个 包含所有使用到的插件的 plugins的集合对象 const plugins = loadPlugins() // { babel: [Getter], imagemin: [Getter], sass: [Getter], swig: [Getter] }
-
3,使用语法
语法如下:
plugins.xxx // xxx 代表插件的名称,即去掉 gulp- 前缀后的名称,若有多级,采用驼峰命名
gulp-useref
useref 模块,会自动处理 HTML文件中的构建注释,但是只有在编译后的 HTML文件中才会存在构建注释,因此,这个插件提供的 useref 任务,需要在编译后再运行。
-
1,构建注释 ,即包含一个开始的 build 和一个结束的 endbuild,会将里面包裹的多个标签,合并一个文件,如下面的 vendor.css。
代码示例如下:
<!-- build:css assets/styles/vendor.css --> <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css"> <!-- endbuild --> <!-- build:css assets/styles/main.css --> <link rel="stylesheet" href="assets/styles/main.css"> <!-- endbuild -->
-
2,安装插件模块
$ yarn add gulp-useref --dev # or npm install gulp-useref --save-dev
-
3,处理构建注释,使多个文件进行合并
代码示例如下:
const { src, dest, parallel, series, watch } = require('gulp') // 自动加载全部插件 const loadPlugins = require('gulp-load-plugins') // // 返回一个 plugins的对象 const plugins = loadPlugins() // 文件引用处理 const useref = () => { return src('dist/*.html', { base: 'dist' }) .pipe(plugins.useref({ searchPath: ['dist', '.'] })) .pipe(dest('dist')) } // 创建并行任务,完成 src目录下面需要编译的文件 const compile = parallel(style, script, page) // 上线之前执行的任务 const build = series(clean, parallel(compile, image, font, extra)) const develop = series(clean, compile, serve, useref) module.exports = { build, // 生产打包 develop // 开发 }
gulp-if
gulp-if模块,可以用来判断读取流中的文件类型,在其内部会自动创建转换流。
-
1,安装插件模块
$ yarn add gulp-if --dev # or npm install gulp-if --save-dev
-
2,具体操作
代码示例如下:
const { src, dest } = require('gulp') // 自动加载全部插件 const loadPlugins = require('gulp-load-plugins') // 返回一个 plugins的对象 const plugins = loadPlugins() // 文件引用处理 exports.useref = () => { return src('dist/*.html', { base: 'dist' }) .pipe(plugins.useref({ searchPath: ['dist', '.'] })) // html js css .pipe(plugins.if(/\.js$/, plugins.uglify())) .pipe(dest('dist')) }
gulp-htmlmin
gulp-htmlmin 模块,对 HTML文件进行压缩,一般在生产上线之前使用
-
1,安装插件模块
$ yarn add gulp-htmlmin --dev # or npm install gulp-htmlmin --save-dev
-
2,具体操作
代码示例如下:
const { src, dest } = require('gulp') // 自动加载全部插件 const loadPlugins = require('gulp-load-plugins') // 返回一个 plugins的对象 const plugins = loadPlugins() // 文件引用处理 exports.useref = () => { return src('dist/*.html', { base: 'dist' }) .pipe(plugins.useref({ searchPath: ['dist', '.'] })) // html .pipe(plugins.if(/\.html$/, plugins.htmlmin({ // collapseWhitespace选项属性,清除所有的空白字符,否则只默认删除空格符 collapseWhitespace: true, minifyCSS: true, // 压缩页面内的 style标签 minifyJS: true // 压缩页面内的 script标签 }))) .pipe(dest('dist')) }
gulp-uglify
gulp-uglify 模块,对 JS文件进行压缩,一般在生产上线之前使用
-
1,安装插件模块
$ yarn add gulp-uglify --dev # or npm install gulp-uglify --save-dev
-
2,具体操作
代码示例如下:
const { src, dest } = require('gulp') // 自动加载全部插件 const loadPlugins = require('gulp-load-plugins') // 返回一个 plugins的对象 const plugins = loadPlugins() // 文件引用处理 exports.useref = () => { return src('dist/*.html', { base: 'dist' }) .pipe(plugins.useref({ searchPath: ['dist', '.'] })) // js .pipe(plugins.if(/\.js$/, plugins.uglify())) .pipe(dest('dist')) }
gulp-clean-css
gulp-clean-css 提供压缩文件的转换流。
-
1,安装插件模块
$ yarn add gulp-clean-css --dev # or npm install gulp-clean-css --save-dev
-
2,生成压缩文件的转换流
代码示例如下:
const { src, dest } = require('gulp') const cleanCss = require('gulp-clean-css') exports.default = () => { return src('src/*.css') .pipe(cleanCss()) // 将文件读取流转换成压缩文件的转换流 .pipe(dest('dist')) }
Extra Plugins
不是 gulp 的插件,属于额外的插件,但是可以在 gulp 中使用。
del
del 模块,用来删除指定文件,它是一个 Promise方法,因此 gulp 支持 Promise模式。
-
1,安装插件模块
$ yarn add del --dev # or npm install del --save-dev
-
2,编译之前,先清除原来生成的 dist目录
代码示例如下:
const { src, dest, parallel, series } = require('gulp') // 文件清除 const del = require('del') // 清除文件 const clean = () => { return del('dist') } // 创建并行任务,完成 src目录下面需要编译的文件 const compile = parallel(style, script, page, image, font) // 创建串行任务 exports.build = series(clean, parallel(compile, extra))
browser-sync
browser-sync 模块,提供开发服务器,他支持修改过后自动热更新到浏览器中,让我们可以即时的看到页面效果。可以通过 gulp 进行管理
-
1,安装插件模块
$ yarn add browser-sync --dev # or npm install browser-sync --save-dev
-
2,具体操作
代码示例如下:
const { src, dest, parallel, series } = require('gulp') const browserSync = require('browser-sync') // 热更新开发服务器依赖 const bs = browserSync.create() // browserSync() 提供一个方法,用于创建服务器 // 自动唤醒浏览器,打开对应的网站 exports.serve = () => { // 初始化web服务器的核心配置 bs.init({ notify: false, // 禁止弹出 “是否连接browser-sync” 提示 port: 2080, // 设置启动端口号,默认 3000 // open: false, // 设置是否在启动服务器时,自动打开浏览器 files: 'dist/**', // 设置哪些文件被监听,使其改变时自动更新浏览器 server: { baseDir: 'dist', // 指定网站的根目录 routes: { // 将某一个路径(key)指定为另一个路径(value) 优先于 baseDir '/node_modules': 'node_modules' } } }) }
其他文件编译
这里指的其他文件就是额外的文件,比如 public目录。
-
具体操作
代码示例如下:
const { src, dest, parallel } = require('gulp') // 其他文件编译 const extra = () => { // 直接拷贝的方式 return src('public/**', { base: 'public' }) .pipe(dest('dist')) } // 创建并行任务,完成 src目录下面需要编译的文件 const compile = parallel(style, script, page, image, font) const build = parallel(compile, extra) module.exports = { build }
封装工作流
目的
如何提取一个可复用的自动化工作流?
准备工作
-
1,依据准备工作,创建 gulp-pages目录结构,如下图所示:
-
2,将 package.json 中指向的入口文件地址,改为 “lib/index.js”,如下图所示:
-
3,编写 lib/index.js 入口文件,此入口文件,就是上面所说的 gulpfile.js文件。
代码示例如下:
const { src, dest, parallel, series, watch } = require('gulp') const del = require('del') const browserSync = require('browser-sync') const loadPlugins = require('gulp-load-plugins') const plugins = loadPlugins() const bs = browserSync.create() 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 = () => { return src('src/assets/styles/*.scss', { base: 'src' }) .pipe(plugins.sass({ outputStyle: 'expanded' })) .pipe(dest('temp')) .pipe(bs.reload({ stream: true })) } const script = () => { return src('src/assets/scripts/*.js', { base: 'src' }) .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 })) .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/**', 'src/assets/fonts/**', 'public/**' ], bs.reload) bs.init({ notify: false, port: 2080, server: { baseDir: ['temp', 'src', 'public'], routes: { '/node_modules': 'node_modules' } } }) } 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, minifyCSS: true, 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 }
-
4,将项目所需要的依赖,添加到 package.js 中,此处安装的是项目所需依赖,而不是项目开发依赖。
代码示例如下:
{ "name": "gulp-pages", "version": "1.0.0", "description": "static web app workflow", "main": "lib/index.js", "bin": "bin/gulp-pages.js", "license": "MIT", "files": ["lib", "bin"], // 需要发布的文件夹 "directories": { "lib": "lib" }, "scripts": { "lint": "standard --fix" }, "dependencies": { "@babel/core": "^7.12.7", "@babel/preset-env": "^7.12.7", "browser-sync": "^2.26.13", "del": "^6.0.0", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", "gulp-clean-css": "^4.3.0", "gulp-cli": "^2.3.0", "gulp-htmlmin": "^5.0.1", "gulp-if": "^3.0.0", "gulp-imagemin": "^7.1.0", "gulp-load-plugins": "^2.0.5", "gulp-sass": "^4.1.0", "gulp-swig": "^0.9.1", "gulp-uglify": "^3.0.2", "gulp-useref": "^5.0.0", "sass": "^1.29.0" }, "devDependencies": { "standard": "^16.0.3" }, "engines": { "node": ">=6" } }
-
5,使用命令,安装项目所需的全部依赖
$ yarn # or npm i
-
6,依据准备工作,创建 gulp-demo目录结构,如下图所示:
-
7,将 gulp-pages 模块链接到全局范围,在 gulp-pages 的命令终端执行
$ yarn link # or npm link
-
8, 将 gulp-pages 模块作为 gulp-demo 的依赖模块,在 gulp-demo 的命令终端执行
$ yarn link gulp-pages # or npm link gulp-pages
运行结果,如下图所示:
可以看到,gulp-demo 的目录结构中,会添加一个node_modules的文件夹,其目录结构和 gulp-pages 的一模一样。这其实是一个软连接,当 gulp-pages 中的内容改变时,gulp-demo 会随着改变。
基本操作
简单工作流
在上面书写的 gulpfile.js 文件代码中,我们采用的是定义任务,再将任务以模块进行导出的形式。现在,我们导入了公共的 gulp-pages 模块,因此,可以直接导出载入的模块。
-
1,在 gulp-demo 的 gulpfile.js 文件中,导出载入模块后的命令
代码示例如下:
module.exports = require('gulp-pages')
运行结果,如下图所示:
可以看到,此时运行会报错。因为用到的 gulp命令是从bin目录中获取的,而此时是不存在这个目录的,此时可以先手动安装 gulp,使编译成功。 -
2,手动安装 gulp 依赖,临时解决 gulp 不识别问题
$ yarn add gulp --dev # or npm install gulp --save-dev
运行结果,如下图所示:
可以看到,此时运行会报错。因为在 index.js中,使用了配置数据,但是每个项目的配置数据可能都有所不同,考虑到约定大于配置,因此需要将配置数据单独抽象出来,形成配置文件,被 gulpfile.js 文件引入。 -
3,在 gulp-demo 创建 pages.config.js 配置文件,书写配置数据(数据省略,可以参考上面的data数据)
代码示例如下:
module.exports = { data: { // config data } }
-
4,改造 gulp-pages 的入口文件 index.js,将原来的 data配置数据 改为以下代码
代码示例如下:
// 返回当前命令行所在的工作目录 const cwd = process.cwd() let config = { // default config } // 使用 try ... catch ,防止 pages.config.js 不存在的情况 try { const loadConfig = require(`${cwd}/pages.config.js`) config = Object.assign({}, config, loadConfig) } catch (e) {} const page = () => { return src('src/*.html', { base: 'src' }) .pipe(plugins.swig({ data: config.data })) // 此时,data属性名和属性值不同,不可以简写 .pipe(dest('temp')) .pipe(bs.reload({ stream: true })) }
运行结果,如下图所示:
可以看到,此时运行会报错。这是因为 根据上面的写法,会在 gulp-demo的node_modules中查找对应的依赖,而 gulp-demo 中并没有。 -
5,解决找不到 ‘@babel/preset-env’ 依赖的问题
代码示例如下:
const script = () => { return src('src/assets/scripts/*.js', { base: 'src' }) .pipe(plugins.babel({ presets: [require('@babel/preset-env')] })) .pipe(dest('temp')) .pipe(bs.reload({ stream: true })) }
可以看到,采用了require 导包的方法,因为 require 会从当前文件开始,逐级往上查找对应的依赖包。
运行结果,如下图所示:
强化工作流
上面已经完成了基本的自动化构建工作流,下面需要深化包装工作流,使其更加灵活。
-
1,抽象路径配置,将原来写死的路径,改成配置路径
代码示例如下:
// 模块的入口文件 const { src, dest, parallel, series, watch } = require('gulp') const del = require('del') const browserSync = require('browser-sync') const loadPlugins = require('gulp-load-plugins') const plugins = loadPlugins() const bs = browserSync.create() // 返回当前命令行所在的工作目录 const cwd = process.cwd() let config = { // default config build: { src: 'src', dist: 'dist', temp: 'temp', public: 'public', path: { styles: 'assets/styles/*.css', scripts: 'assets/scripts/*.js', pages: '*.html', images: 'assets/images/**', fonts: 'assets/fonts/**' } } } // 使用 try ... catch ,防止 pages.config.js 不存在的情况 try { const loadConfig = require(`${cwd}/pages.config.js`) config = Object.assign({}, config, loadConfig) } catch (e) { } const clean = () => { return del([config.build.dist, config.build.temp]) } const style = () => { // 利用cwdp 配置当前的工作目录 return src(config.build.path.styles, { base: config.build.src, cwd: config.build.src }) .pipe(plugins.sass({ outputStyle: 'expanded' })) .pipe(dest(config.build.temp)) .pipe(bs.reload({ stream: true })) } const script = () => { return src(config.build.path.scripts, { base: config.build.src, cwd: config.build.src }) .pipe(plugins.babel({ presets: [require('@babel/preset-env')] })) .pipe(dest(config.build.temp)) .pipe(bs.reload({ stream: true })) } const page = () => { return src(config.build.path.pages, { base: config.build.src, cwd: config.build.src }) .pipe(plugins.swig({ data: config.data })) // 此时,data属性名和属性值不同,不可以简写 .pipe(dest(config.build.temp)) .pipe(bs.reload({ stream: true })) } const image = () => { return src(config.build.path.images, { base: config.build.src, cwd: config.build.src }) .pipe(plugins.imagemin()) .pipe(dest(config.build.dist)) } const font = () => { return src(config.build.path.fonts, { base: config.build.src, cwd: config.build.src }) .pipe(plugins.imagemin()) .pipe(dest(config.build.dist)) } const extra = () => { return src('**', { base: config.build.public, cwd: config.build.public }) .pipe(dest(config.build.dist)) } const serve = () => { watch(config.build.path.styles, { cwd: config.build.src }, style) watch(config.build.path.scripts, { cwd: config.build.src }, script) watch(config.build.path.pages, { cwd: config.build.src }, page) watch([ config.build.path.images, config.build.path.fonts, ], { cwd: config.build.src }, bs.reload) watch('**', { cwd: config.build.public }, bs.reload) bs.init({ notify: false, port: 2080, server: { baseDir: [config.build.temp, config.build.src, config.build.public], routes: { '/node_modules': 'node_modules' } } }) } const useref = () => { return src(config.build.path.pages, { base: config.build.temp, cwd: config.build.temp }) .pipe(plugins.useref({ searchPath: [config.build.temp, '.'] })) .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(config.build.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 }
-
2,gulpfile.js文件比较冗余,可以直接执行下面命令代替 gulpfile.js 文件
$ # --cwd 设置当前执行目录 $ yarn gulp --gulpfile ./node_modules/gulp-pages/lib/index.js --cwd
包装 Gulp CLI
上面的命令需要传参,不太易操作,下面采用脚手架的形式,代替上面的操作。
-
1,创建 cli 的入口文件,一般存放在 项目根目录/bin目录中,这里命名为 gulp-pages.js。
目录结构,如下图所示:
-
2,在 package.json 文件中,添加 cli 入口文件的配置指向,其余配置省略
代码示例如下:
// 配置方式一 { "bin": "bin/gulp-pages.js" } // 配置方式二 { "bin": { "gp": "bin/gulp-pages.js" } }
此时,需要重新将 gulp-pages 模块 link到全局。
-
3,配置 gulp-pages.js 入口文件,替代上述的手动输入
代码示例如下:
#!/usr/bin/env node process.argv.push('--cwd') process.argv.push(process.cwd()) // 指定当前命令的执行工作目录 process.argv.push('--gulpfile') process.argv.push(require.resolve('..')) // 指向 gulpfile 的入口文件 require('gulp/bin/gulp') // 集成 gulp
-
4,在 gulp-demo目录 命令行界面,使用命令,运行任务
$ gulp-pages clean $ gulp-pages build $ gulp-pages develop
运行结果,与未包装时一致。
发布模块
- 使用命令,发布模块
详情参见:【浅谈前端工程化-发布 Generator】$ yarn publish # or npm publish
使用模块
-
1,新建项目文件夹,并创建public文件夹、src文件夹、pages.config.js文件等
$ mkdir project-name $ cd project-name
目录结构,如下图所示:
-
2,创建并初始化 package.json 包管理文件
$ yarn init --yes # or npm init -y
-
3,安装发布的 alisone-gulp-pages 模块
$ yarn add alisone-gulp-pages --dev # or npm install alisone-gulp-pages --save-dev
-
4,运行 gulp-pages 中暴露出的 任务命令
$ yarn alisone-gulp-pages clean $ yarn alisone-gulp-pages build $ yarn alisone-gulp-pages develop
-
5,在 NPM Scripts中,进行配置,即 package.json 中的 scripts 属性
代码示例如下:
{ "scripts": { "clean": "alisone-gulp-pages clean", "build": "alisone-gulp-pages build", "develop": "alisone-gulp-pages develop" } }
-
6,运行命令,进行操作
$ yarn clean # or npm run clean $ yarn build # or npm run build $ yarn develop # or npm run develop
FIS
基本介绍
百度的前端团队推出的一款构建系统,微内核特点,更像是一种捆绑套餐,高度集成,即把项目中的一些需求都尽可能的集中在内部,例如资源加载、模块化开发、代码部署、性能优化等,在国内比较流行。
基本使用
-
1,全局安装 或 局部安装 fis3
$ yarn global add fis3 # or npm install fis3 -g
-
2,运行 fis3 中默认的构建任务 release,会将项目中所有需要被构建的目录,存放到一个临时目录中。
$ fis3 release
-
3,指定项目的输出目录为 output
$ fis3 release -d output
-
4,创建 fis-conf.js 配置文件,进行资源定位
代码示例如下:
// 利用 fis 资源定位 // match() 的第一个参数,是指选择器 // 后面的参数,是对于匹配到的文件的配置 fis.match('*.{js,scss,png}', { release: '/assets/$0' // $0 指的是当前文件的原始结构 })
-
5,安装 fis-parser-node-sass 插件模块,用来编译 sass 文件
$ yarn global add fis-parser-node-sass # or npm install fis-parser-node-sass -g
-
6,在 fis-conf.js 配置文件中,配置 sass 的编译并压缩
代码示例如下:
fis.match('**/*.scss', { rExt: '.css', // 修改编译后的扩展名 parser: fis.plugin('node-sass'), // 自动载入编译插件 optimizer: fis.plugin('clean-css') // css的压缩插件,内置插件 })
-
7,安装 fis-parser-babel-6.x 插件模块,将 ES6 语法转换为 ES5 语法
$ yarn global add fis-parser-babel-6.x # or npm install fis-parser-babel-6.x -g
-
8,在 fis-conf.js 配置文件中,配置 ES6 转换为 ES5
代码示例如下:
fis.match('**/*.js', { parser: fis.plugin('babel-6.x'), // 自动载入编译插件 optimizer: fis.plugin('uglify') // js的压缩插件,内置插件 })
-
9,使用 fis3 inspect 命令,查看在转换过程中,有哪些文件被转换
$ fis3 inspect
上一篇: 基于gulp搭建的前端自动化构建