工程化
自动化构建
scss 处理
npm install sass
自动化构建
- 一些代码需要编译(为css,js),保证浏览器的兼容性
- 将less或sass转化为css
- es6+的新语法转成es5
- 有些代码需要压缩(css,js,html,图片等)
- 代码在上线前解决这些问题的过程就叫构建
NPM script
{
"name": "my-web-app",
"version": "0.1.0",
"main": "index.js",
"author": "zce <w@zce.me> (https://zce.me)",
"license": "MIT",
"scripts": {
"build": "sass scss/main.scss css/style.css --watch",
"serve": "browser-sync . --files \"css/*.css\"",
"start": "run-p build serve"
},
"devDependencies": {
"browser-sync": "^2.26.7",
"npm-run-all": "^4.1.5",
"sass": "^1.22.10"
}
}
- 实现自动化最简单的方式
- npm允许在package.json中,使用scripts字段自定义脚本命令
- 自定义脚本命令需要两步
- 声明命令 2.执行命令
- npm script任务执行的方式
- 并行:任务之间没有先后顺序,同时执行可以提高效率,书写:任务1 & 任务2
- 串行:人物之间有先后顺序,先执行一个再执行下一个,书写:任务1 && 任务2
- “&”在windows下不起作用
- 需要安装 npm-run-all -D
- 执行命令npm-run-all -s 脚本1 脚本2 ,串行执行,简写,run-s
- 执行命令npm-run-all -p 脚本1 脚本2 ,并行执行,简写run-p
- 控制台就输入 npm run 名称 就可以执行自定义的脚本操作
- scripts里面不需要指定完整的路径,可以自动发现node-modules里面的命令,只需要写命令就可以了。
- --watch会自动编译
- --files会自动监听文件的改变然后更新到页面上、
- run-p会同时启动
"start": "run-p build serve" // 并行执行
npm run start
常用的自动化构架工具
zhuanlan.zhihu.com/p/403172420
Gulp、Grunt、FIS
Grunt:
最早的前端构建系统、Grunt的插件可以完成任何想要完成的事情,但是由于它是基于临时文件实现的,所以构建速度相对较慢,每一步都会有磁盘读写操作,将每一个环节的结果都会写入一个临时的磁盘进行存储,然后读取文件进行下一步,进而造成它相对较慢。环节越多文件读写次数越多,构建速度会特别的慢。
如sass:编译 ->添加属性前缀->压缩代码,每一步都会有读写操作
Gulp:
解决了构架速度非常慢的问题,是基于内存实现的。文件的处理环节都是在内存中完成的。默认支持同时执行多个任务,使用方式更加直观易懂,插件生态非常完善,目前更受欢迎,应该是市面上最流行的前端构架系统。
FIS:
百度前端团队推出来的。更像是捆绑套餐,把典型的需求集成到了内部,如资源加载、模块化开发、代码部署、性能优化等,大而全。
总结:初学者:FIS;灵活多变:Gulp、Grunt;新手需要规范、老手需要自由哈哈~
webpack算是模块打包工具,不算真正意义上的自动化构建工具。
Grunt:
1. 基本使用
npm init -y
yarn init --yes //生成package.json
// mac 安装grunt
/usr/bin/sudo npm install -g grunt-cli
grunt --version
yarn add grunt //添加grunt模块
新建 gruntfile.js
//根目录下添加gruntfile.js文件,作为grunt的入口文件,
//用于定义一些自动执行任务
//需要导出一个函数,此函数接收一个grunt的形参,
//grunt 对象中提供一些创建任务时会用到的 API
// mac 运行
grunt foo
gruntfile.js文件:
module.exports = grunt => {
//grunt.registerTask用于添加任务,运行命令就是yarn grunt foo
grunt.registerTask('foo', () => {
console.log('foo')
})
grunt.registerTask('bar', () => {
console.log('bar')
})
// grunt.registerTask('default', () => {
// console.log('grunt默认任务')//运行的时候不需要指定任务名称 直接yarn grunt
// })
//用default去映射其他的任务.即定义默认执行任务
grunt.registerTask('default',['foo','bar'])
grunt.registerTask('async-task',() =>{
setTimeout(()=>{
console.log('async task working~')
},1000)
}) //异步任务的该方法不支持
// 异步必须使用this的async方法得到一个回调函数,
// 在异步操作完成过后去调用这个回调函数,标识任务完成
grunt.registerTask('async-task',function(){
const done = this.async()
setTimeout(()=>{
console.log('async task working~')
done()
},1000)
})
}
2. 标记任务失败
若构建任务中发生逻辑代码错误,此时就可以将任务标记为失败任务。
module.exports = grunt => {
// 任务函数执行过程中如果返回 false
// 则意味着此任务执行失败
grunt.registerTask('bad', () => {
console.log('bad working~')
return false
})
grunt.registerTask('foo', () => {
console.log('foo working~')
})
grunt.registerTask('bar', () => {
console.log('bar working~')
})
// 如果一个任务列表中的某个任务执行失败
// 则后续任务默认不会运行
// 除非 grunt 运行时指定 --force 参数强制执行
grunt.registerTask('default', ['foo', 'bad', 'bar'])
// 异步函数中无法用return false标记任务失败
// 异步函数中标记当前任务执行失败的方式是为回调函数指定一个 false 的实参
grunt.registerTask('bad-async', function () {
const done = this.async()
setTimeout(() => {
console.log('async task working~')
done(false)
}, 1000)
})
}
3. 配置选项方法
grunt提供了用于添加配置选项的API:grunt.initConfig
用配置压缩文件的路径作为实例:
module.exports = grunt => {
// grunt.initConfig() 用于为任务添加一些配置选项
grunt.initConfig({
// 键一般对应任务的名称
// 值可以是任意类型的数据
foo: {
bar: 'baz'
}
})
grunt.registerTask('foo', () => {
// 任务中可以使用 grunt.config() 获取配置
console.log(grunt.config('foo')) //{ bar: 'baz'}
// 如果属性值是对象的话,config 中可以使用点的方式定位对象中属性的值
console.log(grunt.config('foo.bar')) //bar
})
}
4. 多目标任务
支持多目标模式的任务,理解为子任务的概念。使用API:grunt.registerMultiTask
module.exports = grunt => {
// 多目标模式,可以让任务根据配置形成多个子任务
// grunt.initConfig({
// build: {
// foo: 100,
// bar: '456'
// }
// })
// grunt.registerMultiTask('build', function () {
// console.log(`task: build, target: ${this.target}, data: ${this.data}`)
// })
//除了options之外,每一个build里面的东西都会成为一个子任务,build是总体的任务名
grunt.initConfig({
build: {
options: {
msg: 'task options'
},
foo: {
options: {
msg: 'foo target options'
}
},
bar: '456'
}
})
grunt.registerMultiTask('build', function () {
console.log(this.options())
})
}
5、插件使用
插件机制是grunt的核心,很多构建任务都是通用的,类似于压缩代码,所以社区就会有很多通用插件。
流程:
- npm安装
- gruntfile.js去载入:grunt.loadNpmTasks
- 根据插件文档去完成相关配置选项
grunt-contrib-clean:清除项目开发中的临时文件
module.exports = grunt => {
grunt.initConfig({
clean: {
temp: 'temp/**' //删除文件的文件路径
}
})
grunt.loadNpmTasks('grunt-contrib-clean') //多目标任务
}
6、常用插件及总结
grunt-sass(npm的模块非官方)
yarn add grunt-sass sass --dev
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') //多目标任务
}
Gulp:
zhuanlan.zhihu.com/p/403489287
1. 基本使用
核心特点:高效、易用、
使用流程:添加开发依赖、根目录增加一个gulpfile.js的文件,编写自动执行的构建任务,运行
npm init -y
yarn init --yes
/usr/bin/sudo npm install gulp -g
yarn add gulp --dev //会自动安装gulp-cli,让我们后续可以使用gulp命令
gulp -v
创建 gulpfile.js //创建gulp入口文件
gulp foo
gulp
gulpfile.js文件:
// 导出的函数都会作为 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()
})
注意:glup最新取消了同步任务模式,全都是异步任务
2、组合任务
并行任务、串行任务。
例子:编译css和编译js的任务可以用并行parallel、部署的编译需要有顺序可以用串行series
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)
3、异步任务
调用异步任务,一般都是没有办法明确调用完成的,都是在函数内部通过回调或者事件的方式通知外部完成。
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'))
}
-------
//promise回调
exports.promise = () => {
console.log('promise task')
return Promise.resolve()
}
//promise失败回调
exports.promise_error = () => {
console.log('promise task')
return Promise.reject(new Error('task failed'))
}
-------
const timeout = time => {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
//只受限于node环境,可运行async即可
exports.async = async () => {
await timeout(1000)
console.log('async task')
}
-------
//最常用的一种,处理文件
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()
// })
// }
4、构建过程核心工作原理
gulp的官方描述:The streaming build system
大多数构建过程就是将文件读出来,做一个转化最后形成到另外一个位置,其实就是把手工的过程自动化。
读取文件->压缩文件->写入文件
核心概念:读取流、转换流、写入流
手动实现一个流程示例:
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({
// 核心转换过程
//chunk:文件流读取读取到的内容(Buffer)
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) // 写入
}
5、文件操作API
gulp专门提供了底层读取流和写入流的API,相对于Node.js的API,gulp的API更强大也更容易使用。相对于转换流都是使用独立的插件来提供。
gulp构建任务的过程是先通过gulp提供的src方法创建读取流,再借助插件提供的转化流加工文件,最后通过gulp提供的dist方法创建写入流,写入目标文件。
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案例 样式编译
- 安装gulp
// 会自动安装gulp-cli,让我们后续可以使用gulp命令
/usr/bin/sudo npm install gulp -g
// 查看版本
gulp -v
- 创建gulpfile.js,gulp入口文件,增加构建任务
- 样式处理
const { src, dest } = require('gulp')
const style = () => {
return src('src/assets/styles/*.scss',
{ base: 'src' }) // 保证文件的层级
.pipe(dest('dist'))
}
module.exports = {
style
}
添加gulp-sass依赖
npm install gulp-sass --dev
const { src, dest } = require('gulp')
const sass = require('gulp-sass')
const style = () => {
return src('src/assets/styles/*.scss',
{ base: 'src' })
// 保证文件的层级
.pipe(sass({ outputStyle: 'expanded' }))
// 不会处理_开始的scss
// 后面的属性代表扩展开{} 各占一行
.pipe(dest('dist'))
}
module.exports = {
style
}
- js处理
npm install @babel/core@babel/preset-env --dev
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
}
- 页面模板编译
npm install gulp-swig --dev
// src/**/*.html
// 可以从外部引入
const data = {
pkg: 'aaa'
}
const swig = require('gulp-swig')
const page = () => {
return src('src/*.html', { base: 'src' })
.pipe(swig({ data, defaults: { cache: false } }))
// 防止模板缓存导致页面不能及时更新
.pipe(dest('dist'))
}
module.exports = {
page
}
- 组合任务
const { compile } = require('gulp')
const compile = parallel(style, script, page)
module.exports = {
compile
}
- 图片和字体文件转换
npm install gulp-imagemin --dev
const { imagemin } = require('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'))
}
module.exports = {
image,
font
}
- 其他文件和清除
const extra = () => {
return src('public/**', { base: 'public' })
.pipe(dest('dist'))
}
// 上线之前执行的任务
const build = parallel(compile, image, font, extra)
module.exports = {
build
}
npm install del --dev
const { src, dest, parallel, series } = require('gulp')
const del = require('del')
const clean = () => {
return del(['dist'])
}
const compile = parallel(style, script, page)
// 上线之前执行的任务
const build = series(
clean,
parallel(
compile, image, font, extra
)
)
module.exports = {
build
}
- 自动加载插件
npm install gulp-load-plugins --dev
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
const style = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.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' })
.pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
.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 = () => {
return src('src/assets/fonts/**', { base: 'src' })
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
- 开发服务器
支持热更新
npm install browser-sync
// 创建一个服务器
const browserSync = require('browser-sync')
const bs = browserSync.create()
const serve = () => {
bs.init({
// 关掉重启后的提示
notify: false,
// 启动端口号 默认3000
port: 2080,
// dist下面文件被监听,发生变化后页面跟着变化
files: 'dist/**',
server: {
baseDir: 'dist',
// 临时处理第三方引用文件 优先级高
routes: {
'/node_modules': 'node_modules'
}
}
})
}
module.exports = {
serve
}
- 监视变化以及构建优化
const { src, dest, parallel, series, watch } = require('gulp')
const serve = () => {
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/*.html', page)
// 开发阶段没必要监听images\fonts
// watch('src/assets/images/**', image)
// watch('src/assets/fonts/**', font)
// watch('public/**', extra)
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**'
], bs.reload)
bs.init({
notify: false,
port: 2080,
// open: false,
// files: 'dist/**',
server: {
// html\js\css是dist,图片是src和public
baseDir: ['dist', 'src', 'public'],
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const compile = parallel(style, script, page)
const develop = series(compile, serve)
// 上线之前执行的任务
const build = series(
clean,
parallel(
series(compile, useref),
image,
font,
extra
)
)
module.exports = {
build,
compile,
develop
}
- useref文件引用处理、压缩
npm install gulp-useref --dev
const useref = () => {
return src('dist/*.html', { base: 'dist' })
.pipe(plugins.useref({ searchPath: ['dist', '.'] }))
.pipe(dest('dist'))
}
module.exports = {
useref
}
npm install gulp-htmlmin gulp-uglify gulp-clean-css --dev
const useref = () => {
return src('dist/*.html', { base: 'dist' })
.pipe(plugins.useref({ searchPath: ['dist', '.'] }))
// 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,
minifyJS: true
})))
.pipe(dest('dist'))
}
// 清除 HTML 注释
removeComments: true,
// 省略空白
collapseWhitespace: true,
// 省略所有标签内的布尔属性
collapseBooleanAttributes: true,
// 清除所有标签内值为空的属性
removeEmptyAttributes: true,
// 清除 <script> 标签内的 type 属性
removeScriptTypeAttributes: true,
// 清除 <style> 和 <link> 标签内的 type 属性
removeStyleLinkTypeAttributes: true,
// 压缩 html 页面内的 js 代码
minifyJS: true,
// 压缩 html 页面内的 css 代码
minifyCSS: true
module.exports = {
useref
}
module.exports = {
clean,
build,
develop
}
// package.json
"scripts": {
"clean": "gulp clean",
"build": "gulp build",
"develop": "gulp develop"
},
多个项目的自动化构建工作流
yarn link
cd npm-link-module
npm link
cd npm-link-example
npm link npm-link-module
gulpfile.js中写入
require('npm-link-module')
// process.cwd()返回的是当前Node.js进程执行时的工作目录
const cwd = process.cwd()
"bin": "bin/zce-pages.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')
"files": [
"lib",
"bin"
],
npm publish