CJSImportTransformer.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. import {IdentifierRole, isDeclaration, isObjectShorthandDeclaration} from "../parser/tokenizer";
  2. import {ContextualKeyword} from "../parser/tokenizer/keywords";
  3. import {TokenType as tt} from "../parser/tokenizer/types";
  4. import elideImportEquals from "../util/elideImportEquals";
  5. import getDeclarationInfo, {
  6. EMPTY_DECLARATION_INFO,
  7. } from "../util/getDeclarationInfo";
  8. import getImportExportSpecifierInfo from "../util/getImportExportSpecifierInfo";
  9. import isExportFrom from "../util/isExportFrom";
  10. import {removeMaybeImportAttributes} from "../util/removeMaybeImportAttributes";
  11. import shouldElideDefaultExport from "../util/shouldElideDefaultExport";
  12. import Transformer from "./Transformer";
  13. /**
  14. * Class for editing import statements when we are transforming to commonjs.
  15. */
  16. export default class CJSImportTransformer extends Transformer {
  17. __init() {this.hadExport = false}
  18. __init2() {this.hadNamedExport = false}
  19. __init3() {this.hadDefaultExport = false}
  20. constructor(
  21. rootTransformer,
  22. tokens,
  23. importProcessor,
  24. nameManager,
  25. helperManager,
  26. reactHotLoaderTransformer,
  27. enableLegacyBabel5ModuleInterop,
  28. enableLegacyTypeScriptModuleInterop,
  29. isTypeScriptTransformEnabled,
  30. isFlowTransformEnabled,
  31. preserveDynamicImport,
  32. keepUnusedImports,
  33. ) {
  34. super();this.rootTransformer = rootTransformer;this.tokens = tokens;this.importProcessor = importProcessor;this.nameManager = nameManager;this.helperManager = helperManager;this.reactHotLoaderTransformer = reactHotLoaderTransformer;this.enableLegacyBabel5ModuleInterop = enableLegacyBabel5ModuleInterop;this.enableLegacyTypeScriptModuleInterop = enableLegacyTypeScriptModuleInterop;this.isTypeScriptTransformEnabled = isTypeScriptTransformEnabled;this.isFlowTransformEnabled = isFlowTransformEnabled;this.preserveDynamicImport = preserveDynamicImport;this.keepUnusedImports = keepUnusedImports;CJSImportTransformer.prototype.__init.call(this);CJSImportTransformer.prototype.__init2.call(this);CJSImportTransformer.prototype.__init3.call(this);;
  35. this.declarationInfo = isTypeScriptTransformEnabled
  36. ? getDeclarationInfo(tokens)
  37. : EMPTY_DECLARATION_INFO;
  38. }
  39. getPrefixCode() {
  40. let prefix = "";
  41. if (this.hadExport) {
  42. prefix += 'Object.defineProperty(exports, "__esModule", {value: true});';
  43. }
  44. return prefix;
  45. }
  46. getSuffixCode() {
  47. if (this.enableLegacyBabel5ModuleInterop && this.hadDefaultExport && !this.hadNamedExport) {
  48. return "\nmodule.exports = exports.default;\n";
  49. }
  50. return "";
  51. }
  52. process() {
  53. // TypeScript `import foo = require('foo');` should always just be translated to plain require.
  54. if (this.tokens.matches3(tt._import, tt.name, tt.eq)) {
  55. return this.processImportEquals();
  56. }
  57. if (this.tokens.matches1(tt._import)) {
  58. this.processImport();
  59. return true;
  60. }
  61. if (this.tokens.matches2(tt._export, tt.eq)) {
  62. this.tokens.replaceToken("module.exports");
  63. return true;
  64. }
  65. if (this.tokens.matches1(tt._export) && !this.tokens.currentToken().isType) {
  66. this.hadExport = true;
  67. return this.processExport();
  68. }
  69. if (this.tokens.matches2(tt.name, tt.postIncDec)) {
  70. // Fall through to normal identifier matching if this doesn't apply.
  71. if (this.processPostIncDec()) {
  72. return true;
  73. }
  74. }
  75. if (this.tokens.matches1(tt.name) || this.tokens.matches1(tt.jsxName)) {
  76. return this.processIdentifier();
  77. }
  78. if (this.tokens.matches1(tt.eq)) {
  79. return this.processAssignment();
  80. }
  81. if (this.tokens.matches1(tt.assign)) {
  82. return this.processComplexAssignment();
  83. }
  84. if (this.tokens.matches1(tt.preIncDec)) {
  85. return this.processPreIncDec();
  86. }
  87. return false;
  88. }
  89. processImportEquals() {
  90. const importName = this.tokens.identifierNameAtIndex(this.tokens.currentIndex() + 1);
  91. if (this.importProcessor.shouldAutomaticallyElideImportedName(importName)) {
  92. // If this name is only used as a type, elide the whole import.
  93. elideImportEquals(this.tokens);
  94. } else {
  95. // Otherwise, switch `import` to `const`.
  96. this.tokens.replaceToken("const");
  97. }
  98. return true;
  99. }
  100. /**
  101. * Transform this:
  102. * import foo, {bar} from 'baz';
  103. * into
  104. * var _baz = require('baz'); var _baz2 = _interopRequireDefault(_baz);
  105. *
  106. * The import code was already generated in the import preprocessing step, so
  107. * we just need to look it up.
  108. */
  109. processImport() {
  110. if (this.tokens.matches2(tt._import, tt.parenL)) {
  111. if (this.preserveDynamicImport) {
  112. // Bail out, only making progress for this one token.
  113. this.tokens.copyToken();
  114. return;
  115. }
  116. const requireWrapper = this.enableLegacyTypeScriptModuleInterop
  117. ? ""
  118. : `${this.helperManager.getHelperName("interopRequireWildcard")}(`;
  119. this.tokens.replaceToken(`Promise.resolve().then(() => ${requireWrapper}require`);
  120. const contextId = this.tokens.currentToken().contextId;
  121. if (contextId == null) {
  122. throw new Error("Expected context ID on dynamic import invocation.");
  123. }
  124. this.tokens.copyToken();
  125. while (!this.tokens.matchesContextIdAndLabel(tt.parenR, contextId)) {
  126. this.rootTransformer.processToken();
  127. }
  128. this.tokens.replaceToken(requireWrapper ? ")))" : "))");
  129. return;
  130. }
  131. const shouldElideImport = this.removeImportAndDetectIfShouldElide();
  132. if (shouldElideImport) {
  133. this.tokens.removeToken();
  134. } else {
  135. const path = this.tokens.stringValue();
  136. this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path));
  137. this.tokens.appendCode(this.importProcessor.claimImportCode(path));
  138. }
  139. removeMaybeImportAttributes(this.tokens);
  140. if (this.tokens.matches1(tt.semi)) {
  141. this.tokens.removeToken();
  142. }
  143. }
  144. /**
  145. * Erase this import (since any CJS output would be completely different), and
  146. * return true if this import is should be elided due to being a type-only
  147. * import. Such imports will not be emitted at all to avoid side effects.
  148. *
  149. * Import elision only happens with the TypeScript or Flow transforms enabled.
  150. *
  151. * TODO: This function has some awkward overlap with
  152. * CJSImportProcessor.pruneTypeOnlyImports , and the two should be unified.
  153. * That function handles TypeScript implicit import name elision, and removes
  154. * an import if all typical imported names (without `type`) are removed due
  155. * to being type-only imports. This function handles Flow import removal and
  156. * properly distinguishes `import 'foo'` from `import {} from 'foo'` for TS
  157. * purposes.
  158. *
  159. * The position should end at the import string.
  160. */
  161. removeImportAndDetectIfShouldElide() {
  162. this.tokens.removeInitialToken();
  163. if (
  164. this.tokens.matchesContextual(ContextualKeyword._type) &&
  165. !this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, tt.comma) &&
  166. !this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, ContextualKeyword._from)
  167. ) {
  168. // This is an "import type" statement, so exit early.
  169. this.removeRemainingImport();
  170. return true;
  171. }
  172. if (this.tokens.matches1(tt.name) || this.tokens.matches1(tt.star)) {
  173. // We have a default import or namespace import, so there must be some
  174. // non-type import.
  175. this.removeRemainingImport();
  176. return false;
  177. }
  178. if (this.tokens.matches1(tt.string)) {
  179. // This is a bare import, so we should proceed with the import.
  180. return false;
  181. }
  182. let foundNonTypeImport = false;
  183. let foundAnyNamedImport = false;
  184. while (!this.tokens.matches1(tt.string)) {
  185. // Check if any named imports are of the form "foo" or "foo as bar", with
  186. // no leading "type".
  187. if (
  188. (!foundNonTypeImport && this.tokens.matches1(tt.braceL)) ||
  189. this.tokens.matches1(tt.comma)
  190. ) {
  191. this.tokens.removeToken();
  192. if (!this.tokens.matches1(tt.braceR)) {
  193. foundAnyNamedImport = true;
  194. }
  195. if (
  196. this.tokens.matches2(tt.name, tt.comma) ||
  197. this.tokens.matches2(tt.name, tt.braceR) ||
  198. this.tokens.matches4(tt.name, tt.name, tt.name, tt.comma) ||
  199. this.tokens.matches4(tt.name, tt.name, tt.name, tt.braceR)
  200. ) {
  201. foundNonTypeImport = true;
  202. }
  203. }
  204. this.tokens.removeToken();
  205. }
  206. if (this.keepUnusedImports) {
  207. return false;
  208. }
  209. if (this.isTypeScriptTransformEnabled) {
  210. return !foundNonTypeImport;
  211. } else if (this.isFlowTransformEnabled) {
  212. // In Flow, unlike TS, `import {} from 'foo';` preserves the import.
  213. return foundAnyNamedImport && !foundNonTypeImport;
  214. } else {
  215. return false;
  216. }
  217. }
  218. removeRemainingImport() {
  219. while (!this.tokens.matches1(tt.string)) {
  220. this.tokens.removeToken();
  221. }
  222. }
  223. processIdentifier() {
  224. const token = this.tokens.currentToken();
  225. if (token.shadowsGlobal) {
  226. return false;
  227. }
  228. if (token.identifierRole === IdentifierRole.ObjectShorthand) {
  229. return this.processObjectShorthand();
  230. }
  231. if (token.identifierRole !== IdentifierRole.Access) {
  232. return false;
  233. }
  234. const replacement = this.importProcessor.getIdentifierReplacement(
  235. this.tokens.identifierNameForToken(token),
  236. );
  237. if (!replacement) {
  238. return false;
  239. }
  240. // Tolerate any number of closing parens while looking for an opening paren
  241. // that indicates a function call.
  242. let possibleOpenParenIndex = this.tokens.currentIndex() + 1;
  243. while (
  244. possibleOpenParenIndex < this.tokens.tokens.length &&
  245. this.tokens.tokens[possibleOpenParenIndex].type === tt.parenR
  246. ) {
  247. possibleOpenParenIndex++;
  248. }
  249. // Avoid treating imported functions as methods of their `exports` object
  250. // by using `(0, f)` when the identifier is in a paren expression. Else
  251. // use `Function.prototype.call` when the identifier is a guaranteed
  252. // function call. When using `call`, pass undefined as the context.
  253. if (this.tokens.tokens[possibleOpenParenIndex].type === tt.parenL) {
  254. if (
  255. this.tokens.tokenAtRelativeIndex(1).type === tt.parenL &&
  256. this.tokens.tokenAtRelativeIndex(-1).type !== tt._new
  257. ) {
  258. this.tokens.replaceToken(`${replacement}.call(void 0, `);
  259. // Remove the old paren.
  260. this.tokens.removeToken();
  261. // Balance out the new paren.
  262. this.rootTransformer.processBalancedCode();
  263. this.tokens.copyExpectedToken(tt.parenR);
  264. } else {
  265. // See here: http://2ality.com/2015/12/references.html
  266. this.tokens.replaceToken(`(0, ${replacement})`);
  267. }
  268. } else {
  269. this.tokens.replaceToken(replacement);
  270. }
  271. return true;
  272. }
  273. processObjectShorthand() {
  274. const identifier = this.tokens.identifierName();
  275. const replacement = this.importProcessor.getIdentifierReplacement(identifier);
  276. if (!replacement) {
  277. return false;
  278. }
  279. this.tokens.replaceToken(`${identifier}: ${replacement}`);
  280. return true;
  281. }
  282. processExport() {
  283. if (
  284. this.tokens.matches2(tt._export, tt._enum) ||
  285. this.tokens.matches3(tt._export, tt._const, tt._enum)
  286. ) {
  287. this.hadNamedExport = true;
  288. // Let the TypeScript transform handle it.
  289. return false;
  290. }
  291. if (this.tokens.matches2(tt._export, tt._default)) {
  292. if (this.tokens.matches3(tt._export, tt._default, tt._enum)) {
  293. this.hadDefaultExport = true;
  294. // Flow export default enums need some special handling, so handle them
  295. // in that tranform rather than this one.
  296. return false;
  297. }
  298. this.processExportDefault();
  299. return true;
  300. } else if (this.tokens.matches2(tt._export, tt.braceL)) {
  301. this.processExportBindings();
  302. return true;
  303. } else if (
  304. this.tokens.matches2(tt._export, tt.name) &&
  305. this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, ContextualKeyword._type)
  306. ) {
  307. // export type {a};
  308. // export type {a as b};
  309. // export type {a} from './b';
  310. // export type * from './b';
  311. // export type * as ns from './b';
  312. this.tokens.removeInitialToken();
  313. this.tokens.removeToken();
  314. if (this.tokens.matches1(tt.braceL)) {
  315. while (!this.tokens.matches1(tt.braceR)) {
  316. this.tokens.removeToken();
  317. }
  318. this.tokens.removeToken();
  319. } else {
  320. // *
  321. this.tokens.removeToken();
  322. if (this.tokens.matches1(tt._as)) {
  323. // as
  324. this.tokens.removeToken();
  325. // ns
  326. this.tokens.removeToken();
  327. }
  328. }
  329. // Remove type re-export `... } from './T'`
  330. if (
  331. this.tokens.matchesContextual(ContextualKeyword._from) &&
  332. this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, tt.string)
  333. ) {
  334. this.tokens.removeToken();
  335. this.tokens.removeToken();
  336. removeMaybeImportAttributes(this.tokens);
  337. }
  338. return true;
  339. }
  340. this.hadNamedExport = true;
  341. if (
  342. this.tokens.matches2(tt._export, tt._var) ||
  343. this.tokens.matches2(tt._export, tt._let) ||
  344. this.tokens.matches2(tt._export, tt._const)
  345. ) {
  346. this.processExportVar();
  347. return true;
  348. } else if (
  349. this.tokens.matches2(tt._export, tt._function) ||
  350. // export async function
  351. this.tokens.matches3(tt._export, tt.name, tt._function)
  352. ) {
  353. this.processExportFunction();
  354. return true;
  355. } else if (
  356. this.tokens.matches2(tt._export, tt._class) ||
  357. this.tokens.matches3(tt._export, tt._abstract, tt._class) ||
  358. this.tokens.matches2(tt._export, tt.at)
  359. ) {
  360. this.processExportClass();
  361. return true;
  362. } else if (this.tokens.matches2(tt._export, tt.star)) {
  363. this.processExportStar();
  364. return true;
  365. } else {
  366. throw new Error("Unrecognized export syntax.");
  367. }
  368. }
  369. processAssignment() {
  370. const index = this.tokens.currentIndex();
  371. const identifierToken = this.tokens.tokens[index - 1];
  372. // If the LHS is a type identifier, this must be a declaration like `let a: b = c;`,
  373. // with `b` as the identifier, so nothing needs to be done in that case.
  374. if (identifierToken.isType || identifierToken.type !== tt.name) {
  375. return false;
  376. }
  377. if (identifierToken.shadowsGlobal) {
  378. return false;
  379. }
  380. if (index >= 2 && this.tokens.matches1AtIndex(index - 2, tt.dot)) {
  381. return false;
  382. }
  383. if (index >= 2 && [tt._var, tt._let, tt._const].includes(this.tokens.tokens[index - 2].type)) {
  384. // Declarations don't need an extra assignment. This doesn't avoid the
  385. // assignment for comma-separated declarations, but it's still correct
  386. // since the assignment is just redundant.
  387. return false;
  388. }
  389. const assignmentSnippet = this.importProcessor.resolveExportBinding(
  390. this.tokens.identifierNameForToken(identifierToken),
  391. );
  392. if (!assignmentSnippet) {
  393. return false;
  394. }
  395. this.tokens.copyToken();
  396. this.tokens.appendCode(` ${assignmentSnippet} =`);
  397. return true;
  398. }
  399. /**
  400. * Process something like `a += 3`, where `a` might be an exported value.
  401. */
  402. processComplexAssignment() {
  403. const index = this.tokens.currentIndex();
  404. const identifierToken = this.tokens.tokens[index - 1];
  405. if (identifierToken.type !== tt.name) {
  406. return false;
  407. }
  408. if (identifierToken.shadowsGlobal) {
  409. return false;
  410. }
  411. if (index >= 2 && this.tokens.matches1AtIndex(index - 2, tt.dot)) {
  412. return false;
  413. }
  414. const assignmentSnippet = this.importProcessor.resolveExportBinding(
  415. this.tokens.identifierNameForToken(identifierToken),
  416. );
  417. if (!assignmentSnippet) {
  418. return false;
  419. }
  420. this.tokens.appendCode(` = ${assignmentSnippet}`);
  421. this.tokens.copyToken();
  422. return true;
  423. }
  424. /**
  425. * Process something like `++a`, where `a` might be an exported value.
  426. */
  427. processPreIncDec() {
  428. const index = this.tokens.currentIndex();
  429. const identifierToken = this.tokens.tokens[index + 1];
  430. if (identifierToken.type !== tt.name) {
  431. return false;
  432. }
  433. if (identifierToken.shadowsGlobal) {
  434. return false;
  435. }
  436. // Ignore things like ++a.b and ++a[b] and ++a().b.
  437. if (
  438. index + 2 < this.tokens.tokens.length &&
  439. (this.tokens.matches1AtIndex(index + 2, tt.dot) ||
  440. this.tokens.matches1AtIndex(index + 2, tt.bracketL) ||
  441. this.tokens.matches1AtIndex(index + 2, tt.parenL))
  442. ) {
  443. return false;
  444. }
  445. const identifierName = this.tokens.identifierNameForToken(identifierToken);
  446. const assignmentSnippet = this.importProcessor.resolveExportBinding(identifierName);
  447. if (!assignmentSnippet) {
  448. return false;
  449. }
  450. this.tokens.appendCode(`${assignmentSnippet} = `);
  451. this.tokens.copyToken();
  452. return true;
  453. }
  454. /**
  455. * Process something like `a++`, where `a` might be an exported value.
  456. * This starts at the `a`, not at the `++`.
  457. */
  458. processPostIncDec() {
  459. const index = this.tokens.currentIndex();
  460. const identifierToken = this.tokens.tokens[index];
  461. const operatorToken = this.tokens.tokens[index + 1];
  462. if (identifierToken.type !== tt.name) {
  463. return false;
  464. }
  465. if (identifierToken.shadowsGlobal) {
  466. return false;
  467. }
  468. if (index >= 1 && this.tokens.matches1AtIndex(index - 1, tt.dot)) {
  469. return false;
  470. }
  471. const identifierName = this.tokens.identifierNameForToken(identifierToken);
  472. const assignmentSnippet = this.importProcessor.resolveExportBinding(identifierName);
  473. if (!assignmentSnippet) {
  474. return false;
  475. }
  476. const operatorCode = this.tokens.rawCodeForToken(operatorToken);
  477. // We might also replace the identifier with something like exports.x, so
  478. // do that replacement here as well.
  479. const base = this.importProcessor.getIdentifierReplacement(identifierName) || identifierName;
  480. if (operatorCode === "++") {
  481. this.tokens.replaceToken(`(${base} = ${assignmentSnippet} = ${base} + 1, ${base} - 1)`);
  482. } else if (operatorCode === "--") {
  483. this.tokens.replaceToken(`(${base} = ${assignmentSnippet} = ${base} - 1, ${base} + 1)`);
  484. } else {
  485. throw new Error(`Unexpected operator: ${operatorCode}`);
  486. }
  487. this.tokens.removeToken();
  488. return true;
  489. }
  490. processExportDefault() {
  491. let exportedRuntimeValue = true;
  492. if (
  493. this.tokens.matches4(tt._export, tt._default, tt._function, tt.name) ||
  494. // export default async function
  495. (this.tokens.matches5(tt._export, tt._default, tt.name, tt._function, tt.name) &&
  496. this.tokens.matchesContextualAtIndex(
  497. this.tokens.currentIndex() + 2,
  498. ContextualKeyword._async,
  499. ))
  500. ) {
  501. this.tokens.removeInitialToken();
  502. this.tokens.removeToken();
  503. // Named function export case: change it to a top-level function
  504. // declaration followed by exports statement.
  505. const name = this.processNamedFunction();
  506. this.tokens.appendCode(` exports.default = ${name};`);
  507. } else if (
  508. this.tokens.matches4(tt._export, tt._default, tt._class, tt.name) ||
  509. this.tokens.matches5(tt._export, tt._default, tt._abstract, tt._class, tt.name) ||
  510. this.tokens.matches3(tt._export, tt._default, tt.at)
  511. ) {
  512. this.tokens.removeInitialToken();
  513. this.tokens.removeToken();
  514. this.copyDecorators();
  515. if (this.tokens.matches1(tt._abstract)) {
  516. this.tokens.removeToken();
  517. }
  518. const name = this.rootTransformer.processNamedClass();
  519. this.tokens.appendCode(` exports.default = ${name};`);
  520. // After this point, this is a plain "export default E" statement.
  521. } else if (
  522. shouldElideDefaultExport(
  523. this.isTypeScriptTransformEnabled,
  524. this.keepUnusedImports,
  525. this.tokens,
  526. this.declarationInfo,
  527. )
  528. ) {
  529. // If the exported value is just an identifier and should be elided by TypeScript
  530. // rules, then remove it entirely. It will always have the form `export default e`,
  531. // where `e` is an identifier.
  532. exportedRuntimeValue = false;
  533. this.tokens.removeInitialToken();
  534. this.tokens.removeToken();
  535. this.tokens.removeToken();
  536. } else if (this.reactHotLoaderTransformer) {
  537. // We need to assign E to a variable. Change "export default E" to
  538. // "let _default; exports.default = _default = E"
  539. const defaultVarName = this.nameManager.claimFreeName("_default");
  540. this.tokens.replaceToken(`let ${defaultVarName}; exports.`);
  541. this.tokens.copyToken();
  542. this.tokens.appendCode(` = ${defaultVarName} =`);
  543. this.reactHotLoaderTransformer.setExtractedDefaultExportName(defaultVarName);
  544. } else {
  545. // Change "export default E" to "exports.default = E"
  546. this.tokens.replaceToken("exports.");
  547. this.tokens.copyToken();
  548. this.tokens.appendCode(" =");
  549. }
  550. if (exportedRuntimeValue) {
  551. this.hadDefaultExport = true;
  552. }
  553. }
  554. copyDecorators() {
  555. while (this.tokens.matches1(tt.at)) {
  556. this.tokens.copyToken();
  557. if (this.tokens.matches1(tt.parenL)) {
  558. this.tokens.copyExpectedToken(tt.parenL);
  559. this.rootTransformer.processBalancedCode();
  560. this.tokens.copyExpectedToken(tt.parenR);
  561. } else {
  562. this.tokens.copyExpectedToken(tt.name);
  563. while (this.tokens.matches1(tt.dot)) {
  564. this.tokens.copyExpectedToken(tt.dot);
  565. this.tokens.copyExpectedToken(tt.name);
  566. }
  567. if (this.tokens.matches1(tt.parenL)) {
  568. this.tokens.copyExpectedToken(tt.parenL);
  569. this.rootTransformer.processBalancedCode();
  570. this.tokens.copyExpectedToken(tt.parenR);
  571. }
  572. }
  573. }
  574. }
  575. /**
  576. * Transform a declaration like `export var`, `export let`, or `export const`.
  577. */
  578. processExportVar() {
  579. if (this.isSimpleExportVar()) {
  580. this.processSimpleExportVar();
  581. } else {
  582. this.processComplexExportVar();
  583. }
  584. }
  585. /**
  586. * Determine if the export is of the form:
  587. * export var/let/const [varName] = [expr];
  588. * In other words, determine if function name inference might apply.
  589. */
  590. isSimpleExportVar() {
  591. let tokenIndex = this.tokens.currentIndex();
  592. // export
  593. tokenIndex++;
  594. // var/let/const
  595. tokenIndex++;
  596. if (!this.tokens.matches1AtIndex(tokenIndex, tt.name)) {
  597. return false;
  598. }
  599. tokenIndex++;
  600. while (tokenIndex < this.tokens.tokens.length && this.tokens.tokens[tokenIndex].isType) {
  601. tokenIndex++;
  602. }
  603. if (!this.tokens.matches1AtIndex(tokenIndex, tt.eq)) {
  604. return false;
  605. }
  606. return true;
  607. }
  608. /**
  609. * Transform an `export var` declaration initializing a single variable.
  610. *
  611. * For example, this:
  612. * export const f = () => {};
  613. * becomes this:
  614. * const f = () => {}; exports.f = f;
  615. *
  616. * The variable is unused (e.g. exports.f has the true value of the export).
  617. * We need to produce an assignment of this form so that the function will
  618. * have an inferred name of "f", which wouldn't happen in the more general
  619. * case below.
  620. */
  621. processSimpleExportVar() {
  622. // export
  623. this.tokens.removeInitialToken();
  624. // var/let/const
  625. this.tokens.copyToken();
  626. const varName = this.tokens.identifierName();
  627. // x: number -> x
  628. while (!this.tokens.matches1(tt.eq)) {
  629. this.rootTransformer.processToken();
  630. }
  631. const endIndex = this.tokens.currentToken().rhsEndIndex;
  632. if (endIndex == null) {
  633. throw new Error("Expected = token with an end index.");
  634. }
  635. while (this.tokens.currentIndex() < endIndex) {
  636. this.rootTransformer.processToken();
  637. }
  638. this.tokens.appendCode(`; exports.${varName} = ${varName}`);
  639. }
  640. /**
  641. * Transform normal declaration exports, including handling destructuring.
  642. * For example, this:
  643. * export const {x: [a = 2, b], c} = d;
  644. * becomes this:
  645. * ({x: [exports.a = 2, exports.b], c: exports.c} = d;)
  646. */
  647. processComplexExportVar() {
  648. this.tokens.removeInitialToken();
  649. this.tokens.removeToken();
  650. const needsParens = this.tokens.matches1(tt.braceL);
  651. if (needsParens) {
  652. this.tokens.appendCode("(");
  653. }
  654. let depth = 0;
  655. while (true) {
  656. if (
  657. this.tokens.matches1(tt.braceL) ||
  658. this.tokens.matches1(tt.dollarBraceL) ||
  659. this.tokens.matches1(tt.bracketL)
  660. ) {
  661. depth++;
  662. this.tokens.copyToken();
  663. } else if (this.tokens.matches1(tt.braceR) || this.tokens.matches1(tt.bracketR)) {
  664. depth--;
  665. this.tokens.copyToken();
  666. } else if (
  667. depth === 0 &&
  668. !this.tokens.matches1(tt.name) &&
  669. !this.tokens.currentToken().isType
  670. ) {
  671. break;
  672. } else if (this.tokens.matches1(tt.eq)) {
  673. // Default values might have assignments in the RHS that we want to ignore, so skip past
  674. // them.
  675. const endIndex = this.tokens.currentToken().rhsEndIndex;
  676. if (endIndex == null) {
  677. throw new Error("Expected = token with an end index.");
  678. }
  679. while (this.tokens.currentIndex() < endIndex) {
  680. this.rootTransformer.processToken();
  681. }
  682. } else {
  683. const token = this.tokens.currentToken();
  684. if (isDeclaration(token)) {
  685. const name = this.tokens.identifierName();
  686. let replacement = this.importProcessor.getIdentifierReplacement(name);
  687. if (replacement === null) {
  688. throw new Error(`Expected a replacement for ${name} in \`export var\` syntax.`);
  689. }
  690. if (isObjectShorthandDeclaration(token)) {
  691. replacement = `${name}: ${replacement}`;
  692. }
  693. this.tokens.replaceToken(replacement);
  694. } else {
  695. this.rootTransformer.processToken();
  696. }
  697. }
  698. }
  699. if (needsParens) {
  700. // Seek to the end of the RHS.
  701. const endIndex = this.tokens.currentToken().rhsEndIndex;
  702. if (endIndex == null) {
  703. throw new Error("Expected = token with an end index.");
  704. }
  705. while (this.tokens.currentIndex() < endIndex) {
  706. this.rootTransformer.processToken();
  707. }
  708. this.tokens.appendCode(")");
  709. }
  710. }
  711. /**
  712. * Transform this:
  713. * export function foo() {}
  714. * into this:
  715. * function foo() {} exports.foo = foo;
  716. */
  717. processExportFunction() {
  718. this.tokens.replaceToken("");
  719. const name = this.processNamedFunction();
  720. this.tokens.appendCode(` exports.${name} = ${name};`);
  721. }
  722. /**
  723. * Skip past a function with a name and return that name.
  724. */
  725. processNamedFunction() {
  726. if (this.tokens.matches1(tt._function)) {
  727. this.tokens.copyToken();
  728. } else if (this.tokens.matches2(tt.name, tt._function)) {
  729. if (!this.tokens.matchesContextual(ContextualKeyword._async)) {
  730. throw new Error("Expected async keyword in function export.");
  731. }
  732. this.tokens.copyToken();
  733. this.tokens.copyToken();
  734. }
  735. if (this.tokens.matches1(tt.star)) {
  736. this.tokens.copyToken();
  737. }
  738. if (!this.tokens.matches1(tt.name)) {
  739. throw new Error("Expected identifier for exported function name.");
  740. }
  741. const name = this.tokens.identifierName();
  742. this.tokens.copyToken();
  743. if (this.tokens.currentToken().isType) {
  744. this.tokens.removeInitialToken();
  745. while (this.tokens.currentToken().isType) {
  746. this.tokens.removeToken();
  747. }
  748. }
  749. this.tokens.copyExpectedToken(tt.parenL);
  750. this.rootTransformer.processBalancedCode();
  751. this.tokens.copyExpectedToken(tt.parenR);
  752. this.rootTransformer.processPossibleTypeRange();
  753. this.tokens.copyExpectedToken(tt.braceL);
  754. this.rootTransformer.processBalancedCode();
  755. this.tokens.copyExpectedToken(tt.braceR);
  756. return name;
  757. }
  758. /**
  759. * Transform this:
  760. * export class A {}
  761. * into this:
  762. * class A {} exports.A = A;
  763. */
  764. processExportClass() {
  765. this.tokens.removeInitialToken();
  766. this.copyDecorators();
  767. if (this.tokens.matches1(tt._abstract)) {
  768. this.tokens.removeToken();
  769. }
  770. const name = this.rootTransformer.processNamedClass();
  771. this.tokens.appendCode(` exports.${name} = ${name};`);
  772. }
  773. /**
  774. * Transform this:
  775. * export {a, b as c};
  776. * into this:
  777. * exports.a = a; exports.c = b;
  778. *
  779. * OR
  780. *
  781. * Transform this:
  782. * export {a, b as c} from './foo';
  783. * into the pre-generated Object.defineProperty code from the ImportProcessor.
  784. *
  785. * For the first case, if the TypeScript transform is enabled, we need to skip
  786. * exports that are only defined as types.
  787. */
  788. processExportBindings() {
  789. this.tokens.removeInitialToken();
  790. this.tokens.removeToken();
  791. const isReExport = isExportFrom(this.tokens);
  792. const exportStatements = [];
  793. while (true) {
  794. if (this.tokens.matches1(tt.braceR)) {
  795. this.tokens.removeToken();
  796. break;
  797. }
  798. const specifierInfo = getImportExportSpecifierInfo(this.tokens);
  799. while (this.tokens.currentIndex() < specifierInfo.endIndex) {
  800. this.tokens.removeToken();
  801. }
  802. const shouldRemoveExport =
  803. specifierInfo.isType ||
  804. (!isReExport && this.shouldElideExportedIdentifier(specifierInfo.leftName));
  805. if (!shouldRemoveExport) {
  806. const exportedName = specifierInfo.rightName;
  807. if (exportedName === "default") {
  808. this.hadDefaultExport = true;
  809. } else {
  810. this.hadNamedExport = true;
  811. }
  812. const localName = specifierInfo.leftName;
  813. const newLocalName = this.importProcessor.getIdentifierReplacement(localName);
  814. exportStatements.push(`exports.${exportedName} = ${newLocalName || localName};`);
  815. }
  816. if (this.tokens.matches1(tt.braceR)) {
  817. this.tokens.removeToken();
  818. break;
  819. }
  820. if (this.tokens.matches2(tt.comma, tt.braceR)) {
  821. this.tokens.removeToken();
  822. this.tokens.removeToken();
  823. break;
  824. } else if (this.tokens.matches1(tt.comma)) {
  825. this.tokens.removeToken();
  826. } else {
  827. throw new Error(`Unexpected token: ${JSON.stringify(this.tokens.currentToken())}`);
  828. }
  829. }
  830. if (this.tokens.matchesContextual(ContextualKeyword._from)) {
  831. // This is an export...from, so throw away the normal named export code
  832. // and use the Object.defineProperty code from ImportProcessor.
  833. this.tokens.removeToken();
  834. const path = this.tokens.stringValue();
  835. this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path));
  836. removeMaybeImportAttributes(this.tokens);
  837. } else {
  838. // This is a normal named export, so use that.
  839. this.tokens.appendCode(exportStatements.join(" "));
  840. }
  841. if (this.tokens.matches1(tt.semi)) {
  842. this.tokens.removeToken();
  843. }
  844. }
  845. processExportStar() {
  846. this.tokens.removeInitialToken();
  847. while (!this.tokens.matches1(tt.string)) {
  848. this.tokens.removeToken();
  849. }
  850. const path = this.tokens.stringValue();
  851. this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path));
  852. removeMaybeImportAttributes(this.tokens);
  853. if (this.tokens.matches1(tt.semi)) {
  854. this.tokens.removeToken();
  855. }
  856. }
  857. shouldElideExportedIdentifier(name) {
  858. return (
  859. this.isTypeScriptTransformEnabled &&
  860. !this.keepUnusedImports &&
  861. !this.declarationInfo.valueDeclarations.has(name)
  862. );
  863. }
  864. }