또는 JavaScript 가져오기 동작을 제어하는 방법
<script>
에 대해 다시 매핑이 작동하지 않습니다.<base>
요소import.meta.resolve()
이 제안을 사용하면 JavaScript import
문 및 import()
표현식으로 가져오는 URL을 제어할 수 있습니다. 이를 통해 import moment from "moment"
와 같은 "bare import 지정자"가 작동할 수 있습니다.
이를 수행하는 메커니즘은 일반적으로 모듈 지정자의 해상도를 제어하는 데 사용할 수 있는 가져오기 맵을 통해 이루어집니다. 입문 예로 다음 코드를 고려해보세요.
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" ;
<script>
의 type=""
속성에 대한 새로운 "importmap"
값에 대한 자세한 내용은 설치 섹션을 참조하세요. 지금은 설치에 대한 논의를 뒤로 미루고 매핑의 의미에 집중하겠습니다.
CommonJS(Node에 있거나 브라우저용 webpack/browserify를 사용하여 번들로 제공됨)와 같은 ES2015 이전 모듈 시스템에 대한 경험이 있는 웹 개발자는 간단한 구문을 사용하여 모듈을 가져올 수 있는 데 익숙합니다.
const $ = require ( "jquery" ) ;
const { pluck } = require ( "lodash" ) ;
JavaScript 내장 모듈 시스템의 언어로 번역하면 다음과 같습니다.
import $ from "jquery" ;
import { pluck } from "lodash" ;
이러한 시스템에서는 "jquery"
또는 "lodash"
라는 순수 가져오기 지정자가 전체 파일 이름이나 URL에 매핑됩니다. 더 자세히 말하면, 이러한 지정자는 일반적으로 npm에 배포되는 packages 를 나타냅니다. 패키지 이름만 지정하면 해당 패키지의 기본 모듈을 암시적으로 요청하는 것입니다.
이 시스템의 주요 이점은 생태계 전체에서 쉽게 조정이 가능하다는 것입니다. 누구나 모듈을 작성하고 패키지의 잘 알려진 이름을 사용하여 import 문을 포함할 수 있으며, 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을 기준으로 확인됩니다.
특히 node_modules/moment/src/moment.js
와 같은 "기본" 상대 URL은 현재로서는 이러한 위치에서 작동하지 않습니다. 이는 보수적인 기본값으로 수행됩니다. 미래에는 특히 이러한 베어 케이스에 영향을 미치는 방식으로 오른쪽의 의미를 변경할 수 있는 여러 가져오기 맵을 허용할 수 있기 때문입니다.
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"
허용합니다.
이 예에서는 가져오기 맵을 사용하여 확장 없는 가져오기를 허용하는 방법 을 보여주지만 반드시 바람직한 것은 아닙니다. 그렇게 하면 가져오기 맵이 부풀어오르고 사람과 도구 모두에게 패키지 인터페이스가 덜 단순해집니다.
이러한 팽창은 패키지 내에서 확장 없는 가져오기를 허용해야 하는 경우 특히 문제가 됩니다. 이 경우 최상위 진입점뿐만 아니라 패키지의 모든 파일에 대한 가져오기 맵 항목이 필요합니다. 예를 들어 /node_modules/lodash-es/lodash.js
파일 내에서 import "./fp"
허용하려면 /node_modules/lodash-es/fp
/node_modules/lodash-es/fp.js
로 매핑하는 가져오기 항목이 필요합니다. /node_modules/lodash-es/fp.js
. 이제 확장자 없이 참조되는 모든 파일에 대해 이것을 반복한다고 상상해 보십시오.
따라서 가져오기 맵에서 이와 같은 패턴을 사용하거나 모듈을 작성할 때는 주의할 것을 권장합니다. 파일 확장자 관련 불일치를 패치하기 위해 가져오기 맵에 의존하지 않으면 생태계가 더 간단해질 것입니다.
일반적인 지정자 재매핑 허용의 일부로 가져오기 맵은 특히 "https://example.com/foo.mjs"
또는 "./bar.mjs"
와 같은 URL 유사 지정자의 재매핑을 허용합니다. 이에 대한 실제적인 용도는 해시를 매핑하는 것이지만 여기서는 개념을 전달하기 위한 몇 가지 기본 사항을 보여줍니다.
{
"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 "
}
}
이 재매핑을 통해 /app/
내부 파일에서 import "./helpers.mjs"
또는 파일에서 import "../helpers.mjs"
등 /app/helpers.mjs
로 확인되는 URL과 유사한 가져오기가 보장됩니다. /app/models
내부에서는 대신 /app/helpers/index.mjs
로 확인됩니다. 이는 아마도 좋은 생각이 아닐 것입니다. 코드를 난독화하는 간접 참조를 생성하는 대신 소스 파일을 업데이트하여 올바른 파일을 가져와야 합니다. 그러나 이는 가져오기 맵의 기능을 보여주는 유용한 예입니다.
이러한 재매핑은 지정자 키를 후행 슬래시로 종료하여 접두사 일치 방식으로 수행할 수도 있습니다.
{
"imports" : {
"https://www.unpkg.com/vue/" : " /node_modules/vue/ "
}
}
이 버전에서는 하위 문자열 "https://www.unpkg.com/vue/"
로 시작하는 지정자에 대한 import 문이 /node_modules/vue/
아래의 해당 URL에 매핑되도록 보장합니다.
일반적으로 요점은 베어 가져오기와 마찬가지로 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,console.log('test')"
이지만 대신 import "data:text/"
에만 영향을 미칩니다.
캐시 가능성을 높이기 위해 스크립트 파일에는 파일 이름에 고유한 해시가 부여되는 경우가 많습니다. 이 기술에 대한 일반적인 토론이나 JavaScript 및 웹팩에 초점을 맞춘 토론을 참조하세요.
모듈 그래프를 사용하면 이 기술이 문제가 될 수 있습니다.
sub-dep.mjs
에 의존하는 dep.mjs
에 의존하는 app.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
이 다시 생성됩니다. 하지만 이는 dep.mjs
의 프로덕션 버전에서 import
문을 변경해야 함을 의미합니다. 그러면 내용이 변경됩니다. 이는 dep.mjs
자체의 프로덕션 버전에 새 파일 이름이 필요하다는 의미입니다. 하지만 이는 app.mjs
의 프로덕션 버전에서 import
문을 업데이트해야 함을 의미합니다.
즉, 해시된 파일 이름 스크립트 파일이 포함된 모듈 그래프 및 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"
형식의 import 문을 사용합니다. 이제 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>
에 대해 다시 매핑이 작동하지 않습니다. 가져오기 지정자의 의미를 변경하기 위해 가져오기 맵을 사용할 때 중요한 참고 사항은 <script src="">
또는 <link rel="modulepreload">
에 나타나는 것과 같은 원시 URL의 의미를 변경하지 않는다는 것입니다. 즉, 위의 예에서는
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 |
인라인 또는 src=""
속성과 함께 <script>
요소를 사용하여 애플리케이션에 대한 가져오기 맵을 설치할 수 있습니다.
< 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 푸시 또는 번들 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/blocking-1.js
를 기다리는 동안 추측성 파서가 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
라는 것을 알 수 있습니다.
마찬가지로 import map의 경우, 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 유사 지정자는 <base>
의 href
사용하여 절대 URL로 변환됩니다.
< 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 >
브라우저가 HTMLScriptElement의 presents(type) 메소드를 지원하는 경우 HTMLScriptElement.supports('importmap')
true를 반환해야 합니다.
if ( HTMLScriptElement . supports && HTMLScriptElement . supports ( 'importmap' ) ) {
console . log ( 'Your browser supports import maps.' ) ;
}
Node.js와 달리 브라우저에는 모듈을 찾기 위해 크롤링할 수 있는 합리적으로 빠른 파일 시스템이 없습니다. 따라서 노드 모듈 확인 알고리즘을 직접 구현할 수 없습니다. 모든 import
문에 대해 여러 서버 왕복을 수행해야 하며 계속해서 404를 얻으면서 대역폭과 시간을 낭비하게 됩니다. 모든 import
문이 하나의 HTTP 요청만 발생하는지 확인해야 합니다. 이를 위해서는 어느 정도의 사전 계산이 필요합니다.
일부에서는 각 모듈 지정자를 해석하기 위해 JavaScript 후크를 사용하여 브라우저의 모듈 확인 알고리즘을 사용자 정의할 것을 제안했습니다.
불행하게도 이는 성능에 치명적입니다. 모듈 그래프의 모든 가장자리에서 JavaScript로 이동하거나 다시 실행하면 응용 프로그램 시작 속도가 크게 느려집니다. (일반적인 웹 애플리케이션에는 수천 개의 모듈이 있으며 import 문이 3~4배 더 많습니다.) 호출을 가져오기 지정자로만 제한하거나 후크가 지정자를 배치로 가져오도록 요구하는 등 다양한 완화 방법을 상상할 수 있습니다. URL 배치를 반환하지만 결국에는 사전 계산보다 나은 것은 없습니다.
이에 대한 또 다른 문제는 웹 개발자가 이 후크가 제공되더라도 작성할 수 있는 유용한 매핑 알고리즘을 상상하기 어렵다는 것입니다. Node.js에는 하나가 있지만 파일 시스템을 반복적으로 크롤링하고 파일이 존재하는지 확인하는 것을 기반으로 합니다. 위에서 논의한 것처럼 웹에서는 불가능합니다. 일반 알고리즘이 실행 가능한 유일한 상황은 (a) 하위 그래프별 사용자 정의가 필요하지 않은 경우입니다. 즉, 애플리케이션에 모든 모듈의 버전이 하나만 존재합니다. (b) 도구는 일정하고 예측 가능한 방식으로 모듈을 미리 배열하여 알고리즘이 "return /js/${specifier}.js
"가 되도록 관리했습니다. 하지만 어쨌든 우리가 이 세상에 있다면 선언적 솔루션이 더 간단할 것입니다.
현재 사용되는 한 가지 솔루션(예: babel-plugin-unpkg를 통한 unpkg CDN)은 빌드 도구를 사용하여 미리 모든 가져오기 지정자를 적절한 절대 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에서 토론하세요.
이제 사람들이 각 모듈에 메타데이터를 제공하고 싶어하는 경우가 여러 번 나타났습니다. 예를 들어 무결성 메타데이터 또는 가져오기 옵션이 있습니다. 일부에서는 import 문을 사용하여 이 작업을 제안했지만 옵션을 신중하게 고려하면 대역 외 매니페스트 파일을 선호하게 됩니다.
가져오기 맵은 해당 매니페스트 파일 일 수 있습니다 . 그러나 다음과 같은 몇 가지 이유로 인해 이것이 가장 적합하지 않을 수 있습니다.
현재 계획된 대로 애플리케이션의 대부분의 모듈은 가져오기 맵에 항목이 없습니다. 주요 사용 사례는 베어 지정자로 참조해야 하는 모듈 또는 폴리필이나 가상화와 같은 까다로운 작업을 수행해야 하는 모듈에 대한 것입니다. 모든 모듈이 맵에 있는 것을 상상한다면 패키지를 통한 후행 슬래시와 같은 편의 기능을 포함하지 않을 것입니다.
지금까지 제안된 모든 메타데이터는 JavaScript 모듈뿐만 아니라 모든 종류의 리소스에 적용 가능합니다. 솔루션은 아마도 보다 일반적인 수준에서 작동해야 합니다.
다른 유형의 여러 <script>
가 나타날 수 있는 것처럼 여러 <script type="importmap">
이 페이지에 나타나는 것은 자연스러운 일입니다. 우리는 앞으로 이 기능을 활성화하고 싶습니다.
여기서 가장 큰 과제는 여러 가져오기 맵을 구성하는 방법을 결정하는 것입니다. 즉, 동일한 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" ) ;
페이지의 가져오기 맵에 의해 제어되는 somepackage/
네임스페이스 내에서 적절하게 매핑된 resource.json
위치를 제공합니다.
커뮤니티의 몇몇 구성원은 가져오기 맵과 관련된 폴리필 및 도구 작업을 진행해 왔습니다. 우리가 알고 있는 내용은 다음과 같습니다.
package.json
및 node_modules/
디렉터리에서 가져오기 맵을 생성합니다.package.json
에서 가져오기 맵을 생성합니다.<script type="systemjs-importmap">
사용하여 이전 브라우저에서 가져오기 맵을 사용하기 위한 폴리필과 유사한 워크플로를 제공합니다.더 많은 내용을 포함하여 자유롭게 끌어오기 요청을 보내주세요! 또한 이 공간에 대한 토론을 위해 이슈 트래커의 #146을 사용할 수 있습니다.
이 문서는 @domenic, @hiroshige-g, @justinfagnani, @MylesBorins 및 @nyaxt가 참여한 하루 동안의 질주를 통해 작성되었습니다. 그 이후로 @guybedford는 이 제안에 대한 프로토타입을 만들고 토론을 진행하는 데 중요한 역할을 해왔습니다.
제안서를 발전시키는 데 도움을 주신 Issue Tracker의 모든 기여자에게도 감사드립니다!