[JavaScript] Namespaces в JavaScript
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Мне очень сильно импонируют namespace'ы в таких языках программирования, как Java и PHP. Настолько сильно, что я даже как-то запилил о них статью на Хабре. С тех пор прошло уже почти два года, но namespace'ы в JavaScript за это время так и не появились. "А если бы я делал namespace'ы в JS для самого себя, то какими бы они были?" - подумалось мне. Под катом - мои соображения, какие же namespace'ы мне нужны в JavaScript'е.ВводнаяВсе мои рассуждения ниже применяются к ES6-модулям и не касаются других форматов (AMD, UMD, CommonJS) просто потому, что мне интересно смотреть, куда движется JavaScript, а не где он был. Также я в своей практике как-то достаточно плотно столкнулся с GWT, после чего у меня образовалось стойкое неприятие различных транспиляторов (а также, до кучи, минификаторов и обфускаторов). Поэтому vanilla JS и никакого TS. Ну вот есть у меня такие пункты.ES6-модулиES-модуль - это отдельный файл с исходным кодом, в котором в явном виде определены элементы, доступные снаружи модуля:
export function fn() {/*...*/}
Таким образом, для начала, нужно каким-то образом адресовать отдельные ES-модули в рамках всего приложения.ПакетыСовременные приложения состоят из отдельных пакетов, зависимостями между которыми управляют менеджеры пакетов. А уже сами пакеты состоят из отдельных модулей. Отдельный разработчик (vendor) может разложить свой код по множеству пакетов, используя одни и те же пакеты в различных своих приложениях. Исходный код модулей обычно помещают в отдельный каталог внутри пакета (например, ./src).Менеджеры пакетов помещают все пакеты одного приложения в каталог node_modules. Таким образом, структура nodejs-приложения, собранного из пакетов определённого разработчика, может выглядеть примерно в таком виде:
* node_modules
* @vendor
* package1
* src
* module1.js
* ...
* moduleN.js
* ...
* packageN
* src
* module1.mjs
* ...
* moduleN.mjs
Адресация модулейВ файловой структуре исходный код любого ES-модуля адресуется путём к файлу относительно корня приложения:
./node_modules/@vendor/package1/src/module1.js
...
./node_modules/@vendor/packageN/src/moduleN.mjs
В рамках загрузчика модулей nodejs-приложения часть ./node_modules/ уходит:
import SomeThing from '@vendor/package1/src/module1.js';
Если обращаться к модулям в рамках одного пакета, то отпадает и имя пакета, а адресация задаётся относительно точки вызова импорта:
import SomeThing from './module1.js';
Если развернуть web-сервер так, чтобы корень web-приложения указывал на папку node_modules, то с индексной страницы web-приложения можно будет загружать ES-модули, используя почти такие же адреса, как и в nodejs:
<script type="module">
import {fn} from './@vendor/package1/src/module1.js'
fn();
</script>
или при помощи динамического импорта:
<script>
import('./@vendor/package1/src/module1.js').then((mod) => {
mod.fn();
});
</script>
Текущая проблема адресацииПроблема в том, что для web'а нужно использовать ./ в самом начале. Если попробовать использовать просто:
import {fn} from '@vendor/package1/src/module1.js'
то браузер выдаст ошибку:
Uncaught TypeError: Failed to resolve module specifier "@vendor/package1/src/module1.js". Relative references must start with either "/", "./", or "../".
Таким образом, при импорте имеется три варианта адресации одного и того же ES-модуля:
- локальный (внутри пакета): ./module1.js
- серверный (nodejs): @vendor/package1/src/module1.js
- браузерный (web): ./@vendor/package1/src/module1.js
Мы должны при импорте применять адреса модулей без ./ в nodejs-приложениях, и адреса с ./ для браузеров.Другими словами, чтобы один и тот же JS-код мог быть использован и на сервере, и в браузере без изменений, нужно применять динамический импорт и вычислять адреса модулей (и лучше всего - абсолютные) на лету, в зависимости от среды выполнения (сервер или браузер).Логическая адресацияЕсли "натянуть сову на глобус" (провести аналогию с другими ЯП, в которых есть namespace'ы), то каждому ES-модулю в структуре приложения можно поставить в соответствие некоторую строку (логический адрес), которая могла бы однозначно адресовать положение ES-модуля в пространстве исходных файлов приложения, одинаковую, как для nodejs, так и для браузера.Например, мы можем опустить символы ./, которые требуются в браузере, а также расширение (как правило, внутри пакета используется одно и то же расширение для исходников):
@vendor/package1/src/module1
Обычно в пакете исходники находятся в каком-то каталоге: ./src/, ./lib/, ./dist/. Эту часть также можно опустить из адреса модуля - очень редко, когда все исходники в пакете размазывают по разным каталогам, а не помещают в один:
@vendor/package1/module1
Я не вижу, что ещё можно удалить из этой строки, поэтому пусть она и будет являться логическим адресом модуля.Namespace mappingЧтобы подобная адресация заработала, нужно составить карту соответствия логических адресов физическим адресам пакетов и расширениям, в них используемым. Можно даже добавить какой-нибудь промежуточный каталог для web-карты, если на node_modules указывает не корневой каталог web-сервера (в примере - ./packages/):
const node = {
'@vendor/package1': {path: '/.../node_modules/@vendor/package1/src', ext: 'js'},
'@vendor/packageN': {path: '/.../node_modules/@vendor/packageN/src', ext: 'mjs'},
};
const browser = {
'@vendor/package1': {path: 'https://.../packages/@vendor/package1/src', ext: 'js'},
'@vendor/packageN': {path: 'https://.../packages/@vendor/packageN/src', ext: 'mjs'},
};
Module loaderДалее нужен загрузчик, который бы сопоставлял 'логические' адреса модулей (типа @vendor/package1/module1) их физическим адресам (абсолютным или относительным - лучше абсолютным) в зависимости от среды использования (node или браузер):
@vendor/package1/module1 => /.../node_modules/@vendor/package1/src/module1.js // node
@vendor/packageN/moduleN => https://.../packages/@vendor/packageN/src/moduleN.mjs // browser
и использовать его для динамического импорта модулей. Разумеется, нет нужды маппить каждый модуль в пакете - достаточно замаппить корень пакета. На выходе получаем нечто подобное:
const loader = new ModuleLoader();
loader.addNamespace('@vendor/package1', {path: '/.../node_modules/@vendor/package1/src', ext: 'js'});
// ...
loader.addNamespace('@vendor/packageN', {path: '/.../node_modules/@vendor/packageN/src', ext: 'js'});
const module1 = await loader.import('@vendor/package1/module1');
Импорт модулей должен быть асинхронный, т.к. внутри будет использоваться асинхронная функция import(). РезюмеВот таким элегантным образом можно было бы перейти от физической адресации ES-модулей при импорте к их логической адресации (namespace'ам) и использовать одни и те же модули как для nodejs-приложений, так и в браузере. Ничего нового тут не придумано (нечто подобное уже сделано в PHP, откуда эта идея и спёрта).
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, ReactJS] React — Используйте стандартные пропсы для потока данных
- [Информационная безопасность, JavaScript, Google Chrome, Браузеры] Google Chrome начнет блокировать JavaScript-редирект по кликам на ссылки
- [Разработка веб-сайтов, JavaScript, Программирование] 20+ ES6-сниппетов для решения практических задач (перевод)
- [Разработка веб-сайтов, Управление продуктом] Как мы создавали первый онлайн-сервис Автокредит
- [JavaScript, Программирование, Я пиарюсь, Lisp] Как я устал от JavaScript и создал свой собственный язык программирования
- [JavaScript, HTML, Node.JS] Написание графического приложения на Electron JS (начало: Создание окна)
- [Разработка веб-сайтов, JavaScript, Программирование] Пример практического использования модулей
- [Высокая производительность, Разработка веб-сайтов, JavaScript, Клиентская оптимизация, ReactJS] Производительность приложений, работающих с Video и Audio
- [Разработка веб-сайтов, JavaScript, VueJS] Автоматическое обновление скриптов после деплоя
- [Мессенджеры, JavaScript, Программирование, VueJS] Как создать приложение-чат за двадцать минут (перевод)
Теги для поиска: #_javascript, #_javascript, #_es6, #_esmodules, #_import, #_namespaces, #_javascript
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:41
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Мне очень сильно импонируют namespace'ы в таких языках программирования, как Java и PHP. Настолько сильно, что я даже как-то запилил о них статью на Хабре. С тех пор прошло уже почти два года, но namespace'ы в JavaScript за это время так и не появились. "А если бы я делал namespace'ы в JS для самого себя, то какими бы они были?" - подумалось мне. Под катом - мои соображения, какие же namespace'ы мне нужны в JavaScript'е.ВводнаяВсе мои рассуждения ниже применяются к ES6-модулям и не касаются других форматов (AMD, UMD, CommonJS) просто потому, что мне интересно смотреть, куда движется JavaScript, а не где он был. Также я в своей практике как-то достаточно плотно столкнулся с GWT, после чего у меня образовалось стойкое неприятие различных транспиляторов (а также, до кучи, минификаторов и обфускаторов). Поэтому vanilla JS и никакого TS. Ну вот есть у меня такие пункты.ES6-модулиES-модуль - это отдельный файл с исходным кодом, в котором в явном виде определены элементы, доступные снаружи модуля: export function fn() {/*...*/}
* node_modules
* @vendor * package1 * src * module1.js * ... * moduleN.js * ... * packageN * src * module1.mjs * ... * moduleN.mjs ./node_modules/@vendor/package1/src/module1.js
... ./node_modules/@vendor/packageN/src/moduleN.mjs import SomeThing from '@vendor/package1/src/module1.js';
import SomeThing from './module1.js';
<script type="module">
import {fn} from './@vendor/package1/src/module1.js' fn(); </script> <script>
import('./@vendor/package1/src/module1.js').then((mod) => { mod.fn(); }); </script> import {fn} from '@vendor/package1/src/module1.js'
Uncaught TypeError: Failed to resolve module specifier "@vendor/package1/src/module1.js". Relative references must start with either "/", "./", or "../".
@vendor/package1/src/module1
@vendor/package1/module1
const node = {
'@vendor/package1': {path: '/.../node_modules/@vendor/package1/src', ext: 'js'}, '@vendor/packageN': {path: '/.../node_modules/@vendor/packageN/src', ext: 'mjs'}, }; const browser = { '@vendor/package1': {path: 'https://.../packages/@vendor/package1/src', ext: 'js'}, '@vendor/packageN': {path: 'https://.../packages/@vendor/packageN/src', ext: 'mjs'}, }; @vendor/package1/module1 => /.../node_modules/@vendor/package1/src/module1.js // node
@vendor/packageN/moduleN => https://.../packages/@vendor/packageN/src/moduleN.mjs // browser const loader = new ModuleLoader();
loader.addNamespace('@vendor/package1', {path: '/.../node_modules/@vendor/package1/src', ext: 'js'}); // ... loader.addNamespace('@vendor/packageN', {path: '/.../node_modules/@vendor/packageN/src', ext: 'js'}); const module1 = await loader.import('@vendor/package1/module1'); =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:41
Часовой пояс: UTC + 5