UNPKG

es-dev-server

Version:

Development server for modern web apps

171 lines 7.91 kB
"use strict"; 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