UNPKG

@vanilla-extract/rollup-plugin

Version:
273 lines (257 loc) 7.56 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var integration = require('@vanilla-extract/integration'); var path = require('path'); var MagicString = require('magic-string'); function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; } var MagicString__default = /*#__PURE__*/_interopDefault(MagicString); /** Generate a CSS bundle from Rollup context */ function generateCssBundle({ getModuleIds, getModuleInfo, warn }) { const cssBundle = new MagicString.Bundle(); const extractedCssIds = new Set(); // 1. identify CSS files to bundle const cssFiles = {}; for (const id of getModuleIds()) { if (integration.cssFileFilter.test(id)) { cssFiles[id] = buildImportChain(id, { getModuleInfo, warn }); } } // 2. build bundle from import order for (const id of sortModules(cssFiles)) { const { importedIdResolutions } = getModuleInfo(id) ?? {}; for (const resolution of importedIdResolutions ?? []) { if (resolution.meta.css && !extractedCssIds.has(resolution.id)) { extractedCssIds.add(resolution.id); cssBundle.addSource({ filename: resolution.id, content: new MagicString__default["default"](resolution.meta.css) }); } } } return { bundle: cssBundle, extractedCssIds }; } /** [id, order] tuple meant for ordering imports */ /** Trace a file back through its importers, building an ordered list */ function buildImportChain(id, { getModuleInfo, warn }) { let mod = getModuleInfo(id); if (!mod) { return []; } /** [id, order] */ const chain = [[id, -1]]; // resolve upwards to root entry while (!mod.isEntry) { const { id: currentId, importers } = mod; const lastImporterId = importers.at(-1); if (!lastImporterId) { break; } if (chain.some(([id]) => id === lastImporterId)) { warn(`Circular import detected. Can’t determine ideal import order of module.\n${chain.reverse().join('\n → ')}`); break; } mod = getModuleInfo(lastImporterId); if (!mod) { break; } // importedIds preserves the import order within each module chain.push([lastImporterId, mod.importedIds.indexOf(currentId)]); } return chain.reverse(); } /** Compare import chains to determine a flat ordering for modules */ function sortModules(modules) { const sortedModules = Object.entries(modules); // 2. sort CSS by import order sortedModules.sort(([_idA, chainA], [_idB, chainB]) => { const shorterChain = Math.min(chainA.length, chainB.length); for (let i = 0; i < shorterChain; i++) { const [moduleA, orderA] = chainA[i]; const [moduleB, orderB] = chainB[i]; // on same node, continue to next one if (moduleA === moduleB && orderA === orderB) { continue; } if (orderA !== orderB) { return orderA - orderB; } } return 0; }); return sortedModules.map(([id]) => id); } const SIDE_EFFECT_IMPORT_RE = /^\s*import\s+['"]([^'"]+)['"]\s*;?\s*/gm; /** Remove specific side effect imports from JS */ function stripSideEffectImportsMatching(code, sources) { const matches = code.matchAll(SIDE_EFFECT_IMPORT_RE); if (!matches) { return code; } let output = code; for (const match of matches) { if (!match[1] || !sources.includes(match[1])) { continue; } output = output.replace(match[0], ''); } return output; } const { relative, normalize, dirname } = path.posix; function vanillaExtractPlugin({ identifiers, cwd = process.cwd(), esbuildOptions, extract = false } = {}) { let extractedCssIds = new Set(); // only for `extract` return { name: 'vanilla-extract', buildStart() { extractedCssIds = new Set(); // refresh every build }, // Transform .css.js to .js async transform(_code, id) { if (!integration.cssFileFilter.test(id)) { return null; } const [filePath] = id.split('?'); const identOption = identifiers ?? ('short' ); const { source, watchFiles } = await integration.compile({ filePath, cwd, esbuildOptions, identOption }); for (const file of watchFiles) { this.addWatchFile(file); } const output = await integration.processVanillaFile({ source, filePath, identOption }); return { code: output, map: { mappings: '' } }; }, // Resolve .css to external module async resolveId(id) { if (!integration.virtualCssFileFilter.test(id)) { return null; } const { fileName, source } = await integration.getSourceFromVirtualCssFile(id); return { id: fileName, external: true, meta: { css: source } }; }, // Emit .css assets moduleParsed(moduleInfo) { moduleInfo.importedIdResolutions.forEach(resolution => { if (resolution.meta.css && !extract) { resolution.meta.assetId = this.emitFile({ type: 'asset', name: resolution.id, source: resolution.meta.css }); } }); }, // Replace .css import paths with relative paths to emitted css files renderChunk(code, chunkInfo) { const chunkPath = dirname(chunkInfo.fileName); const output = chunkInfo.imports.reduce((codeResult, importPath) => { const moduleInfo = this.getModuleInfo(importPath); if (!(moduleInfo !== null && moduleInfo !== void 0 && moduleInfo.meta.assetId)) { return codeResult; } const assetPath = this.getFileName(moduleInfo === null || moduleInfo === void 0 ? void 0 : moduleInfo.meta.assetId); const relativeAssetPath = `./${normalize(relative(chunkPath, assetPath))}`; return codeResult.replace(importPath, relativeAssetPath); }, code); return { code: output, map: null }; }, // Generate bundle (if extracting) async buildEnd() { if (!extract) { return; } // Note: generateBundle() can’t happen earlier than buildEnd // because the graph hasn’t fully settled until this point. const { bundle, extractedCssIds: extractedIds } = generateCssBundle(this); extractedCssIds = extractedIds; const name = extract.name || 'bundle.css'; this.emitFile({ type: 'asset', name, originalFileName: name, source: bundle.toString() }); if (extract.sourcemap) { const sourcemapName = `${name}.map`; this.emitFile({ type: 'asset', name: sourcemapName, originalFileName: sourcemapName, source: bundle.generateMap({ file: name, includeContent: true }).toString() }); } }, // Remove side effect imports (if extracting) async generateBundle(_options, bundle) { if (!extract) { return; } await Promise.all(Object.entries(bundle).map(async ([id, chunk]) => { if (chunk.type === 'chunk' && id.endsWith('.js') && chunk.imports.some(specifier => extractedCssIds.has(specifier))) { chunk.code = await stripSideEffectImportsMatching(chunk.code, [...extractedCssIds]); } })); } }; } exports.vanillaExtractPlugin = vanillaExtractPlugin;