summary of requirejs optimization - PuZheng/PuZheng-Docs GitHub Wiki
请先看require.js原理分析, 对require.js的工作原理有个初步了解。
首先,require.js的压缩不是变魔术, 它只是在require.js的AMD的框架下, 尽量减少 网络请求,其背后的原理仅仅是, 将多个module注册在一个压缩后的文件里面。这样就 可以减少网络请求。
require.js的脚本压缩在很多开发者看来, 始终是搞得鸡飞狗跳的一件事情, 的确, 由于 异步加载,它的复杂性要高于传统的JS(即靠全局变量,串行加载)压缩。但是并非没有一个 一般解决方法。
在压缩前, 开发者首先得自己作出一个js压缩后的http请求规划:
- 是否利用第三方CDN, 利用CDN请求哪些脚本?
- 所有的应用脚本拆分成几个?
然后再根据方案, 编写压缩脚本。并且决定加载顺序。 特别需要强调, 压缩脚本的编写, 和应用代码的编写完全是两套思维, 做的是两件事情。
先规定一些术语:
- 对于支持AMD的脚本, 脚本被加载到浏览器, 并且被执行,等价于注册了一个模块。
- 只有当被其它模块显式require, 或者被依赖并且依赖模块被导入的时候, AMD模块才会被导入。即define的第三个参数指定的函数被执行。
再来说一下, 为什么AMD会对脚本压缩带来复杂性的原因, 假设有个不支持AMD的脚本, 依赖一个全局变量, 在开发时,其加载发生在引入这个全局变量之后, 但是:
- 在压缩后, 该脚本就有可能在这个全局变量之前加载。
- 有些脚本,比如A, 支持AMD, 但是有个依赖它的脚本,比如B, 只是把它当成普通的全局脚本, 在require.js环境下, A注册成了一个模块, 这个时候, 即使A脚本在B之前加载, B也找不到需要的全局变量, 只有A被导入了, B才能获取这个全局变量。
压缩脚本的工作就是在减少HTTP请求次数的同时, 保证全部变量的依赖不会被破坏。
再区分一下require和define, require是指示require.js去异步加载某个模块,顶级的require不会引导压缩脚本将需要的脚本引入, 而只有define中指定的依赖(以及后续的require)才会在压缩时,并入压缩后的脚本. 所以压缩后的脚本, 在require之后加载的模块,很可能跑到require异步加载前面去。
此外, 还有一个很容易出错的就是require-text, require-css这两个插件,当设定好了依赖, 你可能已经觉得着两个插件已经并入了压缩后的文件, 但是最终浏览器还是分别对这两个文件进行了http请求。原因就是, 这两个插件注册的模块名是实际的文件名, 例如"bower_components/text/text"。 为了找到它, 你需要在main.js中, 指示require.js用正确的名字去找到它们。只需要在require.config中增加一个 map参数即可:
map: {
'*': {
text: 'bower_components/text/text',
css: 'bower_components/css/css',
}
},
下面分析几个实际的例子:
案例一
假设在app.js中有:
define(['parsley'], function ($) {
});
在main.js中, jquery指向:
{
path: {
jquery: 'http://www.cdnboot.com/jquery/jquery.min',
parsley: 'bower_components/parsley/dist/parsley.min'
},
shim: {
parley: {
deps: ['jquery'],
export: "$.fn.parsley"
}
}
}
在开发时,没有任何问题, 因为可以确认,parsley一定是在jquery加载调用后,才被加载的。 但是一旦压缩, parsley并入main.js, 而页面的http请求顺序就是:
index.html
require.js
main.js -> 这里parsley已经被引入, 但是jquery还没有加载
jquery.js
针对此的解决方案就是: 将对jquery有依赖的所有脚本,压缩到一个jquery-deps.js, 保证这个脚本在jquery之后加载。
// jquery-deps.js
define(['jquery'], function () {
require(['parsley', ...], function () {}); // 注意, 这个是非顶级的require, 所以会将parsley压缩到jquery-deps中
});
// main.js
require(['jquery'], function () {
require(['jquery-deps', 'app'], function () {});
});
//build.js
{
modules: {
name: 'js/main',
exclude: ['jquery-deps']
}
}
这样, 压缩后的main.js就没有对jquery的依赖, 而页面的http请求顺序就是:
# index.html
# require.js
# main.js
# jquery.js
# jquery-deps.js
就解决了对jquery的依赖问题。
案例二
jquery在本地, 这个时候就不用考虑加载顺序, 将所有的文件打入main.js即可, 因为jquery在main.js中,先于所有依赖jquery的脚本被执行
案例三
backgrid依赖全局变量Backbone, 将backbone和backgrid打入一个压缩文件, 会导致 backgrid找不到Backbone, 原因是backbone的代码虽然已经执行了, 但是backbone支持 AMD, backbone注册成了require.js的模块, 除非backbone被导入, Backbone这个全局 变量不会暴露。解决方法是: 单独加载backgrid, 并且保证在backbone导入之后。
// build.js
{
modules: {
name: 'js/main',
// 注意,一定不能是backgrid模块,因为backgrid模块依赖backbone, underscore
// exclude: ['backgrid'] DON'T DO THIS
exclude: ['bower_components/backgrid/backgrid.min'] // 只排除backgrid文件本身
}
}
这样http请求顺序为:
# index.html
# require.js
# main.js
# *导入*backbone
# backgrid.js
这样backgrid就能找到Backbone了