《前端工程化设计与实践》阅读笔记

前端工程简史

时间线

  • 2005 ajax
  • 2008 Google推出javascript引擎 V8,大大提高js运行性能,业内开始提倡REST api 和SPA客户端
  • 2009 Node.js问世

前端工程师技能栈

  1. 硬技能:html/css/js,良好的抽象能力和架构能力
  2. 软技能: 用户体验 ,就是性能
  3. 扩展技能: Node.js 代表的web服务端知识。
    有助于前端工程师编写更合理的客户端逻辑和即使定位问题。

前端工程体系不是vue/react这种业务开发框架,而是一种服务,对象是一线业务开发人员。


构建

Node.js 是前端工具得以发展的技术基础, 前端项目不断复杂化是催动前端工具发展的环境因素。
构建,或者叫编译,是将源代码转化为宿主浏览器可执行的代码。

构建需要解决的问题归纳为3类

  • 面向语言(Less/sass/jade/es6)
  • 面向优化
    • 依赖打包,将同步依赖的文件打包在一起,减少http请求数量
    • 资源嵌入,小于10k图片转base64, 减少http请求
    • 文件压缩
    • hash指纹, 文件名加入hash指纹,以应对浏览器缓存策略
  • 面向部署(本地、生产环境)

ECMAScript

Javascript = ECMAScript + 宿主API
ES6给javascript跨时代的意义,不仅仅是语言本身加入了类、静态模块体系、块级作用域等高级编程语言的特性,更多的是以ES6为代表的前端界规范意识的加强。但是目前浏览器对ECMAScript规范的实现仍远远落后于规范的更新。即使最新版本的Chrome浏览器也没有完全支持ECMAScript2015,但可以使用Babel-下一代Js语法编译器。

CSS预编译与postCSS

  1. 使用CSS预编译弥补CSS源码的弱编程能力,比如变量、运算、继承、模块化等。
  2. 使用PostCSS处理针对浏览器的需求,比如autoprefix, 自动CSS Sprites

webpack配置

loader按照索引反向执行

1
2
3
4
5
6
7
8
{
test: /\.less$/,
use:[
'style-loader',
'css-loader',
'less-loader'
]
}

css-loader作用是解析css源文件并获取其引用的资源,比如@import或url(),然后根据webpack配置编译这些资源。
style-loader负责将css代码通过style标签出入html文档,所以独立导出css就不需要style-loader, 可以替换为extract-text-webpack-plugin,如下

1
2
3
4
5
6
7
8
9
10
11
{
test: /\.less/,
use: ExtractTextPlugin.extract({
use:[
{loader:'css-loader', options:{ importLoaders:2 }},
{loader:'postcss-loader', options:{}},
{loader:'less-loader', options:{}}
]
publicPath: '/'
})
}

模块化开发

前端模块化发展史

CommonJs

最初叫ServerJs, 是一种只适合于JavaScript的静态模块化规范,适合Node.js,并不适合浏览器,因为浏览器的前端资源 不仅仅是js, 还有css、图片等,而且CommonJs所有模块是同步阻塞加载,无法实现按需异步加载

AMD/CMD–着力于浏览器的模块化规范

在CommonJS基础上,AMD/CMD扩展了一下功能

  • 可以处理js以外的资源
  • 源码无需编译可在浏览器运行
  • 按需异步加载、并行加载
  • 插件系统

然而CommonJs、 AMD/CMD 三者共同缺点如下:

- 缺点
1 应用场景单一,无法跨环境
2 构建工具不统一,比如针对CommonJs的Broserify,AMD的r.js,CMD的SPM
3 不同规范模块无法混合使用,模块复用性不高
4 未来不可期

ES6 Module

ES6 Module是一种静态模块体系,在最新版本的Node.js中完全可以取代CommonJS,目前处于stage 3阶段的import()函数可以满足按需加载需求。受限于浏览器的实现程度,目前针对浏览器的模块仍然需要构建工具进行编译。

webpack模块化构建

webpack支持CommonJs/ AMD 和ES6。 针对三者异步模块构建中存在的细微差别

模块化规范 特点 构建
CommonJs 不具备异步加载功能 借助webpack提供的api实现(require.ensure API是webpack v1版本,已废弃,v2推荐import函数)
AMD 具备异步加载功能 不能定义输出的文件名称,欠缺语义
ES6 不支持异步加载 目前处于stage3的import()函数已被webpack支持

综上所述,AMD虽然具备异步加载功能,但是webpack对其支持度并不理想。CommonJs和ES6 Module虽然自身不支持异步加载,但是webpack提供了require.ensuire API和支持import()函数。从可移植性角度考虑,虽然require.ensure是webpack特有的api,如果代码移植到其他构件系统,此api会引起未知的错误。import函数处于stage 3状态,非常有可能加入未来的正式规范中,从可移植性和未来可期角度考虑,优于require.ensure。唯一缺点就是需要借助 特注释定义异步文件的名称。

增量更新与缓存

客户端的缓存分以下两种

  1. 本地存储,如localStorage/ SessionStorage等
  2. 利用http缓存,又分为强缓存和协商缓存

增量更新是目前大部分团队采用的缓存更新方案,结合http强缓存,既能保证用户第一时间获取最新资源,又能减少网络消耗。前端工程体系在此中作用如下:

  1. 构建产出文件hash指纹,这是实现增量更新必要条件
  2. 构建更新html文件及其他静态资源的引用url

hash指纹?通过既定的数据摘要算法(MD5)计算出的文件的值,文件内容修改,hash值跟着修改

策略 实现 缺点 优点
覆盖更新 在引用资源的url后加参数?v=1.0 手动操作耗时耗力 -
增量更新 hash指纹作为资源文件名的一部分 利于版本回滚和资源同步

ip、路径、后缀、参数不同的url会被浏览器视为全新的url,发出实体请求,但是hash的改变不会触发浏览器发出请求,这也是单页面应用使用hash作为路由的原因

按需加载与多模块加载架构场景下的增量更新

同步模块修改影响主模块的hash指纹,对异步文件无影响。
异步模块的修改影响对应异步文件的hash指纹,主文件hash指纹也同步修改,这样才能保证用户得到最新的异步文件。(coz主文件可能引用异步模块)

webpack实现增量更新构建方案

hash与chuckhash
hash定义: The hash of the compilation.compilation对象不是针对单个文件,而是针对项目中所有参与构建的文件。因此如果此hash作为构建输出的hash指纹,任何一个文件改动都会影响所有资源的缓存。hash并与不适用于增量更新的构建场景

chuckhash定义: 散列模块经过合并后的块,chuckhash就是一个个块依据自身的代码内容计算得到的hash值

contentHash: 解耦js与css文件hash指纹的关键。
js在webpack中是一等公民,其他类型的资源必须借助js文件参与构建,比如css在js中引入,如main.js引入main.app.css, webpack默认将构建的css代码合并到引用它的js文件中,然而实际项目中往往吧css抽离出来独立维护,有利于浏览器渲染优化和利用客户端缓存。
contentHash并不是webpack自身另外一种hash值,而是有ExtractTextPlugin提供,代表被导出内容计算后的hash