Definer.js — простая модульная система

в 21:57, , рубрики: javascript, метки:

Пока JavaScript не обзавёлся настоящими модулями мы продолжаем импровизировать.
Так появилась на свет ещё одна реализация модулей — definer.

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

Для хорошего понимания идеи, под катом примеры от простого к сложному.

Возьмём абстрактную страницу товаров интернет-магазина, index.html:

<html>
<head>
    <meta charset="utf-8"/>
    <title>Каталог интернет-магазина</title>
    <script src="jquery.js"></script>
    <script src="https://rawgithub.com/tenorok/definer/master/definer.js"></script>
    <script src="modules/cart.js"></script>
    <script src="modules/list.js"></script>
</head>
<body>
    <ul class="list">
        <li class="item">
            <span class="name">Компьютер</span>, <span class="price">250</span>
        </li>
        <li class="item">
            <span class="name">Телевизор</span>, <span class="price">100</span>
        </li>
        <li class="item">
            <span class="name">Холодильник</span>, <span class="price">300</span>
        </li>
    </ul>
</body>
</html>

Будем использовать jQuery для работы с DOM и definer для модулей.

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

definer('Cart', function() {

    function Cart() {
        this.list = [];
    }

    Cart.prototype = {
        add: function(target) {
            var item = $(target);
            this.list.push({
                name: item.find('.name').text(),
                price: +item.find('.price').text()
            });
        },
        sum: function() {
            return this.list.reduce(function(sum, item) {
                return sum + item.price;
            }, 0);
        }
    };

    return Cart;

});

Модуль list, зависящий от Cart и реализующий взаимодействие посетителя с каталогом:

definer('list', function(Cart) {

    var iCart = new Cart();

    $(function() {
        $('.item').on('click', function(e) {
            iCart.add(e.currentTarget);
            console.log(iCart.sum());
        });
    });

});

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

В данном примере, у нас осталась глобальная переменная, которую тоже можно вынести в модуль, это jQuery.

Подключим файл modules/clean.js перед существующими модулями:

definer.clean('$');

Теперь переменной $ нет в глобальном контексте:

console.log($); // undefined

Чтобы продолжать использовать jQuery в модулях, добавим зависимость:

definer('Cart', function($) { ... });
definer('list', function($, Cart) { ... });

Сборка

Теперь всё готово и можно собрать исходники в единый файл.

Устанавливаем сборщик модулей:

npm install definer

Собираем все модули из директории modules в файл index.js:

./node_modules/.bin/definer -d modules/ index.js

Теперь в index.html достаточно подключить только jQuery и собранный файл:

<script src="jquery.js"></script>
<script src="index.js"></script>

Сборка с помощью grunt-definer

Для удобства разработки есть grunt-плагин. Можно установить мониторинг на изменение файлов модулей и автоматически запускать сборку.

Устанавливаем всё, необходимое для гранта:

npm install grunt grunt-cli grunt-contrib-watch grunt-definer

Добавим в корень проекта файл Gruntfile.js:

module.exports = function(grunt) {

    grunt.initConfig({
        watch: {
            scripts: {
                files: ['modules/*.js'],
                tasks: ['definer:all']
            },
        },
        definer: {
            all: {
                target: 'index.js',
                directory: 'modules/'
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-definer');

};

И запустим мониторинг:

./node_modules/.bin/grunt watch

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

Сборка сторонних файлов

Сейчас к странице подключается два файла — jQuery и собранные модули. Можно добавить jQuery в сборку и подключать к странице всего один файл.

Для этого достаточно добавить опцию clean в grunt-цель:

definer: {
    all: {
        target: 'index.js',
        directory: 'modules/',
        clean: {
            $: 'jquery.js'
        }
    }
}

Теперь в index.html достаточно подключить только один собранный файл:

<script src="index.js"></script>

JSDoc

Возможно формирование JSDoc, содержащего информацию о собранном файле.

Для этого добавим опцию jsdoc в grunt-цель:

jsdoc: {
    "file": "Добавление товаров в корзину интернет-магазина",
    "copyright": "2014 Artem Kurbatov, tenorok.ru",
    "license": "MIT license",
    "version": "package.json",
    "date": true
}

Возможно указание относительного пути до JSON-файла, из которого сборщик получит значение одноимённого поля.

Положим в корень проекта файл package.json:

{
    "version": "0.1.0"
}

Перед модулями в собранном файле появится такой JSDoc:

/*!
 * @file Добавление товаров в корзину интернет-магазина
 * @copyright 2014 Artem Kurbatov, tenorok.ru
 * @license MIT license
 * @version 0.1.0
 * @date 17 February 2014
 */

Итого

Definer помогает:

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

Документацию по definer и grunt-definer можно найти на гитхабе.

Автор: tenorok

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js