webpack 是新兴的 web 前端开发工具,他让开发者可以模块化的方式开发 web 前端应用,
在模块化的 web 前端开发领域,还有两个知名工具 requirejs 和 browserify,
- requirejs 是 2011 年发起的老牌项目,曾经风靡一时,目前还有数量可观的使用者,但其使用和调试并不方便
- browserify 小 requirejs 一岁,完全离线的模块化方案,仅支持 CommonJS 模块规范
- webpack 是后起之秀,简单的说,他综合了前二者的优点,且弥补了他们的缺点
事实上还有许多三方解决方案可以用于 web 模块化开发,以下文字翻译自 webpack 文档:
Webpack motivation ,详述了 webpack 项目的发起动机:
今天众多网站已进化到 web app,其共同特点有:
- 单页面上越来越多的 javascript;
- 在现代浏览器中可以做更多事情;
- 整页 reload 越来越少 - 单页上的代码越来越多
结果是客户端会有 许多 代码
大量的代码需要组织。模块系统允许你将代码库拆分成模块
模块系统的风格
存在多种标准用于定义依赖项和模块导出值:
<script>
标签风格(无模块系统)- CommonJs
- AMD 及其方言
- ES6 模块
- 不胜枚举 …
<script>
标签
这是一种不用模块系统处理模块化代码库的方法
1 | <script src="module1.js"></script> |
模块导出接口到全局对象 - 例如:window
对象。模块可以通过全局对象访问依赖项的接口。
缺点
- 在全局对象中出现(定义)冲突
- loading 顺序很重要
- 开发者必须决定 模块/库 依赖
- 在大项目中,依赖列表变得很长,难于管理
CommonJs / 同步
这种风格使用同步的 require
方法加载依赖项并返回其导出的接口。一个模块如果想导出什么,只需往 exports
对象添加属性或者设置 module.exports
的值即可。
1 | require("module"); |
它被 node.js 用于服务器端。
优点
- 服务器端模块可以被重用
- 有许多模块已经是这种风格 (npm)
- 使用起来简单方便
缺点
- 阻塞调用在网络上并不好用。网络请求都是异步的。
- 多个模块没有并行请求
实现
- node.js - 服务器端
- browserify - 编译成一个或多个包
- modules-webmake - 编译成一个包
- wreq - 客户端
AMD / 异步
其他模块系统(浏览器)和同步版本的 require
(CommonJs)放在一起时出现了问题,因此引入其异步版本(以及相应的定义模块和导出值的方法)
1 | require(["module", "../file"], function(module, file) { /* ... */ }); |
优点
- 适合网络中的异步请求风格
- 并行加载多个模块
缺点
- 代码开销。更难阅读和书写
- 像是某种权宜之计(言下之意:并非良策)
实现
- require.js - 客户端
- curl - 客户端
ES6 模块
EcmaScript6 为 javascript 添加了一些语言结构, 这形成了另一种模块系统
1 | import "jquery"; |
优点
- 静态分析变容易了
- 不会过时的 ES 标准
缺点
- 本地浏览器支持还需要时间
- 很少有这种风格的(现成)模块
不偏不倚的解决方案
让开发者选择模块风格,让现有代码可以工作。添加自定义模块风格也很容易
传输
模块应在客户端执行,因此他们必须从服务器传输到浏览器
关于如何传输模块,有两种极端:
- 每个模块一个请求
- 一个请求传输所有模块
两种方法都使用广泛,但他们都不是最优的:
- 每个模块一个请求
- 优点: 只传输被请求的模块
- 缺点: 许多请求意味着开销
- 缺点: 拖慢应用的启动, 因为请求会延迟
- 一个请求传输所有模块
- 优点: 更少的请求开销, 更少的延迟
- 缺点: 未请求的模块也被传输了
分块传输
有种更灵活的传输方案可能更好,多数情形下在极端情况之间妥协是最好的选择。
→ 当编译所有模块时:模块集分成更小的批次(块)
我们得到多个更小的请求。模块中的分块初始时不需要,他们只在需要时被请求。初始请求不包含完整代码并且更小。
“拆分点” 由开发者决定并且是可选的
→ 单一的大代码库也可以
注释: 此创意 来自 Google 的 GWT.
更多细节参见 Code Splitting.
为什么只有 JavaScript?
一个模块系统为什么只应帮助 JavaScript ?还有许多其他静态资源需要处理:
- 样式表
- 图片
- web 字体
- html 模板
- …
并且:
- coffeescript → javascript
- less stylesheets → css stylesheets
- jade templates → javascript which generates html
- i18n files → something
- …
使用起来应尽量简单:
1 | require("./style.css"); |
1 | require("./style.less"); |
静态分析
当编译所有模块时,静态分析算法会尝试查找依赖项。
过去,require 不支持表达式,但是例如:require("./template/" + templateName + ".jade")
是很普通的结构
许多库都各自捣鼓一套风格。好多写法看上去非常怪异 …
策略
一个聪明的解析器会允许更多的现有代码顺畅运行。如果开发者做了些古怪的事,那么他也要为此寻找更多兼容的解决方案。