Современный модульный JavaScript

Решая задачу создания модульной джаваскрипт архитектуры мы пришли к тому, что адекватная модульность объявлена только в новом стандарте языка ECMAScript 6. Кроме деклараций позволяющих объявлять и импортировать модули между джаваскрипт файлами без замыканий и прочих ухищрений стандарт приносит также наконец-то адекватную лаконичную запись классов и их членов.

Среди существующих инструментов решать вопрос модульности позволяет загрузчик SystemJS, кроме формата ES6 или транспилированного в ES5 формат System.register он позволяет также грузить модули в других двух популярных ныне форматах CommonJS (CJS), AMD (RequireJS) и даже глобалы, т.е немодульный код и штуки навроде jquery и их плагинов, что позволяет как иметь современный модульный код, так и пользоваться существующими разработками и библиотеками и мигрировать код легче.

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

Кроме джаваскрипта загрузчик с помощью плагинов может также загружать и другие ресурсы навроде каскадных таблиц стилей или картинок.

Репозиторй проекта: https://github.com/systemjs/systemjs

Там же вы найдете справочник по наиболее частым способам его использования.

Загрузка на странице

Для настройки параметров загрузки модулей на странице используется функция .config глобала System, который инициализируется подключением system.js, также надо подключить traceur-runtime.js или его полную версию и положить в тот же каталог es6-module-loader.js если вы хотите подлкючать модули с директивами import/export из стандарта es6. Все остальные зависимости модули загрузчик тоже подтянет сам, при этом используется принцип ленивой загрузки, на уровне модулей, т. е. пока модуль не импортируется его содержимое не будет прочитано браузером.

Конфигурация

 

<script src="js/traceur-runtime.js"></script>
<script src="js/system.js"></script>
<script>

    System.config({
        baseURL: 'app',
        bundles: {'app-bundle': ['views/testview']},
        paths: {
            app-bundle: 'dist/js/app-bundle.js'
        },
        map: {
            underscore: 'lib/underscore'
        }
    });

</script>

 

для автоматической загруки можно задать общий префикс с помощью параметра baseURL

тип модуля и иногда зависимости загрузчик определяет автоматически, но если надо задать вручную это делается в секции meta

в bundles указываются файлы-сборки содержащие по нескольку модулей

в paths прописываются алиясы для модулей например 'jquery': 'path/to/some/jquery-1.x.y.js' укажет загрузчику использовать модуль именно из этого файла, а не пытаться загрузить его скажем из app/jquery.js

в мап происходит тоже-самое, что и в paths только в терминах модулей, т. е. если надо чтобы модуль somelbrary-1.2.3 воспринимался как somelibrary но этот модуль может быть загружен по стандартным политикам, то можно сделать ссылку второго на первый в этой секции.

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

 

<script>

    var moduleConfig = moduleConfig || { meta: {}, bundles: {}, paths: {}};

    moduleConfig.meta['views/dmgrid'] = { format: 'register', deps: [
        'jquery_ui', 'jquery_ui_css!css',
         ]
    };
    moduleConfig.bundles['app-bundle'] = ['views/testview'];
    moduleConfig.paths['app-bundle'] = '/js/app-bundle.js';
    moduleConfig.paths['jquery_ui_css'] = '/css/jquery-ui.min.css...';

    System.config(moduleConfig);

</script>

Здесь в первой строчке мы как раз получаем ранее определенный конфиг или инициализируем его пустыми значениями.

На второй строчке задается мета информация с дополнительным указанием зависимостей.

Третья указывает что модуль testview находится в app-bundle.

Последующие строчки указывают пути к файлам для отдельно взятых модулей.

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

Использование

Код использующий модули в нешем случае мог бы выглядеть так:

index.html

 

<script>

    System.import('views/testview').then(function(m) {

        var testView = new m.TestView({
            el: '#testView',
            tpl: {
                hello: '#tplHello'
            },
            l11n: {
                helloMessage: 'Hello World !'
            }
        }).render();

    }, console.error.bind(console));

    Promise.all([
        System.import('views/testview'),
        System.import('views/testview2')
    ]).then(function(modules) {

        var testView2 = new modules[1].TestView2({
            el: '#testView2'
        }).render();

    }, console.error.bind(console));

</script>

Здесь директива импорт загружает модуль экспорты которого помещаются в параметр колбека как в неупорядоченный массив и по именам (т. е. В m[0] и m.TestView сразу). Если надо подключить несколько модулей из импорт помещается в вызов функции промиса, а загруженные структуры уже в неупорядоченный массив.

Сборка

Для того чтобы модули загружались быстрее и собирались оптимальнее с учетом зависимостей у SystemJS есть подпроект Builder.

Его репозиторий находится по адресу: https://github.com/systemjs/builder

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

Замечательная особенность сборки также состоит в том что автоматически осуществляется транспиляция из формата ES6 в ES5, при этом формирования списка зависимостей также происходит автоматически, но с учетом конфигурации определенной в скрипте сборки. Конфигурация эта абсолютно та же что для лоадера.

build.js

var builder = require('systemjs-builder'),
  path = require('path');

builder.build('views/testview', {
  baseURL: path.resolve('.'),

  meta: {

  },
  ...
  paths: {

    }

}, 'dist/js/app-bundle.js').then(function() {

  console.log('Build complete');
}).catch(function(err) {
  console.log('Build error');
  console.log(err);
});

Для того чтобы не включать в бандлы используемые повсеместно библиотеки, можно указывать параметр в мета секции конфига:

meta: {
  'jquery': { format: 'amd', build: false },
  'jquery_ui': { format: 'amd', deps: ['jquery'], build: false }
},

Либо делать сборку с вычитанием общих зависимостей (пример можно найти на странице билдера). Сборку этого файла мы можем запустить просто скормив его ноде:

node build.js

В данном скрипте предполагается, что каталог dist/js уже создан или создается предыдущими задачами.

Соответсвенно для gulp и grunt мы можем использовать плагин вызова внешней комманды:

gulpfile.js

var gulp = require('gulp'),
    run = require('gulp-run')
    ;
gulp.task('systemjs-build', function() {
    run('node build.js').exec();
});
gulp.task('default', ['systemjs-build']);


или

Gruntfile.js

grunt.initConfig({
    exec: {
        'systemjs-build': 'node build.js'
    }
});
grunt.loadNpmTasks('grunt-exec');
grunt.registerTask('default', ['systemjs-build']);

Код

Скрипт будет собирать для нас вьюху из файла testview.js из каталога view, которая могла бы выглядеть вот так:

views/testview.js

import $ from "jquery";
import _ from "underscore";
import Backbone from "backbone";

var { View } = Backbone;


export class TestView extends View {

    constructor(params) {
        this.$el = $(params.els.el);

        super(params);
        console.log('test view inited');
    }

    render($el) {
        var $el = $el != undefined ? $el : this.$el;
        $el.html('hello');

    }

}

В данном пример сразу очень много примечательных моментов:

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

мы используем возможности es6 в той мере в которой нам это позволяет транспилятор traceur т. е. В данном случае это лаконичный синтаксис для классов и модульность.

Полный пример проекта: https://bitbucket.org/techminded/es6-backbone-skel/

 

Среднее (0 Голоса)
Средний рейтинг 0.0 звезд из 5.


Пока нет комментариев. Будь первым.