我可以通过requirejs优化器捆绑我的js并从CDN提供结果的单个优化文件吗?

时间:2017-11-28 18:05:51

标签: javascript optimization requirejs

我正在尝试减轻应用服务器上的负载。我希望它只提供一个小页面shell,并从我的CDN提供优化的捆绑js。我可以这样做吗?

我问,因为我没有运气,在这种情况下应该问的第一个问题是“我应该这样做吗?”也许这不是该工具的预期工作流程。

该项目可能包含100个左右的js文件(大多数是我的,有些是第三方)。我通过require使用AMD来使其易于管理。我运行了优化器并生成了一个优化文件。但是当我将该文件放在我的CDN上并从我项目中的脚本标签引用它时,应用程序会抱怨如下:

    GET https://myserver.com/modules/util/utilloader.js net::ERR_ABORTED
    12:50:16.452 require.js:168 Uncaught Error: Script error for "modules/util/utilloader"
    http://requirejs.org/docs/errors.html#scripterror
        at makeError (require.js:168)
        at HTMLScriptElement.onScriptError (require.js:1735)

似乎它可能会抱怨,因为它希望组件/文件位于我服务器上相对于我的应用程序的路径上。但我希望它在捆绑文件中查找该代码。

我正在努力做到可行吗?如果是这样,我如何让应用程序在优化文件中查找代码,而不是在与我服务器上的app root相关的其他文件中?

附加细节(根据要求): 配置文件(application.js)本质上首先需要一堆第三方的东西。然后它需要我制作的模块。这似乎是麻烦开始的地方。如果你查看下面的application.js文件,有两个require语句在依赖项列表中有路径(utilloader,mod1,mod2,mod3等)。它永远不会找到那些因为它们不存在。我已将这些文件捆绑到一个名为application.js的文件中。这就是现在所有,只有一个文件。这种困境告诉我,我对这个工具和流程一般都不了解。如何在没有像“modules / util / utilloader”这样的路径的情况下需要像utilloader这样的东西?我原以为优化器会理解这个问题并在捆绑时解释它。但同样,我认为我不理解这个工具或过程。

以下是相关文件:

建立文件

    ({
        appDir: ".",
        baseUrl: "myapp",
        dir: "../scripts-build",
        mainConfigFile: 'myapp/application.js',
        modules: [
            {
                name: "application"
            }
        ]
    })

代码结构(优化程序目录)

    -optimize
        -r
        -scripts
            -backbone.js
            -backbone.marionette.js
            ...many more 3rd party libs
            -underscore.js
            -myapp
                -application.js
                -controller.js
                -router.js
                -modules (complex modules each with many dependencies.  Sub-structures omitted here for brevity)
                    -mod1
                    -mod2
                    -mod3
                    -util

CONFIG(application.js)

    require.config({
        paths: {

            jquery: '../jquery-2.2.3.min',
            bootstrap: '../bootstrap.min',
            jqueryui: '../jquery-ui-1.11.4.min',
            jqcloud: '../jqcloud.min',
            jqmarquee: '../jquery.marquee.min',
            bootstrapslider: '../bootstrap-slider.min',
            underscore: '../underscore.min',
            backbone: '../backbone.min',
            marionette: '../backbone.marionette.min',
            handlebars: '../handlebars.min',
            hls: '../hls.min'

        },

        shim: {   

            'jqueryui': {
                deps: ['jquery']
            },

            "bootstrap": {
                "deps": ['jquery']
            },

            'jqcloud': {
                deps: ['jquery']
            },

            'jqmarquee': {
                deps: ['jquery']
            },

            'bootstrapslider': {
                deps: ['jquery', 'bootstrap']
            },

            underscore: {
                exports: '_'
            },

            backbone: {
                deps: ["underscore", "jquery"],
                exports: "Backbone"
            },

            marionette: {
                deps: ["backbone"],
                exports: "Marionette"
            }

        }
    });


    //Outer require - Need this 3rd party stuff to make our Marionette app.  
    //There will be 2 more requires inside this outer require.
    require(["marionette", "hls", "jqueryui", "jqcloud", "jqmarquee", "handlebars", "bootstrap", "bootstrapslider"], function (Marionette, Hls) {


        //Create a Marionette js application
        window.App = new Marionette.Application();


        //Do a bunch of stuff in the app's global space that uses some of the 3rd party packages required above
        ...code ommitted here for brevity


        //#1
        //Require some other modules (mine, not 3rd party) before officially "starting" the marionette js application.
        //Each of these modules include many dependencies (many dozens of files) of thier own in their definitions
        require([
            "modules/util/utilloader",
            "modules/mod1/mod1loader",
            "modules/mod2/mod2loader",
            "modules/mod3/mod3loader"
            ], function () {
            App.start();
        });


        //#2
        //Require two more files (mine) to make a backbone router.
        require(["controller", "router"], function (Controller, AppRouter) {
            var router = new AppRouter({ controller: new Controller() });
        });


        //When the marionette js application is officially "started", start the backbone history.  
        App.on("start", function () { 

            //History
            if (Backbone.history) {
                Backbone.history.start();
            }//End history


            //Some more app start functions
            ...code ommitted here for brevity

        });//end App on start handler




    })//end outer require      

我的主要配置文件(application.js)所需的示例模块(utilloader.js)

    // loader for Util module (lets you spread this module across multiple files)
    // the 'loader' includes initial module definition and manages the loading
    // of all module files including javascript and templates.


    // define base module elements; other module files may depend
    // on this, but it must not depend on any other module files.
    //NOTE:  I think this first piece here is what the optimizer is complaining about
    //It's not inside a define?
    App.module("UtilModule", function (UtilModule) {
        UtilModule.views = {}; //put your views and models in structures so you 
        UtilModule.models = {};//can get at them from anywhere in the module
    });


    // Recommended: define all dependencies for this module
    // while you could spread dependency requirements
    // over all your module files on purely "as needed" basis,
    // this adds to complication of code in your module files
    // defining them all, here, has the advantage of limiting use of RequireJS
    // to this loader file only

    var dependencies = [
        "modules/util/views/view1",
        "modules/util/views/view2",
        "modules/util/views/view3",
        "modules/util/models/model1",
        "modules/util/models/model2",
        "modules/util/models/model3"
    ];


    // define the loader last. generally, it should depend on all
    // module files, otherwise they may not get loaded
    define(dependencies,
        function () {
            App.module("UtilModule", function (UtilModule, App, Backbone, Marionette, $, _) {


            //Some functions here to make views, bind them to the models listed above and show them.
            //...ommitted here for space and clarity


            });//Close the module definition
        });//Close the define function

更新12/13/2017

也许我的优化内容不起作用,因为并非所有文件都符合AMD标准(未包含在定义函数中的内容)。所以我完成了整个项目并解决了这个问题。我在整个项目中所做的修订类型的示例如下,并概述了新的问题。

我的主要配置文件(application.js)要求修改的示例模块(utilloader.js)上面的版本没有被优化器(不符合AMD标准)所喜欢。它有一些超出定义功能(模块定义,var依赖)。在这里,我已经修改过,要把它塞进去。现在应该好了。但是这样做我得到了新的错误。现在有人抱怨外部文件中定义的内容并未在此模块中受到限制。因此,我说,我定义了一个外部文件内的一个backBone模型,模块/工具/模型/模型1,尝试在此文件中创建该模型的实例,从而产生未定义的错误。这个模块只是不知道这些外部文件中的内容,即使它们是在这个模块的依赖性阵列中列出的。所以要求外部文件没有任何错误,但是在这个模块中创建的东西并没有在这里受到限制。但是所有这些运行都很精细(如果我不使用优化器)。为什么不同?如果代码以某种方式进行表现,那么我希望它能够以两种方式失败,或者以两种方式成功。

    // loader for Util module (lets you spread this module across multiple files)
    // the 'loader' includes initial module definition and manages the loading
    // of all module files including javascript and templates.

    // Recommended: define all dependencies for this module.
    // while you could spread dependency requirements
    // over all your module files on purely "as needed" basis,
    // this adds to complication of code in your module files.
    // defining them all, here, has the advantage of limiting use of RequireJS
    // to this loader file only



    // define the loader last. generally, it should depend on all
    // module files, otherwise they may not get loaded
    define(
        [
            "modules/util/views/view1",
            "modules/util/views/view2",
            "modules/util/views/view3",
            "modules/util/models/model1",
            "modules/util/models/model2",
            "modules/util/models/model3"            
        ],
        function () {
            App.module("UtilModule", function (UtilModule, App, Backbone, Marionette, $, _) {

            UtilModule.views = {}; //put your views and models in structures so you 
            UtilModule.models = {};//can get at them from anywhere in the module

            //Try to create an instance of a model defined in an external file. 
            var model1 = new Model1();

            //No errors are reported in the parsing of the external file.  But the line above
            //produces 'Model1' is undefined.  I have dozens of external definitions like this
            //in the app.  All of them resolve fine unoptimized.  When I run the requirejs optimizer,
            //none of them resolve.


            });//Close the module definition
        });//Close the define function

2 个答案:

答案 0 :(得分:0)

是的,它应该是可能的,是的,你应该(通常是*)这样做。

如果您没有开始这样做,您可能需要调整代码中的操作方式以使其发挥得很好。如果该捆绑包不能为您工作,那么还有其他人可以尝试(Webpack和汇总是两个流行的)。它应该是可能的。

要实际提供有关如何使其工作的帮助,我们需要了解更多信息,例如配置和代码结构。但一般情况下,请确保在代码中使用相对路径。

一般来说,捆绑是一个好主意。但是有一些例外。

如果您有不同人员访问的网站的谨慎区域(例如,普通用户与管理员),您可能希望将其捆绑为两个(或三个,如果它们共享可以是公共文件的公共代码)文件而不是两个。

另一个例外是如果文件超级疯狂,打破它可能会更好(但这是一个极端的情况)。

最后,您不想捆绑的另一个原因是您使用的是HTTP / 2。通常,使用HTTP / 1进行捆绑,因为由于HTTP / 1的握手,发送一个大文件然后发送一些较小的文件会更快。使用HTTP / 2,该公式会发生变化,因为您基本上可以通过1次握手发送多个文件,因此发送大量较小的文件实际上会变得更快。

对于HTTP / 2,它还取决于用户使用此浏览器支持此功能。对它的支持即将到来,并且在明年内,绝对应该完成。现在,这是一个灰色的区域,你理想的是希望有个性化的分析来帮助指导你的决定。

答案 1 :(得分:0)

是的,你可以做你想做的事。没有什么是禁止的,或者相对于RequireJS支持的界限。

您要求r.js跟踪嵌套在require个调用中的require个调用的依赖关系。为此,您需要在构建配置中使用findNestedDependencies: true。否则,r.js只会查看顶级definerequire来电并仅处理这些调用。处理嵌套依赖项需要付出代价,因此默认情况下它是关闭的。

要么是这样,要么压扁require来电的层次结构。 某些外部约束迫使您拥有层次结构的情况。但是,很多时候根本不需要它。

ETA:我看到了你的编辑。您的模块不是合适的AMD模块。它会调用define,但是您传递给define的工厂函数之外的代码,这是禁止的。

使用r.js创建捆绑包时,默认情况下,define之外的代码按原样填充到捆绑包中。这对您的代码来说是个问题。仅在App获取第一个application.js所需的模块后才会创建require全局,但application.js是包含utilloader.js的同一个包的一部分,这意味着utilloader.js'define调用之外的代码已尝试运行并失败,因为尚未定义App。 (有一个wrapShim选项可以包装在define调用中在全局空间中执行的代码,但它不会帮助您,因为您已经使用define。它可能会导致其他的问题。)

如果您设置r.js配置以生成除utilloader.js之外的所有内容的捆绑包,并将utilloader.js放入第二个捆绑包中,您应该能够获得所需的行为。这样,utilloader.js的代码在请求之前不会执行。 (当您不使用优化程序时也会发生这种情况。)您必须对依赖于App定义的所有模块执行此操作。您可能需要处理其他限制,这些限制在您在问题中显示的代码部分中并不明显。如下所示:

({
    appDir: ".",
    baseUrl: "myapp",
    dir: "../scripts-build",
    mainConfigFile: 'myapp/application.js',
    modules: [
        {
            name: "application",
            exclude: ["modules/util/utilloader"],
        },
        {
            name: "modules/util/utilloader",
        }
    ],
})

您也可以尝试使用excludeShallow。同样,具体工作取决于问题中没有的细节。你必须试验。

但最终,拥有表现良好的AMD模块并停止依赖于通过全局变量传递信息会更好。

另请注意,除非您使用的是旧版本的Underscore,Backbone和Marionette,否则它们不需要shim配置,因为它们都会调用define。并且您不得将shim用于调用define的脚本。充其量,你的shim对他们没有影响。最糟糕的是它可能会产生意想不到的影响。 RequireJS没有指定将shim与调用define的文件一起使用的语义。当我在旧版本中尝试它时,我遇到了崩溃。在这种情况下,较新版本似乎优雅地忽略shim,但您永远不知道问题何时会再次发生。