我们为什么需要Webpack?
现在的网站都在演变成为Web Apps:
- 页面上的JavaScript越来越多。
- 在现代浏览器上用户可以做更多的事情了。
- 整个页面重新加载的情况更少了,与此同时,页面上的代码量更大了。
结果就是:客户端的代码量变得越来越庞大,庞大的代码量意味着我们需要适当地组织代码,而模块系统则提供了把代码分割成不同模块的功能。
模块系统实现的演变
关于怎么定义依赖和导出接口有多种标准:
- Script标签形式
- CommonJS
- AMD和一些变种实现
- ES6模块
- 其它
script标签形式
如果不对代码进行模块化,那么通常的做法就是使用标签引入script文件:
<script src="src=module1.js"></script>
<script src="src=module2.js"></script>
<script src="librariesA.js"></script>
<script src="src=module3.js"></script>
模块把接口导出到全局对象上,比方说window对象上。也就是说模块可以在全局对象上获取到依赖的接口。
这样做的问题是:
- 全局对象上可能会出现命名冲突
- 加载的顺序很重要
- 程序员必须要手动处理模块/库的依赖问题
- 在大型项目里面,这个列表可能会很长、很难管理
CommonJS:同步的require
CommonJS采用同步的require方法来加载依赖并返回导出的接口。一个模块可以通过往exports对象上添加属性或者设置module.exports的值来确定导出哪些接口。
require("module");
require("../file.js");
exports.doStuff = function(){};
module.exports = someValue;
后端的nodejs用的就是这个方法。
优点:
- 服务端的模块可以被复用
- 已经有很多模块供使用了(npm)
- 很容易上手使用
缺点:
- 阻塞式的调用不能适用于网络请求,因为网络请求是异步的
- 无法同步require多个模块
使用CommonJS的项目:
- Nodejs -服务端
- Browserify
- Module-webmake -编译成一个bundle
- Wreq -服务端
AMD:异步require
之前提到的模块系统只能同步require,而AMD则采用异步的实现形式。
require(["module","../file.js"],function(module,file){/*...*/});
define("mymodule",["dep1","dep2"],function(d1,d2){
return someExportValue;
});
优点:
- 能满足网络请求的异步需求
- 能同步加载多个模块
缺点:
- 代码复杂,更难写也更难读
- 似乎是一种绕路的笨办法
使用AMD的项目:
- Require.js -客户端
- Curl -客户端
ES6模块
EcmaScript6给JavaScript加了一些语言特性,其中就包括了模块系统。
import "jQuery";
export function doStuff(){}
module "localModule"{}
优点:
- 静态分析变得更简单了
- 作为ES标准,这个实现未来肯定会成为主流
缺点:
- 浏览器原生支持还需要时间
- 目前只有很少的一些模块可用
最合适(unbiased)的解决办法
让开发人员选择合适的模块系统,让代码能跑起来,也能使得添加自定义模块更简单易行。
模块的传输
模块需要在客户端执行,因此它们需要从服务器传输到浏览器上。
有两个极端的方法来传输模块:
- 每传输一个模块发起一个请求
- 所有的模块都放在一个请求里
这两种方法都有人在用,但是都不是最优解:
- 每传输一个模块发起一个请求
- 优点:只会请求目前需要的模块
- 缺点:很多请求意味着会有很多额外的消耗
- 缺点:请求延迟会使得程序启动变慢
- 所有的模块放在一个请求里
- 优点:更少的请求意味着更低的延迟
- 缺点:无论目前需要与否,所有的模块都一次性传输过来了
分块传输
我们需要一个更灵活的传输方式,在大多数情况下,如果能在极端中间找到一个平衡会是最好的选择。比方说,在编译所有的模块的时候可以把模块分割成很多小模块。这样一来,我们就可以把请求分成很多小请求,而分割后的模块只有在需要的时候才会被请求。所以初始的请求不会包含所有的代码,从而减小传输压力。至于代码怎么分割由程序员决定。
这个想法来源于Google's GWT. 更多的信息在code splitting.
为什么只处理JavaScript?
为什么模块系统只能处理JavaScript?还有很多其他的静态资源需要处理:
- 样式表
- 图片
- 字体
- html模板
- 等等
还有:
- coffeescript ->javascript
- less 样式表 ->css 样式表
- jade 模板 ->javascript which generates html
- i18n files -> something
- 等等
这些都应该很容易处理才对:
require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");
这些都可以做到,更多相关的信息在Using loader和loader章节。
静态分析
在编译所有的模块的时候,静态分析会试图去找到依赖。通常来说在没有表达式的情况下只能找到一些简单的东西,但是像
require("./template/" + templateName + ".jade")
这样的表示方法是很常见的形式。 不同的库会用不同的形式表示,有一些看起来很奇怪...
分析策略
一个智能的解析器会使得大多数代码能跑起来。即便程序员写了些很奇怪的代码,它还是能找到最兼容的办法。