es-dev-server
Version:
Development server for modern web apps
171 lines • 7.91 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveModuleImportsPlugin = exports.resolveModuleImports = void 0;
const tslib_1 = require("tslib");
const deepmerge_1 = tslib_1.__importDefault(require("deepmerge"));
const url_1 = require("url");
//@ts-ignore
const es_module_lexer_1 = require("es-module-lexer");
const dom5_fork_1 = require("@open-wc/building-utils/dom5-fork");
const building_utils_1 = require("@open-wc/building-utils");
const parse5_1 = require("parse5");
const plugin_transform_1 = require("../middleware/plugin-transform");
const babel_transform_1 = require("../utils/babel-transform");
const CONCAT_NO_PACKAGE_ERROR = 'Dynamic import with a concatenated string should start with a valid full package name.';
const babelTransform = babel_transform_1.createBabelTransform(
// @ts-ignore
deepmerge_1.default(babel_transform_1.defaultConfig, {
babelrc: false,
configFile: false,
}));
async function createSyntaxError(code, filePath) {
// if es-module-lexer cannot parse the file, use babel to generate a user-friendly error message
try {
await babelTransform(filePath, code);
}
catch (error) {
throw new plugin_transform_1.TransformSyntaxError(error.message);
}
// ResolveSyntaxError is thrown when resolveModuleImports runs into a syntax error from
// es-module-lexer, but babel didn't see any errors. this means either a bug in the lexer, or
// some experimental syntax. log a message and return the module untransformed to the
// browser
throw new plugin_transform_1.TransformSyntaxError(`Could not resolve module imports in ${filePath.substring(1)}: Unable to parse the module, this can be due to experimental syntax or a bug in the parser.`);
}
/**
* 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) {
let pathToResolve = importSpecifier;
let pathToAppend = '';
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);
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) {
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))) !== null && _a !== void 0 ? _a : importSpecifier;
}
catch (error) {
return importSpecifier;
}
}
else {
resolvedImportFilePath = (_b = (await resolveImport(importSpecifier))) !== null && _b !== void 0 ? _b : importSpecifier;
}
return resolvedImportFilePath;
}
async function resolveModuleImports(code, filePath, resolveImport) {
let imports;
try {
[imports] = await es_module_lexer_1.parse(code, filePath);
}
catch (error) {
throw await createSyntaxError(code, filePath);
}
let resolvedSource = '';
let lastIndex = 0;
for (const imp of imports) {
const { s: start, e: end, d: dynamicImportIndex } = imp;
if (dynamicImportIndex === -1) {
// static import
const importSpecifier = code.substring(start, end);
const resolvedImport = await maybeResolveImport(importSpecifier, false, resolveImport);
resolvedSource += `${code.substring(lastIndex, start)}${resolvedImport}`;
lastIndex = end;
}
else if (dynamicImportIndex >= 0) {
// dynamic import
const dynamicStart = start + 1;
const dynamicEnd = end - 1;
const importSpecifier = code.substring(dynamicStart, dynamicEnd);
const stringSymbol = code[dynamicStart - 1];
const isStringLiteral = [`\``, "'", '"'].includes(stringSymbol);
const concatenatedString = stringSymbol === `\`` || importSpecifier.includes("'") || importSpecifier.includes('"');
const resolvedImport = isStringLiteral
? await maybeResolveImport(importSpecifier, concatenatedString, resolveImport)
: importSpecifier;
resolvedSource += `${code.substring(lastIndex, dynamicStart)}${resolvedImport}`;
lastIndex = dynamicEnd;
}
}
if (lastIndex < code.length - 1) {
resolvedSource += `${code.substring(lastIndex, code.length)}`;
}
return resolvedSource;
}
exports.resolveModuleImports = resolveModuleImports;
async function resolveWithPluginHooks(context, jsCode, rootDir, resolvePlugins) {
const fileUrl = new url_1.URL(`.${context.path}`, `${url_1.pathToFileURL(rootDir)}/`);
const filePath = url_1.fileURLToPath(fileUrl);
async function resolveImport(source) {
var _a;
for (const plugin of resolvePlugins) {
const resolved = await ((_a = plugin.resolveImport) === null || _a === void 0 ? void 0 : _a.call(plugin, { source, context }));
if (resolved)
return resolved;
}
}
return resolveModuleImports(jsCode, filePath, resolveImport);
}
function resolveModuleImportsPlugin(config) {
const { rootDir, plugins = [] } = config;
const resolvePlugins = plugins.filter(pl => !!pl.resolveImport);
return {
async transform(context) {
if (resolvePlugins.length === 0) {
return;
}
// served JS code
if (context.response.is('js')) {
const bodyWithResolvedImports = await resolveWithPluginHooks(context, context.body, rootDir, resolvePlugins);
return { body: bodyWithResolvedImports };
}
// resolve inline scripts
if (context.response.is('html')) {
const documentAst = parse5_1.parse(context.body);
const scriptNodes = building_utils_1.findJsScripts(documentAst, {
jsScripts: true,
jsModules: true,
inlineJsScripts: true,
});
for (const node of scriptNodes) {
const code = dom5_fork_1.getTextContent(node);
const resolvedCode = await resolveWithPluginHooks(context, code, rootDir, resolvePlugins);
dom5_fork_1.setTextContent(node, resolvedCode);
}
return { body: parse5_1.serialize(documentAst) };
}
},
};
}
exports.resolveModuleImportsPlugin = resolveModuleImportsPlugin;
//# sourceMappingURL=resolveModuleImportsPlugin.js.map