sucrase
Version:
Super-fast alternative to Babel for when you can target modern JS runtimes
260 lines (259 loc) • 10.7 kB
JavaScript
"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;