优化 RequireJS 项目(合并与压缩) 已翻译 100%

jinker 投递于 2013/04/29 14:31 (共 7 段, 翻译完成于 05-04)
阅读 33309
收藏 97
8
加载中

本文将演示如何合并与压缩一个基于RequireJS的项目。本文中将用到苦干个工具,这其中就包括Node.js。 因此,如果你手头上还没有Node.js可以点击此处下载一个。

动机

关于RequireJS已经有很多文章介绍过了。这个工具可以将你的JavaScript代码轻易的分割成苦干个模块(module)并且保持你的代码模块化与易维护性。这样,你将获得一些具有互相依赖关系的JavaScript文件。仅仅需要在你的HTML文档中引用一个基于RequireJS的脚本文件,所有必须的文件都将会被自动引用到这个页面上.

但是,在生产环境中将所有的JavaScript文件分离,这是一个不好的做法。这会导致很多次请求(requests),即使这个些文件都很小,也会浪费很多时间。 可以通过合并这些脚本文件,以减少请求的次数达到节省加载时间的目的。

另一种节省加载时间的技巧是缩小这些被加载文件的大小,相对小一些的文件会传输的更快一些。这个过程叫作最小化 (minification) ,它是通过小心的改变脚本文件的代码结构并且不改变代码的形为(behavior)和功能(functionality)来实现的。例如这些:去除不必要的空格,缩短(mangling,或都压缩)变量(variables)名与函数(methods,或者叫方法)名,等等。这种合并并压缩文件的过程叫做代码优化( optimization)。这种方法除了用于优化(optimization)JavaScript文件,同样适用于CSS文件的优化。

RequireJS有两个主要方法(method): define()和require()。这两个方法基本上拥有相同的定义(declaration) 并且它们都知道如何加载的依赖关系,然后执行一个回调函数(callback function)。与require()不同的是, define()用来存储代码作为一个已命名的模块。 因此define()的回调函数需要有一个返回值作为这个模块定义。这些类似被定义的模块叫作AMD (Asynchronous Module Definition,异步模块定义)。

如果你不大熟悉RequireJS或者不太明白我写的东西 - 不要担心。下面有一个关于这些的例子。

裴宝亮
裴宝亮
翻译于 2013/05/04 11:51
4

JavaScript应用程序的优化

在本小节中我将向大家展示如何优化Addy Osmani的TodoMVC Backbone.js + RequireJS 项目。 由于TodoMVC项目在不同的框架下包含许多TodoMVC实现,我下载了1.1.0版并提取出Backbone.js + RequireJS应用程序。点击这里下载该应用程序并解压下载到的zip文件。todo-mvc的解压目录将是我们这个例子的根目录(root path),从现在起我将把这个目录引用为<root>。

查看<root>/index.html的源代码,你会发现它仅仅包含了一个script标签(另外一个是当你使用Internet Explorer时引用的):

index.html引用脚本文件的代码
<script data-main="js/main" src="js/lib/require/require.js"></script>
<!--[if IE]>
    <script src="js/lib/ie.js"></script>
<![endif]-->

其实,整个项目只需要引用require.js这个脚本文件。如果你在浏览器中运行这个项目,并且在你喜欢的(擅长的)调试工具的network标签中, 你就会发现浏览器同时也加载了其它的JavaScript文件:

Loaded JavaScript Files List
  所有在红线边框里面的脚本文件都是由RequireJS自动加载的。

裴宝亮
裴宝亮
翻译于 2013/05/04 12:03
2

我们将用RequireJS Optimizer(RequireJS优化器)来优化这个项目。根据已下载的说明文件,找到r.js并将其复制到<root>目录。 jrburke的r.js是一个能运行基于AMD的项目的命令行工具,但更重要的是,它包含RequireJS Optimizer允许我们对脚本文件(scripts)合并与压缩。

RequireJS Optimizer有很多用处。它不仅能够优化单个JavaScript或单个CSS文件,它还可以优化整个项目或只是其中的一部分,甚至多页应用程序(multi-page application)。它还可以使用不同的缩小引擎(minification engines)或者干脆什么都不用(no minification at all),等等。本文无意于涵盖RequireJS Optimizer的所有可能性,在此仅演示它的一种用法。

正如我之前所提到的,我们将用到Node.js来运行优化器(optimizer)。用如下的命令运行它(optimizer):

运行RequireJS Optimizer
$ node r.js -o <arguments>

有两种方式可以将参数传递给optimizer。一种是在命令行上指定参数:

在命令行上指定参数
$ node r.js -o baseUrl=. name=main out=main-built.js
另一种方式是构建一个配置文件(相对于执行文件夹)并包含指定的参数
$ node r.js -o build.js
build.js的内容:配置文件中的参数
({
    baseUrl: ".",
    name: "main",
    out: "main-built.js"
})
我认为构建一个配置文件比在命令行中使用参数的可读性更高,因此我将采用这种方式。接下来我们就为项目创建一个<root>/build.js文件,并且包括以下的参数: <root>/build.js
({
    appDir: './',
    baseUrl: './js',
    dir: './dist',
    modules: [
        {
            name: 'main'
        }
    ],
    fileExclusionRegExp: /^(r|build)\.js$/,
    optimizeCss: 'standard',
    removeCombined: true,
    paths: {
        jquery: 'lib/jquery',
        underscore: 'lib/underscore',
        backbone: 'lib/backbone/backbone',
        backboneLocalstorage: 'lib/backbone/backbone.localStorage',
        text: 'lib/require/text'
    },
    shim: {
        underscore: {
            exports: '_'
        },
        backbone: {
            deps: [
                'underscore',
                'jquery'
            ],
            exports: 'Backbone'
        },
        backboneLocalstorage: {
            deps: ['backbone'],
            exports: 'Store'
        }
    }
})
裴宝亮
裴宝亮
翻译于 2013/05/04 15:35
2

弄明白RequireJS Optimizer的所有配置项并不是本文的目的所在,但我想解释(描述)一下本文中我所采用的参数:

参数 描述
appDir 应用程序的目录(即<root>)。在这个文件夹下的所有文件将会被复制到dir参数标注的文件夹下。
baseUrl 相对于appDir,代表查找文件的锚点(that represents the anchor path for finding files)。
dir 这是一个输出目录,所有的应用程序文件将会被复制到该文件夹下。
modules 一个包含多个对象的数组。每个对象代表一个将被优化的模块(module)。
fileExclusionRegExp 任何与此规则匹配的文件或文件夹都将不会被复制到输出目录。由于我们把r.js和build.js放置在应用程序目录下,我们希望优化器(optimizer)排除这两个文件。 因此我们可以这样设置/^(r|build)\.js$/。
optimizeCss RequireJS Optimizer会自动优化应用程序下的CSS文件。这个参数控制CSS最优化设置。允许的值: “none”, “standard”, “standard.keepLines”, “standard.keepComments”, “standard.keepComments.keepLines”。
removeCombined 如果为true,优化器(optimizer)将从输出目录中删除已合并的文件。
paths 模块(modules)的相对目录。
shim 为那些没有使用define()声名依赖关系及设置模块值的模块,配置依赖关系与“浏览器全局”出口的脚本。


了解RequireJS Optimizer的更多介绍以及更多高级应用,除了其网页早先提供的资料,你可以点击此处查阅所有可用配置选项的详细的信息。

裴宝亮
裴宝亮
翻译于 2013/05/04 16:23
2

既然现在已经有了构建文件(build file),那么就可以运行优化器(optimizer)了。进入<root> 目录并执行如下命令:

运行优化器(optimizer)
$ node r.js -o build.js 
一个新的文件夹会被生成:<root>/dist。重要的是要注意到,现在<root>/dist/js/main.js包含了所有已合并与压缩的具有依赖关系的文件。 此外,<root>/dist/css/base.css也被优化了。

运行优化后的项目,它看起来与未优化之前的项目完全一样。再检查一下该页面的网络传输(network traffic)信息,会发现仅有两个JavaScript文件被加载。

Loaded Optimized JavaScript Files List

RequireJs Optimizer将服务器上的脚本文件从13个减少到2个并且将文件的总大小从164KB减少到58.6KB(require.js与main.js)。

裴宝亮
裴宝亮
翻译于 2013/05/04 16:43
2

开销

显然,在优化之后,我们再也没有必要引用require.js文件了。因为已经没有被分离的脚本文件了并且所有具有依赖关系的文件也已被加载。

尽管如此,优化过程将我们所有的脚本合并生成了一个优化后的脚本文件,其中包含了很多次define() 和require()调用。 因此,为了保证应用程序能够正常运行,define()和require()必须指定并实施到应用程序的某处(即包含这些文件)。

这会导致一个众所周知的开销:我们总是会有一些代码实现define()和require()。这些代码并不是应用程序的一部分,它们的存在仅仅是为我们的基础建设考虑(infrastructure considerations)。 当我们开发一个JavaScript库(JavaScript library)时,这个问题变得尤为巨大。相比RequireJS,这些库通常都很小,因此在库中包含它会造成一笔巨大的开销。

裴宝亮
裴宝亮
翻译于 2013/05/04 17:16
2


在我写这篇文章的时候,对于这方面的开销还没有一个完整的解决方案,但是我们可以使用almond来缓解这个问题。Almond是一个极简单的AMD加载器,它实现了RequireJS接口(API)。因此,可以用来在已优化过的代码中替代RequireJS实现,我们可以在项目中包含almond。
如令,我正致力于开发一个优化器(optimizer),它将能够优化RequireJS应用程序,而无需开销,但它仍然是一个新的项目(处于开发的初期阶段)因此这里没有任何关于它的展示。

下载与总结

  • 下载 未经优化的TodoMVC Backbone.js + RequireJS 项目或者查看它。
  • 下载 优化后的TodoMVC Backbone.js + RequireJS 项目(位于dist文件夹下)或查看它。

在阅读完这篇文章后,我相信你已经明确的知道如何去优化你的RequireJS应用程序。我会很高兴的解答您的任何问题。

祝你好运!
NaorYe

裴宝亮
裴宝亮
翻译于 2013/05/04 17:44
2
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(12)

marilynn
marilynn
在开销的那部分说,为了保证应用程序能够正常运行define()和require()必须指定并实施到应用程序的某处(即包含这些文件)
这个怎么理解,调用的时候模块名不是和路径一一对应的吗,合并后,它怎么实现可以在调用的时候找到要调用的模块
a
alexlee2
很棒,要是看原文的话估计要很久才能解决我的实际问题~
durban126
durban126
不错 ,合并起来后 就两个js文件了
474398734
474398734

引用来自“474398734”的评论

假设单页面,要加载10个脚本,可以使用楼主上述做法,合并加压缩,如果是多个页面,并且对于requireJS的配置已经抽出公共部分了,那该如何实现,根据每个页面的不同的脚本数,进行自动化合并?

引用来自“jinker”的评论

是的
如果简单封装过的main.js貌似不可以通过r.js合并,我当时的解决方法是一个页面一个main.js去压缩合并。
jinker
jinker

引用来自“474398734”的评论

假设单页面,要加载10个脚本,可以使用楼主上述做法,合并加压缩,如果是多个页面,并且对于requireJS的配置已经抽出公共部分了,那该如何实现,根据每个页面的不同的脚本数,进行自动化合并?
是的
474398734
474398734
假设单页面,要加载10个脚本,可以使用楼主上述做法,合并加压缩,如果是多个页面,并且对于requireJS的配置已经抽出公共部分了,那该如何实现,根据每个页面的不同的脚本数,进行自动化合并?
d
dasdasadsads

引用来自“库特”的评论

r.js只能优化由requirejs加载的AMD模块,对于一般的js能吗
单个文件的话直接执行命令即可,例如压缩一个文件bootstrap.js,生成bootstrap.min.js: node r.js -o baseUrl=. name=bootstrap out=bootstrap.min.js
库特
库特
r.js只能优化由requirejs加载的AMD模块,对于一般的js能吗
lfnb
lfnb
在modules中加入include,就可以了
lfnb
lfnb
根据文章中的配置文件,只是优化了一下,并没有合并!
返回顶部
顶部