Single page apps in depth 3 (перевод)

Поддерживаемость

В этой главе мы посмотрим на то, как идеи, представленные в предыдущей главе могут быть применены в реальном коде — в новом и принятом по наследству. Я также расскажу о распространении кода путем создания выделенного сервера npm.

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

Общие принципы

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

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

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

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

Извлечение модулей/пакетов из вашей архитектуры

Во-первых, проведите осмотр качества кода и устраните плохие практики указанные в предыдущей главе.

Затем начинайте перемещать ваши файлы используя шаблон CommonJS: явные reqire()-ы и только один экспорт на файл.

Далее, обратите внимание на архитектуру. Попробуйте разделить этот клубок на отдельные пакеты:

  • Модели и другой многоразовый код (общие вьюхи/визуальные компоненты), вероятно, принадлежат общему пакету. Это ядро вашего приложения, на котором строится все остальное. Отнеситесь к этому как к сторонней библиотеке в том смысле, что это отдельный пакет, который require()-ится в остальных ваших модулях. Постарайтесь, чтобы в общем пакете не было запуска состояний (инстанциорования и тд). Другие пакеты инстанцируют вещи, основываясь на нем, но общий пакет не должен иметь динамического кода.

  • Помимо своего ядра/общего пакета, какие самые маленькие кусочки кода, которые имеют смысл? Наверняка есть по одному для каждого «primary» акта в вашем приложении. Для ускорения загрузки, вы хотите, чтобы каждый пакет, который может быть загружен независимо после ядра, загружался (начальное время загрузки приложения не увеличивается, так как количество пакетов растет). Если ваша сборка сложна, вы, вероятно, захотите единый механизм, который заботится о вызове правильного инициализатора.

  • Изолируйте код инициализации/создания экземпляра в каждом пакете, перемещая его в одно место: index.js для этого конкретного пакета (или, если есть много настроек, в отдельном файле - но только в одном месте). "Я ненавижу инициализацию, и хочу как можно меньше его в своем коде". Экспорт одной функции Initialize(), которая принимает параметры и настраивает весь модуль. Это позволяет загрузить пакет без изменения глобального состояния. Каждый пакет как "мини-приложение": он должен скрыть свои данные (одноразовые вьюхи, поведения и модели).

  • Пересмотрите цепочки наследования. Классы - ужасная замена для API, ориентированного на использование. Расширение класса требует знания и понимания деталей его реализации. API-интерфейсы, состоящие из простых функций лучше всех, так что если вы можете - пишите API. API часто выглядит как библиотека манипуляции состояниями (например, добавить приглашение, удалить приглашение и т.д.); когда экземпляр с соответствующими вьюхами цепляет этот API.

  • Остановите наследование вьюх друг от друга. Наследование вообще не подходит для вьюх. Можно наследовать от вашего фреймворка, но не стройте сложные иерархии. Представление не должно иметь много кода; это вредная привычка. Наследование имеет свою нишу, но она меньше, чем вы думаете.

  • Почти каждое представление в вашем приложении должно быть инстанцированно вне зависимости от любого другого представления. Вы должны определить вьюхи, которые вы хотите использовать повторно, и переместите их в глобальный модуль. Если вьюхи не предназначены для повторного использования, то они не должны использоваться за пределами их поля деятельности. Многоразовые вьюхи в идеале должны быть задокументированы в интерактивном каталоге, как в Twitter Bootstrap.

  • Извлекайте постоянные сервисы. Это вещи, которые являются активными во всем глобальном мире и поддерживют состояние между различными активностями. Например, бэкенд реального времени и кэш данных. Или состояние пользователя - хотелось бы сохранять, к примеру список открытых элементов от действия к действию (например, если в вашем приложении есть табы).

Рефакторинг существующего модуля

Дано: существующий модуль.

  1. Убедитесь, что каждый файл определяет и экспортирует одну сущность. Если вы определяете объекты Account и связанные с ним Settings, положите их в двух разных файлах.

  2. Не добавляйте - явно или неявно - переменные под window.*. Вместо этого, всегда кладите свой export в module.exports. Это делает возможным для других модулей использовать ваш модуль без использования глобальных имен.

  3. Перестаньте использовать другие модули по их глобальному имени. Используйте var $ = require('jquery'), чтобы указать, что ваш модуль зависит от JQuery. Если ваш модуль требует другой локальный модуль, загружайте его по пути: var User = require('./model/user.js').

  4. Задержите конкретную инстанциацию как можно дольше, поместив этот код в один загрузочный файл/функцию. Определение модуля должно быть отделено от запуска модуля. Это позволяет малым частям системы для тестироваться независимо, так как теперь вы можете requre()-ть модуль, не запуская его.
    Например, если вы раньше определяли класс, а затем сразу присваивали экземпляр этого класса глобальной переменной/неймспейсу в одном файле; вы должны переместить инстанциацию в отдельный загрузочный файл/функцию.

  5. Если у вас есть подмодули (например, чат, который использует backend_service), не открывайте их уровню выше. Инициализация подмодуля должна быть сделана непосредственно над ним (а не два слоя над ним). Конфигурация может переходить от верхнеуровнего Initialize() к initialize() подмодуля, но держите подмодули вне досягаемости от верхних уровней.

  6. Постарайтесь свести к минимуму внешний интерфейс.

  7. Пишите пакетные тесты. Каждый пакет должен иметь юнит- и интеграционные тесты, которые могут быть запущены независимо от других пакетов (кроме сторонних библиотек и ядра).

  8. Начните использовать npm для распространения зависимостей. Npm позволяет легко распространять и использовать небольшие модули Javascript.

Советы для новых проектов

Начните с файла package.json.

Добавьте единую загрузочную функцию (bootstrap funciton). Загрузка модулей не должны иметь побочных эффектов.

Пишите сначала тесты.

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

Минимизацируйте экспорт. Малый внешний интерфейс.

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

Инструментарий: npm

Наконец, давайте поговорим о дистрибьюции. Ваши проекты растут в объеме и модульности, вы хотите иметь возможность простым способом загружать пакеты из различных репозиториев. npm - удивительный инструмент для создания и распространения небольших JS модулей. Если вы еще не использовали его раньше, нагуглите самоучитель или прочитайте документацию, или просмотрите npm cheatsheet Nodejitsu. Создание npm пакета просто вопрос следования условиям CommonJS и добавления метаданных с помощью файла package.json. Вот пример package.json:

{ "name": "modulename",
  "description": "Foo for bar",
  "version": "0.0.1",
  "dependencies": {
    "underscore": "1.1.x",
    "foo": "git+ssh://git@github.com:mixu/foo.git#0.4.1"
  }
}

Этот пакет может быть установлен со всеми его зависимостями через npm install. Для увеличения версии модуля, то просто запустите npm version patch (minor, major).

Вы можете опубликовать свой пакет с помощью одной команды (но только после RTFM). Если вам нужно сохранить ваш код приватным, вы можете использовать git+ssh://user@host:project.git#tag-sha-or-branch для указания зависимости, как показано выше.

Если ваши пакеты могут быть публичными и использованы другими людьми, тогда используйте публичный реестр npm. Недостаток использования своих пакетов через Git - вы не получаете преимущества семантической версионности. Вы можете обратиться к той или иной ветке или коммиту по sha, но это не идеал. Если вы обновляете ваш модуль, то вы должны пойти и поменять теги везде в зависимостях. Это не так уж плохо, но в идеале, мы имели бы возможность сказать:

{
  "dependencies": { "foo": ">1.x.x" }
}

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

Прямо сейчас, возьмите и установите локальную версию npm, если вы хотите работать с семантическими номерами версий. Это включает в себя установку и настройку CouchDB. Если вам нужен read-only кэш (что очень полезно для ускорения/повышения надежности больших одновременных развертываний), взгляните на npm_lazy; он использует статические файлы вместо CouchDB. Я работаю над своим сервером npm, который будет проще в настройке, но еще не сделал его из-за написания этой книги. Но как только это будет сделано, я обновлю этот раздел.

Яндекс.Метрика