概述
自动化构建
在开发网页应用的时候,经常会有一些需要在开发阶段去重复执行的命令
NPM Script是实现自动化构建工具流的最简单方式
- “build”: “sass scss/main.scss css/style.css --watch”,
npm script的preserve钩子机制,在执行yarn serve之前执行yarn build;
- “preserve”: "yarn build,
使用npm-run-all模块可以同时执行多个script任务;// npm install npm-run-all --save-dev
- “start”: “run-p build serve”
使用browser-sync模块可以启动一个本地服务器
- “serve”: “browser-sync”,
// package.json文件中
{
"name": "my-web-app",
"version": "0.1.0",
"main": "index.js",
"author": "xiapeng",
"license": "MIT",
"scripts": {
"build": "sass scss/main.scss css/style.css --watch",
// "preserve": "yarn build,
"serve": "browser-sync",
"start": "run-p build serve"
},
"devDependencies": {
"browser-sync": "^2.26.7",
"npm-run-all": "^4.1.5",
"sass": "^1.22.10"
}
}
常用的构建化自动工具: Grunt、Gulp、FIS
Grunt算是最早的前端构建系统,由于工作过程是基于临时文件的,构建速度会比较慢,每一步都会有磁盘读写操作,在大型项目中,处理的文件较多,会导致构建速度慢;Gulp是基于内存实现的,对于文件的处理都是在内存中处理的,支持同时处理多个文件,生态也比较完善;
Grunt
// 初始化package.json文件
yarn init --yes
// 下载grunt
yarn add grunt
// 运行grunt任务
yarn grunt 任务名
创建Task
创建单个任务:grunt.registerTask
-
grunt.registerTask(‘default’, [‘foo’, ‘bar’]) // 创建默认任务
-
grunt.task.run(‘foo’, ‘bar’) // foo 和 bar 会在当前任务执行完成过后自动依次执行
-
const done = this.async() // done()标记异步任务,异步函数任务中不能使用箭头函数
-
// Grunt 的入口文件 // 用于定义一些需要 Grunt 自动执行的任务 // 需要导出一个函数 // 此函数接收一个 grunt 的对象类型的形参 // grunt 对象中提供一些创建任务时会用到的 API module.exports = grunt => { grunt.registerTask('foo', 'a sample task', () => { console.log('hello grunt') }) grunt.registerTask('bar', () => { console.log('other task') }) // // default 是默认任务名称 // // 通过 grunt 执行时可以省略 // grunt.registerTask('default', () => { // console.log('default task') // }) // 第二个参数可以指定此任务的映射任务, // 这样执行 default 就相当于执行对应的任务 // 这里映射的任务会按顺序依次执行,不会同步执行 grunt.registerTask('default', ['foo', 'bar']) // 也可以在任务函数中执行其他任务 grunt.registerTask('run-other', () => { // foo 和 bar 会在当前任务执行完成过后自动依次执行 grunt.task.run('foo', 'bar') console.log('current task runing~') }) // 默认 grunt 采用同步模式编码 // 如果需要异步可以使用 this.async() 方法创建回调函数 // grunt.registerTask('async-task', () => { // setTimeout(() => { // console.log('async task working~') // }, 1000) // }) // 由于函数体中需要使用 this,所以这里不能使用箭头函数 grunt.registerTask('async-task', function () { const done = this.async() setTimeout(() => { console.log('async task working~') done() }, 1000) }) }
创建多目标任务:grunt.registerMultiTask
-
配置多目标任务时需要配置targets
-
grunt.initConfig({ ** })
-
子任务的options会覆盖父任务的options,可以在任务中通过this.options()来获取;
-
通过this.target来获取当前子任务名,通过this.data来获取当前子任务值;
-
module.exports = grunt => { // 多目标模式,可以让任务根据配置形成多个子任务 grunt.initConfig({ build: { options: { msg: 'task options' }, foo: { options: { msg: 'foo target options' } }, bar: '456' } }) grunt.registerMultiTask('build', function () { console.log(this.options()) console.log(this.target, this.data) }) }
Grunt插件
- 找到插件并安装;
- 通过grunt.loadNpmTasks方法将插件加载进来;
- 在grunt.initConfig中为任务添加配置选项;
module.exports = grunt => {
grunt.initConfig({
clean: {
temp: 'temp/**'
}
})
grunt.loadNpmTasks('grunt-contrib-clean')
}
常用插件
grunt-sass
yarn add grunt-sass sass --dev
yarn grunt sass
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')
}
grunt-babel
yarn add grunt-babel @babel/core @babel/preset-env --dev
yarn grunt babel
- 随着任务插件越来越多,可以使用load-grunt-tasks插件来自动加载所有插件,这样就不用重复导入了;
- yarn add load-grunt-tasks --dev
- presets实际为你需要使用babel转换哪些特性,这里选择的是@babel/preset-env即根据最新的ES特性转换;
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: {
files: ['src/scss/*.scss'],
tasks: ['sass']
}
}
})
loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
grunt.registerTask('default', ['sass', 'babel', 'watch'])
}
grunt-contrib-watch
yarn add grunt-contrib-watch --dev
yarn grunt watch
- 将所有的任务做一个映射到default任务上;
- grunt.registerTask(‘default’, [‘sass’, ‘babel’, ‘watch’])
watch: {
js: {
files: ['src/js/*.js'], // 监视的文件
tasks: ['babel'] // 监视文件更改后执行
},
css: {
files: ['src/scss/*.scss'],
tasks: ['sass']
}
}
grunt.registerTask('default', ['sass', 'babel', 'watch'])
Gulp
yarn add gulp --dev
新建gulpfile.js文件配置gulp
yarn gulp foo
- 最新版本的gulp默认所有的任务都是异步任务
- 接收一个done参数作为回调函数来标记任务结束
// // 导出的函数都会作为 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()
})
Gulp组合任务
series是串行任务,按照顺序执行;
parallel是并行任务,并发执行;
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)
Gulp的异步任务
主要有如下三种方式:
-
通过回调函数的方式,接收done参数并执行回调函数;回调函数标记错误时,执行回调的时候可以传入错误参数,done(new Error(‘task failed’));
-
可以返回一个promise对象,return Promise.resolve();返回一个错误的promise对象,return Promise.reject(new Error(‘task failed’));
-
可以通过async 和await的方式,await一个promise对象;
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'))
}
exports.promise = () => {
console.log('promise task')
return Promise.resolve()
}
exports.promise_error = () => {
console.log('promise task')
return Promise.reject(new Error('task failed'))
}
const timeout = time => {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
exports.async = async () => {
await timeout(1000)
console.log('async task')
}
除了以上三种方式,还有如下方式:
文件流的方式
- 返回文件流是因为gulp会为文件流注册end事件,从而gulp就能获取到任务是否完成,所以也可以手动模拟注册end事件;
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()
//
})
// }
Gulp构建过程核心工作原理
通过对文件流的api来模拟构建过程
构建过程大多数就是将文件读出来,然后去做一些转换,最后去写入到另外一个位置;
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({
// 核心转换过程
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) // 写入
}
Gulp文件操作API
读取文件和写入文件
- const { src, dest } = require(‘gulp’)
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案例
根据业务需求,实现实现一个网页应用的自动化构建流
样式编译
yarn add sass gulp-sass --dev
- src的配置选项base可以设置文件夹的路径,这样写出的时候也会按照原路径导出;
- src(‘src/assets/styles/*.scss’, { base: ‘src’ })
- 以 _ 开头的样式文件默认不会被编译;
- sass配置{ outputStyle: ‘expanded’ }让所有的样式都是展开的;
// gulpfile.js
const { src, dest } = require('gulp')
const sass = require ( 'gulp-sass' ) ( require ( 'sass' ) )
const style = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.pipe(sass({ outputStyle: 'expanded' }))
.pipe(dest('dist'))
}
module.exports = {
style
}
脚本编译
yarn add gulp-babel @babel/core @babel/preset-env --dev
- babel需要配置presets,并且需要配置下载核心代码core
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
}
页面模板编译
yarn add gulp-swig --dev
- swig({ data }); 模板引擎,传入数据data
- swig默认会缓存html,可以配置不缓存以支持热更新,defaults: { cache: false }
const { src, dest } = require('gulp')
const swig = require('gulp-swig')
const data = {...}
const page = () => {
return src('src/**/*.html')
.pipe(swig({ data, defaults: { cache: false } }))
.pipe(dest('dist'))
}
module.exports = {
page
}
将上面的三个编译任务合并成一个parallel任务
const { src, dest, parallel } = require('gulp')
const compile = parallel(style,script,page)
module.exports = {
compile
}
*图片和字体文件转换
const imagemin = require(‘gulp-imagemin’)
// 国内下载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'))
}
其他文件及文件清除
yarn add del --dev
const del = require('del')
const extra = () => {
return src('public/**', { base: 'public' })
.pipe(dest('dist'))
}
const clean = () => {
return del(['dist'])
}
const build = series(clean, parallel(compile, extra))
module.exports = {
build
}
自动加载插件
yarn add gulp-load-plugins
- 所有的gulp插件都以小驼峰的方式会变成plugins下面的属性,使用 plugins. 的方式来使用
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
开发服务器
yarn add browser-sync --dev
- server下面是开发服务器的目录配置
- notify是右上角的通知
- files是监听当前目录下文件,修改时自动更新
const browserSync = require('browser-sync')
const bs = browserSync.create()
const serve = () => {
bs.init({
notify: false,
files: 'dist/**',
server: {
baseDir: 'dist',
routes: {
'/node_modules': 'node_modules'
}
}
})
}
module.exports = {
serve
}
监视变化以及构建优化
gulp提供了watch的API用于监听文件改变去执行相应任务,第一个参数为路径,第二个参数为执行任务;
-
swig需要配置不缓存
-
可以不配置files: 'dist/**'监听文件更改重载, 而在每个需要重新加载的构建任务后面都单独配置.pipe(bs.reload({ stream: true }))
-
开发环境中图片和字体只是压缩,构建前后不影响展示效果,所以可以减少构建次数,直接去src目录下读取文件,配置baseDir: [‘dist’, ‘src’, ‘public’],当在dist目录下找不到对应文件时会依次去src,public文件夹下寻找;此时图片和字体等静态文件发生修改无需重新构建,只需要重新加载bs;
watch([ 'src/assets/images/**', 'src/assets/fonts/**', 'public/**' ], bs.reload)
-
新建develop任务,先构建项目,在启动开发服务器
优化后的gulpfile.js文件:
const { src, dest, parallel, series, watch } = require('gulp')
const page = () => {
return src('src/**/*.html')
.pipe(plugins.swig({ data, defaults: { cache: false } }))
.pipe(dest('dist'))
.pipe(bs.reload({ stream: true }))
}
const serve = () => {
// 监听以下三种文件的更改,重新执行构建任务
// 当dist发生改变时,因为我们配置了files, 所以bs会重新加载
// 也可以手动bs.reload
.pipe(bs.reload({ stream: true }))
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/**/*.html', page)
// 开发环境中图片和字体只是压缩,构建前后不影响展示效果,所以可以减少构建次数,无需重新构建,只需要重新加载bs
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**'
], bs.reload)
bs.init({
files: 'dist/**',
notify: false,
server: {
baseDir: ['dist', 'src', 'public'],
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const compile = parallel(style,script,page, image, font)
const build = series(clean, parallel(compile, extra))
const develop = series(compile, serve)
module.exports = {
build,
serve,
develop
}
useref文件引用处理
yarn add gulp-useref --dev
-
对于构建后的项目中的路径问题,可以使用gulp-useref来对所有的引用文件做一个合并
-
使用注释的方法合并所有的文件
<!-- 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 --> <!-- build:js assets/scripts/vendor.js --> <script src="/node_modules/jquery/dist/jquery.js"></script> <script src="/node_modules/popper.js/dist/umd/popper.js"></script> <script src="/node_modules/bootstrap/dist/js/bootstrap.js"></script> <!-- endbuild --> // 转换后的文件 <link rel="stylesheet" href="assets/styles/vendor.css"> <link rel="stylesheet" href="assets/styles/main.css"> <script src="assets/scripts/vendor.js"></script>
-
searchPath的作用是指定dist/*.html中的引用的寻找路径,如下是先从dist目录找,没找到就去 . 根目录找;
const useref = () => {
return src('dist/*.html', { base: 'dist' })
.pipe(plugins.useref({ searchPath: ['dist', '.'] }))
.pipe(dest('dist'))
}
文件压缩
使用useref插件时可以进行一些操作,比如压缩html,css,js文件;
yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev
分别是压缩html, js, css的插件;
- 此时管道中的文件有三种格式htm, css, js,需要对不同格式的文件使用不同的插件;
- yarn add gulp-if --dev
- 此时一边读取dist一边写入dist会有问题,所以使用新的目录release,但使用release会打破构建文件结构;
const useref = () => {
return src('dist/*.html', { base: 'dist' })
.pipe(plugins.useref({ searchPath: ['dist', '.'] }))
.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'))
.pipe(dest('release'))
}
重新规划构建过程
因为使用useref打破了构建文件结构,所以需要重新规划构建过程;
-
最终打包目录为dist目录
-
开发过程中本地服务器运行temp目录,同时style, script, page任务写入temp目录;
-
useref压缩和文件引入任务监听temp目录,写入dist目录;
-
将任务执行集成进npm script中;
-
"scripts": { "clean": "gulp clean", "build": "gulp build", "dev": "gulp develop" },
最终重新规划后的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 sass = require ( 'gulp-sass' ) ( require ( 'sass' ) )
// const babel = require('gulp-babel')
// const swig = require('gulp-swig')
// const imagemin = require('gulp-imagemin')
// 模板引擎传入的数据
const data = {...}
const clean = () => {
return del(['dist', 'temp'])
}
const style = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.pipe(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')
.pipe(plugins.swig({ data, defaults: { cache: false } }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
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'))
}
const extra = () => {
return src('public/**', { base: 'public' })
.pipe(dest('dist'))
}
const serve = () => {
// 监听以下三种文件的更改,重新执行构建任务
// 当dist发生改变时,因为我们配置了files, 所以bs会重新加载
// 也可以手动bs.reload
.pipe(bs.reload({ stream: true }))
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/**/*.html', page)
// 开发环境中图片和字体只是压缩,构建前后不影响展示效果,所以可以减少构建次数,无需重新构建,只需要重新加载bs
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**'
], bs.reload)
bs.init({
// files: 'dist/**',
notify: false,
server: {
baseDir: ['temp', 'src', 'public'],
routes: {
'/node_modules': 'node_modules'
}
}
})
}
// 执行压缩之前要先执行compile任务,因为第一次执行的时候已经把dist中的注释转换了
// 不建议一边读dist一边写入dist
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,
}
封装工作流
一次性写成的gulpfile.js想要封装成多个项目复用的结构,如果放在笔记中不便于二次修改,所以结合工程化工具封装成一套工作流以便于复用;
- package.json中main属性为指定入口文件,引用或者开发某个依赖包的时候需要此属性的支持,不然工程中无法用import导入依赖包;
- 自己开发的依赖包通常喜欢将入口文件放在lib目录下的index.js文件中;
提取gulpfile
-
新建工作流目录文件夹
-
初始化package.json文件,并将原来的gulp开发依赖放入工作流的生产依赖,并配置入口文件
"dependencies": { "@babel/core": "^7.16.7", "@babel/preset-env": "^7.16.7", "gulp-babel": "^8.0.0", "gulp-imagemin": "^8.0.0", "gulp-swig": "^0.9.1", "sass": "^1.47.0", "browser-sync": "^2.27.7", "del": "^6.0.0", "gulp": "^4.0.2", "gulp-clean-css": "^4.3.0", "gulp-htmlmin": "^5.0.1", "gulp-if": "^3.0.0", "gulp-load-plugins": "^2.0.7", "gulp-sass": "^5.1.0", "gulp-uglify": "^3.0.2", "gulp-useref": "^5.0.0" }, "main": "lib/index.js",
-
将gulpfile.js文件内容复制到入口文件中,作为工作流的入口文件,并在新项目中gulpfile.js文件中引入我们的工具流
// 新项目的gulpfile.js文件 module.exports = require('xp-web')
-
新项目中配置script
"scripts": { "clean": "gulp clean", "build": "gulp build", "dev": "gulp develop" },
解决模块中的问题
提取gulpfile.js文件后运行yarn build时会发现找不到模板引擎的data数据,并且data数据应该暴露给用户给不是写在工具流中,此时可以考虑在新项目中创建配置文件,在工具流中去读取该配置文件;
-
在新项目中新建配置文件page.config.js
module.exports = { 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() } }
-
入口文件中将data替换成动态引入配置文件
// 返回当前命令行所在的工作目录 const cwd = process.cwd() // 默认配置 let config = {} try { const loadConfig = require(`${cwd}/pages.config.js`) config = Object.assign({}, config, loadConfig) } catch (e){}
原来gulpfile.js中使用的’@babel/preset-env’,会去工作目录的node_modules中去寻找,但是此时在工具流中无法找到,我们需要修改载入’@babel/preset-env’的方式为require(’@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 })) }
抽象路径配置
把所有写死的路径全部暴露到配置文件中,并写入默认配置
- src的配置cwd属性用来指定src从哪个目录开始寻找,默认为当前工作目录
let config = {
build: {
src: 'src',
dist: 'dist',
temp: 'temp',
public: 'public',
paths: {
styles: 'assets/styles/*.scss',
scripts: 'assets/scripts/*.js',
pages: '*.html',
images: 'assets/images/**',
fonts: 'assets/fonts/**'
}
}
}
// 抽象路径配置并且配置cwd
const clean = () => {
return del([config.build.dist, config.build.temp])
}
const style = () => {
return src(config.build.paths.styles, { base: 'src', cwd: config.build.src })
.pipe(sass({ outputStyle: 'expanded' }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const script = () => {
return src(config.build.paths.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.paths.pages, { cwd: config.build.src })
.pipe(plugins.swig({ data: config.data, defaults: { cache: false } }))
.pipe(dest(config.build.temp))
.pipe(bs.reload({ stream: true }))
}
const image = () => {
return src(config.build.paths.images, { base: config.build.src, cwd: config.build.src })
// .pipe(imagemin())
.pipe(dest(config.build.dist))
}
const font = () => {
return src(config.build.paths.fonts, { base: config.build.src, cwd: config.build.src })
// .pipe(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 = () => {
// 监听以下三种文件的更改,重新执行构建任务
// 当dist发生改变时,因为我们配置了files, 所以bs会重新加载
// 也可以手动bs.reload
.pipe(bs.reload({ stream: true }))
watch(config.build.paths.styles, { cwd: config.build.src }, style)
watch(config.build.paths.scripts, { cwd: config.build.src }, script)
watch(config.build.paths.pages, { cwd: config.build.src }, page)
// 开发环境中图片和字体只是压缩,构建前后不影响展示效果,所以可以减少构建次数,无需重新构建,只需要重新加载bs
watch([
config.build.paths.images,
config.build.paths.fonts,
],{ cwd: config.build.src }, bs.reload)
watch([
'**'
],{ cwd: config.build.public }, bs.reload)
bs.init({
// files: 'dist/**',
notify: false,
server: {
baseDir: [config.build.temp, config.build.src, config.build.public],
routes: {
'/node_modules': 'node_modules'
}
}
})
}
// 执行压缩之前要先执行compile任务,因为第一次执行的时候已经把dist中的注释转换了
// 不建议一边读dist一边写入dist
const useref = () => {
return src(config.build.paths.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))
}
新项目的配置文件pages.config.js
module.exports = {
build: {
src: 'src',
dist: 'release',
temp: '.tmp',
public: 'public',
paths: {
styles: 'assets/styles/*.scss',
scripts: 'assets/scripts/*.js',
pages: '*.html',
images: 'assets/images/**',
fonts: 'assets/fonts/**'
}
},
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()
}
}
包装Gulp CLI
新项目中的gulpfile.js唯一的作用就是引入我们自己的工作流
module.exports = require('xp-web')
考虑去除gulpfile.js文件,将gulpfile封装到我们自己的工作流中,此时会报错
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GMDQxUnI-1641735897755)(C:UsersAdministratorAppDataRoamingTyporatypora-user-imagesimage-20220109195742182.png)]
- gulp命令提供–gulpfile 配置属性来指定gulpfile.js的路径,–cwd配置属性来指定工作目录;
- gulp build --gulpfile ./node_modules/xp-web/lib/index.js --cwd .
但这样命令行中内容太多,考虑封装自己的cli
-
自己的工作流要封装CLI,新建bin目录下xp-web.js文件,并在package.json中配置bin属性指定CLI目录
-
CLI文件必须以 #!/usr/bin/env node 开头
-
process.argv是命令行中添加的指令,例如: gulp build --gulpfile ./node_modules/xp-web/lib/index.js --cwd . 中的–gulpfile和–cwd
-
"bin": "bin/xp-web", // xp-web.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')
-
此时需要重新yarn link,将CLI注册到全局
发布并使用模块
npm pubilsh的时候会默认把根目录和package.json中files属性下的文件夹发布
当我们发布到Npm的时候,使用淘宝镜像源下载的时候可能会存在一个时间差,可以去https://npmmirror.com/查看并点击SYNC同步更新
这里我上传了自己写的xp-web,可以供大家学习参考,使用npm i xp-web就可以下载了;
最后
以上就是难过康乃馨为你收集整理的【前端工程化】二:自动化构建工具Grunt和Gulp的使用及封装工作流与发布自动化构建的全部内容,希望文章能够帮你解决【前端工程化】二:自动化构建工具Grunt和Gulp的使用及封装工作流与发布自动化构建所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复