当今的网站已经发展为web app:
- 越来越多的js脚本放入页面;
- 在现代浏览器中,我们能做越来越的事情;
- 如今很少有网站会采用整页面重载,因而页面的代码越来越多;
最终导致大量的代码放入客户端中了。
庞大的前端代码库需要重新组织。模块系统提供了这样的能力来帮助你拆分代码库到各个模块中。
目录
- Module system styles
<script>
标签(无模块化)- CommonJs: (同步加载)
- AMD: (异步加载)
- ES6 模块系统
- 公正的解决方案
- 传输
- 分块传输
- 为什么仅仅是Javascript
- 静态分析
- 策略
Module system styles
如今有很多标准来定义依赖(dependencies)和导出(export)的值:
<script>
标签(无模块化)- CommonJs: (同步加载)
- AMD: (异步加载)
- ES6 模块系统
- 更多…
<script>
标签(无模块化)
下面是未使用模块系统来加载使用已经模块化了的代码库
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="libraryA.js"></script>
<script src="module3.js"></script>
这种方式会把加载的模块对象添加到全局对象上,如window对象。模块能通过window对象访问到其所依赖的各模块对象。
存在的问题
- 全局对象命名冲突;
- 加载顺序非常重要;
- 开发人员不得不解决模块/库之间的依赖;
- 在大型项目中,如此多的引入脚本是相当费时和难以维护的;
这种方式使用同步require方法加载依赖并返回对外的接口。一个模块能通过给exports对象添加属性或者给module.exports设置值来指定开放哪些接口。
require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;
CommonJs多用于服务端NodeJs
支持者
- 服务端模块能复用;
- npm中已经有很多模块使用该方式;
- 非常简单和容易使用;
反对者
- 阻塞式的加载不适用于网络环境,网络请求通常都是异步的;
- 无法并行请求多个模块;
基于该方式实现的库
- node.js - server-side
- browserify
- modules-webmake - compile to one bundle
- wreq - client-side
AMD: (异步加载)
异步模块定义Asynchronous Module Definition
对于浏览器端的模块系统使用同步加载的方式会存在问题,因而引入了异步加载的方式。
require(["module", "../file"], function(module, file) { /* ... */ });
define("mymodule", ["dep1", "dep2"], function(d1, d2) {
return someExportedValue;
});
支持者
- 适合网络环境的异步请求方式;
- 多模块的并行加载;
反对者
- 编码开销:更难以读和编写;
- 看起来像某种变通方案;
基于该方式实现的库
- require.js - client-side
- curl - client-side
ES6模块系统
EcmaScript6添加了一些语言结构到javascript当中,使之形成了另一套模块系统。
import "jquery";
export function doStuff() {}
module "localModule" {}
支持者
- 静态分析很容易;
- 成为ES标准:不会过时;
反对者
- 本地浏览器支持尚需时日;
- 使用这种方式的模块太少;
公正的解决方案
允许开发者自己选择使用哪种模块系统;兼容现有的代码;添加自定义模块更加容易;
传输
模块代码需要在客户端执行,因此它们必须要从服务端传输到浏览器中。
有两种传输模块的极端方式:
- 一个请求一个模块;
- 一个请求所有模块;
两种方式都使用很广,但都不是最佳方案:
一个请求一个模块;
- 支持者:只传输必须加载的模块;
- 反对者:很多的请求意味着更多的开销;
- 反对者:因为延迟加载,导致应用程序启动变慢;
一个请求所有模块;
- 支持者:更少的请求开销,更少的延迟;
- 反对者:会加载不需要(多余)的模块;
分块传输
一种更灵活的传输方式可能会更好。在很多案例中对于上述两种极端情形进行折中会更合适。
当编译所有模块时:可以将多个模块组成模块集分别放入多个更小的打包分块的文件中。
我们会得到大量更小的请求。大模块集不会在初始化时就加载,只有当需要时才会去加载它们。初次加载不会请求项目所有的代码库。
代码分割的“拆分点 ”取决于开发者,并且是可选的。
编写大代码库成为可能。
注:该思想来源于Google’s GWT
阅读更多关于代码拆分
为什么仅仅是Javascript
为什么一个模块系统只能帮助开发者处理Javascript。要知道前端代码中存在很多需要被处理的其他静态资源:
- 样式表
- 图片
- 网络字体
- html模板
- 等等…
同时还有:
- coffeescript → javascript
- elm → javascript
- less/sass stylesheets→ css stylesheets
- jade templates → javascript which generates html
- i18n files → something
- 等等…
这些都应该同样可以简单使用:
require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");
阅读更多关于Using Loaders和Loaders
静态分析
当编译所有模块时,静态分析试图查找所有的依赖。
通常静态分析只能通过简单没有表达式的字符串进行查找,但是像require("./template/" + templateName + ".jade")
是一种很常见的结构。
很多库使用不同的风格,有些事非常怪异的…
策略
一个聪明的解析器需要能够保证大量已经存在的代码可以运行。如果开发人员使用了一些怪异的方式编写,解析器要能找到最兼容的解决方案。