在应用程序其余部分之前加载 app.js

Load app.js before rest of application

我正在尝试弄清楚如何在允许用户获取实际应用程序之前加载 app.js。我想要做的是在我的所有 class Ext.defines 触发之前加载用户的配置文件......我想这样做的原因是因为 Ext.defines 实际上取决于值在用户的配置中。因此,例如,在 Ext.define 中,我可以将标题 属性 设置为从此全局用户配置 var 中提取。不,我不想通过更改所有这些属性来使用 initComponent...这可能需要相当长的时间。

相反,我想做的是加载配置,然后让 Ext.defines 运行,但我需要 Ext JS 和我定义的 class es 将在 classes 的其余部分之前加载。这可能吗?我一直在研究 Sencha Cmd 设置,但我一直非常不成功地让它工作。我正在玩 bootstrap.manifest.exclude: "loadOrder" 属性,它加载 classic.json,但没有定义我的 classes,但不幸的是,它也没有完全加载 Ext JS,所以Ext.onReady不能使用...我也不能使用我的模型来加载配置。

我在下面有一个非常高级的示例(这里是 Fiddle)。

Ext.define('MyConfigurationModel', {
    extend: 'Ext.data.Model',
    singleton: true,

    fields: [{
        name: 'testValue',
        type: 'string'
    }],

    proxy: {
        type: 'ajax',
        url: '/configuration',
        reader: {
            type: 'json'
        }
    }
});
// Pretend this would be the class we're requiring in our Main file
Ext.define('MyApp.view.child.ClassThatUsesConfiguration', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.classThatUsesConfiguration',
    /* We get an undefined value here because MyConfigurationModel hasn't
     * actually loaded yet, so what I need is to wait until MyConfigurationModel
     * has loaded, and then I can include this class, so the define runs and
     * adds this to the prototype... and no, I don't want to put this in
     * initComponent, as that would mean I would have to update a ton of classes
     * just to accomplish this */
    title: MyConfigurationModel.get('testValue')
});
Ext.define('MyApp.view.main.MainView', {
    extend: 'Ext.Viewport',
    alias: 'widget.appMain',
    requires: [
        'MyApp.view.child.ClassThatUsesConfiguration'
    ],
    items: [{
        xtype: 'classThatUsesConfiguration'
    }]
});
Ext.define('MyApp.Application', {
    extend: 'Ext.app.Application',
    mainView: 'MyApp.view.main.MainView',
    launch: function() {
        console.log('launched');
    }
});

/* In app.js... right now, this gets called after classic.json is downloaded and
 * after our Ext.defines set up, but I basically want this to run first before
 * all of my classes run their Ext.define */
Ext.onReady(function() {
    MyConfigurationModel.load({
        callback: onLoadConfigurationModel
    })
});
function onLoadConfigurationModel(record, operation, successful) {
    if (successful) {
        Ext.application({
            name: 'MyApp',
            extend: 'MyApp.Application'
        });
    }
    else {
        // redirect to login page
    }
}

据我所知,这对于 Sencha Cmd 是不可能的,因为虽然 Sencha Cmd 可以,但不可能告诉生产微加载器等待第二个文件,直到来自第一个文件做了一些事情(大概是从服务器加载了一些东西?)。

所以唯一的方法是在加载 ExtJS 之前获取 ExtJS 之外的选项。

您必须编写自己的 javascript,使用裸同步 XmlHttpRequest 将配置加载到全局变量中,并将其包含在 ExtJS 脚本之前的 index.html 中.这样,脚本在加载 ExtJS 之前执行,并且您在开发、测试和生产构建中具有完全一致的行为,而无需修改在框架升级期间可能被覆盖的任何框架文件。

我想这就是您要搜索的内容。

那么我是怎么做到的:在 index.html 中,我添加了一个自定义脚本来填充一些全局变量:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<script type="text/javascript">
    APIURI = '../api/', // <- also used in ExtJS.
    var xhr = new XMLHttpRequest();
    xhr.open('GET', APIURI+'GetOptions', false);
    xhr.setRequestHeader('Accept','application/json');
    xhr.send(null);
    try {
        var configdata = eval("(" + xhr.responseText + ")");
    } catch(e) {
         // snip: custom code for the cases where responseText was invalid JSON because of a shitty backend
    }
    if(configdata.options!=undefined) Settings = configdata.options;
    else Settings = {};
    if(configdata.translations!=undefined) Translations = configdata.translations;
    else Translations = {};
    Translations.get=function(str) {
        if(typeof Translations[str]=="string") return Translations[str];
        return "Translation string "+str+" missing.";
    };
 </script>
<link rel="icon" type="image/vnd.microsoft.icon" href="../favicon.ico">
<title>Application</title>
<script id="microloader" data-app="1a7a9de2-a3b2-2a57-b5af-df428680b72b" type="text/javascript" src="bootstrap.js"></script>

然后我可以在 Ext.define() 中使用,例如title: Translations.get('TEST')hidden: Settings.HideSomeButtonurl: APIURI + 'GetUserData'.

但是,这有一些主要缺点,您在继续之前应该考虑一下。

短时间后,出现了新的功能请求,我认为固定的设置应该在 运行 时更改,我意识到在设置更改时总是重新加载应用程序不是好的用户体验。一段时间后,我还发现 Chrome 已弃用同步 XmlHttpRequests,并且这种方法会延迟应用程序启动时间。

因此,决定在漫长的 运行 中,唯一明智的方法是能够在 运行 时间对任何配置值的更改做出反应,而无需完全重新加载应用程序。这样,可以在加载应用程序后应用设置,并且可以放弃在继续应用程序之前等待设置加载的要求。

为此,我必须完全解决完全本地化支持所需的一切,这样用户就可以在不重新加载应用程序的情况下切换语言,而且任何其他设置都可以在 运行 时间更改并且是自动应用于应用程序。

短期来看,这是一项相当大的工作,这对我来说并不重要,因为我计划重新设计整个应用程序布局,但从长远来看,这会节省很多时间和麻烦,尤其是当有人决定我们应该开始轮询服务器设置的更改,或者我们应该使用 ExtJS 表单登录而不是旧的 Basic 身份验证(当时已经要求多次,但我们无法交付,因为说糟糕的 ExtJS 应用架构)。

我们实际上使用了 Sencha CMD 方法。正如 @Alexander 提到的,我们还使用一个全局变量来保存应用程序的配置。这种方法还意味着服务器 returns 全局配置变量的实际声明。

如果你深入 app.json 文件,找到 js 配置键,你会在描述中看到

List of all JavaScript assets in the right execution order.

因此,我们在 app.js 资产

之前添加配置的端点
"js": [
    {
        "path": "data/config",
        "remote": true
    },
    {
        "path": "${framework.dir}/build/ext-all-debug.js"
    },
    {
        "path": "app.js",
        "bundle": true
    }
]

同时指定 remote: true.

// Specify as true if this file is remote and should not be copied into the build folder

"data/config" 端点 returns 类似于:

var CONFIG = {
    user: {
        id: 1,
        name: 'User'
    },
    app: {
        language: 'en'
    }
}

现在我们可以在 类.

中的任何位置引用 CONFIG 变量

我称之为 "splitting the build",因为它从 Ext.app.Application class 中删除了 Ext.container.Viewport class 的依赖关系树。所有 Ext JS 应用程序都有一个设置为主视图的视口。通过将应用程序核心的所有要求声明移动到视口 class,应用程序可以从应用程序 class 显式加载视口,并且生产构建可以配置为输出两个单独的文件,app.js 和 viewport.js。然后在加载应用程序的核心之前可以发生任意数量的操作。

// The app.js file defines the application class and loads the viewport
// file.
Ext.define('MyApp.Application', {
   extend: 'Ext.app.Application',
   requires: [
      // Ext JS
      'Ext.Loader'
   ],
   appProperty: 'application',
   name: 'MyApp',

   launch: function() {
      // Perform additional operations before loading the viewport
      // and its dependencies.
      Ext.Ajax.request({
         url: 'myapp/config',
         method: 'GET',
         success: this.myAppRequestSuccessCallback
      });
   },

   myAppRequestSuccessCallback: function(options, success, response) {
      // Save response of the request and load the viewport without
      // declaring a dependency on it.
      Ext.Loader.loadScript('classic/viewport.js');
   }
});

-

// The clasic/viewport.js file requires the viewport class which in turn
// requires the rest of the application.    
Ext.require('MyApp.container.Viewport', function() {
   // The viewport requires all additional classes of the application.
   MyApp.application.setMainView('MyApp.container.Viewport');
});

在生产中构建时,视口及其依赖项不会包含在 app.js 中,因为它没有在 requires 语句中声明。将以下内容添加到应用程序的 build.xml 文件中,以将视口及其所有依赖项编译到 viewport.js 中。方便的是,开发和生产文件结构保持不变。

<target name="-after-js">
   <!-- The following is derived from the compile-js target in
        .sencha/app/js-impl.xml. Compile the viewport and all of its
        dependencies into viewport.js. Include in the framework
        dependencies in the framework file. -->
    <x-compile refid="${compiler.ref.id}">
        <![CDATA[
            union
              -r
              -class=${app.name}.container.Viewport
            and
            save
              viewport
            and
            intersect
              -set=viewport,allframework
            and
            include
              -set=frameworkdeps
            and
            save
              frameworkdeps
            and
            include
              -tag=Ext.cmd.derive
            and
            concat
              -remove-text-references=${build.remove.references}
              -optimize-string-references=${build.optimize.string.references}
              -remove-requirement-nodes=${build.remove.requirement.nodes}
              ${build.compression}
              -out=${build.framework.file}
              ${build.concat.options}
            and
            restore
              viewport
            and
            exclude
              -set=frameworkdeps
            and
            exclude
              -set=page
            and
            exclude
              -tag=Ext.cmd.derive,derive
            and
            concat
              -remove-text-references=${build.remove.references}
              -optimize-string-references=${build.optimize.string.references}
              -remove-requirement-nodes=${build.remove.requirement.nodes}
              ${build.compression}
              -out=${build.out.base.path}/${build.id}/viewport.js
              ${build.concat.options}
            ]]>
    </x-compile>

    <!-- Concatenate the file that sets the main view. -->
    <concat destfile="${build.out.base.path}/${build.id}/viewport.js" append="true">
       <fileset file="classic/viewport.js" />
    </concat>
</target>

<target name="-before-sass">
    <!-- The viewport is not explicitly required by the application,
         however, its SCSS dependencies need to be included. Unfortunately,
         the property required to filter the output, sass.name.filter, is
         declared as local and cannot be overridden. Use the development
         configuration instead. -->
    <property name="build.include.all.scss" value="true"/>
</target>

这个特定的实现将框架依赖项保存在它们自己的文件中,framework.js。这被配置为 app.json 文件中输出声明的一部分。

"output": {
   ...
   "framework": {
      // Split the framework from the application.
      "enable": true
   }
}

https://docs.sencha.com/extjs/6.2.0/classic/Ext.app.Application.html#cfg-mainView https://docs.sencha.com/extjs/6.2.0/classic/Ext.container.Viewport.html https://docs.sencha.com/cmd/guides/advanced_cmd/cmd_build.html#advanced_cmd-_-cmd_build_-_introduction