@web/dev-server-core
Version:
198 lines • 9.81 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transformModuleImportsPlugin = exports.transformImports = void 0;
/* eslint-disable @typescript-eslint/ban-ts-comment */
const path_1 = __importDefault(require("path"));
// @ts-ignore
const es_module_lexer_1 = require("es-module-lexer");
const index_js_1 = require("../dom5/index.js");
const parse5_1 = require("parse5");
const PluginSyntaxError_js_1 = require("../logger/PluginSyntaxError.js");
const utils_js_1 = require("../utils.js");
const parseDynamicImport_js_1 = require("./parseDynamicImport.js");
const CONCAT_NO_PACKAGE_ERROR = 'Dynamic import with a concatenated string should start with a valid full package name.';
/**
* Resolves an import which is a concatenated string (for ex. import('my-package/files/${filename}'))
*
* Resolving is done by taking the package name and resolving that, then prefixing the resolves package
* to the import. This requires the full package name to be present in the string.
*/
async function resolveConcatenatedImport(importSpecifier, resolveImport, code, line, column) {
let pathToResolve = importSpecifier;
let pathToAppend = '';
if (['/', '../', './'].some(p => pathToResolve.startsWith(p))) {
// don't handle non-bare imports
return pathToResolve;
}
const parts = importSpecifier.split('/');
if (importSpecifier.startsWith('@')) {
if (parts.length < 2) {
throw new Error(CONCAT_NO_PACKAGE_ERROR);
}
pathToResolve = `${parts[0]}/${parts[1]}`;
pathToAppend = parts.slice(2, parts.length).join('/');
}
else {
if (parts.length < 1) {
throw new Error(CONCAT_NO_PACKAGE_ERROR);
}
[pathToResolve] = parts;
pathToAppend = parts.slice(1, parts.length).join('/');
}
// TODO: instead of package, we could resolve the bare import and take the first one or two segments
// this will make it less hardcoded to node resolution
const packagePath = `${pathToResolve}/package.json`;
const resolvedPackage = await resolveImport(packagePath, code, line, column);
if (!resolvedPackage) {
throw new Error(`Could not resolve conatenated dynamic import, could not find ${packagePath}`);
}
const packageDir = resolvedPackage.substring(0, resolvedPackage.length - 'package.json'.length);
return `${packageDir}${pathToAppend}`;
}
async function maybeResolveImport(importSpecifier, concatenatedString, resolveImport, code, line, column) {
var _a, _b;
let resolvedImportFilePath;
if (concatenatedString) {
// if this dynamic import is a concatenated string, try our best to resolve. Otherwise leave it untouched and resolve it at runtime.
try {
resolvedImportFilePath =
(_a = (await resolveConcatenatedImport(importSpecifier, resolveImport, code, line, column))) !== null && _a !== void 0 ? _a : importSpecifier;
}
catch (error) {
return importSpecifier;
}
}
else {
resolvedImportFilePath =
(_b = (await resolveImport(importSpecifier, code, line, column))) !== null && _b !== void 0 ? _b : importSpecifier;
}
return resolvedImportFilePath;
}
async function transformImports(code, filePath, resolveImport) {
let imports;
try {
const parseResult = await (0, es_module_lexer_1.parse)(code, filePath);
imports = parseResult[0];
}
catch (error) {
if (typeof error.idx === 'number') {
const lexerError = error;
throw new PluginSyntaxError_js_1.PluginSyntaxError('Syntax error', filePath, code, code.slice(0, lexerError.idx).split('\n').length, lexerError.idx - code.lastIndexOf('\n', lexerError.idx - 1));
}
throw error;
}
let resolvedSource = '';
let lastIndex = 0;
for (const imp of imports) {
const { s: start, e: end, d: dynamicImportIndex, n: unescaped } = imp;
if (dynamicImportIndex === -1) {
// static import
const importSpecifier = unescaped || code.substring(start, end);
const lines = code.slice(0, end).split('\n');
const line = lines.length;
const column = lines[lines.length - 1].indexOf(importSpecifier);
const resolvedImport = await maybeResolveImport(importSpecifier, false, resolveImport, code, line, column);
resolvedSource += `${code.substring(lastIndex, start)}${resolvedImport}`;
lastIndex = end;
}
else if (dynamicImportIndex >= 0) {
// dynamic import
const { importString, importSpecifier, stringLiteral, concatenatedString, dynamicStart, dynamicEnd, } = (0, parseDynamicImport_js_1.parseDynamicImport)(code, start, end);
const lines = code.slice(0, dynamicStart).split('\n');
const line = lines.length;
const column = lines[lines.length - 1].indexOf('import(') || 0;
let rewrittenImport;
if (stringLiteral) {
const resolvedImport = await maybeResolveImport(importSpecifier, concatenatedString, resolveImport, code, line, column);
rewrittenImport = `${importString[0]}${resolvedImport}${importString[importString.length - 1]}`;
}
else {
rewrittenImport = importString;
}
resolvedSource += `${code.substring(lastIndex, dynamicStart)}${rewrittenImport}`;
lastIndex = dynamicEnd;
}
}
if (lastIndex < code.length - 1) {
resolvedSource += `${code.substring(lastIndex, code.length)}`;
}
return resolvedSource;
}
exports.transformImports = transformImports;
async function transformModuleImportsWithPlugins(logger, context, jsCode, rootDir, resolvePlugins) {
const filePath = path_1.default.join(rootDir, (0, utils_js_1.toFilePath)(context.path));
async function resolveImport(source, code, column, line) {
var _a;
for (const plugin of resolvePlugins) {
const resolved = await ((_a = plugin.resolveImport) === null || _a === void 0 ? void 0 : _a.call(plugin, { source, context, code, column, line }));
if (typeof resolved === 'string') {
logger.debug(`Plugin ${plugin.name} resolved import ${source} in ${context.path} to ${resolved}.`);
return resolved;
}
if (typeof resolved === 'object') {
logger.debug(`Plugin ${plugin.name} resolved import ${source} in ${context.path} to ${resolved.id}.`);
return resolved.id;
}
}
}
async function transformImport(source, code, column, line) {
var _a, _b;
let resolvedImport = (_a = (await resolveImport(source, code, column, line))) !== null && _a !== void 0 ? _a : source;
for (const plugin of resolvePlugins) {
const resolved = await ((_b = plugin.transformImport) === null || _b === void 0 ? void 0 : _b.call(plugin, {
source: resolvedImport,
context,
column,
line,
}));
if (typeof resolved === 'string') {
logger.debug(`Plugin ${plugin.name} transformed import ${resolvedImport} in ${context.path} to ${resolved}.`);
resolvedImport = resolved;
}
if (typeof resolved === 'object' && typeof resolved.id === 'string') {
logger.debug(`Plugin ${plugin.name} transformed import ${resolvedImport} in ${context.path} to ${resolved.id}.`);
resolvedImport = resolved.id;
}
}
return resolvedImport;
}
return transformImports(jsCode, filePath, transformImport);
}
function transformModuleImportsPlugin(logger, plugins, rootDir) {
const importPlugins = plugins.filter(pl => !!pl.resolveImport || !!pl.transformImport);
return {
name: 'resolve-module-imports',
async transform(context) {
if (importPlugins.length === 0) {
return;
}
// resolve served js code
if (context.response.is('js')) {
const bodyWithResolvedImports = await transformModuleImportsWithPlugins(logger, context, context.body, rootDir, importPlugins);
return { body: bodyWithResolvedImports };
}
// resolve inline scripts
if (context.response.is('html') && typeof context.body === 'string') {
const documentAst = (0, parse5_1.parse)(context.body);
const inlineModuleNodes = (0, index_js_1.queryAll)(documentAst, index_js_1.predicates.AND(index_js_1.predicates.hasTagName('script'), index_js_1.predicates.hasAttrValue('type', 'module'), index_js_1.predicates.NOT(index_js_1.predicates.hasAttr('src'))));
let transformed = false;
for (const node of inlineModuleNodes) {
const code = (0, index_js_1.getTextContent)(node);
const resolvedCode = await transformModuleImportsWithPlugins(logger, context, code, rootDir, importPlugins);
if (code !== resolvedCode) {
(0, index_js_1.setTextContent)(node, resolvedCode);
transformed = true;
}
}
if (transformed) {
return { body: (0, parse5_1.serialize)(documentAst) };
}
}
},
};
}
exports.transformModuleImportsPlugin = transformModuleImportsPlugin;
//# sourceMappingURL=transformModuleImportsPlugin.js.map
;