RequireJS进阶:模块的优化及配置的详解

8

概述

关于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项目。(这段话来自下面的参考连接)

所需环境

Node.js、r.js、RequireJS、以及示例程序,示例程序的下载地址为:http://www.webdeveasy.com/code/optimize-requirejs-projects/todo-mvc.zip

配置参数介绍

涉及的操作请可以参考上面的示例,本文的主要目的主要是通过示例来讲解配置参数的使用。

appDir

appDir: './'

应用程序的顶级目录,如果这个选项被开启的话,那么你的脚本文件是这个目录路径下的一个子目录。这个是个可选项。如果没有设置的话,则通过baseUrl这个参数下的锚点来查找文件(?)。如果设置了该选项的话,那么这个路径下的所有文件都会被复制到dir指定的输出目录,并且baseUrl这个目录的路径是相对于该路径。

baseUrl

baseUrl: './js'

默认情况下,所以的模块都相对于这个路径存在(有人称为脚本的跟路径),如果baseUrl没有明确指定的话,那么所有的模块路径都相对与build构建文件的路径。如果appDir已设置,那么baseUrl 的路径是相对与appDir

mainConfigFile

mainConfigFile: '../some/path/to/main.js'

RequireJS的主配置文件。这里要区分配置文件和入口文件的区别,示例中的main.js即包含配置文件同时又是文件的入口文件。

paths

paths: {
        "foo.bar": "../scripts/foo/bar",
        "baz": "../another/path/baz"
    },

参见: RequireJS进阶:配置文件的学习

map

map: {},

参见: RequireJS进阶:配置文件的学习

packages

packages: [],

参见: RequireJS进阶:配置文件的学习

dir

dir: "../some/path"

文件输出的顶级目录。如果没有指定的话,默认会创建一个“build”目录在build文件的路径中。所有相对路径是相对于构建文件。

keepBuildDir

keepBuildDir: true

在 RequireJS 2.0.2 中,输出目录的所有资源会在 build 前被删除。值为 true 时 rebuild 更快,但某些特殊情景下可能会出现无法预料的异常

shim

shim: {},

参见: RequireJS进阶:配置文件的学习

wrapShim

wrapShim: false,

禁止非模块包裹define函数。

locale

locale: "en-us",

国际化配置设置。

optimize

optimize: "uglify",

优化脚本文件的方式。值只能取下面的任何值中的一个。

  • "uglify": (default) uses UglifyJS to minify the code.

  • "uglify2": in version 2.1.2+. Uses UglifyJS2.

  • "closure": uses Google's Closure Compiler in simple optimizationmode to minify the code. Only available if running the optimizer using Java.

  • "closure.keepLines": Same as closure option, but keeps line returns in the minified files.

  • "none": no minification will be done.

skipDirOptimize

skipDirOptimize: false,

当设置为true时,优化器将会跳过非构建中被约束的JS文件。

generateSourceMaps

generateSourceMaps: false,

是否生成SourceMaps文件,什么是SourceMaps,参考SourceMaps

normalizeDirDefines

normalizeDirDefines: "skip",

2.1.11中:如果dir被声明且不为”none”,并且skipDirOptimize 为false,通常所有的JS文件都会被压缩,这个值自动设置为”all”。为了让JS文件能够在压缩过正确执行,优化器会加一层define()调用并且会插入一个依赖数组。当然,这样会有一点点慢如果有很多文件或者有大文件的时候。所以,设置该参数为”skip”这个过程就不会执行,如果optimize设置为”none”也一样。如果你想手动设置值的话:

  • 优化后:如果你打算压缩没在modules声明的JS文件,在优化器执行过后,你应该设置这个值为”all”

  • 优化中:但在动态加载过后,你想做一个会文件优化,但不打算在动态加载这些文件可以设置成”skip”

最后:所有生成的文件(无论在不在modules里声明过)自动标准化

uglify

uglify: {
        toplevel: true,
        ascii_only: true,
        beautify: true,
        max_line_length: 1000,

        //How to pass uglifyjs defined symbols for AST symbol replacement,
        //see "defines" options for ast_mangle in the uglifys docs.
        defines: {
            DEBUG: ['name', 'false']
        },

        //Custom value supported by r.js but done differently
        //in uglifyjs directly:
        //Skip the processor.ast_mangle() part of the uglify call (r.js 2.0.5+)
        no_mangle: true
    },

使用UglifyJS进行代码压缩,具体参数配置可见UglifyJS

uglify2

uglify2: {
        //Example of a specialized config. If you are fine
        //with the default options, no need to specify
        //any of these properties.
        output: {
            beautify: true
        },
        compress: {
            sequences: false,
            global_defs: {
                DEBUG: false
            }
        },
        warnings: true,
        mangle: false
    },

使用UglifyJS2进行代码压缩, 具体参数配置可见UglifyJS2

closure

closure: {
    CompilerOptions: {},
    CompilationLevel: 'SIMPLE_OPTIMIZATIONS',
    loggingLevel: 'WARNING'
},

如果用Closure Compiler优化,这个参数可以用来配置Closure Compiler,详细请看Closure Compiler的文档

optimizeCss

optimizeCss: "standard.keepLines.keepWhitespace",

是否优化CSS文件,以那种方式优化。

  • "standard": @import inlining and removal of comments, unnecessary whitespace and line returns.Removing line returns may have problems in IE, depending on the type of CSS.
  • "standard.keepLines": like "standard" but keeps line returns.
  • "none": skip CSS optimizations.
  • "standard.keepComments": keeps the file comments, but removes linereturns. (r.js 1.0.8+)
  • "standard.keepComments.keepLines": keeps the file comments and linereturns. (r.js 1.0.8+)
  • "standard.keepWhitespace": like "standard" but keeps unnecessary whitespace.

cssImportIgnore

 cssImportIgnore: null,

是否忽略 CSS 资源文件中的 @import 指令

cssIn

cssIn: "path/to/main.css",

一般用于命令行,可将多个 CSS 资源文件打包成单个 CSS 文件

out

out: "path/to/css-optimized.css",

一般用于命令行,可将多个 CSS 资源文件打包成单个 CSS 文件

cssPrefix

cssPrefix: "",

如果”out”和”cssIn”不是同一目录,并且在cssIn文件里面有url()相对目录的,用这个去设置URL前置。仅仅在优化后URL不正确的情况下使用。

inlineText

inlineText: true,

处理所有的文本资源依赖项,从而避免为加载资源而产生的大量单独xhr请求

useStrict

useStrict: false,

是否开启严格模式, 由于很多浏览器不支持 ES5 的严格模式,故此配置默认值为 false

pragmas

pragmas: {
    fooExclude: true
},

指定生成编译指示。如果源文件包含类似如下注释:>>excludeStart(“fooExlude”,pragmas.fooExclude); >>excludeEnd(“fooExclude”);那么以//>>开头的注释就是编译指示。excludeStart/excludeEnd和includeStart/includeEnd起作用,在includeStart或excludeStart中的编译指示值将参与计算来判断Start和End之前的编译指示是include还是exclude。如果你可以选择用”has”或编译指示,建议用”has”代替。 编译指示比较难于阅读,但是它在对代码移除上比较灵活。基于”has”的代码必须遵守JavaScript规则。编译指示还可以在未压缩的代码中删除代码,而”has”只能通过UglifyJS或者Closure Compiler来做。

pragmasOnSave

pragmasOnSave: {
        //Just an example
        excludeCoffeeScript: true
    },

和”pragmas”一样,但只能在文件保存的优化阶段应用一次。”pragmas”可以同时在依赖映射和文件保存优化阶段应用。有些”pragmas”可能不会在依赖映射时被执行,例如在CoffeeScript的loader插件中,只想CoffeeScript做依赖映射,但是一旦这个文件被保存为一个javascript文件,CoffeeScript compiler就没用了。那样的话,pragmasOnSave就会用于在保存期排除编译代码。

has

has: {
        'function-bind': true,
        'string-trim': false
    },

使用”has”允许trimming代码树。基于js的特征检测:https://github.com/phiggins42/has.js。代码树修饰仅仅在使用UglifyJS或Closure Compiler压缩时发生。更多请见:http://requirejs.org/docs/optimization.html#hasjs

hasOnSave

 hasOnSave: {
        'function-bind': true,
        'string-trim': false
    },

和pragmasOnSave类似。

namespace

namespace: 'foo',

命名空间,完整实例可以参考 http://requirejs.org/docs/faq-advanced.html#rename

skipPragmas

skipPragmas: false,

跳过执行pragmas

skipModuleInsertion

skipModuleInsertion: false,

如果是false,文件就不会用define()来定义模块而是用一个define()占位符插入其中。另外,require.pause/resume调用也会被插入。设置为”true”来避免。这个参数用在你不是用require()来创建项目或者写js文件,但是又想使用RquireJS的优化工具来合并模块是非常有用的。

stubModules

stubModules: ['text', 'bar'],

将模块排除在优化文件之外。

optimizeAllPluginResources

optimizeAllPluginResources: false,

如果不是一个文件的优化,描述输出目录的所有.js文件的插件依赖,如果这个插件支持优化成为一个单独的文件,就优化它。可能是一个比较慢的优化过程。仅仅在有些插件用了像XMLHttpRequest不支持跨域,并且生成的代码会被放在另一个域名。

findNestedDependencies

findNestedDependencies: false,

寻找require()里面的require或define调用的依赖。默认为false是因为这些资源应该被认为是动态加载或者实时调用的。当然,有些优化场景也需要将它们合并在一起。

removeCombined

removeCombined: false

如果设置为true,在输出目录将会删除掉已经合并了的文件

modules

modules: [
        //Just specifying a module name means that module will be converted into
        //a built file that contains all of its dependencies. If that module or any
        //of its dependencies includes i18n bundles, they may not be included in the
        //built file unless the locale: section is set above.
        {
            name: "foo/bar/bop",

            //create: true can be used to create the module layer at the given
            //name, if it does not already exist in the source location. If
            //there is a module at the source location with this name, then
            //create: true is superfluous.
            create: true,

            //For build profiles that contain more than one modules entry,
            //allow overrides for the properties that set for the whole build,
            //for example a different set of pragmas for this module.
            //The override's value is an object that can
            //contain any of the other build options in this file.
            override: {
                pragmas: {
                    fooExclude: true
                }
            }
        },

        //This module entry combines all the dependencies of foo/bar/bop and foo/bar/bee
        //and any of their dependencies into one file.
        {
            name: "foo/bar/bop",
            include: ["foo/bar/bee"]
        },

        //This module entry combines all the dependencies of foo/bar/bip into one file,
        //but excludes foo/bar/bop and its dependencies from the built file. If you want
        //to exclude a module that is also another module being optimized, it is more
        //efficient if you define that module optimization entry before using it
        //in an exclude array.
        {
            name: "foo/bar/bip",
            exclude: [
                "foo/bar/bop"
            ]
        },

        //This module entry shows how to specify a specific module be excluded
        //from the built module file. excludeShallow means just exclude that
        //specific module, but if that module has nested dependencies that are
        //part of the built file, keep them in there. This is useful during
        //development when you want to have a fast bundled set of modules, but
        //just develop/debug one or two modules at a time.
        {
            name: "foo/bar/bin",
            excludeShallow: [
                "foo/bar/bot"
            ]
        },

        //This module entry shows the use insertRequire (first available in 2.0):
        {
            name: "foo/baz",
            insertRequire: ["foo/baz"]
        }
    ],

列出要优化的模块。如果有依赖i18n的,只有root层会被包含进来除非locale:块在上面被声明过。

①仅定义模块会被转换成一个生成目标文件的名字,包含所有依赖项。i18n依赖同上,

create:true可用来生成在源文件目录不存在的给定模块名。如果源文件目录已经存在一个相同名称的模块,create参数就没用了;

override:可以重写全局的pragmas

②这个模块声明编译foo/bar/bop的所有依赖和foo/bar/bee及其所有依赖

③编译所有foo/bar/bip的依赖到一个文件,但是排除foo/bar/bop和它的所有依赖文件,如果想把另一个模块单独优化,这是一个很好用的方法

④excludeShallow只排除掉这个模块,但是如果输出模块的依赖和它有相同就保留不排除。

⑤这个模块声明表示用insertRequire(在2.0中新加入)

insertRequire

insertRequire: ['foo/bar/bop'],

如果目标模块在顶层级只调用了define没有调用require(),并且输出文件在data-main中使用,如果顶层没有require,就不会有任何模块被加载。定义insertRequire在文件尾部来执行其它模块,更多参见:https://github.com/jrburke/almond

name

name: "foo/bar/bop",

include

include: ["foo/bar/bee"],

insertRequire

insertRequire: ['foo/bar/bop'],

out

out: "path/to/optimized-file.js",

如果只优化一个模块(和它的依赖项),而且是生成一个单文件,你可以在行内定义模块的选项,以代替modules参数的定义方式,”exclude”, “excludeShallow”, “include”和”insertRquire”都可以以兄弟属性的方式定义。

deps

deps: ["foo/bar/bee"],

“include”的替换方案。一般用requirejs.config()来定义并用mainConfigFile引入。

out

out: function (text, sourceMapText) {
        //Do what you want with the optimized text here.
        //Starting in 2.1.10, if generateSourceMaps was set to true
        //and optimize: 'uglify2' was used, then the second argument
        //to this function, sourceMapText, will be the text of the source map.
    },

在2.0,”out”可以是一个函数, 对单个JS文件优化可以调用requirejs.optimize(), 用out函数表示优化过后的内容不会被写到磁盘,而是传递给out函数

out

out: “stdout”

在2.0.12+, 设置”out”为”stdout”, 优化输出会写到STDOUT,这对于r.js整合其它命令行工具很有用。为了避免额外的输出”logLevel: 4”应该被使用。

wrap

wrap: {
    start: "(function() {",
    end: "}());"
},

wrap任何东西在start和end之间,用于define/require不是全局的情况下,在end里可以暴露全局对象在文件中。

wrap

wrap: true,

wrap的另一种方式,默认是(function() { + content + }())

wrap

wrap: {
    startFile: "parts/start.frag",
    endFile: "parts/end.frag"
},

用文件来wrap

wrap

wrap: {
    startFile: ["parts/startOne.frag", "parts/startTwo.frag"],
    endFile: ["parts/endOne.frag", "parts/endTwo.frag"]
},

多个文件的wrap

fileExclusionRegExp

fileExclusionRegExp: /^./,
跳过任何以.开头的目录和文件,比如.files, .htaccess等

preserveLicenseComments

preserveLicenseComments: true,

默认注释有授权在里面。当然,在大项目生成时,文件比较多,注释也比较多,这样可以把所有注释写在文件的顶部。

logLevel

logLevel: 0

设置logLevel。

TRACE: 0,

INFO: 1

WARN: 2

ERROR: 3

SILENT: 4

throwWhen

throwWhen: {
  optimize: true
}

在2.1.3,有些情况下当错误发生时不会抛出异常并停止优化,你可能想让优化器在某些错误发生时停止,就可以使用这个参数

onBuildRead

onBuildRead: function (moduleName, path, contents) {
    //Always return a value.
    //This is just a contrived example.
    return contents.replace(/foo/g, 'bar');
},

当每个文件被读取的时候调用这个方法来改变文件内容

onBuildWrite

onBuildWrite: function (moduleName, path, contents) {
    //Always return a value.
    //This is just a contrived example.
    return contents.replace(/bar/g, 'foo');
},

允许在写入目标文件前执行方法改变内容

onModuleBundleComplete

onModuleBundleComplete: function (data) {
    /*
    data.name: the bundle name.
    data.path: the bundle path relative to the output directory.
    data.included: an array of items included in the build bundle.
    If a file path, it is relative to the output directory. Loader
    plugin IDs are also included in this array, but depending
    on the plugin, may or may not have something inlined in the
    module bundle.
    */
},

每个JS模块集完成后执行。 模块集是指一个modules数组项。

rawText

rawText: {
    'some/id': 'define(["another/id"], function () {});'
},

在2.1.3,种子raw text是模块ID的列表。这些文本内容用于代替模块的文件IO调用。用于模块ID是基于用户动态输入的情况,在网页生成工具中常用。

cjsTranslate

cjsTranslate: true,

在2.0.2中。如果为true, 优化器会添加define(require, exports, module) {});包裹每一个没有调用define()的文件。

useSourceUrl

useSourceUrl: true,

在2.0.2,有点实验性质。每一个模块集最后都会添加一段//# sourceUrl的注释。

waitSeconds

waitSeconds: 7

参见: RequireJS进阶:配置文件的学习

skipSemiColonInsertion

skipSemiColonInsertion: false

在2.1.9,通常r.js插入一个分号在文件末尾,如果没有的话。

keepAmdefine

keepAmdefine: false

在2.1.10, 如果是true,就不会删除amdefine,详情见:https://github.com/jrburke/amdefine

allowSourceOverwrites

allowSourceOverwrites: false

在2.1.11中, 作为修复BUG的一部分https://github.com/jrburke/r.js/issues/444。设置为true就允许源代码进行重写覆盖。当然,为了安全起见,请正确配置,比如你可能想设置”keepBuildDir”为true。

帮助文档

参考链接:http://jiongks.sinaapp.com/blog/build-any-web-project-with-requirejs-optimizer/
英文文档(r.js):https://github.com/jrburke/r.js/blob/master/build/example.build.js
参考链接:http://www.oschina.net/translate/optimize-requirejs-projects
参考链接:http://www.cnblogs.com/haoliang/p/3656475.html
参考链接:http://www.yfznw.com/node/22

PS:如果涉及侵权或者文章有错误,请及时通知。

你可能感兴趣的

10 条评论
两仪 作者 · 2014年12月04日

配置参数太多了,不可能一下全部学会运用 只能边用边学了。

回复

EMMA · 2015年03月27日

仪兄,请问下,为什么这样函数依赖jquery是错误的,出现undefined。路径是正确的
define(['../libs/jquery-1.7.2'],function($){
alert($);
});

回复

两仪 作者 · 2015年03月27日

路径不对,你看下调试工具生成的路径是什么,然后有一点注意的是,建议不要这样写,可以把路径写在配置里。

回复

EMMA · 2015年03月28日

哦谢谢哈

回复

十字架 · 2015年04月02日

问个问题,请大神解答:
RequireJs优化时可以根据依赖将所用到的Js压缩合并成一份,那么这就会存在冗余压缩
例如jquery、backbone被很多js模块所依赖,a.js依赖它们,b.js依赖它们
那么jquery、backbone会被2次压进去,这种方式好?还是说想办法将公共部分提取出来?
如果要提取,该如何提取呢?
如果您有时间,可以写篇文章讲讲一些实际优化中遇到的问题以及解决方案吗

回复

SAMPAN · 2015年05月22日

这个应该不能直接引用的,需要在path中进行配置才可以的。

回复

dusdong · 2015年09月22日

谢谢楼主的翻译,对我帮助很大!

回复

lioff · 2015年10月13日

楼主英文真牛

回复

lioff · 2015年10月13日

想问一下,我用 uglify2 压缩之后 并没有清除掉文件中的注释,因为之前直接用 grunt uglify 是会清除掉注释的,在Grunt-requirejs 中压缩却没有去掉注释 如:eval("/*\n @license AngularJS v1.3.7\n (c) 2010-2014 Google, Inc. http://angularjs.org\n License: MIT\n */\n(function(window, document, undefined)

回复

m2maomao · 2015年12月24日

jquery应该是不能用路径的形式调用,因为jquery支持amd规范,在里面已经定义好了,必须用define(['jquery])才可以;

回复

载入中...