axy.define — бициклет на тему CommonJS

А тем временем полку бесполезных велосипедов всё пребывает.

Я забросил последнее время JavaScript, а он тут развивается семимильными шагами. Так что я решил освежить знания и посмотреть на новые веяния. Для начала взялся разобраться с TypeScript (потому что я типизированный задрот и педант), Node.js (потому что куда без него), а заодно подумать над модульной системой для клиента (потому что 21-й век на дворе).

И чтобы всё это объединить, я написал на TypeScript модульную систему, которая эмулирует окружение ноды в браузере (не полностью, конечно, но немного).

Вот — axy.define.

Имеется некая виртуальная файловая система, в которой в виде файлов лежат модули и любые другие данные, с ними можно работать с помощью модуля fs. require() ищет модули внутри этой «ФС». Полный алгоритм ноды: относительные пути, поиск по node_modules + системные каталоги, подстановка расширений, индексные файлы, package.json…

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

Частично реализованы некоторые элементы окружения, global, process, модули ядра, такие как fs, path.

Пример

Ссылка

Тестовое приложение тупо меняет цвет страницы на серый.

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

В примере тестовый проект собран вместе с node_modules в которой лежит сама утилита сборки.
В браузере же:

  1. Из «командной строки» опять запускается сборка проекта уже в виртуальной ФС.
  2. Собранный проект запускает отдельную песочницу.
  3. В песочнице опять проходит сборка с нуля.
  4. Собранный повторно проект уже запускается и меняет цвет страницы.
  5. Стандартный поток вывода перенаправлен из консоли в дивы.

Такой вот треш.

JavaScript, суррогатные пары и фекалии

Если кратко: JavaScript не поддерживает суррогатные пары. Что в общем и правильно, так как в большинстве случаев это не нужно, а только лишние расходы. Однако, в меньшинстве случаев из-за этого придётся повозиться.

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

UTF-16 vs UCS-2

Как, наверняка, знают все здесь присутствующие, строки в компьютерах представляются, как последовательность байтиков. А каким образом эти байтики соответствуют буковкам, это определяет кодировка.

Старые добрые кодировки, вроде Windows-1251 были однобайтными. Одному символу — один байт. Работать с ними было одно удовольствие. Хочешь узнать длину строки (количество символов): это просто количество байт. Нужен 5-й символ: просто нужен пятый байт от начала строки. Единственный минус: в нашем многополярном мире 256 различных символов, это слишком мало. Особенно для китайцев с их тысячами иероглифов.

Поэтому теперь мы все любим Unicode. В юникоде много разных кодировок на все случаи жизни. Вот, например, UTF-8: в нём можно закодировать любой Unicode-символ хитрой переменного размера последовательностью байт. При этом базовая ASCII в нём остаётся такого же вида, как и в старых кодировках. Можно читать английские тексты не думая о кодировках, исходники программ и HTML/XML файлы сохраняют свою структуру, а наиболее популярные символы занимают меньше места, чем всякие иероглифы, что тоже приятно.

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

JavaScript: пользовательские исключения

В JavaScript есть обработка исключений. try-catch-finally, throw, всё как у взрослых. Большинство программистов, правда, ими не пользуется, но не в этом суть.

И вот мы пишем, допустим, библиотеку и хотим, чтобы её функции в случае чего бросали определённые исключения. Язык позволяет нам здесь бросать что угодно, например, строку: throw "Ой, ошибка, ошибка!";.

Но, это не наш путь. Есть предопределённые типы ошибок, унаследованные от Error и есть стандарт, который говорит нам об объекте с полями name и message.

MDN показывает нам, как сделать свой «класс» исключений, унаследованный от Error:

// Create a new object, that prototypally inherits from the Error constructor.
function MyError(message) {
    this.name = "MyError";
    this.message = message || "Default Message";
}
MyError.prototype = new Error();
MyError.prototype.constructor = MyError;

Мозиловцы правда здесь согрешили и использовали для создания прототипа рабочий конструктор Error. Сделаем по правильному:
Остальной текст под катом

Javascript: объекты vs структуры

Как мы рассмотрели в прошлых статьях, система типов в JavaScript не так, чтобы уж очень стройная.

И есть ещё один теоретический нюанс. Object в JS, по-сути, отвечает за две совершенно различные сущности.

С одной стороны это объект из ООП: отдельная индивидуальность со своим поведением и интерфейсом.

С другой: хранилище структурированных данных. Тоже, что и struct в Си, Dict в Питоне, Hash в Перле или ассоциативные массивы в PHP.
В питоне Dict, конечно, тоже объект, но там мы чётко можем разобрать — это словарь а не что-то иное. В JS разобрать это сложнее.
Остальной текст под катом

JavaScript: типы, классы и фреймы

С год назад мы тут разбирались с определением типов в JavaScript.

Рассмотрим ещё один аспект: фреймы. Из фрейма во фрейм можно передавать данные.

Например, у нас есть функция, которая обрабатывает массив немного по другому, чем объект:

function func(value) {
    if (value instanceof Array) {
        // массив
    } else {
        // объект
    }
}

И на странице есть iframe, в котором следующее:

window.parent.func([1, 2, 3]);

Это будет работать некорректно. Так как мы сравниваем значение с нашим Array из нашего основного окна. А значение создано во фрейме, в котором свой window и свой window.Array и именно от него производится этот массив.
Остальной текст под катом

Function Expression и ебучий осёл

В JavaScript, как известно, есть два способа объявить функцию.

/* Function Declaration. Функция сразу определяется в локальном контексте под именем func */
function func() {
 
}

и

/* Function Expression.  */
var func; // func просто переменная. Изначально в ней, как обычно, undefined.
 
func = (function () {}); // В определённый момент в качестве значения ей присваивается объект функции.

FE ещё называют анонимной функцией.

И есть ещё один подвид Function Expression. Именованное (Named) Function Expression:

var func;
 
func = (function fufunc() { /* ... */ });

Анонимная неанонимная функция :).

В нашем локальном контексте по прежнему есть переменная func.
Но в контексте вызываемой функции также появляется её «имя» fufunc.
Остальной текст под катом

JavaScript: вызов конструктора с произвольным числом аргументов

Есть у нас, предположим, функция и есть у нас массив произвольного количества аргументов для неё. Как нам её вызвать? Очень просто:

function f(a, b, c) {
    // ...
}
var args = [1, 2, 3];
 
f.apply(f, args);

Здорово.
Следующая задача: у нас есть функция-конструктор и массив аргументов для неё. Как нам использовать этот массив совместно с оператором new? Может new f.apply(f, args)? Хрен там, здесь new будет пытаться использовать в качестве конструктора результат от f.apply().
Остальной текст под катом

Путь извращенца: Singleton на JavaScript

Понадобился нам в нашем js-сценарии, допустим, Singleton. Ну, мы берём и делаем, как все взрослые дяди:

/**
 * @class MyClass
 * @property {String} x
 * @property {String} y
 */
function MyClass() {
    this.x = "x";
    this.y = "y";
}
MyClass.prototype = {/* ... */};
 
/**
 * Get instance of singleton
 *
 * @static
 * @return {MyClass}
 */
MyClass.getInstance = function () {
    if (!this.instance) {
        this.instance = new this();
    }
    return this.instance;
};
 
var instance1 = MyClass.getInstance(),
    instance2 = MyClass.getInstance();
 
instance1 == instance2; // true

Но, ведь, JavaScript идеальный язык для того, чтобы сделать какое-нибудь извращение. Грех такой шанс упустить.
Остальной текст под катом

Сборка JS: хрень о двух концах

Или вот зашла речь о сборке JavaScript и CSS.

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

Например, на одной страничке у нас подключаются файлы A.js, B.js и C.js. Загружаются они браузером за три запроса и сохраняются в кэше.

Но мы, при деплое, запускаем свою супер-склеивалку и у нас вместо трёх файлов получается один ABC.js. Браузеру приходится загружать тот же самый объём, но всего за один запрос, что намного оптимальнее. Пока всё хорошо.

А теперь мы переходим на другую страницу, на которой у нас используются сценарии A.js, B.js и D.js. В обычной ситуации браузер бы запросил D.js, а остальные два файла взял бы из кэша. Но теперь у нас на продакшене вместо трёх файлов — один ABD.js. И браузеру приходится тащить к себе по второму разу содержимое A и B.

А потом мы вносим изменения в B.js. В случае без сборки, браузеру пришлось бы обновить только один этот файл. В случае со сборокой же, пришлось обновлять все сборки, где участвует B. В нашем случае, это и ABC.js и ABD.js.

Мораль, как обычно: ни одна крутая тулза не избавляет от необходимости думать головой. Собирать имеет смысл только самое ядро, которое используется на всём сайте и очень редко меняется.

JavaScript, типы, классы и функция-определялка

Набросал на скору руку функцию-определялку «расширенного типа» javascript-значения, как обещал в недавней статье.

Если у кого тесты завершаются с ошибками, не поленитесь, отпишитесь.

Функция возвращает следующий набор «типов»:

  • number (включая объекты, созданные через new Number())
  • string (включая объекты, созданные через new String())
  • boolean (включая объекты, созданные через new Boolean())
  • undefined
  • null
  • function (включая функции, созданные через new Function() и host-функции в IE)
  • array (включая экземпляры унаследованный «классов»)
  • regexp
  • error (экземпляры Error и производных)
  • date (объекты Date)
  • element (DOM-элементы)
  • textnode (текстовые узлы DOM)
  • collection (коллекции DOM-элементов)
  • arguments (объекты arguments функции)
  • object (всё остальное)

Также дополнил сводные таблицы по типам.

По страницам:12