UNPKG

@web/dev-server-core

Version:
198 lines 9.81 kB
"use strict"; 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