UNPKG

sucrase

Version:

Super-fast alternative to Babel for when you can target modern JS runtimes

260 lines (259 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const isMaybePropertyName_1 = require("./util/isMaybePropertyName"); /** * Class responsible for preprocessing and bookkeeping import and export * declarations within the file. */ class ImportProcessor { constructor(nameManager, tokens) { this.nameManager = nameManager; this.tokens = tokens; this.importInfoByPath = new Map(); this.importsToReplace = new Map(); this.identifierReplacements = new Map(); this.exportBindingsByLocalName = new Map(); } getPrefixCode() { let prefix = ''; prefix += ` function ${this.interopRequireWildcardName}(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }`.replace(/\s+/g, ' '); prefix += ` function ${this.interopRequireDefaultName}(obj) { return obj && obj.__esModule ? obj : { default: obj }; }`.replace(/\s+/g, ' '); return prefix; } preprocessTokens() { this.interopRequireWildcardName = this.nameManager.claimFreeName('_interopRequireWildcard'); this.interopRequireDefaultName = this.nameManager.claimFreeName('_interopRequireDefault'); for (let i = 0; i < this.tokens.tokens.length; i++) { if (isMaybePropertyName_1.default(this.tokens, i)) { continue; } if (this.tokens.matchesAtIndex(i, ['import'])) { this.preprocessImportAtIndex(i); } if (this.tokens.matchesAtIndex(i, ['export'])) { this.preprocessExportAtIndex(i); } } this.generateImportReplacements(); } generateImportReplacements() { for (const [path, importInfo] of this.importInfoByPath.entries()) { const { defaultNames, wildcardNames, namedImports, namedExports, hasStarExport } = importInfo; if (defaultNames.length === 0 && wildcardNames.length === 0 && namedImports.length === 0 && namedExports.length === 0 && !hasStarExport) { // Import is never used, so don't even assign a name. this.importsToReplace.set(path, `require('${path}');`); continue; } const primaryImportName = this.getFreeIdentifierForPath(path); const secondaryImportName = wildcardNames.length > 0 ? wildcardNames[0] : this.getFreeIdentifierForPath(path); let requireCode = `var ${primaryImportName} = require('${path}');`; if (wildcardNames.length > 0) { for (const wildcardName of wildcardNames) { requireCode += ` var ${wildcardName} = ${this.interopRequireWildcardName}(${primaryImportName});`; } } else if (defaultNames.length > 0) { requireCode += ` var ${secondaryImportName} = ${this.interopRequireDefaultName}(${primaryImportName});`; } for (const { importedName, localName } of namedExports) { requireCode += ` Object.defineProperty(exports, '${localName}', {enumerable: true, get: () => ${primaryImportName}.${importedName}});`; } if (hasStarExport) { requireCode += ` Object.keys(${primaryImportName}).filter(key => key !== 'default' && key !== '__esModule').forEach(key => { Object.defineProperty(exports, key, {enumerable: true, get: () => ${primaryImportName}[key]}); });`; } this.importsToReplace.set(path, requireCode); for (const defaultName of defaultNames) { this.identifierReplacements.set(defaultName, `${secondaryImportName}.default`); } for (const { importedName, localName } of namedImports) { this.identifierReplacements.set(localName, `${primaryImportName}.${importedName}`); } } } getFreeIdentifierForPath(path) { const components = path.split('/'); const lastComponent = components[components.length - 1]; const baseName = lastComponent.replace(/\W/g, ''); return this.nameManager.claimFreeName(`_${baseName}`); } preprocessImportAtIndex(index) { let defaultNames = []; let wildcardNames = []; let namedImports = []; index++; if (this.tokens.matchesAtIndex(index, ['name'])) { defaultNames.push(this.tokens.tokens[index].value); index++; if (this.tokens.matchesAtIndex(index, [','])) { index++; } } if (this.tokens.matchesAtIndex(index, ['*'])) { // * as index += 2; wildcardNames.push(this.tokens.tokens[index].value); index++; } if (this.tokens.matchesAtIndex(index, ['{'])) { index++; ({ newIndex: index, namedImports } = this.getNamedImports(index)); } if (this.tokens.matchesNameAtIndex(index, 'from')) { index++; } if (!this.tokens.matchesAtIndex(index, ['string'])) { throw new Error('Expected string token at the end of import statement.'); } const path = this.tokens.tokens[index].value; const importInfo = this.getImportInfo(path); importInfo.defaultNames.push(...defaultNames); importInfo.wildcardNames.push(...wildcardNames); importInfo.namedImports.push(...namedImports); } preprocessExportAtIndex(index) { if (this.tokens.matchesAtIndex(index, ['export', 'var']) || this.tokens.matchesAtIndex(index, ['export', 'let']) || this.tokens.matchesAtIndex(index, ['export', 'const']) || this.tokens.matchesAtIndex(index, ['export', 'function']) || this.tokens.matchesAtIndex(index, ['export', 'class'])) { const exportName = this.tokens.tokens[index + 2].value; this.exportBindingsByLocalName.set(exportName, exportName); } else if (this.tokens.matchesAtIndex(index, ['export', 'name', 'function'])) { const exportName = this.tokens.tokens[index + 3].value; this.exportBindingsByLocalName.set(exportName, exportName); } else if (this.tokens.matchesAtIndex(index, ['export', '{'])) { this.preprocessNamedExportAtIndex(index); } else if (this.tokens.matchesAtIndex(index, ['export', '*'])) { this.preprocessExportStarAtIndex(index); } } /** * Walk this export statement just in case it's an export...from statement. * If it is, combine it into the import info for that path. Otherwise, just * bail out; it'll be handled later. */ preprocessNamedExportAtIndex(index) { // export { index += 2; const { newIndex, namedImports } = this.getNamedImports(index); index = newIndex; if (this.tokens.matchesNameAtIndex(index, 'from')) { index++; } else { // Reinterpret "a as b" to be local/exported rather than imported/local. for (const { importedName: localName, localName: exportedName } of namedImports) { this.exportBindingsByLocalName.set(localName, exportedName); } return; } if (!this.tokens.matchesAtIndex(index, ['string'])) { throw new Error('Expected string token at the end of import statement.'); } const path = this.tokens.tokens[index].value; const importInfo = this.getImportInfo(path); importInfo.namedExports.push(...namedImports); } preprocessExportStarAtIndex(index) { // export * from index += 3; if (!this.tokens.matchesAtIndex(index, ['string'])) { throw new Error('Expected string token at the end of star export statement.'); } const path = this.tokens.tokens[index].value; let importInfo = this.getImportInfo(path); importInfo.hasStarExport = true; } getNamedImports(index) { const namedImports = []; while (true) { const importedName = this.tokens.tokens[index].value; let localName; index++; if (this.tokens.matchesNameAtIndex(index, 'as')) { index++; localName = this.tokens.tokens[index].value; index++; } else { localName = importedName; } namedImports.push({ importedName, localName }); if (this.tokens.matchesAtIndex(index, [',', '}'])) { index += 2; break; } else if (this.tokens.matchesAtIndex(index, ['}'])) { index++; break; } else if (this.tokens.matchesAtIndex(index, [','])) { index++; } else { throw new Error('Unexpected token.'); } } return { newIndex: index, namedImports }; } /** * Get a mutable import info object for this path, creating one if it doesn't * exist yet. */ getImportInfo(path) { const existingInfo = this.importInfoByPath.get(path); if (existingInfo) { return existingInfo; } else { const newInfo = { defaultNames: [], wildcardNames: [], namedImports: [], namedExports: [], hasStarExport: false, }; this.importInfoByPath.set(path, newInfo); return newInfo; } } /** * Return the code to use for the import for this path, or the empty string if * the code has already been "claimed" by a previous import. */ claimImportCode(importPath) { const result = this.importsToReplace.get(importPath); this.importsToReplace.set(importPath, ''); return result || ''; } getIdentifierReplacement(identifierName) { return this.identifierReplacements.get(identifierName) || null; } resolveExportBinding(assignedName) { return this.exportBindingsByLocalName.get(assignedName) || null; } } exports.ImportProcessor = ImportProcessor;