前端工程化概述
1、工程化的定义和主要解决的问题
工程化的定义
- 前端工程化是指遵循一定的标准和规范,通过工具去提高效率,降低成本的一种手段。
- 全副武装:通过工程化提升[战斗力]
- 工程化出现的原因:前端应用功能要求不断提高,业务逻辑日益复杂。
主要解决的问题
技术是为了解决问题而存在的。
- 想要使用ES6+新特性,但是兼容有问题
- 想要使用Less/Sass/PostCSS增强CSS的编程性,但是运行环境不能直接支持。
- 想要使用模块化的方式提高项目的可维护性,但运行环境不能直接支持。
- 部署上线前需要手动压缩代码及资源文件,部署过程需要手动上传代码到服务器。
- 多人协作开发,无法硬性统一大家的代码风格,从仓库中pull回来的代码质量无法保证。
- 部分功能开发时需要等待后端服务接口提前完成。
2、一个项目过程中的工程化的表现
- 一切以提高效率、降低成本、质量保证为目的的手段都属于[工程化]。
- 一切重复的工作都应该被自动化。
3、工程化不等于工具
- 工程化 ≠ 工具
- 工程化的核心,是对项目整体的规划或架构,而工具只是去帮我们实现这种规划或架构的一种手段。
- 一个工程化应该有的过程:
- 一些成熟的工程化集成
4、工程化与Node.js
- 工程化的一切都应该归功于Node。
- 如果说Ajax给前端带来了新的生命力,那么Node对于前端而言,它除了让JavaScript有了一个新的舞台,更多的是让我们整个前端行业进行了一次工业革命,可以毫无夸张的说,没有Node.js,就没有今天的前端。
- 前端工程化是由Node.js强烈驱动的。
脚手架工具
1、脚手架工具概要
- 前端工程化的发起者。
2、脚手架的作用
- 本质作用:创建项目基础结构、提供项目规范和约定。
- 通常开发相同类型的项目时,存在相同的约定:
- 相同的组织结构
- 相同的开发范式
- 相同的模块依赖
- 相同的工具配置
- 相同的基础代码
- 可以通过脚手架工具去快速搭建特定类型项目的骨架,然后去基于这个骨架进行后续开发工作。
- 比如:Eclipse/Visual Studio 大型IDE创建项目的过程就是一个脚手架的工作流程。
- 因为前端技术选项比较多样,另外也没有一个统一的标准,所以前端方向的脚手架不会集成在某一个IDE当中,都是以一个独立的工具存在,相对会复杂些。
- 脚手架目标都是一样的,都是为了解决我们在创建项目过程当中那些复杂的工作。
3、常用的脚手架工具
创建项目时使用的脚手架工具:
-
适用于特例项目类型服务的脚手架工具
目前一些成熟的脚手架工具(但大都是为了一些特例项目类型服务的):- React项目 => create-react-app
- Vue.js项目 => vue-cli
- Angular项目 => angular-cli
这些工具的实现方式都大同小异,无非都是根据信息创建对应的项目基础结构。
-
通用性脚手架工具: Yeoman
Yeoman
- 是一款用于创造现代化web应用的脚手架工具。
- 不同于cli工具,Yeoman更像是一个脚手架运行平台,我们可以通过它,搭配不同的Generator,去创建任何类型的项目。
- Yeoman过于通用,不够专注。
- Yeoman是一款基于Node.js开发的一个工具模块。
- 使用步骤:
- 明确你的需求
- 全局范围安装yo:yarn global add yo
- 找到合适的Generator
- 全局范围安装找到的Generator:yarn global add generator-node
- 通过yo运行对应的Generator:yo node
- 通过命令行交互填写选项
- 生成你所需要的项目结构
项目开发过程中使用的脚手架工具:
- Plop
用于在项目开发过程中去创建一些特定类型的文件,例如创建一个组件/模板所需要的文件。
Plop是一款小而美的脚手架工具。
4、脚手架的工作原理
脚手架工具其实就是一个node cli应用。
自动化构建
一切重复化工作本应被自动化。
1、NPM Scripts
实现自动化构建工作流最简单的方式。
2、常用的自动化构建工具
Grunt
- Grunt是最早的自动化构建工具,它的插件生态非常完善。
- 由于它的工作过程是基于临时文件生成的,所以它的构建速度相对较慢。
Gulp
- 很好的解决了Grunt中构建速度非常慢的问题,因为它是基于内存去实现的。
- 它默认支持同时去执行多个任务,效率自然大大提高。
- 使用方式相对于Grunt直观很多。
- 插件生态也同样非常完善。
- Gulp的组合任务
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的异步任务
//Gulp的异步任务的三种方式
//1、回调函数方式
exports.callback = done => {
console.log('callback task');
done()
}
exports.callback_error = done => {
console.log('callback error task');
done(new Error('task failed!'))
}
//2、Promise方式
exports.promise = () => {
console.log('promise task');
return Promise.resolve()
}
exports.promise_error = () => {
console.log('promise error task');
return Promise.reject(new Error('task failed!'))
}
//3、语法糖方式
const timeout = time => {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
exports.async = async () => {
await timeout(1000)
console.log('async task');
}
//其他方式
const fs = require('fs')
// exports.stream = () => {
// const readStream = fs.createReadStream('package.json')
// const writeStream = fs.createWriteStream('temp.txt')
// readStream.pipe(writeStream)
// return readStream
// }
exports.stream = done => {
const readStream = fs.createReadStream('package.json')
const writeStream = fs.createWriteStream('temp.txt')
readStream.pipe(writeStream)
readStream.on('end', () => {
done()
})
}
- Gulp文件操作
const { src, dest } = require('gulp')
const cleanCss = require('gulp-clean-css')
const rename = require('gulp-rename')
exports.default = () => {
return src('src/normal.css')
.pipe(cleanCss())
.pipe(rename({ extname: '.min.css' }))
.pipe(dest('dist'))
}
- Gulp构建过程核心工作原理
//Gulp构建过程核心工作原理
const fs = require('fs')
const { Transform } = require('stream')
exports.default = () => {
//文件读取流
const read = fs.createReadStream('a.css')
//文件写入流
const write = fs.createWriteStream('a.min.css')
//文件转换流
const transform = new Transform({
transform: (chunk, encoding, callback) => {
//核心转换过程实现
//chunk=> 读取流中读取到的内容(Buffer)
const input = chunk.toString()
const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
callback(null, output)
}
})
//把读取处理的文件流导入写入文件流
read.pipe(transform) //转换
.pipe(write) //写入
return read
}
- Gulp实现的自动化构建
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, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
.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/**', 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: {
baseDir: ['temp', 'src', 'public'],
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const useref = () => {
return src('temp/*.html', { base: 'temp' })
.pipe(plugins.useref({ searchPath: ['temp', '.'] }))
// 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'))
}
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
}
封装自动化构建工作流
FIS
- 是百度前端推荐的一款前端构建系统。
- FIS更像是一种捆绑套餐。
- 可以很轻松地去处理资源加载,模块化开发、代码部署和性能优化。