一、大概思路
一般自动化构建做的主要是以下几件事情:
(一)开发阶段的构建
- 将scss转成css,并在临时目录下生成对应的css文件(在开发阶段不需要压缩,所以将压缩放在单独的步骤里)。
- 将es6等浏览器不支持的新特性用babel转成浏览器支持的js语法,并生成文件。
- 根据提供的html模板(主要做的是数据填充)生成对应的html代码和文件。
- 启动一个服务器打开页面,并监听上面三步和图片等文件变化,若有变化则刷新页面。
(二)发布阶段的构建
- ...前三个步骤和开发阶段一样,都是处理html、js、css。
- 清空构建目录下的所有旧文件。
- 压缩字体文件和图片文件资源到构建目录下。
- 将public里不需要处理的资源,直接拷贝生成到构建目录下。
- 将前三步临时目录里的css、js、html文件做压缩混淆合并处理,并生成对应文件到构建目录下,将处理后的生成css和js文件插入到html里。
注意前三步的编译都是在临时目录下,最后压缩混淆合并才在构建目录下。之所以分两个目录,是因为在第7步对同个目录边读边写会产生冲突。
二、“开发阶段构建任务”具体实现
(一)准备工作
- 请确保自己本地已有npm或yarn等包管理工具,本文用yarn做演示。
- 下载该演示项目,或者自行vue-cli创建一个项目。
- 在该文件夹下空白处“按shift+鼠标右键”,选中“在此处打开命令行/powershell窗口”打开命令行窗口,或者自行通过命令窗口cd到该文件目录下。
- 命令行输入
yarn init
回车,自行填写信息一路回车,最后生成package.json配置文件。 - 命令行输入
yarn add gulp --dev
安装gulp自动构建工具。 - 在演示项目下的gulpfile.js输入下面代码(gulpfile.js是gulp的运行文件)。
const { src, dest } = require("gulp")
const extra = () => {
// src是gulp中读取文件的方法,dest是gulp中写入文件到目标位置的方法
// 将public目录下,以public为根目录的所有文件拷贝到dist目录下
return src('public/**', {base: 'public'})
.pipe(dest('dist'))
}
module.exports = {
extra,
}
- 命令行输入
yarn gulp extra
(extra是gulpfile.js对应的函数任务名),如果能将public下文件拷贝到dist则证明成功。
(二)css处理
- 由于需要依赖到node-sass国外源可能会出现安装失败的问题,建议先设置npm的安装源为国内的淘宝镜像
npm config set registry http://registry.npm.taobao.org
。 - 命令行输入
yarn add gulp-sass sass --dev
安装gulp处理sass的插件。 - gulpfile.js文件输入以下代码,再执行
yarn gulp style
成功生成temp对应css文件则成功。
const { src, dest } = require("gulp")
const sass = require('gulp-sass')
const style = () => {
return src('src/assets/styles/*.scss', {base: 'src'})
.pipe(sass({outputStyle: 'expanded'})) // _开头的sass文件被认为是依赖文件,不会被转换。expanded可以让右括号换行而不是跟在分号后面
.pipe(dest('temp'))
}
module.exports = {
style,
}
由于后续需要加载比较多的gulp插件,需要频繁写require,此处采用一个gulp-load-plugins来自动加载所需插件,命令行输入yarn add gulp-load-plugins --dev
,代码改写为如下
const { src, dest } = require("gulp")
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
const style = () => {
return src('src/assets/styles/*.scss', {base: 'src'})
.pipe(plugins.sass({outputStyle: 'expanded'})) // _开头的sass文件被认为是依赖文件,不会被转换。expanded可以让右括号换行而不是跟在分号后面
.pipe(dest('temp'))
}
module.exports = {
style,
}
(三)js处理
- 命令行输入
yarn add gulp-babel @babel/core @babel/preset-env --dev
安装gulp转码js的插件。 - gulpfile.js文件增加以下代码,再执行
yarn gulp script
成功生成temp对应已转码好的js文件则成功。
const script = () => {
return src('src/assets/scripts/*.js', {base: 'src'})
.pipe(plugins.babel({presets: ['@babel/preset-env']}))
.pipe(dest('temp'))
}
module.exports = {
...,
script,
}
(四)html处理
- 命令行输入
yarn add gulp-swig --dev
安装gulp启动服务器并监听文件变化的插件。 - gulpfile.js文件增加以下代码,再执行
yarn gulp page
成功生成temp对应已插入好数据的html文件则成功。
const data = {
menu: [],
pkg: require('./package.json'),
date: new Date(),
}
const page = () => {
return src('src/*.html', {base: 'src'})
.pipe(plugins.swig({data, defaults: {cache: false}})) // 指定页面中的插值data,根据模板生成html。记得忽略缓存,否则可能更新失败
.pipe(dest('temp'))
.pipe(bs.reload({stream: true}))
}
module.exports = {
...,
page,
}
(五)服务器上启动页面并监听变化
- 命令行输入
yarn add browser-sync --dev
安装gulp对页面模板进行编译的插件。 - gulpfile.js文件增加以下代码,再执行
yarn gulp serve
,成功在浏览器启动页面,并且修改sass/js/html时会同步刷新页面变化则成功。
const { src, dest, watch } = require("gulp")
const browserSync = require('browser-sync') // 不是gulp插件所以要自己导入
const bs = browserSync.create() // 创建服务器
const serve = () => {
// 由于bs.init只监听了temp下已经编译好文件的变化,但我们真正编辑是在src下的未处理文件,所以要先监听src文件变化触发编译到temp触发bs监听
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/*.html', page)
// 图片等资源不在temp目录下,监听到变化了要手动调用bs.reload
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**',
], bs.reload)
bs.init({
notify: false, // 不提示刷新
port: 2000,
open: true, // 是否自动打开浏览器
files: 'temp/**', // 哪些文件需要监听修改
server: { // 先根据routes配置去指定目录找文件,找不到再根据baseDir配置去找
routes: { // 遇到/node_modules路径时去node_modules目录下找文件
'/node_modules': 'node_modules',
},
baseDir: ['temp', 'src', 'public'], // 需要寻找对应文件时,可以先在temp目录下找有没有,没有再找src再public
},
})
}
module.exports = {
...,
serve,
}
(六)整合为开发阶段构建任务
此处通过gulp的两个方法parallel
和series
来整合上面四步任务。parallel
方法可以同时开始执行任务,series
方法是按顺序先后执行任务。整合代码如下,命令行yarn gulp develop
即可运行该任务。
const compile = parallel(style, script, page) // 因为三个任务互不影响所以用parallel
const develop = series(compile, serve) // 要先编译才有temp目录,才能监听变化,所以用series
module.exports = {
compile,
develop,
}
三、“发布阶段构建任务”具体实现
(一)清空构建目录
- 命令行输入
yarn add del --dev
安装删除文件的插件。 - gulpfile.js文件增加以下代码,再执行
yarn gulp clean
,成功删除目录下的指定文件则成功。
const del = require('del')
const clean = () => {
return del(['dist']) // del返回的是一个promise
}
module.exports = {
clean,
}
(二)压缩图片和字体
- 命令行输入
yarn add gulp-imagemin --dev
安装压缩图片的插件。 - gulpfile.js文件增加以下代码,再执行
yarn gulp image font
,生成了对应图片并比原文件小则成功。
const image = () => {
return src('src/assets/images/**', {base: 'src'}) // **任意文件
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
const font = () => { // 字体也有svg可以用同样压缩
return src('src/assets/fonts/**', {base: 'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
module.exports = {
image,
font,
}
(三)拷贝public资源
这段在开头准备工作中其实已经讲过,直接复制代码过来,不再赘述。
const extra = () => {
// src是gulp中读取文件的方法,dest是gulp中写入文件到目标位置的方法
// 将public目录下,以public为根目录的所有文件拷贝到dist目录下
return src('public/**', {base: 'public'})
.pipe(dest('dist'))
}
(四)压缩处理js/css/html,并将处理后的js/css插入html
- 命令行输入
yarn add gulp-useref --dev
安装处理引用关系的插件,可以将类似如下的特定注释内的引用文件打包合并到指定文件中。(这段代码表示把bootstrap.css和test.css两个文件打包合并成assets/styles/目录下一个vendor.css文件)
<!-- build:css assets/styles/vendor.css -->
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="/node_modules/test/test.css">
<!-- endbuild -->
- 命令行输入
yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev
安装压缩处理html/js/css的gulp插件,yarn add gulp-if --dev
安装用于判断当前文件类型并做对应处理的gulp插件。 - gulpfile.js文件增加以下代码,再执行
yarn gulp useref
,生成了对应各压缩文件并插入到html则成功。
const useref = () => {
return src('temp/*.html', {base: 'temp'})
.pipe(plugins.useref({searchPath: ['temp', '.']})) // 指定html里写的引入文件去哪里找
// 找到的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, // 对html里的css代码做压缩
minifyJS: true,
})))
.pipe(dest('dist'))
}
module.exports = {
...,
useref,
}
(五)整合为发布阶段构建任务
由于useref执行完一次之后,temp里的html已经被压缩,特定注释被删除,所以再次运行useref任务无法达到想要的效果,所以必须先编译compile生成注释代码,再执行useref才有效。整合代码如下,命令行yarn gulp build
即可运行该任务。
// 先删除旧目录,再执行构建任务生成对应文件
const build = series(clean, parallel(series(compile, useref), image, font, extra))
module.exports = {
compile,
develop,
build,
}
四、最终gulpfile.js代码
const { src, dest, watch, parallel, series } = require("gulp")
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
const browserSync = require('browser-sync') // 不是gulp插件(gulp-开头)所以要自己导入
const bs = browserSync.create() // 创建服务器
const del = require('del')
const data = {
menu: [],
pkg: require('./package.json'),
date: new Date(),
}
const style = () => {
return src('src/assets/styles/*.scss', {base: 'src'})
.pipe(plugins.sass({outputStyle: 'expanded'})) // _开头的sass文件被认为是依赖文件,不会被转换。expanded可以让右括号换行而不是跟在分号后面
.pipe(dest('temp'))
// .pipe(bs.reload({stream: true})) // 如果bs初始化files项没配置,也可以在watch到之后在任务里reload
}
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, defaults: {cache: false}})) // 指定页面中的插值data,根据模板生成html。记得忽略缓存,否则可能更新失败
.pipe(dest('temp'))
// .pipe(bs.reload({stream: true}))
}
const serve = () => {
// 由于bs.init只监听了temp下已经编译好文件的变化,但我们真正编辑是在src下的未处理文件,所以要先监听src文件变化触发编译到temp触发bs监听
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/*.html', page)
// 图片等资源不在temp目录下,监听到变化了要手动调用bs.reload
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**',
], bs.reload)
bs.init({
notify: false, // 不提示刷新
port: 2000,
open: true, // 是否自动打开浏览器
files: 'temp/**', // 哪些文件需要监听修改
server: { // 先根据routes配置去指定目录找文件,找不到再根据baseDir配置去找
routes: { // 遇到/node_modules路径时去node_modules目录下找文件
'/node_modules': 'node_modules',
},
baseDir: ['temp', 'src', 'public'], // 需要寻找对应文件时,可以先在temp目录下找有没有,没有再找src再public
},
})
}
const clean = () => {
return del(['dist']) // del返回的是一个promise
}
const image = () => {
return src('src/assets/images/**', {base: 'src'}) // **任意文件
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
const font = () => { // 字体也有svg可以用同样压缩
return src('src/assets/fonts/**', {base: 'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
const extra = () => {
// src是gulp中读取文件的方法,dest是gulp中写入文件到目标位置的方法
// 将public目录下,以public为根目录的所有文件拷贝到dist目录下
return src('public/**', {base: 'public'})
.pipe(dest('dist'))
}
const useref = () => {
return src('temp/*.html', {base: 'temp'})
.pipe(plugins.useref({searchPath: ['temp', '.']})) // 指定html里写的引入文件去哪里找
// 找到的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, // 对html里的css代码做压缩
minifyJS: true,
})))
.pipe(dest('dist'))
}
const compile = parallel(style, script, page) // 因为三个任务互不影响所以用parallel
const develop = series(compile, serve) // 要先编译才有temp目录,才能监听变化,所以用series
// 先删除旧目录,再执行构建任务生成对应文件
const build = series(clean, parallel(series(compile, useref), image, font, extra))
module.exports = {
compile,
develop,
build,
}