RequireJS使用map config优化多页面应用程序

时间:2015-02-20 20:42:52

标签: javascript requirejs marionette requirejs-optimizer

我正在尝试通过将功能分解为共享许多常用代码的单独应用程序来模块化我现有的项目。它是一个Backbone / Marionette应用程序,在开发模式下一切正常,但我无法使优化工作。我目前有两个页面,有2个主文件和2个应用程序文件。 main文件都包含几乎完全相同的requirejs.config块,但第二个块使用map config选项将app模块映射到loginApp。这样做的原因是大多数其他模块依赖于app模块来实现某些应用程序范围的功能,包括消息传递和一些全局状态变量。

main.js

requirejs.config({
    shim: { ... },
    paths: { ... }
});

define(['vendor'], function() {
    // This loads app.js
    require(['app'], function(Application) {
        Application.start();
    });
});

主要-login.js

requirejs.config({
    shim: { ... },
    paths: { ... },
    map: {
        "*": { "app": "loginApp" }
    }
});

define(['vendor'], function() {
    // This loads loginApp.js because of the mapping above
    require(['app'], function(Application) {
        Application.start();
    });
});

这很有效,直到我优化。我收到一个关于丢失文件的错误,但是使用requirejs已经足够长了,我知道这与问题无关。 :)

来自文档:

  

注意:使用map config进行构建时,需要使用map配置   馈送到优化器,构建输出必须仍然包含   requirejs配置调用,用于设置映射配置。优化器确实如此   在构建期间不做ID重命名,因为有些依赖   项目中的引用可能取决于运行时变量状态。所以   优化器不会使之后的map配置无效   建立。

我的 build.js 文件如下所示:

({
    baseUrl: "js",
    dir: "build",
    mainConfigFile: "js/main.js",
    removeCombined: true,
    findNestedDependencies: true,
    skipDirOptimize: true,
    inlineText: true,
    useStrict: true,
    wrap: true,
    keepBuildDir: false,
    optimize: "uglify2",
    modules: [
        {
            name: "vendor"
        },
        {
            name: "main",
            exclude: ["vendor"]
        },
        {
            name: "main-login",
            exclude: ["vendor"],
            override: {
                mainConfigFile: "js/main-login.js",
                map: {
                    "*": {
                        "app": "loginApp"
                    }
                }
            }
        }
    ]
});

如果可能的话,我想避免使用2个单独的构建文件,我正在努力将requirejs.config块分解为单个共享文件并加载2个main文件然后加载app文件(这类似于multipage example的工作方式),但我需要在优化器中使用该映射配置才能使其正常工作。我在这里缺少什么想法?

更新

我已将配置拆分为自己的文件config.js,该文件包含在main-*文件中。在main-login.js文件中,我将地图配置包含在define上方,一切都在开发模式下。

require.config({
    map: {
        "*": {
            "app": "loginApp"
        }
    }
});

define(['module', 'config'], function(module, config) {
    ...

build.js文件与上面相同,但第二个mainConfigFile已删除。但优化仍然失败。我认为正在发生的是,由于这是一个Marionette应用程序,通常的做法是将Application对象作为依赖项传递给应用程序的其他部分,包括视图,控制器和模型。当我优化时,我遇到了两个不同的问题。如果我将removeCombined保留为true,优化器将构建来自第一个应用程序的依赖项,然后删除这些文件,因此当它在第二个应用程序中看到它们时,它将失败,因为它不能再找到源文件。将此设置为false似乎是合理的,但问题是这会给我以下错误:

Error: RangeError: Maximum call stack size exceeded

我无法找到有关此特定错误的任何一致信息。它可能与hbs插件有关(类似于text,但是用于预编译Handlebars模板)但我不是肯定的。由于没有堆栈跟踪,我不知道从哪里开始查找。我的直觉是它在某处是一个循环依赖。所以,我更新的问题是,如何将多页Marionette应用程序解耦,以便使共享代码(不仅仅是第三方代码,而是自定义代码,如数据模型和视图)成为可能?我是否需要删除核心Application对象的任何依赖项? (这将需要大量的重构。)因为它在开发模式下工作得很好,有r.js的配置有一些技巧我忽略了吗?我已尝试将app添加到exclude列表以及stubModules,但似乎没有任何效果。我正在考虑创建2个构建文件并完成它,但我真的想知道如何以“正确”的方式解决这个问题。

2 个答案:

答案 0 :(得分:1)

您的构建文件可以是这样的:

({
    ...
    include: [ './config/main.js' ],
    pragmasOnSave: {
        excludeBuildConfig: true
    }
})

您可以使用pragmasOnSave告诉优化器在优化结果中排除文件中的部分,因此Requirejs配置文件可以像下面的代码

requirejs.config({
//>>excludeStart('excludeBuildConfig', pragmas.excludeBuildConfig)
    shim: { ... },
    paths: { ... },
//>>excludeEnd('excludeBuildConfig')
    map: {
          "*": { "app": "loginApp" }
    }
});

答案 1 :(得分:0)

使用的最终解决方案是将Grunt合并到构建工作流程中。在Grunt里面,我正在为requirejs任务动态创建任务目标。我重构了我的多个应用程序以使用相同的文件夹结构,因此很容易为每个应用程序重用相同的构建配置。对于vendor文件进行多次编译仍然存在轻微的不便,但这是一个很小的代价。

这是我在dev任务中创建配置的函数,以防有人感兴趣:

var buildRequireTargets = function(appList) {
    var requireTargets = {},
        buildConfig = {
            baseUrl: "<%= sourceDir %>/js",
            dir: "<%= buildDir %>/js",
            mainConfigFile: "<%= sourceDir %>/js/config.js",
            removeCombined: true,
            findNestedDependencies: true,
            skipDirOptimize: true,
            inlineText: true,
            useStrict: true,
            wrap: true,
            keepBuildDir: true,
            optimize: "none",
            pragmasOnSave: {
                excludeHbs: true
            }
        };

    _.each(appList, function (app) {
        requireTargets[app] = {
            options: _.extend({
                map: {
                    "*": {
                        "app": app + "/app"
                    }
                },
                modules: [
                    {
                        name: "vendor"
                    },
                    {
                        name: app + "/main",
                        exclude: ["vendor"]
                    }
                ]
            }, buildConfig)
        };
    });

    grunt.config("requirejs", requireTargets);
};