[TOC]
什么是前端工程化
前端工程化:将复杂的前端应用模块化、组件化、规范化、自动化的一个过程。
解决的问题:做到多人协作的高效性;做到项目的可维护性;提高项目的开发质量;
一、模块化
模块化是针对文件层面,将大文件拆分成互相依赖的小文件,再进行统一的拼装和加载。
1.JS模块化
1.1 CommonJs
服务器端javascript模块化解决方案,适用于同步模块加载。NodeJs是CommonJs规范的主要践行者。
CommonJs用同步的方式加载模块,在服务端,模块文件都存在本地磁盘,读取非常快。但是在浏览器端,限于网络原因,更合理的方式是采用异步加载。
// 定义模块math.js
var basicNum = 0;
function add(a, b) {
return a + b;
}
module.exports = { //在这里写上需要向外暴露的函数、变量
add: add,
basicNum: basicNum
}
// 引用自定义的模块时,参数包含路径,可省略.js
var math = require('./math');
math.add(2, 5);
// 引用核心模块时,不需要带路径
var http = require('http');
http.createService(...).listen(3000);
1.2 AMD 和require.js
AMD采用异步方式加载模块,模块的加载不影响后续语句的运行。所有依赖这个模块的语句,都统一定义在一个回调函数中,等到加载完成后,这个函数才会运行。require.js实现AMD规范的模块化:用require.config()指定引用路径,用define()定义模块,用require()加载模块。
首先需要引入require.js和入口文件main.js。main.js中配置require.config()并规定项目中用到的基础模块。
/** 网页中引入require.js及main.js **/
/** data-main 表示指定的js将在加载完require.js后处理,并且require会默认的将data-main指定的js作为根路径 */
<script src="js/require.js" data-main="js/main"></script>
/** main.js 入口文件/主模块 **/
// 首先用config()指定各模块路径和引用名
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min", //实际路径为js/lib/jquery.min.js
"underscore": "underscore.min",
}
});
// 执行基本操作
require(["jquery","underscore"],function($,_){
// some code here
});
引用模块的时候,将模块名放在[ ]
中作为require()
的第一参数;如果定义的模块本身也依赖其他模块的时候,需要将它们放在[ ]
中作为define()
的第一参数。
// 定义math.js模块
define(function () {
var basicNum = 0;
var add = function (x, y) {
return x + y;
};
return {
add: add,
basicNum :basicNum
};
});
// 定义一个依赖underscore.js的模块
define(['underscore'],function(_){
var classify = function(list){
_.countBy(list,function(num){
return num > 30 ? 'old' : 'young';
})
};
return {
classify :classify
};
})
// 引用模块,将模块放在[]内
require(['jquery', 'math'],function($, math){
var sum = math.add(10,20);
$("#sum").html(sum);
});
1.3 CMD和sea.js
CMD是另一种js模块化方案,它与AMD和类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范是在推广sea.js过程中产生的。
/** AMD写法 **/
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
// 等于在最前面声明并初始化了要用到的所有模块
a.doSomething();
if (false) {
// 即便没用到某个模块 b,但 b 还是提前执行了
b.doSomething()
}
});
/** CMD写法 **/
define(function(require, exports, module) {
var a = require('./a'); //在需要时申明
a.doSomething();
if (false) {
var b = require('./b');
b.doSomething();
}
});
/** sea.js **/
// 定义模块 math.js
define(function(require, exports, module) {
var $ = require('jquery.js');
var add = function(a,b){
return a+b;
}
exports.add = add;
});
// 加载模块
seajs.use(['math.js'], function(math){
var sum = math.add(1+2);
});
1.4 UMD
UMD允许在环境中同时使用AMD和CommonJs规范。
(function(define) {
define(function () {
var helloInLang = {
en: 'Hello world!'
};
return {
sayHello: function (lang) {
return helloInLang[lang];
}
};
});
}(
typeof module === 'object' && module.exports && typeof define !== 'function' ?
function (factory) { module.exports = factory(); } :
define
));
1.5 ES6 Module
ES6 的模块功能汲取了CommonJS 和 AMD 的优点,拥有简洁的语法并支持异步加载,并且还有其他诸多更好的支持。(例如导入是实时只读的。(CommonJS 只是相当于把导出的代码复制过来))
// CommonJS代码
// lib/counter.js
var counter = 1;
function increment() {
counter++;
}
function decrement() {
counter--;
}
module.exports = {
counter: counter,
increment: increment,
decrement: decrement
};
// src/main.js
var counter = require('../../lib/counter');
counter.increment();
console.log(counter.counter); // 1
// 使用 es6 modules 通过 import 语句导入
// lib/counter.js
export let counter = 1;
export function increment() {
counter++;
}
export function decrement() {
counter--;
}
// src/main.js
import * as counter from '../../counter';
console.log(counter.counter); // 1
counter.increment();
console.log(counter.counter); // 2
1.5.1 ES6
模块与CommonJs
模块的差异
-
CommonJS
模块输出的是一个值得拷贝,ES6输出的是值得引用。-
CommonJS
模块输出的是值得拷贝,一旦输出一个值,模块内部的改变影响不到这个值。 -
ES6
模块是动态引用,变量会随着模块的改变而改变。
-
-
CommonJS
模块是运行时加载,ES6
是编译时输出接口。- 运行时加载:
CommonJS
模块就是对象;在输入时先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为"运行时加载"。 - 编译时加载:
ES6
模块不是对象,而是通过export
命令格式显示指定输出的代码,在import
时可以指定加载某个输出值,而不是加载整个模块,这种加载称为"编译时加载"。
- 运行时加载:
2.CSS
的模块化
在less、sass、stylus等预处理器的import/mixin特性支持下实现、css modules。
虽然SASS、LESS、Stylus等预处理器实现了CSS的文件拆分,但没有解决CSS模块化的一个重要问题:选择器的全局污染问题;
CSS in JS是彻底抛弃CSS,使用JS或JSON来写样式。这种方法很激进,不能利用现有的CSS技术,而且处理伪类等问题比较困难;
CSS Modules 原理:使用JS 来管理样式模块,它能够最大化地结合CSS生态和JS模块化能力,通过在每个 class 名后带一个独一无二 hash 值,这样就不有存在全局命名冲突的问题了。
webpack 自带的 css-loader 组件,自带了 CSS Modules,通过简单的配置即可使用。
{
test: /\.css$/,
loader: "css?modules&localIde ntName=[name]__[local]--[hash:base64:5]"
}
二、组件化
从UI拆分下来的 每个包含模板(HTML)+样式(CSS)+逻辑(JS)功能完备的结构单元,我们称之为组件。
模块化只是在文件层面上,对代码或资源的拆分;而组件化是在设计层面上,对UI(用户界面)的拆分。
其实,组件化更重要的是一种分治思想。
Keep Simple. Everything can be a component.
这句话就是说页面上所有的东西都是组件。页面是个大型组件,可以拆成若干个中型组件,然后中型组件还可以再拆,拆成若干个小型组件,小型组件也可以再拆,直到拆成DOM元素为止。DOM元素可以看成是浏览器自身的组件,作为组件的基本单元。
三、规范化
规范化其实是工程化中很重要的一个部分,项目初期规范制定的好坏会直接影响到后期的开发质量。
- 目录结构的制定
- 编码规范
- 前后端接口规范
- 文档规范
- 组件管理
-
Git
分支管理 -
Commit
描述规范 - 定期
CodeReview
- 视觉图标规范
四、自动化
任何简单机械的重复劳动都应该让机器去完成。
- 图标合并
- 持续集成
- 自动化部署
- 自动化构建
- 自动化测试