UNPKG

snowpack

Version:

The ESM-powered frontend build tool. Fast, lightweight, unbundled.

178 lines (177 loc) 7.39 kB
import * as meriyah from 'meriyah'; import MagicString from 'magic-string'; import { analyze, extract_names } from 'periscopic'; import { walk } from 'estree-walker'; import is_reference from 'is-reference'; export function transform(data) { const code = new MagicString(data); const ast = meriyah.parseModule(data, { ranges: true, next: true, }); const { map, scope } = analyze(ast); const all_identifiers = new Set(); // first, get a list of all the identifiers used in the module... walk(ast, { enter(node, parent) { if (is_reference(node, parent)) { all_identifiers.add(node.name); } }, }); // ...then deconflict injected values... function deconflict(name) { while (all_identifiers.has(name)) name += '_'; return name; } const exports = deconflict('exports'); const __import = deconflict('__import'); const __import_meta = deconflict('__import_meta'); const __export = deconflict('__export'); const __export_all = deconflict('__export_all'); // ...then extract imports/exports... let uid = 0; const get_import_name = () => deconflict(`__import${uid++}`); const replacements = new Map(); const deps = []; const css = []; ast.body.forEach((node) => { if (node.type === 'ImportDeclaration') { const is_namespace = node.specifiers[0] && node.specifiers[0].type === 'ImportNamespaceSpecifier'; const default_specifier = node.specifiers.find((specifier) => !specifier.imported); const name = is_namespace ? node.specifiers[0].local.name : default_specifier ? default_specifier.local.name : get_import_name(); const source = node.source.value; if (source.endsWith('.css.proxy.js') && !source.endsWith('.module.css.proxy.js')) { // CSS proxy: only include CSS css.push(source.replace(/\.proxy\.js$/, '')); } else { if (source.endsWith('.module.css.proxy.js')) { // CSS Modules Proxy: include both CSS and JS const cssSource = source.replace(/\.proxy\.js$/, ''); css.push(cssSource); } deps.push({ name, source }); if (!is_namespace) { node.specifiers.forEach((specifier) => { const prop = specifier.imported ? specifier.imported.name : 'default'; replacements.set(specifier.local.name, `${name}.${prop}`); }); } } code.remove(node.start, node.end); } if (node.type === 'ExportAllDeclaration') { const source = node.source.value; const name = get_import_name(); deps.push({ name, source }); code.overwrite(node.start, node.end, `${__export_all}(${name})`); } if (node.type === 'ExportDefaultDeclaration') { code.overwrite(node.start, node.declaration.start - 1, `${exports}.default = `); } if (node.type === 'ExportNamedDeclaration') { if (node.source) { const name = get_import_name(); const source = node.source.value; deps.push({ name, source }); const export_block = node.specifiers .map((specifier) => { return `${__export}('${specifier.exported.name}', () => ${name}.${specifier.local.name})`; }) .join('; '); code.overwrite(node.start, node.end, export_block); } else if (node.declaration) { // `export const foo = ...` or `export function foo() {...}` code.remove(node.start, node.declaration.start); let suffix; if (node.declaration.type === 'VariableDeclaration') { const names = []; node.declaration.declarations.forEach((declarator) => { names.push(...extract_names(declarator.id)); }); suffix = names.map((name) => ` ${__export}('${name}', () => ${name});`).join(''); } else { const { name } = node.declaration.id; suffix = ` ${__export}('${name}', () => ${name});`; } code.appendLeft(node.end, suffix); } else { if (node.specifiers.length > 0) { code.remove(node.start, node.specifiers[0].start); node.specifiers.forEach((specifier) => { code.overwrite(specifier.start, specifier.end, `${__export}('${specifier.exported.name}', () => ${specifier.local.name})`); }); code.remove(node.specifiers[node.specifiers.length - 1].end, node.end); } else { // export {}; code.remove(node.start, node.end); } } } }); // ...then rewrite import references if (replacements.size) { let current_scope = scope; walk(ast, { enter(node, parent) { if (map.has(node)) { current_scope = map.get(node) || current_scope; } if (node.type === 'ImportDeclaration') { this.skip(); return; } if (!is_reference(node, parent)) return; if (!replacements.has(node.name)) return; if (parent.type === 'ExportSpecifier') return; if (current_scope.find_owner(node.name) === scope) { let replacement = replacements.get(node.name); if (parent.type === 'Property' && node === parent.key && node === parent.value) { replacement = `${node.name}: ${replacement}`; } code.overwrite(node.start, node.end, replacement); } }, leave(node) { if (map.has(node)) { if (!current_scope.parent) { throw new Error('Unexpected: (!current_scope.parent.parent)'); } current_scope = current_scope.parent; } }, }); } // replace import.meta and import(dynamic) if (/import\s*\.\s*meta/.test(data) || /import\s*\(/.test(data)) { walk(ast.body, { enter(node) { if (node.type === 'MetaProperty' && node.meta.name === 'import') { code.overwrite(node.start, node.end, __import_meta); } else if (node.type === 'ImportExpression') { code.overwrite(node.start, node.start + 6, __import); } }, }); } return { code: code.toString(), deps, css, names: { exports, __import, __import_meta, __export, __export_all }, }; }