หรือวิธีการควบคุมลักษณะการทำงานของการนำเข้า 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 (ไม่ว่าจะในโหนดหรือรวมกลุ่มโดยใช้ webpack/browserify สำหรับเบราว์เซอร์) คุ้นเคยกับความสามารถในการนำเข้าโมดูลโดยใช้ไวยากรณ์ง่ายๆ:
const $ = require ( "jquery" ) ;
const { pluck } = require ( "lodash" ) ;
แปลเป็นภาษาของระบบโมดูลในตัวของ JavaScript สิ่งเหล่านี้จะเป็น
import $ from "jquery" ;
import { pluck } from "lodash" ;
ในระบบดังกล่าว ตัวระบุการนำเข้าเปล่าของ "jquery"
หรือ "lodash"
จะถูกแมปกับชื่อไฟล์หรือ URL แบบเต็ม ในรายละเอียดเพิ่มเติม ตัวระบุเหล่านี้แสดงถึง packages ซึ่งปกติจะกระจายในเวลา 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) ที่มีหลายโมดูลหรือไฟล์อื่นๆ ในกรณีดังกล่าว เราต้องการแมปคำนำหน้าในพื้นที่ตัวระบุโมดูล เข้ากับคำนำหน้าอื่นในพื้นที่ fetchable-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 และ Webpack เพิ่มเติม
เมื่อใช้กราฟโมดูล เทคนิคนี้อาจเป็นปัญหาได้:
พิจารณากราฟโมดูลอย่างง่าย โดยมี 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')
จะต้องคืนค่าเป็นจริง
if ( HTMLScriptElement . supports && HTMLScriptElement . supports ( 'importmap' ) ) {
console . log ( 'Your browser supports import maps.' ) ;
}
ต่างจากใน Node.js ในเบราว์เซอร์ เราไม่มีระบบไฟล์ที่รวดเร็วพอสมควรซึ่งเราสามารถรวบรวมข้อมูลเพื่อค้นหาโมดูลได้ ดังนั้นเราจึงไม่สามารถใช้อัลกอริธึมการแก้ปัญหาโมดูลโหนดได้โดยตรง มันจะต้องมีเซิร์ฟเวอร์หลายรอบไปกลับสำหรับทุกคำสั่ง import
เป็นการสิ้นเปลืองแบนด์วิดท์และเวลาในขณะที่เรายังคงรับ 404 ต่อไป เราต้องแน่ใจว่าคำสั่ง import
ทุกคำสั่งทำให้เกิดการร้องขอ HTTP เพียงครั้งเดียว สิ่งนี้จำเป็นต้องมีการวัดการคำนวณล่วงหน้าบางอย่าง
บางคนแนะนำให้ปรับแต่งอัลกอริธึมความละเอียดโมดูลของเบราว์เซอร์โดยใช้ตะขอ JavaScript เพื่อตีความตัวระบุแต่ละโมดูล
น่าเสียดายที่นี่ส่งผลร้ายแรงต่อประสิทธิภาพ การกระโดดเข้าและออกจาก JavaScript สำหรับทุกขอบของกราฟโมดูลทำให้การเริ่มต้นแอปพลิเคชันช้าลงอย่างมาก (แอปพลิเคชันเว็บทั่วไปมีโมดูลเรียงกันเป็นพันโมดูล โดยมีคำสั่งนำเข้าจำนวนมากถึง 3-4×) คุณสามารถจินตนาการถึงการบรรเทาผลกระทบต่างๆ เช่น การจำกัดการเรียกเฉพาะตัวระบุการนำเข้าเปล่าๆ หรือกำหนดให้ hook ใช้ชุดของตัวระบุและ ส่งคืน URL เป็นกลุ่ม แต่ท้ายที่สุดแล้วไม่มีอะไรจะดีไปกว่าการคำนวณล่วงหน้า
ปัญหาอีกประการหนึ่งของเรื่องนี้ก็คือ เป็นการยากที่จะจินตนาการถึงอัลกอริธึมการทำแผนที่ที่เป็นประโยชน์ที่นักพัฒนาเว็บสามารถเขียนได้ แม้ว่าพวกเขาจะได้รับความช่วยเหลือนี้ก็ตาม Node.js มีหนึ่งไฟล์ แต่ขึ้นอยู่กับการรวบรวมข้อมูลระบบไฟล์ซ้ำ ๆ และตรวจสอบว่าไฟล์มีอยู่หรือไม่ ตามที่กล่าวไว้ข้างต้น ซึ่งเป็นไปไม่ได้บนเว็บ สถานการณ์เดียวที่อัลกอริธึมทั่วไปจะเป็นไปได้คือ (ก) คุณไม่จำเป็นต้องปรับแต่งกราฟย่อยต่อรายการ กล่าวคือ มีเพียงเวอร์ชันเดียวของทุกโมดูลที่มีอยู่ในแอปพลิเคชันของคุณ; (b) เครื่องมือที่ได้รับการจัดการเพื่อจัดเรียงโมดูลของคุณล่วงหน้าในรูปแบบที่เหมือนกันและคาดเดาได้ ดังนั้น เช่น อัลกอริธึมจะกลายเป็น "return /js/${specifier}.js
" แต่ถ้าเราอยู่ในโลกนี้ วิธีการแก้ปัญหาที่เปิดเผยจะง่ายกว่า
โซลูชันหนึ่งที่ใช้อยู่ในปัจจุบัน (เช่นใน unpkg CDN ผ่าน 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
หลายครั้งที่ผู้คนต้องการจัดหาข้อมูลเมตาสำหรับแต่ละโมดูล เช่น ข้อมูลเมตาความสมบูรณ์ หรือการดึงตัวเลือกต่างๆ แม้ว่าบางคนเสนอให้ทำเช่นนี้ด้วยคำสั่งนำเข้า แต่การพิจารณาอย่างรอบคอบเกี่ยวกับตัวเลือกต่างๆ นำไปสู่การเลือกใช้ไฟล์ Manifest ที่ไม่อยู่ในกลุ่ม
แผนที่นำเข้า อาจ เป็นไฟล์รายการนั้น อย่างไรก็ตาม อาจไม่เหมาะที่สุดด้วยเหตุผลบางประการ:
ตามที่คาดไว้ในปัจจุบัน โมดูลส่วนใหญ่ในแอปพลิเคชันจะไม่มีรายการในแผนที่นำเข้า กรณีการใช้งานหลักมีไว้สำหรับโมดูลที่คุณต้องอ้างอิงด้วยตัวระบุเปล่า หรือโมดูลที่คุณต้องทำบางอย่างที่ยุ่งยาก เช่น การทำโพลิฟิลล์หรือการจำลองเสมือน หากเราจินตนาการว่าทุกโมดูลอยู่ในแผนที่ เราจะไม่รวมคุณสมบัติอำนวยความสะดวก เช่น แพ็คเกจผ่านเครื่องหมายทับ
ข้อมูลเมตาที่เสนอทั้งหมดสามารถใช้ได้กับทรัพยากรทุกประเภท ไม่ใช่แค่โมดูล 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 ก็มีบทบาทสำคัญในการสร้างต้นแบบและขับเคลื่อนการอภิปรายเกี่ยวกับข้อเสนอนี้
ขอขอบคุณผู้ร่วมให้ข้อมูลทั้งหมดในเครื่องมือติดตามปัญหาสำหรับความช่วยเหลือในการพัฒนาข้อเสนอ!