เจาะลึก JavaScript Import Attributes สำหรับโมดูล JSON เรียนรู้ไวยากรณ์ใหม่ `with { type: 'json' }` ประโยชน์ด้านความปลอดภัย และวิธีที่มันมาแทนที่วิธีเก่าๆ เพื่อเวิร์กโฟลว์ที่สะอาด ปลอดภัย และมีประสิทธิภาพยิ่งขึ้น
JavaScript Import Attributes: วิธีที่ทันสมัยและปลอดภัยในการโหลดโมดูล JSON
เป็นเวลาหลายปีที่นักพัฒนา JavaScript ต้องต่อสู้กับงานที่ดูเหมือนง่าย นั่นคือการโหลดไฟล์ JSON แม้ว่า JavaScript Object Notation (JSON) จะเป็นมาตรฐานโดยพฤตินัยสำหรับการแลกเปลี่ยนข้อมูลบนเว็บ แต่การผสานรวมเข้ากับโมดูล JavaScript อย่างราบรื่นนั้นเป็นเส้นทางที่เต็มไปด้วยโค้ดสำเร็จรูป (boilerplate) วิธีแก้ปัญหาเฉพาะหน้า และความเสี่ยงด้านความปลอดภัยที่อาจเกิดขึ้น ตั้งแต่การอ่านไฟล์แบบซิงโครนัสใน Node.js ไปจนถึงการเรียก `fetch` ที่ยืดยาวในเบราว์เซอร์ วิธีแก้ปัญหาต่างๆ รู้สึกเหมือนเป็นเพียงการปะแก้มากกว่าฟีเจอร์ที่มีมาแต่เดิม ยุคนั้นกำลังจะสิ้นสุดลงแล้ว
ขอต้อนรับสู่โลกของ Import Attributes ซึ่งเป็นโซลูชันที่ทันสมัย ปลอดภัย และสวยงามซึ่งกำหนดมาตรฐานโดย TC39 ซึ่งเป็นคณะกรรมการที่กำกับดูแลภาษา ECMAScript ฟีเจอร์นี้ซึ่งมาพร้อมกับไวยากรณ์ `with { type: 'json' }` ที่เรียบง่ายแต่ทรงพลัง กำลังปฏิวัติวิธีที่เราจัดการกับแอสเซทที่ไม่ใช่ JavaScript โดยเริ่มจากสิ่งที่พบบ่อยที่สุด: JSON บทความนี้จะให้คำแนะนำที่ครอบคลุมสำหรับนักพัฒนาทั่วโลกเกี่ยวกับ import attributes คืออะไร ปัญหาสำคัญที่มันช่วยแก้ไข และวิธีที่คุณสามารถเริ่มใช้งานได้ตั้งแต่วันนี้เพื่อเขียนโค้ดที่สะอาด ปลอดภัย และมีประสิทธิภาพมากขึ้น
โลกยุคเก่า: ย้อนดูการจัดการ JSON ใน JavaScript
เพื่อให้เข้าใจถึงความสวยงามของ import attributes อย่างถ่องแท้ เราต้องเข้าใจภูมิทัศน์ที่มันกำลังจะเข้ามาแทนที่ก่อน โดยขึ้นอยู่กับสภาพแวดล้อม (ฝั่งเซิร์ฟเวอร์หรือฝั่งไคลเอ็นต์) นักพัฒนาได้พึ่งพาเทคนิคที่หลากหลาย ซึ่งแต่ละเทคนิคก็มีข้อดีข้อเสียแตกต่างกันไป
ฝั่งเซิร์ฟเวอร์ (Node.js): ยุคของ `require()` และ `fs`
ในระบบโมดูล CommonJS ซึ่งเป็นระบบดั้งเดิมของ Node.js มาหลายปี การนำเข้า JSON นั้นง่ายอย่างน่าทึ่ง:
// ในไฟล์ CommonJS (เช่น index.js)
const config = require('./config.json');
console.log(config.database.host);
วิธีนี้ทำงานได้อย่างยอดเยี่ยม Node.js จะแยกวิเคราะห์ (parse) ไฟล์ JSON เป็นอ็อบเจกต์ JavaScript โดยอัตโนมัติ อย่างไรก็ตาม ด้วยการเปลี่ยนแปลงทั่วโลกไปสู่ ECMAScript Modules (ESM) ฟังก์ชัน `require()` แบบซิงโครนัสนี้จึงเข้ากันไม่ได้กับธรรมชาติของ JavaScript สมัยใหม่ที่เป็นแบบอะซิงโครนัสและรองรับ top-level-await คำสั่งที่เทียบเท่ากันโดยตรงใน ESM อย่าง `import` ในตอนแรกไม่รองรับโมดูล JSON ทำให้นักพัฒนาต้องกลับไปใช้วิธีที่เก่ากว่าและต้องทำด้วยตนเองมากขึ้น:
// การอ่านไฟล์ด้วยตนเองในไฟล์ ESM (เช่น index.mjs)
import fs from 'fs';
import path from 'path';
const configPath = path.resolve('config.json');
const configFile = fs.readFileSync(configPath, 'utf8');
const config = JSON.parse(configFile);
console.log(config.database.host);
แนวทางนี้มีข้อเสียหลายประการ:
- ความยืดยาว: ต้องใช้โค้ดสำเร็จรูปหลายบรรทัดสำหรับการทำงานเพียงอย่างเดียว
- I/O แบบซิงโครนัส: `fs.readFileSync` เป็นการทำงานแบบ blocking ซึ่งอาจเป็นคอขวดด้านประสิทธิภาพในแอปพลิเคชันที่มีการทำงานพร้อมกันสูง เวอร์ชันอะซิงโครนัส (`fs.readFile`) ยิ่งเพิ่มโค้ดสำเร็จรูปมากขึ้นด้วย callbacks หรือ Promises
- ขาดการบูรณาการ: รู้สึกไม่เชื่อมต่อกับระบบโมดูล โดยมองไฟล์ JSON เป็นไฟล์ข้อความทั่วไปที่ต้องทำการแยกวิเคราะห์ด้วยตนเอง
ฝั่งไคลเอ็นต์ (เบราว์เซอร์): โค้ดสำเร็จรูปของ `fetch` API
ในเบราว์เซอร์ นักพัฒนาพึ่งพา `fetch` API มานานเพื่อโหลดข้อมูล JSON จากเซิร์ฟเวอร์ แม้ว่าจะมีประสิทธิภาพและยืดหยุ่น แต่ก็ค่อนข้างยืดยาวสำหรับสิ่งที่ควรจะเป็นการนำเข้าที่ตรงไปตรงมา
// รูปแบบ fetch แบบคลาสสิก
let config;
fetch('/config.json')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json(); // แยกวิเคราะห์เนื้อหา JSON
})
.then(data => {
config = data;
console.log(config.api.key);
})
.catch(error => console.error('Error fetching config:', error));
รูปแบบนี้ แม้จะมีประสิทธิภาพ แต่ก็มีข้อเสีย:
- โค้ดสำเร็จรูป: การโหลด JSON ทุกครั้งต้องการชุดคำสั่งที่คล้ายกันของ Promises, การตรวจสอบ response และการจัดการข้อผิดพลาด
- ภาระจากความเป็นอะซิงโครนัส: การจัดการธรรมชาติที่เป็นอะซิงโครนัสของ `fetch` อาจทำให้ตรรกะของแอปพลิเคชันซับซ้อนขึ้น ซึ่งมักจะต้องมีการจัดการสถานะเพื่อรองรับช่วงที่กำลังโหลดข้อมูล
- ไม่มีการวิเคราะห์แบบสถิต: เนื่องจากเป็นการเรียกใช้ขณะรันไทม์ เครื่องมือสร้าง (build tools) จึงไม่สามารถวิเคราะห์การพึ่งพานี้ได้ง่าย ซึ่งอาจพลาดโอกาสในการเพิ่มประสิทธิภาพ
ก้าวไปข้างหน้า: Dynamic `import()` กับ Assertions (รุ่นก่อนหน้า)
เมื่อตระหนักถึงความท้าทายเหล่านี้ คณะกรรมการ TC39 จึงได้เสนอ Import Assertions ขึ้นมาก่อน นี่เป็นก้าวสำคัญสู่วิธีแก้ปัญหา โดยอนุญาตให้นักพัฒนาให้ข้อมูลเมทาเดตาเกี่ยวกับการนำเข้าได้
// ข้อเสนอ Import Assertions เดิม
const configModule = await import('./config.json', { assert: { type: 'json' } });
const config = configModule.default;
นี่เป็นการปรับปรุงครั้งใหญ่ มันรวมการโหลด JSON เข้ากับระบบ ESM ส่วนของ `assert` บอกให้เอนจิ้น JavaScript ตรวจสอบว่าทรัพยากรที่โหลดมาเป็นไฟล์ JSON จริง อย่างไรก็ตาม ในระหว่างกระบวนการออกมาเป็นมาตรฐาน ก็เกิดความแตกต่างทางความหมายที่สำคัญขึ้น ซึ่งนำไปสู่วิวัฒนาการมาเป็น Import Attributes
เข้าสู่ Import Attributes: แนวทางที่ชัดเจนและปลอดภัย
หลังจากการหารืออย่างกว้างขวางและข้อเสนอแนะจากผู้พัฒนาเอนจิ้น Import Assertions ก็ได้รับการปรับปรุงให้เป็น Import Attributes ไวยากรณ์แตกต่างกันเล็กน้อย แต่การเปลี่ยนแปลงทางความหมายนั้นลึกซึ้ง นี่คือวิธีใหม่ที่เป็นมาตรฐานในการนำเข้าโมดูล JSON:
การนำเข้าแบบสถิต (Static Import):
import config from './config.json' with { type: 'json' };
การนำเข้าแบบไดนามิก (Dynamic Import):
const configModule = await import('./config.json', { with: { type: 'json' } });
const config = configModule.default;
คีย์เวิร์ด `with`: มากกว่าแค่การเปลี่ยนชื่อ
การเปลี่ยนแปลงจาก `assert` เป็น `with` ไม่ใช่แค่เรื่องผิวเผิน มันสะท้อนถึงการเปลี่ยนแปลงพื้นฐานในวัตถุประสงค์:
- `assert { type: 'json' }`: ไวยากรณ์นี้หมายถึง การตรวจสอบหลังการโหลด เอนจิ้นจะดึงโมดูลมาก่อนแล้วจึงตรวจสอบว่าตรงกับที่ยืนยันไว้หรือไม่ หากไม่ตรง ก็จะเกิดข้อผิดพลาด นี่เป็นเพียงการตรวจสอบความปลอดภัยเป็นหลัก
- `with { type: 'json' }`: ไวยากรณ์นี้หมายถึง คำสั่งก่อนการโหลด มันให้ข้อมูลแก่สภาพแวดล้อมโฮสต์ (เบราว์เซอร์หรือ Node.js) เกี่ยวกับ วิธี โหลดและแยกวิเคราะห์โมดูลตั้งแต่เริ่มต้น มันไม่ใช่แค่การตรวจสอบ แต่เป็นคำสั่ง
ความแตกต่างนี้มีความสำคัญอย่างยิ่ง คีย์เวิร์ด `with` บอกกับเอนจิ้น JavaScript ว่า "ฉันตั้งใจจะนำเข้าทรัพยากร และฉันกำลังให้ attributes แก่คุณเพื่อเป็นแนวทางในกระบวนการโหลด ใช้ข้อมูลนี้เพื่อเลือก loader ที่ถูกต้องและใช้นโยบายความปลอดภัยที่เหมาะสมตั้งแต่ต้น" ซึ่งช่วยให้สามารถเพิ่มประสิทธิภาพได้ดีขึ้นและมีข้อตกลงที่ชัดเจนยิ่งขึ้นระหว่างนักพัฒนาและเอนจิ้น
ทำไมสิ่งนี้ถึงเป็นตัวเปลี่ยนเกม? ความจำเป็นด้านความปลอดภัย
ประโยชน์ที่สำคัญที่สุดเพียงอย่างเดียวของ import attributes คือความปลอดภัย พวกมันถูกออกแบบมาเพื่อป้องกันการโจมตีประเภทหนึ่งที่เรียกว่า MIME-type confusion ซึ่งอาจนำไปสู่การรันโค้ดจากระยะไกล (Remote Code Execution - RCE)
ภัยคุกคาม RCE จากการนำเข้าที่กำกวม
ลองจินตนาการถึงสถานการณ์ที่ไม่มี import attributes ซึ่งมีการใช้ dynamic import เพื่อโหลดไฟล์การตั้งค่าจากเซิร์ฟเวอร์:
// การนำเข้าที่อาจไม่ปลอดภัย
const { settings } = await import('https://api.example.com/user-settings.json');
จะเกิดอะไรขึ้นถ้าเซิร์ฟเวอร์ที่ `api.example.com` ถูกบุกรุก? ผู้ไม่หวังดีสามารถเปลี่ยน endpoint `user-settings.json` ให้ส่งไฟล์ JavaScript แทนไฟล์ JSON แต่ยังคงใช้นามสกุล `.json` อยู่ เซิร์ฟเวอร์จะส่งโค้ดที่สามารถรันได้กลับมาพร้อมกับเฮดเดอร์ `Content-Type` เป็น `text/javascript`
หากไม่มีกลไกในการตรวจสอบประเภท เอนจิ้น JavaScript อาจเห็นโค้ด JavaScript และรันมัน ซึ่งจะทำให้ผู้โจมตีสามารถควบคุมเซสชันของผู้ใช้ได้ นี่เป็นช่องโหว่ด้านความปลอดภัยที่ร้ายแรงมาก
Import Attributes ลดความเสี่ยงได้อย่างไร
Import attributes แก้ปัญหานี้ได้อย่างสวยงาม เมื่อคุณเขียนการนำเข้าพร้อมกับ attribute คุณกำลังสร้างข้อตกลงที่เข้มงวดกับเอนจิ้น:
// การนำเข้าที่ปลอดภัย
const { settings } = await import('https://api.example.com/user-settings.json' with { type: 'json' });
นี่คือสิ่งที่จะเกิดขึ้น:
- เบราว์เซอร์ร้องขอ `user-settings.json`
- เซิร์ฟเวอร์ซึ่งตอนนี้ถูกบุกรุก ตอบกลับด้วยโค้ด JavaScript และเฮดเดอร์ `Content-Type: text/javascript`
- ตัวโหลดโมดูลของเบราว์เซอร์เห็นว่า MIME type ของการตอบกลับ (`text/javascript`) ไม่ ตรงกับประเภทที่คาดหวังจาก import attribute (`json`)
- แทนที่จะแยกวิเคราะห์หรือรันไฟล์ เอนจิ้นจะโยน `TypeError` ทันที ซึ่งจะหยุดการทำงานและป้องกันไม่ให้โค้ดที่เป็นอันตรายทำงาน
การเพิ่มเติมที่เรียบง่ายนี้เปลี่ยนช่องโหว่ RCE ที่อาจเกิดขึ้นให้กลายเป็นข้อผิดพลาดขณะรันไทม์ที่ปลอดภัยและคาดเดาได้ มันทำให้มั่นใจได้ว่าข้อมูลยังคงเป็นข้อมูลและจะไม่ถูกตีความว่าเป็นโค้ดที่สามารถรันได้โดยไม่ได้ตั้งใจ
กรณีการใช้งานจริงและตัวอย่างโค้ด
Import attributes สำหรับ JSON ไม่ใช่แค่ฟีเจอร์ความปลอดภัยทางทฤษฎีเท่านั้น แต่ยังช่วยปรับปรุงการใช้งานในการพัฒนางานประจำวันในหลากหลายด้าน
1. การโหลดค่ากำหนดของแอปพลิเคชัน
นี่เป็นกรณีการใช้งานแบบคลาสสิก แทนที่จะต้องจัดการไฟล์ I/O ด้วยตนเอง ตอนนี้คุณสามารถนำเข้าไฟล์การตั้งค่าของคุณได้โดยตรงและแบบสถิต
ไฟล์: `config.json`
{
"database": {
"host": "db.production.example.com",
"port": 5432,
"user": "api_user"
},
"featureFlags": {
"newDashboard": true,
"enableLogging": false
}
}
ไฟล์: `database.mjs`
import config from './config.json' with { type: 'json' };
export function getDbHost() {
return config.database.host;
}
console.log(`Connecting to database at: ${getDbHost()}`);
โค้ดนี้สะอาด ชัดเจน และง่ายต่อการเข้าใจทั้งสำหรับมนุษย์และเครื่องมือสร้าง (build tools)
2. ข้อมูล Internationalization (i18n)
การจัดการคำแปลเป็นอีกหนึ่งกรณีที่เหมาะสมอย่างยิ่ง คุณสามารถเก็บข้อความภาษาต่างๆ ไว้ในไฟล์ JSON แยกกันและนำเข้ามาใช้ตามต้องการได้
ไฟล์: `locales/en-US.json`
{
"welcomeMessage": "Hello, welcome to our application!",
"logoutButton": "Log Out"
}
ไฟล์: `locales/es-MX.json`
{
"welcomeMessage": "¡Hola, bienvenido a nuestra aplicación!",
"logoutButton": "Cerrar Sesión"
}
ไฟล์: `i18n.mjs`
// นำเข้าภาษาเริ่มต้นแบบสถิต
import defaultStrings from './locales/en-US.json' with { type: 'json' };
// นำเข้าภาษาอื่นแบบไดนามิกตามความต้องการของผู้ใช้
async function getTranslations(locale) {
if (locale === 'es-MX') {
const module = await import('./locales/es-MX.json', { with: { type: 'json' } });
return module.default;
}
return defaultStrings;
}
const userLocale = 'es-MX';
const strings = await getTranslations(userLocale);
console.log(strings.welcomeMessage); // แสดงข้อความภาษาสเปน
3. การโหลดข้อมูลสถิตสำหรับเว็บแอปพลิเคชัน
ลองนึกภาพการสร้างเมนู dropdown ด้วยรายชื่อประเทศหรือการแสดงแคตตาล็อกสินค้า ข้อมูลสถิตเหล่านี้สามารถจัดการได้ในไฟล์ JSON และนำเข้าโดยตรงไปยังคอมโพเนนต์ของคุณ
ไฟล์: `data/countries.json`
[
{ "code": "US", "name": "United States" },
{ "code": "DE", "name": "Germany" },
{ "code": "JP", "name": "Japan" }
]
ไฟล์: `CountrySelector.js` (คอมโพเนนต์สมมติ)
import countries from '../data/countries.json' with { type: 'json' };
export class CountrySelector {
constructor(elementId) {
this.element = document.getElementById(elementId);
this.render();
}
render() {
const options = countries.map(country =>
``
).join('');
this.element.innerHTML = options;
}
}
// การใช้งาน
new CountrySelector('country-dropdown');
เบื้องหลังการทำงาน: บทบาทของ Host Environment
พฤติกรรมของ import attributes ถูกกำหนดโดยสภาพแวดล้อมโฮสต์ (host environment) ซึ่งหมายความว่ามีความแตกต่างเล็กน้อยในการใช้งานระหว่างเบราว์เซอร์และสภาพแวดล้อมฝั่งเซิร์ฟเวอร์อย่าง Node.js แม้ว่าผลลัพธ์จะสอดคล้องกันก็ตาม
ในเบราว์เซอร์
ในบริบทของเบราว์เซอร์ กระบวนการนี้จะเชื่อมโยงอย่างใกล้ชิดกับมาตรฐานเว็บ เช่น HTTP และ MIME types
- เมื่อเบราว์เซอร์พบ `import data from './data.json' with { type: 'json' }` มันจะเริ่มส่งคำขอ HTTP GET ไปยัง `./data.json`
- เซิร์ฟเวอร์จะได้รับคำขอและควรตอบกลับด้วยเนื้อหา JSON ที่สำคัญคือการตอบกลับ HTTP ของเซิร์ฟเวอร์ต้องมีเฮดเดอร์: `Content-Type: application/json`
- เบราว์เซอร์ได้รับการตอบกลับและตรวจสอบเฮดเดอร์ `Content-Type`
- มันจะเปรียบเทียบค่าของเฮดเดอร์กับ `type` ที่ระบุใน import attribute
- หากตรงกัน เบราว์เซอร์จะแยกวิเคราะห์เนื้อหาการตอบกลับเป็น JSON และสร้างอ็อบเจกต์โมดูล
- หากไม่ตรงกัน (เช่น เซิร์ฟเวอร์ส่ง `text/html` หรือ `text/javascript`) เบราว์เซอร์จะปฏิเสธการโหลดโมดูลและโยน `TypeError`
ใน Node.js และ Runtimes อื่นๆ
สำหรับการดำเนินการบนระบบไฟล์ในเครื่อง Node.js และ Deno ไม่ได้ใช้ MIME types แต่จะใช้การผสมผสานระหว่างนามสกุลไฟล์และ import attribute เพื่อตัดสินใจว่าจะจัดการกับไฟล์อย่างไร
- เมื่อ ESM loader ของ Node.js เห็น `import config from './config.json' with { type: 'json' }` มันจะระบุเส้นทางไฟล์ก่อน
- มันใช้ attribute `with { type: 'json' }` เป็นสัญญาณที่ชัดเจนในการเลือกตัวโหลดโมดูล JSON ภายในของมัน
- ตัวโหลด JSON จะอ่านเนื้อหาไฟล์จากดิสก์
- มันจะแยกวิเคราะห์เนื้อหาเป็น JSON หากไฟล์มี JSON ที่ไม่ถูกต้อง จะเกิดข้อผิดพลาดทางไวยากรณ์ (syntax error)
- อ็อบเจกต์โมดูลจะถูกสร้างขึ้นและส่งคืน โดยทั่วไปจะมีข้อมูลที่แยกวิเคราะห์แล้วเป็น `default` export
คำสั่งที่ชัดเจนจาก attribute นี้ช่วยหลีกเลี่ยงความกำกวม Node.js รู้ได้อย่างชัดเจนว่าไม่ควรพยายามรันไฟล์เป็น JavaScript โดยไม่คำนึงถึงเนื้อหาของมัน
การรองรับบนเบราว์เซอร์และ Runtime: พร้อมสำหรับใช้งานจริงแล้วหรือยัง?
การนำฟีเจอร์ภาษาใหม่มาใช้จำเป็นต้องพิจารณาถึงการรองรับในสภาพแวดล้อมเป้าหมายอย่างรอบคอบ โชคดีที่ import attributes สำหรับ JSON ได้รับการยอมรับอย่างรวดเร็วและแพร่หลายในระบบนิเวศของ JavaScript ณ ปลายปี 2023 การรองรับนั้นยอดเยี่ยมในสภาพแวดล้อมที่ทันสมัย
- Google Chrome / Chromium Engines (Edge, Opera): รองรับตั้งแต่เวอร์ชัน 117
- Mozilla Firefox: รองรับตั้งแต่เวอร์ชัน 121
- Safari (WebKit): รองรับตั้งแต่เวอร์ชัน 17.2
- Node.js: รองรับเต็มรูปแบบตั้งแต่เวอร์ชัน 21.0 ในเวอร์ชันก่อนหน้า (เช่น v18.19.0+, v20.10.0+) จะใช้งานได้ภายใต้แฟล็ก `--experimental-import-attributes`
- Deno: ในฐานะ runtime ที่ก้าวหน้า Deno ได้รองรับฟีเจอร์นี้ (ซึ่งพัฒนามาจาก assertions) ตั้งแต่เวอร์ชัน 1.34
- Bun: รองรับตั้งแต่เวอร์ชัน 1.0
สำหรับโปรเจกต์ที่ต้องรองรับเบราว์เซอร์หรือ Node.js เวอร์ชันเก่า เครื่องมือสร้างและบันเดลเลอร์สมัยใหม่ เช่น Vite, Webpack (พร้อม loaders ที่เหมาะสม) และ Babel (พร้อม transform plugin) สามารถแปลงไวยากรณ์ใหม่ให้อยู่ในรูปแบบที่เข้ากันได้ ช่วยให้คุณสามารถเขียนโค้ดที่ทันสมัยได้ตั้งแต่วันนี้
มากกว่าแค่ JSON: อนาคตของ Import Attributes
แม้ว่า JSON จะเป็นกรณีการใช้งานแรกและโดดเด่นที่สุด แต่ไวยากรณ์ `with` ได้รับการออกแบบมาให้สามารถขยายได้ มันเป็นกลไกทั่วไปสำหรับแนบเมทาเดตาไปกับการนำเข้าโมดูล ซึ่งเป็นการปูทางให้ทรัพยากรประเภทอื่นที่ไม่ใช่ JavaScript สามารถรวมเข้ากับระบบโมดูล ES ได้
CSS Module Scripts
ฟีเจอร์สำคัญถัดไปที่กำลังจะมาคือ CSS Module Scripts ข้อเสนอนี้ช่วยให้นักพัฒนาสามารถนำเข้าสไตล์ชีต CSS เป็นโมดูลได้โดยตรง:
import sheet from './styles.css' with { type: 'css' };
document.adoptedStyleSheets = [sheet];
เมื่อไฟล์ CSS ถูกนำเข้าด้วยวิธีนี้ มันจะถูกแยกวิเคราะห์เป็นอ็อบเจกต์ `CSSStyleSheet` ที่สามารถนำไปใช้กับเอกสารหรือ shadow DOM ผ่านโปรแกรมได้ นี่เป็นก้าวกระโดดครั้งใหญ่สำหรับเว็บคอมโพเนนต์และการจัดสไตล์แบบไดนามิก โดยไม่ต้องแท็ก `