Русский
Русский
English
Статистика
Реклама

Namespaces в JavaScript

Мне очень сильно импонируют 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
К списку статей
Опубликовано: 12.11.2020 00:07:35
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Javascript

Es6

Esmodules

Import

Namespaces

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru