snowpack
Version:
The ESM-powered frontend build tool. Fast, lightweight, unbundled.
128 lines (127 loc) • 5.41 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.transformAddMissingDefaultExport = exports.transformFileImports = exports.transformEsmImports = exports.scanCodeImportsExports = void 0;
const scan_imports_1 = require("./scan-imports");
const util_1 = require("./util");
const { parse } = require('es-module-lexer');
const WEBPACK_MAGIC_COMMENT_REGEX = /\/\*[\s\S]*?\*\//g;
function applyRewrites(source, rewrites) {
let result = ``;
let index = 0;
rewrites
.sort((a, b) => a.start - b.start)
.forEach(({ start, end, rewrite }) => {
result += source.substring(index, start) + rewrite;
index = end;
});
result += source.substring(index);
return result;
}
async function scanCodeImportsExports(code) {
const [imports] = await parse(code);
return imports.filter((imp) => {
//imp.d = -2 = import.meta.url = we can skip this for now
if (imp.d === -2) {
return false;
}
// imp.d > -1 === dynamic import
if (imp.d > -1) {
const importStatement = code.substring(imp.s, imp.e);
return !!scan_imports_1.matchDynamicImportValue(importStatement);
}
return true;
});
}
exports.scanCodeImportsExports = scanCodeImportsExports;
async function transformEsmImports(_code, replaceImport) {
const imports = await scanCodeImportsExports(_code);
const collectedRewrites = [];
await Promise.all(imports.map(async (imp) => {
let spec = _code.substring(imp.s, imp.e).replace(/(\/|\\)+$/, '');
let webpackMagicCommentMatches;
if (imp.d > -1) {
// Extracting comments from spec as they are stripped in `matchDynamicImportValue`
webpackMagicCommentMatches = spec.match(WEBPACK_MAGIC_COMMENT_REGEX);
spec = scan_imports_1.matchDynamicImportValue(spec) || '';
}
let rewrittenImport = await replaceImport(spec);
if (imp.d > -1) {
rewrittenImport = webpackMagicCommentMatches
? `${webpackMagicCommentMatches.join(' ')} ${JSON.stringify(rewrittenImport)}`
: JSON.stringify(rewrittenImport);
}
collectedRewrites.push({ rewrite: rewrittenImport, start: imp.s, end: imp.e });
}));
const result = applyRewrites(_code, collectedRewrites);
return result;
}
exports.transformEsmImports = transformEsmImports;
async function transformHtmlImports(code, replaceImport) {
const collectedRewrites = [];
let match;
const jsImportRegex = new RegExp(util_1.HTML_JS_REGEX);
while ((match = jsImportRegex.exec(code))) {
const [, scriptTag, scriptCode] = match;
// Only transform a script element if it contains inlined code / is not empty.
if (scriptCode.trim()) {
collectedRewrites.push({
rewrite: await transformEsmImports(scriptCode, replaceImport),
start: match.index + scriptTag.length,
end: match.index + scriptTag.length + scriptCode.length,
});
}
}
const cssImportRegex = new RegExp(util_1.HTML_STYLE_REGEX);
while ((match = cssImportRegex.exec(code))) {
const [, styleTag, styleCode] = match;
// Only transform a script element if it contains inlined code / is not empty.
if (styleCode.trim()) {
collectedRewrites.push({
rewrite: await transformCssImports(styleCode, replaceImport),
start: match.index + styleTag.length,
end: match.index + styleTag.length + styleCode.length,
});
}
}
const rewrittenCode = applyRewrites(code, collectedRewrites);
return rewrittenCode;
}
async function transformCssImports(code, replaceImport) {
const collectedRewrites = [];
let match;
const importRegex = new RegExp(util_1.CSS_REGEX);
while ((match = importRegex.exec(code))) {
const [fullMatch, spec] = match;
// Only transform a script element if it contains inlined code / is not empty.
collectedRewrites.push({
// CSS doesn't support proxy files, so always point to the original file
rewrite: `@import "${(await replaceImport(spec)).replace('.proxy.js', '')}";`,
start: match.index,
end: match.index + fullMatch.length,
});
}
const rewrittenCode = applyRewrites(code, collectedRewrites);
return rewrittenCode;
}
async function transformFileImports({ type, contents }, replaceImport) {
if (type === '.js') {
return transformEsmImports(contents, replaceImport);
}
if (type === '.html') {
return transformHtmlImports(contents, replaceImport);
}
if (type === '.css') {
return transformCssImports(contents, replaceImport);
}
throw new Error(`Incompatible filetype: cannot scan ${type} files for ESM imports. This is most likely an error within Snowpack.`);
}
exports.transformFileImports = transformFileImports;
async function transformAddMissingDefaultExport(_code) {
// We need to add a default export, just so that our re-importer doesn't break
const [, allExports] = await parse(_code);
if (!allExports.includes('default')) {
return _code + '\n\nexport default null;';
}
return _code;
}
exports.transformAddMissingDefaultExport = transformAddMissingDefaultExport;
;