No Description

ESMImportTransformer.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  2. var _keywords = require('../parser/tokenizer/keywords');
  3. var _types = require('../parser/tokenizer/types');
  4. var _elideImportEquals = require('../util/elideImportEquals'); var _elideImportEquals2 = _interopRequireDefault(_elideImportEquals);
  5. var _getDeclarationInfo = require('../util/getDeclarationInfo'); var _getDeclarationInfo2 = _interopRequireDefault(_getDeclarationInfo);
  6. var _getImportExportSpecifierInfo = require('../util/getImportExportSpecifierInfo'); var _getImportExportSpecifierInfo2 = _interopRequireDefault(_getImportExportSpecifierInfo);
  7. var _getNonTypeIdentifiers = require('../util/getNonTypeIdentifiers');
  8. var _removeMaybeImportAssertion = require('../util/removeMaybeImportAssertion');
  9. var _shouldElideDefaultExport = require('../util/shouldElideDefaultExport'); var _shouldElideDefaultExport2 = _interopRequireDefault(_shouldElideDefaultExport);
  10. var _Transformer = require('./Transformer'); var _Transformer2 = _interopRequireDefault(_Transformer);
  11. /**
  12. * Class for editing import statements when we are keeping the code as ESM. We still need to remove
  13. * type-only imports in TypeScript and Flow.
  14. */
  15. class ESMImportTransformer extends _Transformer2.default {
  16. constructor(
  17. tokens,
  18. nameManager,
  19. helperManager,
  20. reactHotLoaderTransformer,
  21. isTypeScriptTransformEnabled,
  22. options,
  23. ) {
  24. super();this.tokens = tokens;this.nameManager = nameManager;this.helperManager = helperManager;this.reactHotLoaderTransformer = reactHotLoaderTransformer;this.isTypeScriptTransformEnabled = isTypeScriptTransformEnabled;;
  25. this.nonTypeIdentifiers = isTypeScriptTransformEnabled
  26. ? _getNonTypeIdentifiers.getNonTypeIdentifiers.call(void 0, tokens, options)
  27. : new Set();
  28. this.declarationInfo = isTypeScriptTransformEnabled
  29. ? _getDeclarationInfo2.default.call(void 0, tokens)
  30. : _getDeclarationInfo.EMPTY_DECLARATION_INFO;
  31. this.injectCreateRequireForImportRequire = Boolean(options.injectCreateRequireForImportRequire);
  32. }
  33. process() {
  34. // TypeScript `import foo = require('foo');` should always just be translated to plain require.
  35. if (this.tokens.matches3(_types.TokenType._import, _types.TokenType.name, _types.TokenType.eq)) {
  36. return this.processImportEquals();
  37. }
  38. if (
  39. this.tokens.matches4(_types.TokenType._import, _types.TokenType.name, _types.TokenType.name, _types.TokenType.eq) &&
  40. this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, _keywords.ContextualKeyword._type)
  41. ) {
  42. // import type T = require('T')
  43. this.tokens.removeInitialToken();
  44. // This construct is always exactly 8 tokens long, so remove the 7 remaining tokens.
  45. for (let i = 0; i < 7; i++) {
  46. this.tokens.removeToken();
  47. }
  48. return true;
  49. }
  50. if (this.tokens.matches2(_types.TokenType._export, _types.TokenType.eq)) {
  51. this.tokens.replaceToken("module.exports");
  52. return true;
  53. }
  54. if (
  55. this.tokens.matches5(_types.TokenType._export, _types.TokenType._import, _types.TokenType.name, _types.TokenType.name, _types.TokenType.eq) &&
  56. this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 2, _keywords.ContextualKeyword._type)
  57. ) {
  58. // export import type T = require('T')
  59. this.tokens.removeInitialToken();
  60. // This construct is always exactly 9 tokens long, so remove the 8 remaining tokens.
  61. for (let i = 0; i < 8; i++) {
  62. this.tokens.removeToken();
  63. }
  64. return true;
  65. }
  66. if (this.tokens.matches1(_types.TokenType._import)) {
  67. return this.processImport();
  68. }
  69. if (this.tokens.matches2(_types.TokenType._export, _types.TokenType._default)) {
  70. return this.processExportDefault();
  71. }
  72. if (this.tokens.matches2(_types.TokenType._export, _types.TokenType.braceL)) {
  73. return this.processNamedExports();
  74. }
  75. if (
  76. this.tokens.matches2(_types.TokenType._export, _types.TokenType.name) &&
  77. this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, _keywords.ContextualKeyword._type)
  78. ) {
  79. // export type {a};
  80. // export type {a as b};
  81. // export type {a} from './b';
  82. // export type * from './b';
  83. // export type * as ns from './b';
  84. this.tokens.removeInitialToken();
  85. this.tokens.removeToken();
  86. if (this.tokens.matches1(_types.TokenType.braceL)) {
  87. while (!this.tokens.matches1(_types.TokenType.braceR)) {
  88. this.tokens.removeToken();
  89. }
  90. this.tokens.removeToken();
  91. } else {
  92. // *
  93. this.tokens.removeToken();
  94. if (this.tokens.matches1(_types.TokenType._as)) {
  95. // as
  96. this.tokens.removeToken();
  97. // ns
  98. this.tokens.removeToken();
  99. }
  100. }
  101. // Remove type re-export `... } from './T'`
  102. if (
  103. this.tokens.matchesContextual(_keywords.ContextualKeyword._from) &&
  104. this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, _types.TokenType.string)
  105. ) {
  106. this.tokens.removeToken();
  107. this.tokens.removeToken();
  108. _removeMaybeImportAssertion.removeMaybeImportAssertion.call(void 0, this.tokens);
  109. }
  110. return true;
  111. }
  112. return false;
  113. }
  114. processImportEquals() {
  115. const importName = this.tokens.identifierNameAtIndex(this.tokens.currentIndex() + 1);
  116. if (this.isTypeName(importName)) {
  117. // If this name is only used as a type, elide the whole import.
  118. _elideImportEquals2.default.call(void 0, this.tokens);
  119. } else if (this.injectCreateRequireForImportRequire) {
  120. // We're using require in an environment (Node ESM) that doesn't provide
  121. // it as a global, so generate a helper to import it.
  122. // import -> const
  123. this.tokens.replaceToken("const");
  124. // Foo
  125. this.tokens.copyToken();
  126. // =
  127. this.tokens.copyToken();
  128. // require
  129. this.tokens.replaceToken(this.helperManager.getHelperName("require"));
  130. } else {
  131. // Otherwise, just switch `import` to `const`.
  132. this.tokens.replaceToken("const");
  133. }
  134. return true;
  135. }
  136. processImport() {
  137. if (this.tokens.matches2(_types.TokenType._import, _types.TokenType.parenL)) {
  138. // Dynamic imports don't need to be transformed.
  139. return false;
  140. }
  141. const snapshot = this.tokens.snapshot();
  142. const allImportsRemoved = this.removeImportTypeBindings();
  143. if (allImportsRemoved) {
  144. this.tokens.restoreToSnapshot(snapshot);
  145. while (!this.tokens.matches1(_types.TokenType.string)) {
  146. this.tokens.removeToken();
  147. }
  148. this.tokens.removeToken();
  149. _removeMaybeImportAssertion.removeMaybeImportAssertion.call(void 0, this.tokens);
  150. if (this.tokens.matches1(_types.TokenType.semi)) {
  151. this.tokens.removeToken();
  152. }
  153. }
  154. return true;
  155. }
  156. /**
  157. * Remove type bindings from this import, leaving the rest of the import intact.
  158. *
  159. * Return true if this import was ONLY types, and thus is eligible for removal. This will bail out
  160. * of the replacement operation, so we can return early here.
  161. */
  162. removeImportTypeBindings() {
  163. this.tokens.copyExpectedToken(_types.TokenType._import);
  164. if (
  165. this.tokens.matchesContextual(_keywords.ContextualKeyword._type) &&
  166. !this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, _types.TokenType.comma) &&
  167. !this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, _keywords.ContextualKeyword._from)
  168. ) {
  169. // This is an "import type" statement, so exit early.
  170. return true;
  171. }
  172. if (this.tokens.matches1(_types.TokenType.string)) {
  173. // This is a bare import, so we should proceed with the import.
  174. this.tokens.copyToken();
  175. return false;
  176. }
  177. // Skip the "module" token in import reflection.
  178. if (
  179. this.tokens.matchesContextual(_keywords.ContextualKeyword._module) &&
  180. this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 2, _keywords.ContextualKeyword._from)
  181. ) {
  182. this.tokens.copyToken();
  183. }
  184. let foundNonTypeImport = false;
  185. let needsComma = false;
  186. if (this.tokens.matches1(_types.TokenType.name)) {
  187. if (this.isTypeName(this.tokens.identifierName())) {
  188. this.tokens.removeToken();
  189. if (this.tokens.matches1(_types.TokenType.comma)) {
  190. this.tokens.removeToken();
  191. }
  192. } else {
  193. foundNonTypeImport = true;
  194. this.tokens.copyToken();
  195. if (this.tokens.matches1(_types.TokenType.comma)) {
  196. // We're in a statement like:
  197. // import A, * as B from './A';
  198. // or
  199. // import A, {foo} from './A';
  200. // where the `A` is being kept. The comma should be removed if an only
  201. // if the next part of the import statement is elided, but that's hard
  202. // to determine at this point in the code. Instead, always remove it
  203. // and set a flag to add it back if necessary.
  204. needsComma = true;
  205. this.tokens.removeToken();
  206. }
  207. }
  208. }
  209. if (this.tokens.matches1(_types.TokenType.star)) {
  210. if (this.isTypeName(this.tokens.identifierNameAtRelativeIndex(2))) {
  211. this.tokens.removeToken();
  212. this.tokens.removeToken();
  213. this.tokens.removeToken();
  214. } else {
  215. if (needsComma) {
  216. this.tokens.appendCode(",");
  217. }
  218. foundNonTypeImport = true;
  219. this.tokens.copyExpectedToken(_types.TokenType.star);
  220. this.tokens.copyExpectedToken(_types.TokenType.name);
  221. this.tokens.copyExpectedToken(_types.TokenType.name);
  222. }
  223. } else if (this.tokens.matches1(_types.TokenType.braceL)) {
  224. if (needsComma) {
  225. this.tokens.appendCode(",");
  226. }
  227. this.tokens.copyToken();
  228. while (!this.tokens.matches1(_types.TokenType.braceR)) {
  229. const specifierInfo = _getImportExportSpecifierInfo2.default.call(void 0, this.tokens);
  230. if (specifierInfo.isType || this.isTypeName(specifierInfo.rightName)) {
  231. while (this.tokens.currentIndex() < specifierInfo.endIndex) {
  232. this.tokens.removeToken();
  233. }
  234. if (this.tokens.matches1(_types.TokenType.comma)) {
  235. this.tokens.removeToken();
  236. }
  237. } else {
  238. foundNonTypeImport = true;
  239. while (this.tokens.currentIndex() < specifierInfo.endIndex) {
  240. this.tokens.copyToken();
  241. }
  242. if (this.tokens.matches1(_types.TokenType.comma)) {
  243. this.tokens.copyToken();
  244. }
  245. }
  246. }
  247. this.tokens.copyExpectedToken(_types.TokenType.braceR);
  248. }
  249. return !foundNonTypeImport;
  250. }
  251. isTypeName(name) {
  252. return this.isTypeScriptTransformEnabled && !this.nonTypeIdentifiers.has(name);
  253. }
  254. processExportDefault() {
  255. if (
  256. _shouldElideDefaultExport2.default.call(void 0, this.isTypeScriptTransformEnabled, this.tokens, this.declarationInfo)
  257. ) {
  258. // If the exported value is just an identifier and should be elided by TypeScript
  259. // rules, then remove it entirely. It will always have the form `export default e`,
  260. // where `e` is an identifier.
  261. this.tokens.removeInitialToken();
  262. this.tokens.removeToken();
  263. this.tokens.removeToken();
  264. return true;
  265. }
  266. const alreadyHasName =
  267. this.tokens.matches4(_types.TokenType._export, _types.TokenType._default, _types.TokenType._function, _types.TokenType.name) ||
  268. // export default async function
  269. (this.tokens.matches5(_types.TokenType._export, _types.TokenType._default, _types.TokenType.name, _types.TokenType._function, _types.TokenType.name) &&
  270. this.tokens.matchesContextualAtIndex(
  271. this.tokens.currentIndex() + 2,
  272. _keywords.ContextualKeyword._async,
  273. )) ||
  274. this.tokens.matches4(_types.TokenType._export, _types.TokenType._default, _types.TokenType._class, _types.TokenType.name) ||
  275. this.tokens.matches5(_types.TokenType._export, _types.TokenType._default, _types.TokenType._abstract, _types.TokenType._class, _types.TokenType.name);
  276. if (!alreadyHasName && this.reactHotLoaderTransformer) {
  277. // This is a plain "export default E" statement and we need to assign E to a variable.
  278. // Change "export default E" to "let _default; export default _default = E"
  279. const defaultVarName = this.nameManager.claimFreeName("_default");
  280. this.tokens.replaceToken(`let ${defaultVarName}; export`);
  281. this.tokens.copyToken();
  282. this.tokens.appendCode(` ${defaultVarName} =`);
  283. this.reactHotLoaderTransformer.setExtractedDefaultExportName(defaultVarName);
  284. return true;
  285. }
  286. return false;
  287. }
  288. /**
  289. * In TypeScript, we need to remove named exports that were never declared or only declared as a
  290. * type.
  291. */
  292. processNamedExports() {
  293. if (!this.isTypeScriptTransformEnabled) {
  294. return false;
  295. }
  296. this.tokens.copyExpectedToken(_types.TokenType._export);
  297. this.tokens.copyExpectedToken(_types.TokenType.braceL);
  298. while (!this.tokens.matches1(_types.TokenType.braceR)) {
  299. const specifierInfo = _getImportExportSpecifierInfo2.default.call(void 0, this.tokens);
  300. if (specifierInfo.isType || this.shouldElideExportedName(specifierInfo.leftName)) {
  301. // Type export, so remove all tokens, including any comma.
  302. while (this.tokens.currentIndex() < specifierInfo.endIndex) {
  303. this.tokens.removeToken();
  304. }
  305. if (this.tokens.matches1(_types.TokenType.comma)) {
  306. this.tokens.removeToken();
  307. }
  308. } else {
  309. // Non-type export, so copy all tokens, including any comma.
  310. while (this.tokens.currentIndex() < specifierInfo.endIndex) {
  311. this.tokens.copyToken();
  312. }
  313. if (this.tokens.matches1(_types.TokenType.comma)) {
  314. this.tokens.copyToken();
  315. }
  316. }
  317. }
  318. this.tokens.copyExpectedToken(_types.TokenType.braceR);
  319. return true;
  320. }
  321. /**
  322. * ESM elides all imports with the rule that we only elide if we see that it's
  323. * a type and never see it as a value. This is in contrast to CJS, which
  324. * elides imports that are completely unknown.
  325. */
  326. shouldElideExportedName(name) {
  327. return (
  328. this.isTypeScriptTransformEnabled &&
  329. this.declarationInfo.typeDeclarations.has(name) &&
  330. !this.declarationInfo.valueDeclarations.has(name)
  331. );
  332. }
  333. } exports.default = ESMImportTransformer;