Хабрахабр

MAM: сборка фронтенда без боли

MАМ управляет Агностик Модулями, избавляя меня от львиной доли рутины. Здравствуйте, меня зовут Дмитрий Карловский, и я… обожаю MAM.

Типичный Агностик Модуль

При желании не сложно прикрутить поддержку любого другого языка. Агностик Модуль, в отличие от традиционного, это не файл с исходником, а директория, внутри которой могут быть исходники на самых разных языках: программная логика на JS/TS, тесты к ней на TS/JS, композиция компонент на view.tree, стили на CSS, локализация в locale=*.json, картинки и тд, и тп. Например, Stylus для написания стилей, или HTML для описания шаблонов.

Если модуль включается, то включается целиком — каждый исходник модуля транспилируется и попадает в соответствующий бандл: скрипты — отдельно, стили — отдельно, тесты — отдельно. Зависимости между модулями трекаются автоматически путём анализа исходников. Для разных платформ — свои бандлы: для ноды — свои, для браузера — свои.

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

Вот основные принципы: МАМ — это смелый эксперимент по радикальному изменению способа организации кода и процесса работы с ним.

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

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

Не используешь — не включается. Не платить за то, что не используешь. Используешь какой-то модуль — он включается в бандл со всеми своими зависимостями. Чем меньше модули, тем больше гранулярность и меньше лишнего кода в бандле.

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

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

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

Самый простой путь — самый верный. Если правильный путь требует дополнительных усилий, то будьте уверены, что никто им не пойдёт.

Открываем первый попавшийся проект с использованием современной системы модулей: Модуль меньше чем на 300 строк, 30 из них — импорты.

Но это ещё цветочки: Для функции из 9 строк требуется 8 импортов.

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

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

Эту проблему для JS худо-бедно пытаются решить усложнением сборочного пайплайна, путём добавления так называемого "tree-shaking", вырезающего лишнее из того, что вы наимпортировали. Всё это приводит к низкой гранулярности кода и раздуванию размеров бандлов неиспользуемым кодом, которому повезло оказаться рядом с тем, который используется. Это замедляет сборку, но вырезает далеко не всё.

Идея: Что если мы не будем импортировать, а будем просто брать и использовать, а сборщик уже сам разберётся что нужно заимпортировать?

Если это может сделать IDE, то что мешает сделать это сборщику? Современные IDE умеют автоматически генерировать импорты для использованных вами сущностей. В PHP давно есть такое стандартное соглашение: PSR-4. Достаточно иметь простое соглашение об именовании и расположении файлов, которое было бы удобно для пользователя и понятно для машины. Простой пример из двух модулей: MAM вводит аналогичное для .ts и .jam.js файлов: имена, начинающиеся с $ являются Fully Qualified Name какой-либо глобальной сущности, код которой подгружается по пути, получаемому из FQN путём замены разделителей на слеши.

my/alert/alert.ts

const $my_alert = alert // FQN предотвращает конфликты имён

my/app/app.ts

$my_alert( 'Hello!' ) // Ага, зависимость от /my/alert/

Результат не заставляет себя долго ждать: простота создания и использования модулей приводит к минимизации их размеров. Целый модуль из одной строки — что может быть проще? И как вишенка — минимизации размеров бандлов без каких-либо tree-shaking. Как следствие — к максимизации гранулярности.

Если вы воспользуетесь где-либо в своём коде функцией $mol_data_integer, то в бандл будут включены модули /mol/data/integer и /mol/data/number, от которого зависит $mol_data_integer. Наглядный пример — семейство модулей валидации JSON /mol/data. А вот, например, /mol/data/email сборщик даже не прочитает с диска, так как от него никто не зависит.

Как вы думаете, где искать объявление функции applyStyles? Раз уж мы начали пинать Angular, то не будем останавливаться. Возможность помещать что угодно куда угодно приводит к тому, что в каждом проекте мы наблюдаем уникальную систему расположения файлов, часто не поддающуюся никакой логике. Ни за что не догадаетесь, в /packages/core/src/render3/styling_next/bindings.ts. И если в IDE зачастую спасает "прыжок к определению", то просмотр кода на гитхабе или обзор пулреквеста лишены такой возможности.

Идея: Что если имена сущностей будут строго соответствовать их расположению?

А ведь имена в коде хочется видеть короткими и лаконичными, поэтому из названия разработчик постарается исключить всё лишнее, оставив лишь важное: $angular_render3_applyStyles. Чтобы расположить код в файле /angular/packages/core/src/render3/stylingNext/bindings.ts, в МАМ архитектуре придётся назвать сущность $angular_packages_core_src_render3_stylingNext_applyStyles, но так, конечно, никто не поступит, ведь тут столько всего лишнего в имени. А расположится это соответственно в /angular/render3/applyStyles/applyStyles.ts.

Например, в сообщениях комитов эти имена позволяют быстро и точно уловить о чём они: Обратите внимание как MAM использует слабости разработчиков, чтобы добиваться нужного результата: у каждой сущности получается короткое глобально уникальное имя, которое можно использовать в любом контексте.

73ebc45e517ffcc3dcce53f5b39b6d06fc95cae1 $mol_vector: range expanding support
3a843b2cb77be19688324eeb72bd090d350a6cc3 $mol_data: allowed transformations
24576f087133a18e0c9f31e0d61052265fd8a31a $mol_data_record: support recursion

Или, допустим, вы хотите найти все упоминания модуля $mol_fiber в интернете — сделать это проще простого благодаря FQN.

Напишем в одном файле 7 строк простого кода:

export class Foo
} export class Bar extends Foo {} console.log(new Foo().bar);

Разобьём его на 3 файла: Не смотря на циклическую зависимость он работает корректно.

my/foo.js

import { Bar } from './bar.js'; export class Foo { get bar() { return new Bar(); }
}

my/bar.js

import { Foo } from './foo.js'; export class Bar extends Foo {}

my/app.js

import { Foo } from './foo.js'; console.log(new Foo().bar);

Что за бред? Опа, ReferenceError: Cannot access 'Foo' before initialization. Поэтому нам надо сначала заимпортировать bar.js, который заимпортирует foo.js. Чтобы это починить, наш app.js должен знать, что foo.js зависит от bar.js. После чего мы уже можем заимпортировать foo.js без ошибки:

my/app.js

import './bar.js';
import { Foo } from './foo.js'; console.log(new Foo().bar);

И ладно бы они их просто запрещали — можно было бы сразу усложнить код так, чтобы циклов не было. Что браузеры, что NodeJS, что Webpack, что Parcel — все они криво работают с циклическими зависимостями. Но они могут работать нормально, а потом бац, и выдать непонятную ошибку.

Идея: Что если при сборке мы будем просто склеивать файлы в правильном порядке, как если бы весь код был изначально написан в одном файле?

Давайте разделим код, используя принципы МАМ:

my/foo/foo.ts

class $my_foo { get bar() { return new $my_bar(); }
}

my/bar/bar.ts

class $my_bar extends $my_foo {}

my/app/app.ts

console.log(new $my_foo().bar);

И они просто работают без дополнительных шаманств. Всё те же 7 строчек кода, что были изначально. А значит включать в бандл эти модули следует именно в таком порядке: my/foo, my/bar, my/app. Всё дело в том, что сборщик понимает, что зависимость my/bar от my/foo более жёсткая, чем my/foo от my/bar.

Сейчас эвристика простая — по числу отступов в строке, в которой обнаружена зависимость. Как сборщик это понимает? Обратите внимание, что более сильная зависимость в нашем примере имеет нулевой отступ, а слабая — двойной.

Из наиболее распространённых это: JS, TS, CSS, HTML, SVG, SCSS, Less, Stylus. Так уж получилось, что для разных вещей у нас есть разные языки под эти разные вещи заточенные. Что и говорить про 100500 видов более специфичных языков. У каждого своя система модулей, никак не взаимодействующая с другими языками. В результате, чтобы подключить компонент, приходится отдельно подключать его скрипты, отдельно стили, отдельно регистрировать шаблоны, отдельно настраивать деплой необходимых ему статических файлов и тд, и тп.

Но у него точкой входа является скрипт, который подключает уже файлы на остальных языках. Webpack благодаря лоадерам пытается решить эту проблему. Например, у нас есть модуль с красивыми стилями для табличек и мы хотим, чтобы в светлой теме они имели одни цвета, а в тёмной другие: А если нам не нужен скрипт?

.dark-theme table { background: black;
}
.light-theme table { background: white;
}

То есть CSS фактически зависит от JS. При этом, если мы зависим от темы, то должен быть подгружен скрипт, который установит нужную тему в зависимости от времени суток.

Идея: Что если модульная система не будет зависеть от языков?

CSS может зависеть от JS, который может зависеть от TS, который может зависеть от другого JS. Так как в MAM модульная система отделена от языков, то зависимости могут быть кроссязыковыми. В случае примера с темами это выглядит так: Достигается это за счёт того, что в исходниках обнаруживаются зависимости от модулей, а модули подключаются целиком и могут содержать исходники на любых языках.

/my/table/table.css

/* Ага, зависимость от /my/theme */
[my_theme="dark"] table { background: black;
}
[my_theme="light"] table { background: white;
}

/my/theme/theme.js

document.documentElement.setAttribute( 'my_theme' , ( new Date().getHours() + 15 ) % 24 < 12 ? 'light' : 'dark' ,
)

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

В случае Webpack это JS. Обычно точкой входа для сборки бандла является какой-то файл. И для каждого бандла нужно создавать отдельную точку входа. Если вы разрабатываете множество отчуждаемых библиотек и приложений, то вам нужно множество же и бандлов. Но для библиотек это как-то не очень подходит. В случае Parcel точкой входа является HTML, который для приложений в любом случае придётся создавать.

Идея: Что если любой модуль можно будет собрать в независимый бандл без предварительной подготовки?

Давайте соберём последнюю версию сборщика MAM проектов $mol_build:

mam mol/build

А теперь запустим этот сборщик и пусть он соберёт сам себя ещё раз чтобы убедиться, что он всё ещё способен сам себя собрать:

node mol/build/-/node.js mol/build

Хотя, нет, давайте вместе со сборкой попросим его ещё и тесты прогнать:

node mol/build/-/node.test.js mol/build

И если всё прошло успешно, опубликуем результат в NPM:

npm publish mol/build/-

Давайте пройдёмся по файлам, которые можно там обнаружить: Как можно заметить, при сборке модуля создаётся поддиректория с именем - и туда помещаются все артефакты сборки.

  • web.dep.json — вся информация о графе зависимостей
  • web.js — бандл скриптов для браузеров
  • web.js.map — сорсмапы для него
  • web.esm.js — он же в виде es-модуля
  • web.esm.js.map — и для него сорсмапы
  • web.test.js — бандл с тестами
  • web.test.js.map — и для тестов сорсмапы
  • web.d.ts — бандл с типами всего, что есть в бандле скриптов
  • web.css — бандл со стилями
  • web.css.map — и сорсмапы для него
  • web.test.html — точка входа, чтобы запустить тесты на исполнение в браузере
  • web.view.tree — декларации всех включённых в бандл view.tree компонент
  • web.locale=*.json — бандлы с локализованными текстами, для каждого обнаруженного языка свой бандл
  • package.json — позволяет тут же опубликовать собранный модуль в NPM
  • node.dep.json — вся информация о графе зависимостей
  • node.js — бандл скриптов для ноды
  • node.js.map — сорсмапы для него
  • node.esm.js — он же в виде es-модуля
  • node.esm.js.map — и для него сорсмапы
  • node.test.js — тот же бандл, но ещё и с тестами
  • node.test.js.map — и для него сорсмапы
  • node.d.ts — бандл с типами всего, что есть в бандле скриптов
  • node.view.tree — декларации всех включённых в бандл view.tree компонент
  • node.locale=*.json — бандлы с локализованными текстами, для каждого обнаруженного языка свой бандл

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

  • /mol/app/quine/quine.view.tree
  • /mol/app/quine/quine.view.ts
  • /mol/app/quine/index.html
  • /mol/app/quine/quine.locale=ru.json

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

/mol/app/quine/quine.meta.tree

deploy \/mol/app/quine/quine.view.tree
deploy \/mol/app/quine/quine.view.ts
deploy \/mol/app/quine/index.html
deploy \/mol/app/quine/quine.locale=ru.json

В результате сборки /mol/app/quine, они будут скопированы по следующим путям:

  • /mol/app/quine/-/mol/app/quine/quine.view.tree
  • /mol/app/quine/-/mol/app/quine/quine.view.ts
  • /mol/app/quine/-/mol/app/quine/index.html
  • /mol/app/quine/-/mol/app/quine/quine.locale=ru.json

Теперь директорию /mol/app/quine/- можно выложить на любой статический хостинг и приложение будет полностью работоспособно.

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

Идея: Что если предназначение файла будет отражено в его имени?

Например, модуль $mol_state_arg предоставляет доступ к задаваемым пользователем параметрам приложения. В MAM используется система тегов в именах файлов. А в ноде — через аргументы командной строки. В браузере эти параметры задаютя через строку адреса. $mol_sate_arg абстрагирует всё остальное приложение от этих нюансов путём реализации обоих вариантов с единым интерфейсом, располагая их в файлах:

  • /mol/state/arg/arg.web.ts — реализация для браузеров
  • /mol/state/arg/arg.node.ts — реализация для ноды

Исходники не помеченные этими тегами включаются независимо от целевой платформы.

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

  • /mol/state/arg/arg.test.ts — тесты модуля, они попадут в бандл с тестами

Например, с каждым модулем могут идти тексты на самых разных языках и они должны быть включены в соответствующие языковые бандлы. Теги могут быть и параметрическими. Файл с текстами — это обычный JSON-словарь, именованный с указанием локали в имени:

  • /mol/app/life/life.locale=ru.json — тексты для русского языка
  • /mol/app/life/life.locale=jp.json — тексты для японского языка

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

  • /hyoo/toys/.git — начинается с точки, поэтому сборщик эту директорию проигнорирует

Потом он создал совершенно новый фреймворк с похожим названием — Angular и опубликовал его под тем же самым именем, но уже версии 2. Сперва Гугл выпустил AngularJS и опубликовал его в NPM как angular. Только у одного ломающие API изменения происходят между мажорными версиями. Теперь эти два феймворка развиваются независимо. А так как поставить две версии одной зависимости на одном уровне невозможно, то ни о каком плавном переходе, когда в приложении какое-то время сосуществуют одновременно две версии библиотеки, не может быть и речи. А у другого — между минорными.

И вот ещё одни: код фреймворка разбит на несколько крупных модулей. Кажется команда Ангуляра наступила уже на все возможные грабли. Поддержка множества версий множества модулей — это большая проблема как для самих мейнтейнеров, так и для экосистемы в целом. Сначала они версионировали их независимо, но очень быстро даже сами начали путаться какие версии модулей совместимы между собой, что уж говорить о рядовых разработчиках. Ведь куча ресурсов всех участников сообщества тратится на обеспечение совместимости с уже устаревшими модулями.

Поэтому во многих проектах фиксируют конкретную версию зависимости. Красивая идея Semantic Versioning разбивается о суровую реальность — вы никогда не знаете, сломается ли у вас что-то, при изменении минорной версии или даже версии патча. Эта неразбериха приводит к тому, что вы никогда не можете положиться на зафиксированную версию и вам регулярно необходимо проверять совместимость с актуальными версиями (как минимум транзитивных) зависимостей. Однако, такая фиксация не действует на транзитивные зависимости, которые могут притянуться последней версии при установке с нуля, а могут остаться прежней, если уже стоят.

Если вы разрабатываете библиотеку, устанавливаемую через зависимости, лок-файл вам не поможет, ибо будет проигнорирован менеджером пакетов. А как же лок-файлы? Но давайте будем честными. Для конечного же приложения лок-файл даст вам так называемую "воспроизводимость сборок". Ровно один раз. Сколько раз вам нужно собирать конечное приложение из одних и тех же исходников? Надеюсь вы не делаете npm install на проде? Получая на выходе, не зависящий ни от каких NPM, артефакт сборки: исполнимый бинарник, докер-контейнер или просто архив со всем необходимым для запуска кодом.

Но постойте, просто собрать может и сам разработчик на своей локальной машине. Некоторые находят пользу лок-файлов в том, чтобы CI-сервер собрал именно то, что закомитил разработчик. Continuous Integration не только и не столько про сборку, сколько про проверку совместимости того, что написал один разработчик, с тем, что написал кто-то другой. Более того, он должен это сделать, чтобы убедиться, что ничего не сломал. Концепция CI заключается в как можно более скором обнаружении несовместимостей, и как следствие как можно более раннем старте работ по их устранению.

Например, однажды в одной компании стартанули проект на актуальном на тот момент Angular@4 (или даже 3). С фиксацией версий же зависимости очень быстро протухают, создавая вам даже больше проблем, чем решают. Была написана куча кода под Angular@4 и никто даже не знал, что он не совместим с Angular@5. Фреймворк развивался, но никто его не обновлял, ибо "это не входит в скоуп задачи" и "мы не брали это в спринт". Новый Angular потребовал новый TypeScript и кучу других зависимостей. Когда же на горизонте замаячил Angular@6, команда решила таки взять обновление этой зависимости в спринт. В итоге, по истечении 2 недель спринта, было решено… отложить обновление фреймворка до лучших времён, так как business value сам себя не создаст, пока команда возвращает технический долг, взятый с, как выяснилось, адскими процентами. Потребовалось переписать кучу собственного кода.

А всё оказывается просто: одна зависимость требует одну версию Реакта, другая — другую, третья — третью. А вишенкой на торте из версионных граблей является спонтанное появление в бандле нескольких версий одной и той же зависимости, о чём вы узнаёте, лишь когда замечаете аномально долгую загрузку приложения, и лезете разбираться, почему размер вашего бандла вырос в 2 раза. В результате на страницу грузится уж 3 React, 5 jQuery, 7 lodash.

Идея: Что если у всех модулей будет только одна версия — последняя?

Но мы можем научиться как-то с этим жить. Мы фундаментально не можем решить проблему несовместимости при обновлениях. При каждой установке любой зависимости, будет скачан самый актуальный код. Признав попытки фиксации версий несостоятельными, мы можем отказаться от указания версий вовсе. Тот код, который сейчас видят все остальные потребители библиотеки. Тот код, который сейчас поддерживается мейнтейнером. А не так, что одни уже обновились и бьются над проблемой, а у других хата с краю и они ни чем не помогают. И все вместе решают проблемы с этой библиотекой, если они вдруг возникают. Чем больше людей одновременно испытывают одну и ту же боль, тем скорее найдётся тот, кто эту боль устранит. А помощь может быть самая разная: завести issue, объяснить мейнтейнерам важность проблемы, найти workaround, сделать pull request, форкнуть в конце концов, если мейнтейнеры совсем забили на поддержку. В то же время версионирование фрагментирует сообщество по куче разных используемых версий. Это объединяет людей для улучшения единой кодовой базы.

Зная, что неосторожный комит может сломать сборку всем потребителям, мейнтейнер будет более ответственен ко внесению изменений. Без версионирования мейнтейнер гораздо быстрее получит обратную связь от своих потребителей и либо выпустит хотфикс, либо попросту откатит изменения для лучшей их проработки. И тут появится запрос на более продвинутый тулинг. Ну либо его библиотеками никто не будет пользоваться. Те проверяют с этой фиче-веткой интеграцию и если обнаруживаются проблемы — присылают подробности о них в репозиторий зависимости. Например такой: репозиторий зависимости, рассылает уведомления всем зависимым проектам о том, что появился комит в фиче-ветке. Такой пайплайн был бы очень полезен и при версионировании, но, как видим, в экосистеме NPM ничего такого до сих пор не распространено. Таким образом, мейнтейнер библиотеки мог бы получать обратную связь от потребителей ещё до того, как влить свою фиче-ветку в мастер. Отказ от версий же форсирует развитие экосистемы. Всё потому, что нет острой потребности в этом.

Всё просто — создаём новый модуль. Но что если всё же надо сломать обратную совместимость, но не хочется ломать всем сборку? Казалось бы — это то же версионирование, но есть фундаментальная разница: раз это два разных модуля, то они могут быть оба поставлены одновременно. Был mobx, стал mobx2 и меняй в нём API как захочешь. Таким образом можно осуществлять плавный переход между несовместимыми API, не раздувая бандл дублирующимся кодом. При этом последняя реализация mobx может быть реализована как легковесный адаптер к mobx2, реализующий на его основе старый API.

Обнаружив зависимость сборщик всегда знает какую именно версию надо установить — последнюю. Отсутствие версионирования даёт и ещё один неожиданный эффект. То есть, чтобы воспользоваться снипетом из интернета вида:

var pages_count = $mol_atom2_sync( ()=> $lib_pdfjs.getDocument( uri ).promise ).document().numPages

Вам не нужно ставить модули mol_atom2_sync и lib_pdfjs, подбирая подходящие для этого снипета версии:

npm install mol_atom2_sync@2.1 lib_pdfjs@5.6

Но как сборщик узнаёт откуда брать какие модули? Всё, что вам нужно, — это написать код, а все зависимости установятся автоматически при сборке. Всё очень просто — не найдя ожидаемой директории, он смотрит файлы *.meta.tree, где может быть указано какие директории из каких репозиториев брать:

/.meta.tree

pack node git \https://github.com/nin-jin/pms-node.git
pack mol git \https://github.com/eigenmethod/mol.git
pack lib git \https://github.com/eigenmethod/mam-lib.git

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

Однако, пытаться перекладывать код из одной системы в другую — контрпродуктивно. MAM — совершенно иная нежели NPM экосистема. Поэтому, мы работаем над тем, чтобы использовать опубликованные в NPM модули было бы не слишком болезненно.

Например, давайте найдём какой-нибудь свободный порт и поднимем на нём статический веб-сервер: Если вам нужно на сервере обратиться к уже установленному NPM модулю, то можете воспользоваться модулем $node.

/my/app/app.ts

$node.portastic.find({ min : 8080 , max : 8100 , retrieve : 1
}).then( ( ports : number[] ) => { $node.express().listen( ports[0] )
})

Поэтому-то и появился пакет lib содержащий адаптеры к некоторым популярным NPM библиотекам. Если же надо именно включить в бандл, то тут всё немного сложнее. Например, вот как выглядит подключение NPM-модуля pdfjs-dist:

/lib/pdfjs/pdfjs.ts

namespace $ { export let $lib_pdfjs : typeof import( 'pdfjs-dist' ) = require( 'pdfjs-dist/build/pdf.min.js' ) $lib_pdfjs.disableRange = true $lib_pdfjs.GlobalWorkerOptions.workerSrc = '-/node_modules/pdfjs-dist/build/pdf.worker.min.js'
}

/lib/pdfjs/pdfjs.meta.tree

deploy \/node_modules/pdfjs-dist/build/pdf.worker.min.js

Надеюсь в будущем нам удастся упростить эту интеграцию, но пока что так.

Именно поэтому появились всякие create-react-app и angular-cli, но ни прячут от вас свои конфиги. Для старта нового проекта часто приходится настраивать очень много вещей. Но тогда он станет намертво привязан к этой эджектнутой инфраструктуре. Вы, конечно, можете сделать eject и эти конфиги переедут в ваш проект. Если вы разрабатываете множество библиотек и приложений, то хотели бы единообразно работать с каждым из них, и вносить свои кастомизации сразу для всех.

Идея: Что если инфраструктура будет отделена от кода?

У вас может быть множество проектов в рамках одной инфраструктуры. Инфраструктура в случае MAM живёт в отдельном от кода репозитории.

Проще всего начать работать с MAM форкнув репозиторий с базовой MAM инфраструктурой, где уже всё настроено:

git clone https://github.com/eigenmethod/mam.git ./mam && cd mam
npm install
npm start

Всё, что вам останется — лишь писать код в соответствии с принципами MAM. На порту 8080 поднимется сервер разработчика.

Заведите себе собственный неймспейс (для примера — acme) и пропишите в нём ссылки на ваши проекты (для примера — hello и home ):

/acme/acme.meta.tree

pack hello git \https://github.com/acme/hello.git
pack home git \https://github.com/acme/home.git

Для сборки конкретных модулей достаточно приписать пути до них после npm start:

npm start acme/hello acme/home

А вот начать новый — самое то. Переводить уже существующий проект на эти рельсы довольно затруднительно. А если столкнётесь трудностями — пишите нам телеграммы: https://t.me/mam_mol Попробуйте, будет сложно, но вам понравится.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть