Загрузить приложение.js перед остальной частью приложения


Я пытаюсь понять, как я могу загрузить приложение.js перед тем, как позволить пользователю получить фактическое приложение. То, что я пытаюсь сделать, это загрузить файл конфигурации пользователя перед всем моим классом Ext.определяет огонь... причина, по которой я хочу это сделать, заключается в том, что Ext.определения фактически зависят от значений в конфигурации пользователя. Так, например, в Ext.определите, я мог бы установить свойство title для извлечения из этой глобальной пользовательской конфигурации var. И нет, я не хочу проходить через это. измените все эти свойства, чтобы использовать initComponent... это может занять довольно много времени.

Вместо этого я хотел бы загрузить конфигурацию, а затем позволить Ext.определяет run, но мне понадобится Ext JS и один из моих определенных классов, которые будут загружены до остальных классов. Возможно ли это? Я изучал настройки Sencha Cmd, но мне очень не удалось заставить это работать. Я играл со свойством bootstrap.manifest.exclude: "loadOrder", которое загружает классику.json, и не определяет мои классы, но, к сожалению, это также не полностью загружает Ext JS, так что Ext.onReady нельзя использовать... я также не могу использовать свою модель для загрузки конфигурации.

У меня есть пример очень высокого уровня ниже (Вот Скрипка).

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
    }
}
3 4

3 ответа:

Я называю это "разбиением сборки", потому что оно удаляет Ext.контейнер.Дерево зависимостей класса Viewport от Ext.апп.Класс приложения. Все приложения Ext JS имеют видовой экран, который устанавливается в качестве основного вида. Перемещая все объявления required ядра приложения в класс viewport, приложение может явно загрузить видовой экран из класса application, а рабочую сборку можно настроить на вывод двух отдельных файлов app.js и обзорный экран.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');
});

При построении в рабочей среде видовой экран и его зависимости не будут включены в приложение.js, потому что он не объявлен в операторе requires. Добавьте в сборку приложения следующее.xml-файл для компиляции видового экрана и всех его зависимостей в видовой экран.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. Это настраивается как часть объявления вывода в приложении.файл 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

Насколько я знаю, это невозможно с Sencha Cmd, потому что в то время как Sencha Cmd может загружать фреймворк и приложение отдельно , невозможно сказать производственному микролоадеру подождать со вторым файлом, пока код из первого файла не сделает что-то (предположительно загрузил что-то с сервера?).

Таким образом, единственным подходом было бы получить параметры вне ExtJS, прежде чем загружать ExtJS.

Вам придется написать свой собственный javascript, который загружается конфигурация в глобальную переменную, используя голый, синхронный XmlHttpRequest, и включить его в index.html перед скриптом ExtJS. Таким образом, сценарий выполняется до того, как ExtJS загружается вообще, и у вас есть полностью согласованное поведение при разработке, тестировании и производственных сборках без изменения какого-либо файла платформы, который может быть перезаписан во время обновления платформы.

Я думаю, это то, что вы ищете.

Итак, как я это сделал: в индексе.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.HideSomeButton или url: APIURI + 'GetUserData'.

Тем не менее, это имеет серьезные недостатки, которые вы должны рассмотреть, прежде чем продолжить.

Через короткий промежуток времени появились новые запросы функций, и настройки, которые я считал фиксированными, должны были измениться во время выполнения, и я понял, что всегда перезагружать приложение, когда меняется настройка, не очень хорошо для пользователя. Некоторое время спустя я также обнаружил, что Chrome устарел синхронный XmlHttpRequests, и этот подход задерживает время запуска приложения.

Таким образом, было принято решение, что в долгосрочной перспективе единственным разумным подходом является возможность реагировать на изменения любого значения конфигурации во время выполнения, без полной перезагрузки приложения. Таким образом, настройки могут быть применены после загрузки приложения, и требование может быть отброшено, чтобы дождаться загрузки настроек, прежде чем продолжить работу с приложением.

Для этого мне пришлось полностью проработайте все необходимое для полной поддержки локализации, чтобы пользователь мог переключаться между языками без перезагрузки приложения, а также любые другие настройки могут изменяться во время выполнения и автоматически применяться к приложению.

В краткосрочной перспективе это довольно большая работа, которая на самом деле не имела значения для меня, потому что я планировал переработать весь макет приложения, но в долгосрочной перспективе это сэкономит довольно много времени и головной боли, особенно когда кто-то решит, что мы должны начать. опрос для изменения настроек с сервера, или что мы должны использовать форму ExtJS для входа в систему вместо старой доброй базовой аутентификации (которая к тому времени уже запрашивалась несколько раз, но мы не могли доставить из-за упомянутой дерьмовой архитектуры приложения ExtJS).

Мы действительно используем подход Sencha CMD. Как уже упоминалось @Alexander, мы также используем глобальную переменную для сохранения конфигурации приложения. Этот подход также подразумевает, что сервер возвращает фактическое объявление переменной global config.

Если вы копнетесь в файле app.json и найдете ключ конфигурации js, вы увидите, что в описании он говорит

Список всех ресурсов JavaScript в правильном порядке выполнения.

Итак, мы добавляем конечная точка конфигурации перед активом app.js

"js": [
    {
        "path": "data/config",
        "remote": true
    },
    {
        "path": "${framework.dir}/build/ext-all-debug.js"
    },
    {
        "path": "app.js",
        "bundle": true
    }
]

Также указывая remote: true.

/ / укажите значение true, если этот файл удален и не должен быть скопирован в папку сборки

Конечная точка "data/config" возвращает что-то вроде:

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

И теперь мы можем иметь ссылку на переменную CONFIG в любом месте наших классов.