如何使用Backbone.js和Require.js(r.js)一起,但在优化后结果是2个文件?

8
我已经跟着基础教程(在运行r.js之后,会生成一个文件)进行了学习。
问题是,我的main.js文件最终达到了500KB。太大了。我想把它分成两个文件。
我想将我的main.js文件优化成两个文件:
  1. 一个用于保存主页和用户个人资料页面,因为它们是最常访问的
  2. 一个用于保存所有其他页面(订单,帐户设置,个人资料设置等)
大多数人都会访问主页和用户个人资料页面,我希望这些页面首先快速加载(同时让其他页面在第二个主文件中后台加载)。
问题是,我不知道该如何做。虽然有类似这样的在线示例,但这些示例并未使用Backbone框架。它们没有涉及到如何处理路由和app.js
我感到困惑…因为我只有一个app.js,一个router.js…我该如何将router.js拆分成两个文件呢?
我不知道在处理Backbone时如何分割我的项目。
以下是代码: HTML页面(我的单页面应用程序的入口点)
<html>
<head>
    <script type="text/javascript" data-main='/media/js/main' src='/media/js/lib/requirejs/require-jquery.js'></script>
</head>
<body>
    Hello
</body>
</html>

Main.js

require.config({
    paths:{
        jquery: 'lib/requirejs/require-jquery',
        jquery_ui:'lib/jquery-ui/jquery-ui-1.10.3.custom',
        underscore: 'lib/underscore/underscore-min',
        backbone:'lib/backbone/backbone-min',
        backbone_viewhelper:'lib/backbone/backbone.viewhelper',
        text: 'lib/requirejs/text',
        birthdaypicker: 'lib/birthdaypicker/bday-picker',
        //more paths
    },
    waitSeconds: 30,
    shim:{
        'underscore':{
            exports: '_'
        },
        'backbone':{
            deps:[ 'underscore', 'jquery'],
            exports: 'Backbone'
        },
        'backbone_viewhelper':{
            deps:['underscore','backbone']
        }
    }
});


require([
    'app',
    'json2',
    'jquery_ui',
    'backbone_viewhelper',
    'bootstrap_js',
    'bootstrap_select',
    'birthdaypicker',
    'accounting',
    'numbersonly',
    'main_alert',
    'string_tools',
    'plupload',
    //more things here
], function(App){
    App.initialize();
});

App.js

define([
    'jquery',
    'underscore',
    'backbone',
    'router'
], function($, _, Backbone, Router){    
    var initialize = function(){
        Router.initialize();
    }
    return {
        initialize: initialize
    };

});

Router.js

define([
    'jquery',
    'underscore',
    'backbone',
    'modules/index/view',
    'modules/home/view',
    'modules/listings_search/view',
    'modules/profile/view',
    //more modules
], function($, _, Backbone, indexView, homeView,searchView, profileView){
    var AppRouter = Backbone.Router.extend({
        initialize:function(){
            _.bindAll(this);
        },
        routes:{
            '':'index',
            'home': 'home',
            'register': 'register',
            'login': 'login',
            'listings(/start/:start)(/num/:num)': 'search',
            'listings/create': 'listingsCreate',
            'listings/:listing_id/edit': 'listingsEdit',
            'orders/listings/:listing_id/create': 'ordersCreate',
            'orders/buyer(/start/:start)(/num/:num)': 'ordersListBuyer',
            'orders/seller(/start/:start)(/num/:num)': 'ordersListSeller',
            'orders/:order_id': 'orders',
            'orders/:order_id/messages':'messages',
            '*actions': 'defaultAction'
            //more stuff
        },
        index:function(){
            app_router_view.show(indexView);
        },
        search:function(start, num){
            var options = {
                filters:{
                    start: start,
                    num: num
                }
            };
            app_router_view.show(searchView, options);
        },
        static:function(template){
            app_router_view.show(staticView, { static_view: { template: template }});
        },
        profile:function(){
            app_router_view.show(profileView);
        },
        passResetCode:function(code){
            app_router_view.show(passCodeView, {'code':code});
        },
        //more stuff
        home:function(){
            app_router_view.show(homeView);
        },
        defaultAction:function(actions){
            this.navigate('/', { trigger:true});
        }
    });
    var initialize = function(){
        var app_router = new AppRouter;
        Backbone.history.start({pushState:true, root: '/'});
        $(document).on('click', 'a:not([data-bypass])', function (evt) {
            var href = $(this).attr('href');
            if(href){
                var protocol = this.protocol + '//';
                if (href.slice(protocol.length) !== protocol && href != '#') {
                    evt.preventDefault();
                    app_router.navigate(href, { trigger: true});
                }
            }else{
            }
        });
    };
    return {
        initialize:initialize
    }
});

正如您所看到的,我的整个应用程序始于 main.js,然后转到 app.js,最后进入 router.js
我该如何进行拆分?

你是否考虑通过将一些供应商库移动到CDN并不将它们打包到all.js文件中来减小all.js的大小?(例如jquery) - ekeren
这个文件经过gzip压缩后的大小是多少?如果你有使用nodejs的选项,你也可以尝试像http://ezeljs.com/这样的东西,在第一次响应中发送渲染页面。 - Jordan Denison
4个回答

3

根据您分享的代码,我创建了一个示例Web应用程序,并将代码提交到git-hub中。

该应用程序分为两个模块:

  • main包括modules/index/view和modules/profile/view
  • other包括'modules/order/view'和'modules/search/view'

当请求modules/index/viewmodules/profile/view时,如果尚未下载,则会下载main.js。类似地,当请求modules/order/viewmodules/search/view时,如果尚未下载,则会下载other.js。请记得使用require.js v2.1.10或更高版本,因为它具有生成other.js所需的bundle功能。

您可以通过在build.js中定义order、search、profile作为独立模块来进一步模块化,以便仅在需要时下载它们。

执行构建命令的输出:

media/js/main.js
----------------
media/js/lib/jquery/jquery-min.js
media/js/lib/underscore/underscore-min.js
media/js/lib/backbone/backbone-min.js
media/js/router.js
media/js/app.js
media/js/main.js
media/js/modules/index/model.js
media/js/modules/index/view.js
media/js/modules/profile/model.js
media/js/modules/profile/view.js

media/js/other.js
----------------
media/js/modules/order/model.js
media/js/modules/order/view.js
media/js/modules/search/model.js
media/js/modules/search/view.js

执行流程如下: index.html => media/js/main [它包含 index/view, profile/view, app.js 和所有依赖项]。默认情况下,显示 Index 视图,因为它被配置为主页路由。
当点击 Profile 链接时,不会再下载任何文件,因为 main.js 已经被下载。 当点击搜索/订单链接时,将下载 other.js

嗨@Manish,你知道我为什么在优化时会出现这个错误吗?正在跟踪“other”的依赖项。 错误:错误:ENOENT,没有此文件或目录'/mypath/media/js/other.js' - TIMEX
@TIMEX 你能分享一下 main.js 文件吗? - Manish Mulimani

3
我已经创建了一个示例来展示如何完成。其中包含了Backbone应用程序的框架。该应用程序被分为:
  • 一个main捆绑包,其中包含应用程序的核心,并仅呈现“main”视图(在此称为views/app),

  • 和一个secondary捆绑包,其中包含所有其他视图。

secondary捆绑包仅在需要时加载。在这个应用程序中,这意味着它只应在使用foobar视图时加载,而不是之前。(您可以通过检查浏览器中的网络操作来验证这一点。)

关键点是:

  1. The view in views/app is the "main" view of the application. Loading it instantiates and renders the view right away.

  2. The js/router module does not use the other views directly. It calls require to load the view first. This makes the foo and bar function asynchronous.

  3. This is the part of the build.js file that divides the application into two bundles:

    modules: [
        {
            name: "main"
        },
        {
            name: "secondary",
            // There no module named secondary in the source, so create it.
            create: true,
            // Make sure to include all the views other than the main one.
            include: [
                "views/foo",
                "views/bar"
            ],
            // Exclude everything we've included in `main`.
            exclude: ["main"]
        }
    ]
    
  4. The optimized version needs an initial configuration like this:

      bundles: {
         "secondary": ["views/foo", "views/bar"]
      }
    

    This tells RequireJS that the modules views/foo and views/bar are loaded by loading secondary.

请阅读README.md文件以获取更多详细信息。

谢谢 @Louis,我已经找到我需要的东西了 :) - V.S.V

0

-3

将代码拆分成两个文件并不是正确的方法。

将代码拆分成两个文件只会增加整体加载时间,因为需要额外的包装器代码。此外,拆分代码可能会不必要地增加代码的模块化程度,从而在未来造成设计问题。

正确的方法是懒加载路由文件(这样你就可以先加载首页和用户资料页面),然后在加载完首页和用户资料页面后再在后台加载其他文件。

虽然有可能拆分代码,但这样做只会创建不必要的代码,真的没有什么帮助。

这里有一个可能有用的链接。


1
TIMEX试图做的是缩短用户请求页面到页面准备好运行的时间。在这种情况下,将应用程序裁剪成仅加载最基本的内容,而用户可能稍后才能使用的其余内容则稍后在后台加载,这是绝对有意义的。我说的是从实际生产中使用RequireJS的经验。 - Louis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接