【问题标题】:Can I bundle my js via requirejs optimizer and serve the resultant single optimized file from a CDN?我可以通过 requirejs 优化器捆绑我的 js 并从 CDN 提供生成的单个优化文件吗?
【发布时间】:2017-12-14 05:48:54
【问题描述】:

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

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

该项目可能包含 100 个左右的 js 文件(大部分是我的,一些是第 3 方)。我通过 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)

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

我正在尝试做的事情可行吗?如果是这样,我如何让应用程序在优化文件中查找代码,而不是在相对于我服务器上的应用程序根目录的其他文件中查找代码?

其他详细信息(每个请求): 配置文件 (application.js) 基本上首先需要一堆 3rd 方的东西。然后它需要我制作的模块。这似乎是麻烦开始的地方。如果您查看下面的 application.js 文件,则有两个 require 语句在依赖项列表中具有路径(utilloader、mod1、mod2、mod3 等)。它永远不会找到那些,因为它们不存在。我已将这些文件捆绑到一个名为 application.js 的文件中。这就是现在的全部内容,只有一个文件。这个困境告诉我我只是不了解这个工具和过程。我如何需要像 utilloader 这样的东西而没有像“modules/util/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

配置(应用程序.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

2017 年 12 月 13 日更新

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

我的主配置文件 (application.js) 所需的修订示例模块 (utilloader.js) 优化器不喜欢上述版本(不符合 AMD 标准)。它在 DEFINE 函数之外有一些东西(模块定义,var 依赖项)。在这里,我已对其进行了修改,以将这些内容放入定义中。现在应该很好。但是这样做我得到了新的错误。现在有人抱怨外部文件中定义的内容不在本模块的范围内。因此,如果我在诸如 modules/util/models/model1 之类的外部文件中定义了一个主干模型,则尝试在此文件中创建该模型的实例会产生未定义的错误。这个模块只是不知道这些外部文件中有什么,即使它们列在这个模块的依赖数组中。所以需要加载没有错误的外部文件,但在这些文件中创建的内容不在本模块的范围内。但是所有这一切都可以在未优化的情况下运行良好(如果我不使用优化器)。为什么会有差异?如果代码以某种方式格式不正确,我希望它以两种方式都失败,或者两种方式都成功。

    // 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

【问题讨论】:

    标签: javascript optimization requirejs


    【解决方案1】:

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

    如果您没有开始这样做,您可能需要调整代码中的操作方式以使其正常运行。如果该捆绑器不适合您,那么您也可以尝试其他捆绑器(Webpack 和 rollup 是两个流行的)。不过应该是可以的。

    要真正提供有关如何使其正常工作的帮助,我们需要了解更多信息,例如您的配置和代码结构。不过,一般来说,请确保您在代码中使用相对路径。


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

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

    另一个例外是,如果文件非常庞大,将其拆分可能会更好(但这是一种极端情况)。

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

    对于 HTTP/2,它还取决于用户是否支持此浏览器。对它的支持即将到来,并且在明年内,肯定应该完成。目前,它有点处于灰色地带,理想情况下,您希望拥有个性化的分析来帮助指导您的决策。

    【讨论】:

      【解决方案2】:

      是的,你可以做你想做的事。相对于 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 的同一个包的一部分,那么这意味着@987654339 之外的代码@' 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,但您永远不知道什么时候会再次出现问题。

      【讨论】:

      • 感谢您的回复。好吧,我删除了垫片。我在构建配置中添加了 findNestedDependencies: true 。还没有成功,但看起来这动摇了一些事情。我现在得到一个未定义的错误。请参阅我在 application.js 中设置的变量“App”。它在该文件中定义,并且没有报告错误。但它在 utilloader.js 中报告为未定义,这是 application.js 尝试要求的第一个模块。这里的层次结构有些令人不快。
      • 仅查看您问题中的代码,我看不出是什么导致了您在评论中描述的问题。你的模块是正确的 AMD 模块吗(调用 define)?
      • 嗯.. 我看到了一个潜在的问题。我正在使用这个处方 (johndavidmathis.wordpress.com/2013/04/23/…) 来构建我的应用程序。它可能与正确的 AMD 定义相冲突。具体来说,“允许模块使用多个文件和逻辑文件夹结构”中的功能让我把东西放在我的模块中的定义之外。我已经发布了上面的 utilloader 模块作为示例。这运行良好非优化。没有范围问题。我猜优化器不喜欢这个 - mod def 外部定义加载多个文件?
      • @Robert 是的,这是个问题。我已经编辑了答案以解决新信息。
      • 好的。这是一个大型项目 - 100 多个 js 文件。今天下午我一直在努力尝试编辑以适应规范。基本上我必须内联(不是在定义之上)并编辑当前未包含在定义中的 90 多个文件。相当多的工作。不知道为什么没有优化器一切运行良好。如果这是不,我认为非优化版本会以完全相同的方式破坏。以为他们是等价的。猜猜优化器更严格?无论如何将在星期一继续,并在我完成所有文件后发布我的结果。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多