Comments 15
из esm до сих пор нельзя собрать бандл без транспиляции
Хм... Што? Как же я это делаю, если нельзя?
обычный zip-архив из файлов
Ээээ... Што? Серьезно?
Ну и тема про загрузку зависимостей через CDN (или со своих эндпоинтов) без описания работы importmaps - это незачет. Ну, то есть, вся эта кухня прекрасно работает без богомерзких сорсмапов и без необходимости что-то грузить атомарно, со всеми плюшками в виде прямого доступа к исходникам в дев-окружении со всеми их типами.
Спасибо за публикацию!
Транспиляция транспиляции рознь. Например, перевод из Typescript в JavaScript - это транспиляция, а ещё при сборке бандла на esbuild код модулей транспилируется в iife (или что-то подобное), при этом сам код модуля может остаться почти незатронутым на том же языке (если версия js остаётся той же для бандла). То есть в первом случае генерируется новый код полностью, а во втором - склеиваются файлики модулей особым способом. Второй случай называть транспиляцией язык не поворачивается, хотя под капотом почти те же процессы.
Кстати, если юзать бандлер rollup для создания бандла без минификации, то на выходе есть большая вероятность получить один большой склеенный js-файл из кусочков модулей. Так что новый код генерируется, но он довольно идентичен источнику.
По поводу cache storage. Честно говоря, я не понял: а зачем вы используете cache storage при работе с esm? Ну, окей, предположим 2 разных модуля импортируют третий модуль по одному и тому же урлу. Я ни разу не замечал, что браузер будет по сети обращаться повторно. Может быть проблема надумана?
Можно в принципе делать минифицированный бандл с сорсмапом. И настроить сервер на выдачу в gzip. Будет тот же эффект, и ошибки можно ловить.
Кстати, относительные пути и тем более урлы, указанные в импорте модулей в ваших сорсах можно заменить на некие неймспейсы, описав их в importmap. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap
Прекрасная штука, сам юзаю.
Да, namespaces - это отличный инструмент для организации кода. В Java он был с самого начала (1996), в PHP добавили в версии 5.3 (2009), в JS их упорно путают с областью действия/видимости (scope). Я у себя в коде не использую importmap на уровне браузера, вместо этого делаю аналогичный маппинг на уровне Контейнера Объектов (одинаково работает и в браузере, и в ноде). Когда я начинал его делать в 2019-м, importmap'ов ещё не было, они где-то с 21-го года начали появляться в браузерах (в Safari так вообще с 2023-го). Возможно, с ними у меня получилось бы по-другому, но как есть. На данный момент я не вижу смысла использовать у себя importmap. У меня этот функционал встроен в Контейнер Объектов.
Спасибо. В принципе, соглашусь. Разная среда (nodejs и браузер), и как следствие разные загрузчики. А вы нашли компромисс.
Из интересного, в качестве альтернативы вашему решению (не призываю, а просто ради любопытства), можно посмотреть в сторону загрузки импортмапа в браузере через файл, а также динамическую загрузку импортмапов. https://github.com/WICG/import-maps?tab=readme-ov-file#import-map-processing
В общем, идея в том, что из package.json можно создать файл import map. А в своем коде проверять среду. Если среда - это браузер, то можно динамически подгрузить этот importmap. И тогда это будет довольно универсальным решением.
Чем оно принципиально отличается от вашего? Тем, что можно использовать имена пакетов в браузере, прямо как в ноде. Без урлов и относительных путей. Но это удобно только при работе с внешними вашими подключенным библиотеками.
Если же речь о работе с модулями в коде, где идёт основная бизнес-логика приложения, то вполне нормально работать с относительными путями.
Тем, что можно использовать имена пакетов в браузере, прямо как в ноде.
Я как раз пытался уйти от привязки к физической адресации (пакет:путь/к/файлу/в/пакете
) и перейти к логической адресации Vendor_Project_Plugin_Area_Service
. Для меня было важно уйти от статического связывания файлов в момент написания кода и перейти к динамическому связыванию в момент выполнения. При внедрении зависимостей через конструктор всё равно нужно преобразовать идентификатор зависимости в путь к конкретному экспорту внутри es-модуля с учётом условий проекта, в котором код выполняется. При логической адресации не так странно, что получаешь имплементацию в месте внедрения интерфейса.
Объявляешь в базовом модуле зависимость от базового интерфейса:
export default class Fl32_Auth_Back_Web_Api_Password_Salt_Read {
/**
* @param {Fl32_Auth_Back_Api_Mod_User} modUser
*/
constructor(
{
Fl32_Auth_Back_Api_Mod_User$: modUser,
}
) {}
}
А по факту Контейнер Объектов внедрит имплементацию этого интерфейса в соотв. проекте. Типа такой:
/**
* @implements Fl32_Auth_Back_Api_Mod_User
*/
export default class Lp_Desk_Back_Di_Replace_Fl32_Auth_Back_Api_Mod_User {...}
Ну и в случае привязки к именам пакетов и файлу внутри конструктор выглядел бы чуть чудовее:
/**
* @param {Fl32_Auth_Back_Api_Mod_User} modUser
*/
constructor(
{
'@flancer32/teq-ant-auth/src/Back/Api/Mod/User.mjs#default': modUser,
}
) {}
В общем, importmap хорош для статических импортов. В динамических уже можно вычислять самому путь к нужному ресурсу, а в моём DI он идёт как пятое колесо в телеге.
Принцип importmap'а, по большому счёту, совпадает с сопоставленим логического namespace'а физическому пути к файлу в моём Контейнере. Только importmap делает это сопоставление на уровне браузера, а я - на уровне Контейнера. Но перед началом работы и то, и то нужно конфигурить.
Директиву @ts-check не используете?
Я просто увидел, что в вашем в jsdoc есть упоминание интерфейса, но нет его объявления (отсутствует явный импорт типа). Очевидно, это сделано в другом модуле.
Я к тому, что я сам приверженец чистого js с jsdoc. Но jsdoc не классический, а на базе typescript. Может быть заинтересует вас.
Я стараюсь не использовать статических импортов, а вместо этого использую namespace'ы. Из имени интерфейса (Fl32_Auth_Back_Api_Mod_User
) в месте его применения разработчику понятно, где искать объявление самого интерфейса. IDE же сканирует JSDoc'и в исходниках и составляет свою таблицу имён классов/интерфейсов/т.п. с привязкой к файлам и ей всё равно, как называются соответствующие объекты кода.
По большому счёту, код интерфейса нужен только на момент написания/редактирования кода, чтобы IDE контролировала его имплементации и показывала их взаимосвязь с интерфейсом, облегчая разработчику навигацию по коду. У меня это не исполняемый файл, а документ в стиле JS (@flancer32/teq-ant-auth/src/Back/Api/Mod/User.js
):
/**
* An interface for an application specific model of the user.
* This interface is used by `Fl32_Auth_Back_Mod_Session` to retrieve the app specific user profile.
*
* @interface
*/
export default class Fl32_Auth_Back_Api_Mod_User {
/**
* Read the application-specific data from storage (RDb, ...) to use on the backend.
*
* @param {TeqFw_Db_Back_RDb_ITrans} [trx]
* @param {number} userBid
* @returns {Promise<{profileBack: *, profileFront: *}>}
*/
async readProfiles({trx, userBid}) {}
/**
* Load app specific user data from a storage (RDb, ...).
* @param {TeqFw_Db_Back_RDb_ITrans} [trx]
* @param {*} userRef - app-specific identifier for the user (email, uuid, ...) or some object
* @returns {Promise<{bid:number, dbUser: Fl32_Auth_Back_Store_RDb_Schema_User.Dto}>}
*/
async read({trx, userRef}) {}
}
Его нет нужды куда-либо импортировать в виде исходника, это просто документ. Я думаю, что можно было бы объявить аналогичный интерфейс и чисто в JSDoc'ах так, чтобы его понимали и разработчики, и IDE, но это будет более многословно, чем в JS.
Кстати, насколько мне известно, интерфейсы в TS через транспиляцию не проходят и в готовый JS-код не попадают. Так что они тоже в своём роде только для документации.
Вместо того, чтобы загрузить в браузер один большой файл, приходится загружать много маленьких.
Кстати, есть возможность заранее загрузить модули, особенно помогает, когда есть цепочка зависимостей. Не знаю насколько поможет.
<link rel="modulepreload" href="module/path.js">
Загрузка es-модулей в браузерные приложения