import { readFile } from 'node:fs/promises'; import { defineConfig, type Plugin } from 'vite'; const ATTRIBUTE_IMPORT = /(from\s*)(['"])([^'"]+)\2\s*with\s*\{\s*type\s*:\s*(['"])(css|text)\4\s*\}/g; /** * Standard import attributes, built for browsers that don't support them yet (Safari): * * - `import sheet from './x.css' with { type: 'css' }` → a `CSSStyleSheet` * - `import text from './x.html' with { type: 'text' }` → the file's contents as a string * * The attribute is rewritten to a query before Vite sees the import. `?raw` makes Vite's own CSS * handling leave the file alone; `&sheet` is loaded here as a module that builds the stylesheet. */ function importAttributes(): Plugin { return { name: 'import-attributes', enforce: 'pre', transform(code, id) { if (!/\.[cm]?[jt]s$/.test(id) || !code.includes('with')) return null; const out = code.replace( ATTRIBUTE_IMPORT, (_, from: string, quote: string, path: string, _q: string, type: string) => `${from}${quote}${path}?raw${type === 'css' ? '&sheet' : ''}${quote}`, ); return out === code ? null : { code: out, map: null }; }, async load(id) { if (!id.endsWith('?raw&sheet')) return null; const file = id.slice(0, -'?raw&sheet'.length); this.addWatchFile(file); const css = await readFile(file, 'utf8'); return `const sheet = new CSSStyleSheet();\nsheet.replaceSync(${JSON.stringify(css)});\nexport default sheet;\n`; }, }; } export default defineConfig({ root: 'examples', plugins: [importAttributes()], });