我是靠谱客的博主 难过康乃馨,最近开发中收集的这篇文章主要介绍【前端工程化】二:自动化构建工具Grunt和Gulp的使用及封装工作流与发布自动化构建,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

自动化构建

在开发网页应用的时候,经常会有一些需要在开发阶段去重复执行的命令

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插件

  1. 找到插件并安装;
  2. 通过grunt.loadNpmTasks方法将插件加载进来;
  3. 在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的异步任务

主要有如下三种方式:

  1. 通过回调函数的方式,接收done参数并执行回调函数;回调函数标记错误时,执行回调的时候可以传入错误参数,done(new Error(‘task failed’));

  2. 可以返回一个promise对象,return Promise.resolve();返回一个错误的promise对象,return Promise.reject(new Error(‘task failed’));

  3. 可以通过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的使用及封装工作流与发布自动化构建所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部