Или как контролировать поведение импорта JavaScript
<script>
<base>
элементimport.meta.resolve()
Это предложение позволяет контролировать, какие URL-адреса извлекаются с помощью операторов import
JavaScript и выражений import()
. Это позволяет работать «голым спецификаторам импорта», таким как import moment from "moment"
.
Для этого используется карта импорта , которую можно использовать для управления разрешением спецификаторов модулей в целом. В качестве вводного примера рассмотрим код
import moment from "moment" ;
import { partition } from "lodash" ;
Сегодня это вызывает ошибки, поскольку такие голые спецификаторы явно зарезервированы. Предоставляя браузеру следующую карту импорта
< script type =" importmap " >
{
"imports" : {
"moment" : "/node_modules/moment/src/moment.js" ,
"lodash" : "/node_modules/lodash-es/lodash.js"
}
}
</ script >
вышеизложенное будет действовать так, как если бы вы написали
import moment from "/node_modules/moment/src/moment.js" ;
import { partition } from "/node_modules/lodash-es/lodash.js" ;
Дополнительную информацию о новом значении "importmap"
для атрибута type=""
<script>
см. в разделе установки. А пока мы сосредоточимся на семантике отображения, отложив обсуждение установки.
Веб-разработчики, имеющие опыт работы с модульными системами до ES2015, такими как CommonJS (либо в Node, либо в комплекте с использованием webpack/browserify для браузера), привыкли импортировать модули, используя простой синтаксис:
const $ = require ( "jquery" ) ;
const { pluck } = require ( "lodash" ) ;
В переводе на язык встроенной системы модулей JavaScript это будет так:
import $ from "jquery" ;
import { pluck } from "lodash" ;
В таких системах эти голые спецификаторы импорта "jquery"
или "lodash"
сопоставляются с полными именами файлов или URL-адресами. Более подробно, эти спецификаторы представляют собой пакеты , обычно распространяемые через npm; указывая только имя пакета, они неявно запрашивают основной модуль этого пакета.
Основное преимущество этой системы заключается в том, что она позволяет легко координировать действия всей экосистемы. Любой может написать модуль и включить оператор импорта, используя общеизвестное имя пакета, и позволить среде выполнения Node.js или его инструментам времени сборки позаботиться о его трансляции в реальный файл на диске (включая выяснение вопросов управления версиями).
Сегодня многие веб-разработчики даже используют собственный синтаксис модулей JavaScript, но комбинируют его с простыми спецификаторами импорта, что делает их код неспособным работать в Интернете без предварительной модификации для каждого приложения. Мы хотели бы решить эту проблему и перенести эти преимущества в Интернет.
Мы объясним особенности карты импорта на ряде примеров.
Как упоминалось во введении,
{
"imports" : {
"moment" : " /node_modules/moment/src/moment.js " ,
"lodash" : " /node_modules/lodash-es/lodash.js "
}
}
предоставляет голую поддержку спецификатора импорта в коде JavaScript:
import moment from "moment" ;
import ( "lodash" ) . then ( _ => ... ) ;
Обратите внимание, что правая часть сопоставления (известная как «адрес») должна начинаться с /
, ../
или ./
или быть анализируемой как абсолютный URL-адрес, чтобы идентифицировать URL-адрес. В случае адресов, подобных относительным URL-адресам, они разрешаются относительно базового URL-адреса карты импорта, т. е. базового URL-адреса страницы для встроенных карт импорта и URL-адреса ресурса карты импорта для внешних карт импорта.
В частности, «голые» относительные URL-адреса, такие как node_modules/moment/src/moment.js
на данный момент не будут работать в этих позициях. Это сделано по умолчанию, поскольку в будущем мы можем захотеть разрешить несколько карт импорта, что может изменить значение правой части таким образом, что это особенно затронет эти простые случаи.
В экосистеме JavaScript обычно пакет (в смысле npm) содержит несколько модулей или других файлов. В таких случаях мы хотим сопоставить префикс в пространстве спецификаторов модуля с другим префиксом в пространстве извлекаемых URL-адресов.
Карты импорта делают это, придавая особое значение ключам-описателям, которые заканчиваются косой чертой. Таким образом, карта типа
{
"imports" : {
"moment" : " /node_modules/moment/src/moment.js " ,
"moment/" : " /node_modules/moment/src/ " ,
"lodash" : " /node_modules/lodash-es/lodash.js " ,
"lodash/" : " /node_modules/lodash-es/ "
}
}
позволит не только импортировать основные модули, такие как
import moment from "moment" ;
import _ from "lodash" ;
но и неосновные модули, например
import localeData from "moment/locale/zh-cn.js" ;
import fp from "lodash/fp.js" ;
В экосистеме Node.js также распространено импортирование файлов без включения расширения. Мы не можем позволить себе роскошь пробовать несколько расширений файлов, пока не найдем подходящее. Однако мы можем эмулировать нечто подобное, используя карту импорта. Например,
{
"imports" : {
"lodash" : " /node_modules/lodash-es/lodash.js " ,
"lodash/" : " /node_modules/lodash-es/ " ,
"lodash/fp" : " /node_modules/lodash-es/fp.js " ,
}
}
позволит не только import fp from "lodash/fp.js"
, но также разрешить import fp from "lodash/fp"
.
Хотя этот пример показывает, как можно разрешить импорт без расширений с помощью карт импорта, это не обязательно желательно . Это приводит к раздуванию карты импорта и делает интерфейс пакета менее простым — как для людей, так и для инструментов.
Это раздувание особенно проблематично, если вам нужно разрешить импорт без расширений внутри пакета. В этом случае вам понадобится запись карты импорта для каждого файла в пакете, а не только для точек входа верхнего уровня. Например, чтобы разрешить import "./fp"
из файла /node_modules/lodash-es/lodash.js
, вам потребуется сопоставление записи импорта /node_modules/lodash-es/fp
с /node_modules/lodash-es/fp.js
. Теперь представьте, что вы повторяете это для каждого файла, на который есть ссылка без расширения.
Поэтому мы рекомендуем соблюдать осторожность при использовании подобных шаблонов в картах импорта или написании модулей. Для экосистемы будет проще, если мы не будем полагаться на карты импорта для исправления несоответствий, связанных с расширениями файлов.
В рамках общего переназначения спецификаторов карты импорта позволяют переназначать URL-подобные спецификаторы, такие как "https://example.com/foo.mjs"
или "./bar.mjs"
. Практическое применение этого — сопоставление хэшей, но здесь мы продемонстрируем некоторые основные способы передачи концепции:
{
"imports" : {
"https://www.unpkg.com/vue/dist/vue.runtime.esm.js" : " /node_modules/vue/dist/vue.runtime.esm.js "
}
}
Такое переназначение гарантирует, что любой импорт версии Vue unpkg.com (по крайней мере, по этому URL-адресу) вместо этого будет захватывать версию с локального сервера.
{
"imports" : {
"/app/helpers.mjs" : " /app/helpers/index.mjs "
}
}
Такое переназначение гарантирует, что любой URL-подобный импорт, который разрешается в /app/helpers.mjs
, включая, например, import "./helpers.mjs"
из файлов внутри /app/
или import "../helpers.mjs"
из файлов внутри /app/models
вместо этого будет разрешено /app/helpers/index.mjs
. Вероятно, это не очень хорошая идея; вместо создания косвенного обращения, которое запутывает ваш код, вам следует просто обновить исходные файлы, чтобы импортировать правильные файлы. Но это полезный пример для демонстрации возможностей импорта карт.
Такое переназначение также может быть выполнено на основе соответствия префикса, заканчивая ключ спецификатора косой чертой:
{
"imports" : {
"https://www.unpkg.com/vue/" : " /node_modules/vue/ "
}
}
Эта версия гарантирует, что операторы импорта для спецификаторов, которые начинаются с подстроки "https://www.unpkg.com/vue/"
будут сопоставлены с соответствующим URL-адресом под /node_modules/vue/
.
В общем, дело в том, что переназначение работает одинаково для импорта по URL-адресу, как и для простого импорта. В наших предыдущих примерах изменилось разрешение спецификаторов, таких как "lodash"
, и, таким образом, изменилось значение import "lodash"
. Здесь мы меняем разрешение спецификаторов типа "/app/helpers.mjs"
и, таким образом, меняем смысл import "/app/helpers.mjs"
.
Обратите внимание, что этот вариант сопоставления URL-подобного спецификатора с косой чертой работает только в том случае, если URL-подобный спецификатор имеет специальную схему: например, сопоставление "data:text/": "/foo"
не повлияет на смысл import "data:text/javascript,console.log('test')"
, но вместо этого повлияет только на import "data:text/"
.
Файлам сценариев часто присваивается уникальный хеш в имени файла, чтобы улучшить кэшируемость. См. это общее обсуждение метода или это обсуждение, более ориентированное на JavaScript и веб-пакеты.
С графами модулей этот метод может быть проблематичным:
Рассмотрим простой граф модулей, в котором app.mjs
зависит от dep.mjs
, который зависит от sub-dep.mjs
. Обычно, если вы обновляете или меняете sub-dep.mjs
, app.mjs
и dep.mjs
могут оставаться в кэше, требуя только передачи нового sub-dep.mjs
по сети.
Теперь рассмотрим тот же граф модуля, используя для производства хешированные имена файлов. Там у нас есть процесс сборки, генерирующий app-8e0d62a03.mjs
, dep-16f9d819a.mjs
и sub-dep-7be2aa47f.mjs
из исходных трех файлов.
Если мы обновим или изменим sub-dep.mjs
, наш процесс сборки повторно сгенерирует новое имя файла для производственной версии, скажем, sub-dep-5f47101dc.mjs
. Но это означает, что нам нужно изменить оператор import
в производственной версии dep.mjs
. При этом его содержимое меняется, а это означает, что рабочей версии dep.mjs
потребуется новое имя файла. Но тогда это означает, что нам нужно обновить оператор import
в производственной версии app.mjs
...
То есть, если графы модулей и операторы import
содержат файлы сценариев с хешированными именами, обновления любой части графа становятся вирусными для всех его зависимостей, теряя все преимущества кэширования.
Карты импорта позволяют решить эту дилемму, отделяя спецификаторы модулей, которые появляются в операторах import
, от URL-адресов на сервере. Например, наш сайт может начаться с карты импорта, например
{
"imports" : {
"/js/app.mjs" : " /js/app-8e0d62a03.mjs " ,
"/js/dep.mjs" : " /js/dep-16f9d819a.mjs " ,
"/js/sub-dep.mjs" : " /js/sub-dep-7be2aa47f.mjs "
}
}
и с операторами импорта формы import "./sub-dep.mjs"
вместо import "./sub-dep-7be2aa47f.mjs"
. Теперь, если мы изменим sub-dep.mjs
, мы просто обновим нашу карту импорта:
{
"imports" : {
"/js/app.mjs" : " /js/app-8e0d62a03.mjs " ,
"/js/dep.mjs" : " /js/dep-16f9d819a.mjs " ,
"/js/sub-dep.mjs" : " /js/sub-dep-5f47101dc.mjs "
}
}
и оставьте оператор import "./sub-dep.mjs"
в покое. Это означает, что содержимое dep.mjs
не меняется и остается в кэше; то же самое для app.mjs
.
<script>
Важное замечание об использовании карт импорта для изменения значения спецификаторов импорта: это не меняет значения необработанных URL-адресов, например тех, которые появляются в <script src="">
или <link rel="modulepreload">
. То есть, учитывая приведенный выше пример, в то время как
import "./app.mjs" ;
будет правильно переназначаться на свою хешированную версию в браузерах, поддерживающих импорт карт,
< script type =" module " src =" ./app.mjs " > </ script >
не будет: во всех классах браузеров он попытается получить app.mjs
напрямую, что приведет к ошибке 404. В браузерах, поддерживающих импорт-карты, будет работать следующее:
< script type =" module " > import "./app.mjs" ; </ script >
Часто бывает так, что вы хотите использовать один и тот же спецификатор импорта для ссылки на несколько версий одной библиотеки, в зависимости от того, кто их импортирует. Это инкапсулирует версии каждой используемой зависимости и позволяет избежать ада зависимостей (более длинная запись в блоге).
Мы поддерживаем этот вариант использования в картах импорта, позволяя вам изменять значение спецификатора в заданной области :
{
"imports" : {
"querystringify" : " /node_modules/querystringify/index.js "
},
"scopes" : {
"/node_modules/socksjs-client/" : {
"querystringify" : " /node_modules/socksjs-client/querystringify/index.js "
}
}
}
(Это один из нескольких реальных примеров нескольких версий каждого приложения, предоставленных @zkat. Спасибо, @zkat!)
При таком сопоставлении внутри любых модулей, URL-адреса которых начинаются с /node_modules/socksjs-client/
, спецификатор "querystringify"
будет ссылаться на /node_modules/socksjs-client/querystringify/index.js
. В противном случае сопоставление верхнего уровня будет гарантировать, что "querystringify"
относится к /node_modules/querystringify/index.js
.
Обратите внимание, что попадание в область действия не меняет способ разрешения адреса; базовый URL-адрес карты импорта по-прежнему используется вместо, например, префикса URL-адреса области.
Области видимости «наследуют» друг друга намеренно простым способом, объединяясь, но переопределяя по ходу действия. Например, следующая карта импорта:
{
"imports" : {
"a" : " /a-1.mjs " ,
"b" : " /b-1.mjs " ,
"c" : " /c-1.mjs "
},
"scopes" : {
"/scope2/" : {
"a" : " /a-2.mjs "
},
"/scope2/scope3/" : {
"b" : " /b-3.mjs "
}
}
}
дал бы следующие резолюции:
Спецификатор | Реферер | Результирующий URL |
---|---|---|
а | /scope1/foo.mjs | /a-1.mjs |
б | /scope1/foo.mjs | /b-1.mjs |
с | /scope1/foo.mjs | /c-1.mjs |
а | /scope2/foo.mjs | /a-2.mjs |
б | /scope2/foo.mjs | /b-1.mjs |
с | /scope2/foo.mjs | /c-1.mjs |
а | /scope2/scope3/foo.mjs | /a-2.mjs |
б | /scope2/scope3/foo.mjs | /b-3.mjs |
с | /scope2/scope3/foo.mjs | /c-1.mjs |
Вы можете установить карту импорта для своего приложения, используя элемент <script>
, встроенный или с атрибутом src=""
:
< script type =" importmap " >
{
"imports" : { ... } ,
"scopes" : { ... }
}
</ script >
< script type =" importmap " src =" import-map.importmap " > </ script >
При использовании атрибута src=""
результирующий ответ HTTP должен иметь тип MIME application/importmap+json
. (Почему бы не повторно использовать application/json
? Это может позволить обходить CSP.) Как и в сценариях модулей, запрос выполняется с включенным CORS, а ответ всегда интерпретируется как UTF-8.
Поскольку они влияют на весь импорт, любые карты импорта должны присутствовать и успешно извлекаться до того, как будет выполнено разрешение любого модуля. Это означает, что получение графа модуля блокируется при получении карты импорта.
Это означает, что для достижения наилучшей производительности настоятельно рекомендуется использовать встроенную форму импорта карт. Это похоже на лучшую практику встраивания критического CSS; оба типа ресурсов не позволяют вашему приложению выполнять важную работу до тех пор, пока они не будут обработаны, поэтому введение второго обратного сетевого обхода (или даже обратного обхода дискового кэша) — плохая идея. Если вы настроены на использование внешних карт импорта, вы можете попытаться смягчить этот двусторонний штраф с помощью таких технологий, как HTTP/2 Push или объединенные обмены HTTP.
Еще одним следствием того, как карты импорта влияют на весь импорт, является ошибка при попытке добавить новый <script type="importmap">
после начала выборки графа любого модуля. Карта импорта будет проигнорирована, а элемент <script>
выдаст событие error
.
На данный момент на странице разрешен только один <script type="importmap">
. Мы планируем расширить это в будущем, как только выясним правильную семантику для объединения нескольких карт импорта. См. обсуждение в №14, №137 и №167.
Что мы делаем в рабочих? Вероятно, new Worker(someURL, { type: "module", importMap: ... })
? Или вам следует установить его изнутри работника? Должны ли выделенные сотрудники использовать карту своего контролирующего документа по умолчанию или всегда? Обсудите в №2.
Вышеупомянутые правила означают, что вы можете динамически создавать карты импорта, если вы делаете это перед выполнением любого импорта. Например:
< script >
const im = document . createElement ( 'script' ) ;
im . type = 'importmap' ;
im . textContent = JSON . stringify ( {
imports : {
'my-library' : Math . random ( ) > 0.5 ? '/my-awesome-library.mjs' : '/my-rad-library.mjs'
}
} ) ;
document . currentScript . after ( im ) ;
</ script >
< script type =" module " >
import 'my-library' ; // will fetch the randomly-chosen URL
</ script >
Более реалистичный пример мог бы использовать эту возможность для сборки карты импорта на основе обнаружения объектов:
< script >
const importMap = {
imports : {
moment : '/moment.mjs' ,
lodash : someFeatureDetection ( ) ?
'/lodash.mjs' :
'/lodash-legacy-browsers.mjs'
}
} ;
const im = document . createElement ( 'script' ) ;
im . type = 'importmap' ;
im . textContent = JSON . stringify ( importMap ) ;
document . currentScript . after ( im ) ;
</ script >
< script type =" module " >
import _ from "lodash" ; // will fetch the right URL for this browser
</ script >
Обратите внимание, что (как и другие элементы <script>
) изменение содержимого <script type="importmap">
после того, как оно уже вставлено в документ, не будет работать. Вот почему мы написали приведенный выше пример, собирая содержимое карты импорта перед созданием и вставкой <script type="importmap">
.
Карты импорта — это вещь уровня приложения, что-то вроде сервисных работников. (Более формально, это будут карты для каждого модуля и, следовательно, для каждой области.) Они не предназначены для составления, а создаются человеком или инструментом с целостным представлением вашего веб-приложения. Например, для библиотеки не имеет смысла включать карту импорта; библиотеки могут просто ссылаться на модули по спецификаторам и позволять приложению решать, каким URL-адресам сопоставляются эти спецификаторы.
Это, помимо общей простоты, отчасти является причиной вышеуказанных ограничений для <script type="importmap">
.
Поскольку карта импорта приложения меняет алгоритм разрешения для каждого модуля в карте модулей, на них не влияет то, был ли исходный текст модуля изначально взят из URL-адреса с перекрестным происхождением. Если вы загружаете модуль из CDN, который использует голые спецификаторы импорта, вам необходимо заранее знать, какие голые спецификаторы импорта этот модуль добавляет в ваше приложение, и включить их в карту импорта вашего приложения. (То есть вам необходимо знать, каковы все транзитивные зависимости вашего приложения.) Важно, чтобы контроль над тем, какие URL-адреса используются для каждого пакета, оставался за автором приложения, чтобы он мог целостно управлять версиями и общим доступом к модулям.
Большинство браузеров имеют спекулятивный анализатор HTML, который пытается обнаружить ресурсы, объявленные в разметке HTML, в то время как анализатор HTML ожидает загрузки и выполнения блокирующих сценариев. Это еще не указано, хотя в Whatwg/html#5959 предпринимаются постоянные попытки сделать это. В этом разделе обсуждаются некоторые потенциальные взаимодействия, о которых следует знать.
Во-первых, обратите внимание: хотя, насколько нам известно, в настоящее время ни один браузер не делает этого, в следующем примере спекулятивный парсер может получить https://example.com/foo.mjs
, пока он ожидает скрипт блокировки https://example.com/blocking-1.js
:
<!DOCTYPE html >
<!-- This file is https://example.com/ -->
< script src =" blocking-1.js " > </ script >
< script type =" module " >
import "./foo.mjs" ;
</ script >
Аналогично, браузер может спекулятивно получить https://example.com/foo.mjs
и https://example.com/bar.mjs
в следующем примере, анализируя карту импорта как часть процесса спекулятивного анализа:
<!DOCTYPE html >
<!-- This file is https://example.com/ -->
< script src =" blocking-2.js " > </ script >
< script type =" importmap " >
{
"imports" : {
"foo" : "./foo.mjs" ,
"https://other.example/bar.mjs" : "./bar.mjs"
}
}
</ script >
< script type =" module " >
import "foo" ;
import "https://other.example/bar.mjs" ;
</ script >
Здесь следует отметить одно взаимодействие: браузеры, которые спекулятивно анализируют встроенные модули JS, но не поддерживают карты импорта, вероятно, будут предполагать неправильно для этого примера: они могут спекулятивно получать https://other.example/bar.mjs
вместо https://example.com/bar.mjs
с которым он сопоставлен.
В более общем плане, спекуляции на основе карт импорта могут быть подвержены тем же ошибкам, что и другие спекуляции. Например, если содержимое blocking-1.js
было
const el = document . createElement ( "base" ) ;
el . href = "/subdirectory/" ;
document . currentScript . after ( el ) ;
тогда спекулятивная выборка https://example.com/foo.mjs
в примере карты без импорта будет потрачена впустую, поскольку к тому времени, когда придет время выполнить фактическую оценку модуля, мы заново вычислим относительный спецификатор "./foo.mjs"
и поймите, что на самом деле запрошен https://example.com/subdirectory/foo.mjs
.
Аналогично для случая с картой импорта, если содержимое blocking-2.js
было
document . write ( `<script type="importmap">
{
"imports": {
"foo": "./other-foo.mjs",
"https://other.example/bar.mjs": "./other-bar.mjs"
}
}
</script>` ) ;
тогда спекулятивные выборки https://example.com/foo.mjs
и https://example.com/bar.mjs
будут потрачены впустую, поскольку будет действовать только что написанная карта импорта вместо той, которая была замечена. встроенный в HTML.
<base>
элемент Если в документе присутствует элемент <base>
, все URL-адреса и URL-подобные спецификаторы в карте импорта преобразуются в абсолютные URL-адреса с использованием href
из <base>
.
< base href =" https://www.unpkg.com/vue/dist/ " >
< script type =" importmap " >
{
"imports" : {
"vue" : "./vue.runtime.esm.js" ,
}
}
</ script >
< script >
import ( "vue" ) ; // resolves to https://www.unpkg.com/vue/dist/vue.runtime.esm.js
</ script >
Если браузер поддерживает метод support(type) HTMLScriptElement, HTMLScriptElement.supports('importmap')
должен возвращать true.
if ( HTMLScriptElement . supports && HTMLScriptElement . supports ( 'importmap' ) ) {
console . log ( 'Your browser supports import maps.' ) ;
}
В отличие от Node.js, в браузере мы не можем позволить себе роскошь достаточно быстрой файловой системы, которую мы могли бы сканировать в поисках модулей. Таким образом, мы не можем напрямую реализовать алгоритм разрешения модуля Node; это потребовало бы выполнения нескольких обращений к серверу для каждого оператора import
, тратя впустую полосу пропускания и время, поскольку мы продолжаем получать ошибки 404. Нам необходимо гарантировать, что каждый оператор import
вызывает только один HTTP-запрос; это требует некоторой меры предварительных вычислений.
Некоторые предложили настроить алгоритм разрешения модулей браузера, используя перехватчик JavaScript для интерпретации каждого спецификатора модуля.
К сожалению, это губительно для производительности; переход в JavaScript и выход из него для каждого края графа модуля резко замедляет запуск приложения. (Типичные веб-приложения имеют порядка тысячи модулей, а количество операторов импорта в 3-4 раза больше.) Вы можете представить себе различные способы смягчения последствий, такие как ограничение вызовов только пустыми спецификаторами импорта или требование, чтобы перехватчик принимал пакеты спецификаторов и возвращать пакеты URL-адресов, но, в конце концов, ничто не сравнится с предварительным вычислением.
Другая проблема заключается в том, что трудно представить полезный алгоритм отображения, который мог бы написать веб-разработчик, даже если бы ему дали этот крючок. В Node.js он есть, но он основан на многократном сканировании файловой системы и проверке существования файлов; Как мы уже говорили выше, в Интернете это невозможно. Единственная ситуация, в которой общий алгоритм был бы возможен, это если (а) вам никогда не требовалась настройка каждого подграфа, т. е. в вашем приложении существовала только одна версия каждого модуля; (б) инструментарий сумел заранее организовать ваши модули каким-то единообразным и предсказуемым образом, так что, например, алгоритм становится «return /js/${specifier}.js
». Но если мы все равно находимся в этом мире, декларативное решение было бы проще.
Одно из решений, используемых сегодня (например, в CDN unpkg через Babel-plugin-unpkg), заключается в том, чтобы заранее переписать все голые спецификаторы импорта на соответствующие абсолютные URL-адреса, используя инструменты сборки. Это также можно сделать во время установки, чтобы при установке пакета с помощью npm он автоматически перезаписывал содержимое пакета, используя абсолютные или относительные URL-адреса вместо голых спецификаторов импорта.
Проблема этого подхода в том, что он не работает с динамическим import()
, поскольку невозможно статически анализировать строки, передаваемые в эту функцию. Вы можете внедрить исправление, которое, например, заменяет каждый экземпляр import(x)
на import(specifierToURL(x, import.meta.url))
, где specifierToURL
— это другая функция, созданная инструментом сборки. Но в конечном итоге это довольно дырявая абстракция, и функция specifierToURL
и так во многом дублирует работу этого предложения.
На первый взгляд, сервис-воркеры кажутся подходящим местом для такого рода перевода ресурсов. Ранее мы говорили о том, как найти способ передать спецификатор вместе с событием выборки сервис-воркера, что позволит ему вернуть соответствующий Response
.
Однако работники службы недоступны при первой загрузке . Таким образом, они не могут быть частью критической инфраструктуры, используемой для загрузки модулей. Их можно использовать только в качестве прогрессивного улучшения поверх выборок, которые в противном случае обычно работают.
Если у вас есть простые приложения, не требующие разрешения ограниченных зависимостей, и у вас есть инструмент установки пакета, который удобно перезаписывает пути на диске внутри пакета (в отличие от текущих версий npm), вы можете обойтись гораздо более простым сопоставлением. Например, если ваш инструмент установки создал простой список вида
node_modules_flattened/
lodash/
index.js
core.js
fp.js
moment/
index.js
html-to-dom/
index.js
тогда единственная информация, которая вам нужна, это
/node_modules_flattened/
).index.js
).Вы можете представить себе формат конфигурации импорта модуля, в котором указаны только эти вещи или даже только некоторое их подмножество (если мы заложили предположения для остальных).
Эта идея не работает для более сложных приложений, которым требуется ограниченное разрешение, поэтому мы считаем, что предложение полной карты импорта необходимо. Но он остается привлекательным для простых приложений, и мы задаемся вопросом, есть ли способ сделать это предложение еще и простым режимом, который не требует перечисления всех модулей, а вместо этого полагается на соглашения и инструменты, обеспечивающие минимальное отображение. Обсудите в №7.
Уже несколько раз возникало желание предоставить метаданные для каждого модуля; например, метаданные целостности или параметры выборки. Хотя некоторые предлагают сделать это с помощью оператора импорта, тщательное рассмотрение вариантов приводит к предпочтению внешнего файла манифеста.
Карта импорта может быть этим файлом манифеста. Однако это может оказаться не лучшим решением по нескольким причинам:
В настоящее время предполагается, что большинство модулей в приложении не будут иметь записей в карте импорта. Основной вариант использования — это модули, к которым нужно обращаться с помощью голых спецификаторов, или модули, в которых вам нужно сделать что-то сложное, например полизаполнение или виртуализацию. Если бы мы предполагали, что каждый модуль присутствует на карте, мы бы не включали такие удобные функции, как пакеты через косую черту.
Все предложенные на данный момент метаданные применимы к любому типу ресурсов, а не только к модулям JavaScript. Решение, вероятно, должно работать на более общем уровне.
Вполне естественно, что на странице появляется несколько <script type="importmap">
, так же как и несколько <script>
других типов. Мы хотели бы включить это в будущем.
Самая большая проблема здесь — решить, как составить несколько карт импорта. То есть, учитывая две карты импорта, которые переназначают один и тот же URL-адрес, или два определения области, которые охватывают одно и то же пространство префикса URL-адреса, каково должно быть влияние на страницу? Текущим ведущим кандидатом является каскадное разрешение, которое превращает карты импорта из спецификатора импорта → сопоставления URL-адресов в каскадную серию сопоставлений спецификатора импорта → сопоставления спецификатора импорта, в конечном итоге сводясь к «извлекаемому спецификатору импорта» (по сути, URL-адресу).
См. эти открытые вопросы для дальнейшего обсуждения.
В некоторых случаях требуется способ чтения или управления картой импорта области из сценария вместо вставки декларативных элементов <script type="importmap">
. Считайте это «объектной моделью карты импорта», похожей на объектную модель CSS, которая позволяет манипулировать обычно декларативными правилами CSS страницы.
Проблемы здесь заключаются в том, как согласовать карты декларативного импорта с любыми программными изменениями, а также в том, когда в жизненном цикле страницы может работать такой API. Как правило, более простые конструкции менее эффективны и могут соответствовать меньшему количеству сценариев использования.
См. эти открытые вопросы для более подробного обсуждения и случаев использования, в которых программный API может помочь.
import.meta.resolve()
Предлагаемая функция import.meta.resolve(specifier)
позволяет сценариям модуля преобразовывать спецификаторы импорта в URL-адреса в любое время. Дополнительную информацию см. в Whatwg/html#5572. Это связано с картами импорта, поскольку позволяет разрешать ресурсы, относящиеся к пакету, например
const url = import . meta . resolve ( "somepackage/resource.json" ) ;
предоставит вам правильно сопоставленное местоположение resource.json
в пространстве имен somepackage/
контролируемом картой импорта страницы.
Несколько членов сообщества работали над полифилами и инструментами, связанными с импортом карт. Вот те, о которых мы знаем:
package.json
и node_modules/
.package.json
.<script type="systemjs-importmap">
.Не стесняйтесь отправить запрос на вытягивание с дополнительными сведениями! Кроме того, вы можете использовать номер 146 в системе отслеживания проблем для обсуждения этого раздела.
Этот документ создан в результате однодневного спринта с участием @domenic, @hiroshige-g, @justinfagnani, @MylesBorins и @nyaxt. С тех пор @guybedford сыграл важную роль в создании прототипа и продвижении обсуждения этого предложения.
Спасибо также всем участникам системы отслеживания проблем за помощь в разработке предложения!