前端工程化
前端工程化的必要性
“软件工程”是一门研究如何系统化、规范化、数量化的开发和维护软件的学科
随着前端项目复杂度的增加、代码量加大、更高的性能要求,出现了大量的重复性工作,例如:
- JavaScript、css文件的混淆和压缩
- 图片的压缩和雪碧图合并
- ES6、Sass/Less的预编译
- 三方件的依赖管理和瘦身
- 自动化的静态代码检查、测试执行
- 自动化打包部署
- 本地调试时的自动编译和浏览器自动刷新
- ……
发展历史
石器时代
早期的Web页面主要承担静态内容的展现,用户交互仅仅限于提交表单或者点击链接。
页面内容通常由后台生成,例如在php或jsp中,在页面中嵌入后台代码。
随着项目复杂度的增加,这种方式已经难以维护。
铜器时代
比较典型的改进是组件化和异步加载
以Gmail为代表的Web 2.0时代的到来,大量AJAX技术的应用,使得页面提供更为丰富的用户交互功能,承担了更多的业务逻辑。
以往的方式已经难以处理快速增加的复杂度,独立的业务逻辑被分割到单独的js文件中,页面通过*
农业时代
使用script标签引入脚本时,当脚本之间存在依赖关系,就必须要保证脚本的加载顺序,同时,这种方式下所有的内容都是声明在全局变量之上,依然难以满足高度复杂的业务需要。
由于JavaScript自身的先天缺陷,缺少模块化的支持,开发人员通过命名空间、AMD、CMD等模块化方案来分割模块。
工业时代
伴随着移动互联网的快速发展,前端承担了越来越多的业务应用。为了降低开发难度,合理的控制项目复杂性,各种MV*框架被应用到实际项目中。
同时,ES6标准的确定、前后端分离的普及、工程化理念的加深,出现了预编译、自动化测试、静态代码检查、工程化管理等优秀的开发实践,使得开发人员可以充分利用自动化工具来简化日常工作、提高工作效率。
常见的工程化工具
- Grunt - 基于JavaScript的任务管理器,通过配置文件和外置插件,自动进行预编译、混淆压缩、单元测试、项目构建等自动化任务
- Gulp - 针对Grunt一次性加载文件,导致构建效率低、系统资源消耗高的缺陷,Glup通过流技术将待处理的文件在插件之间传递。
- Webpack - 后起之秀却有着一统天下之势,能够把包括脚本文件、样式文件、图片等所有资源都进行加载和处理
基于Gulp构建ES6、Sass应用
构建一个简单的应用
- 创建一个定时器,在页面刷新当前事件
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Main</title>
<link rel="stylesheet" href="sass/index.scss">
</head>
<body>
<div id="timer"></div>
<script type="text/javascript" src="js/timer.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>
- index.js
window.onload = () => {
load()
}
function load() {
const dom = document.getElementById('timer')
const timer = new Timer(dom)
timer.render()
}
- timer.js
class Timer { constructor(dom) { this.dom = dom } render() { setInterval(() => { this.dom.textContent = new Date() }, 1000) } }
- index.scss
#timer {
color: cornflowerblue;
font-size: 20px;
font-weight: bold;
}
安装gulp和gulp-cli
$ yarn add gulp gulp-cli --dev
加入静态代码检查,纠正编码风格
- 安装eslint(全局安装),和eslint-cli(本地安装)
$ yarn global add eslint $ yarn add eslint-cli --dev
- 通过命令行初始化配置文件,回答一系列问题后,生成.eslintrc文件
$ eslint --init
module.exports = {
"env": {
"browser": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
]
}
};
- 安装gulp的eslint插件
$ yarn add gulp-eslint --dev
- 创建gulpfile.js文件,加入eslint任务
const eslint = require('gulp-eslint')
const gulp = require('gulp')
gulp.task('eslint', () => {
gulp.src('src/**/*.js')
.pipe(eslint({
useEslintrc: true // 使用.eslintrc配置文件
}))
.pipe(eslint.format()) // 输出检查结果
.pipe(eslint.failAfterError() // 检查失败时,终止任务
})
- 执行gulp task命令执行检查,看似简单的实现却隐藏者众多的缺陷
- 修复错误后,任务正常完成
类似的步骤,添加sass的静态检查
需要先安装gem:sudo gem install scss_lint
$ yarn add gulp-sass-lint --dev
gulp.task('scsslint', () => {
gulp.src('src/**/*.scss')
.pipe(scsslint())
.pipe(scsslint.failReporter())
})
使用babel编译js文件
$ yarn add babel babel-cli --dev
$ yarn add gulp-babel babel-preset-es2015 --dev
gulp.task('compile-js', () => {
gulp.src('src/**/*.js')
.pipe(babel({
presets: ['es2015'] // 使用es6插件集编译
}))
.pipe(gulp.dest('dist/js')) // 编译后的文件输出到dist
})
编译后的文件位于dist目录下:
编译scss文件
$ yarn add gulp-sass --dev
gulp.task('compile-scss', () => {
gulp.src('src/**/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest('dist/css')) // 编译后的文件输出到dist
})
代码合并、压缩、重命名
$ yarn add gulp-concat gulp-uglify gulp-clean-css gulp-rename --dev
gulp.task('concat', ['compile-js', 'compile-scss'], () => {
gulp.src('dist/css/**/*.css')
.pipe(concat('style.css'))
.pipe(gulp.dest('dist/latest'))
gulp.src('dist/js/**/*.js')
.pipe(concat('app.js'))
.pipe(gulp.dest('dist/latest'))
})
gulp.task('minify', ['concat'], () => {
gulp.src('dist/latest/app.js')
.pipe(uglify())
.pipe(rename((path) => {
return path.basename += '.min'
}))
.pipe(gulp.dest('dist/latest'))
gulp.src('dist/latest/style.css')
.pipe(cleanCSS())
.pipe(rename((path) => {
return path.basename += '.min'
}))
.pipe(gulp.dest('dist/latest'))
})
监听文件变化
- 当文件变更时,重新生成文件
gulp.task('watch', ['concat'], () => {
gulp.watch(['src/**/*.js', 'src/**/*.scss'], ['concat'])
})
替换html中的url引用
$ yarn add gulp-html-replace --dev
在html文件中,通过注释配置替换块
其中build后的名称与gulp中的配置对应:
gulp.task('html-replace',function() {
return gulp.src('./src/*.html')
.pipe(htmlreplace({
'css': 'style.min.css',
'js': 'app.min.js'
}))
.pipe(gulp.dest('./dist/latest'));
});
执行替换后的html文件:
文件变化后,自动刷新浏览器
$ yarn add browser-sync --dev
gulp.task('watch', ['concat', 'minify', 'html-replace'], () => {
gulp.watch(['src/**/*.js', 'src/**/*.scss'], ['concat', 'minify', 'html-replace'])
})
gulp.task('browser-sync', ['watch'], () => {
browserSync({
server: 'dist/latest'
})
})
使用webpack构建一个React应用
安装相关依赖
$ yarn add react react-dom
$ yarn add babel-core babel-loader babel-preset-latest babel-preset-react webpack webpack-dev-server --dev
初始化项目结构如下
简单实现页面功能
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Webpack Demo</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script> <!-- 引用打包后的文件 -->
</body>
</html>
- index.js
import React, {Component} from 'react'
import ReactDom from 'react-dom'
import Hello from './hello'
class App extends Component {
render() {
return (
<div>
<Hello/>
</div>
)
}
}
ReactDom.render(<App />, document.getElementById('root'))
- hello.js ```js import React, {Component} from ‘react’
export default class Hello extends Component { render() { return ( <div> Hello </div> ) } }
### 创建webpack配置文件*webpack.config.js*
```js
const webpack = require('webpack')
module.exports = {
entry: __dirname + '/app/index.js', // 入口文件
output: {
path: __dirname + '/public', // 输入路径
filename: 'bundle.js' // 打包文件名
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015', 'react']
}
}
}
]
},
devServer: {
contentBase: './public',
inline: true
}
}