babel-plugin-transform-modules-commonjs-ec.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. /**
  2. * Both used by zrender and echarts.
  3. */
  4. const assert = require('assert');
  5. const nodePath = require('path');
  6. const basename = nodePath.basename;
  7. const extname = nodePath.extname;
  8. const babelTypes = require('@babel/types');
  9. const babelTemplate = require('@babel/template');
  10. const helperModuleTransforms = require('@babel/helper-module-transforms');
  11. const isModule = helperModuleTransforms.isModule;
  12. const isSideEffectImport = helperModuleTransforms.isSideEffectImport;
  13. const ensureStatementsHoisted = helperModuleTransforms.ensureStatementsHoisted;
  14. module.exports = function ({types, template}, options) {
  15. return {
  16. visitor: {
  17. Program: {
  18. exit(path) {
  19. // For now this requires unambiguous rather that just sourceType
  20. // because Babel currently parses all files as sourceType:module.
  21. if (!isModule(path, true /* requireUnambiguous */)) {
  22. return;
  23. }
  24. // Rename the bindings auto-injected into the scope so there is no
  25. // risk of conflict between the bindings.
  26. path.scope.rename('exports');
  27. path.scope.rename('module');
  28. path.scope.rename('require');
  29. path.scope.rename('__filename');
  30. path.scope.rename('__dirname');
  31. const meta = rewriteModuleStatementsAndPrepare(path);
  32. let headers = [];
  33. let tails = [];
  34. const checkExport = createExportChecker();
  35. for (const [source, metadata] of meta.source) {
  36. headers.push(...buildRequireStatements(types, source, metadata));
  37. headers.push(...buildNamespaceInitStatements(meta, metadata, checkExport));
  38. }
  39. tails.push(...buildLocalExportStatements(meta, checkExport));
  40. ensureStatementsHoisted(headers);
  41. // FIXME ensure tail?
  42. path.unshiftContainer('body', headers);
  43. path.pushContainer('body', tails);
  44. checkAssignOrUpdateExport(path, meta);
  45. }
  46. }
  47. }
  48. };
  49. };
  50. /**
  51. * Remove all imports and exports from the file, and return all metadata
  52. * needed to reconstruct the module's behavior.
  53. * @return {ModuleMetadata}
  54. */
  55. function normalizeModuleAndLoadMetadata(programPath) {
  56. nameAnonymousExports(programPath);
  57. const {local, source} = getModuleMetadata(programPath);
  58. removeModuleDeclarations(programPath);
  59. // Reuse the imported namespace name if there is one.
  60. for (const [, metadata] of source) {
  61. if (metadata.importsNamespace.size > 0) {
  62. // This is kind of gross. If we stop using `loose: true` we should
  63. // just make this destructuring assignment.
  64. metadata.name = metadata.importsNamespace.values().next().value;
  65. }
  66. }
  67. return {
  68. exportName: 'exports',
  69. exportNameListName: null,
  70. local,
  71. source
  72. };
  73. }
  74. /**
  75. * Get metadata about the imports and exports present in this module.
  76. */
  77. function getModuleMetadata(programPath) {
  78. const localData = getLocalExportMetadata(programPath);
  79. const sourceData = new Map();
  80. const getData = sourceNode => {
  81. const source = sourceNode.value;
  82. let data = sourceData.get(source);
  83. if (!data) {
  84. data = {
  85. name: programPath.scope.generateUidIdentifier(
  86. basename(source, extname(source))
  87. ).name,
  88. interop: 'none',
  89. loc: null,
  90. // Data about the requested sources and names.
  91. imports: new Map(),
  92. // importsNamespace: import * as util from './a/b/util';
  93. importsNamespace: new Set(),
  94. // Metadata about data that is passed directly from source to export.
  95. reexports: new Map(),
  96. reexportNamespace: new Set(),
  97. reexportAll: null,
  98. };
  99. sourceData.set(source, data);
  100. }
  101. return data;
  102. };
  103. programPath.get('body').forEach(child => {
  104. if (child.isImportDeclaration()) {
  105. const data = getData(child.node.source);
  106. if (!data.loc) {
  107. data.loc = child.node.loc;
  108. }
  109. child.get('specifiers').forEach(spec => {
  110. if (spec.isImportDefaultSpecifier()) {
  111. const localName = spec.get('local').node.name;
  112. data.imports.set(localName, 'default');
  113. const reexport = localData.get(localName);
  114. if (reexport) {
  115. localData.delete(localName);
  116. reexport.names.forEach(name => {
  117. data.reexports.set(name, 'default');
  118. });
  119. }
  120. }
  121. else if (spec.isImportNamespaceSpecifier()) {
  122. const localName = spec.get('local').node.name;
  123. assert(
  124. data.importsNamespace.size === 0,
  125. `Duplicate import namespace: ${localName}`
  126. );
  127. data.importsNamespace.add(localName);
  128. const reexport = localData.get(localName);
  129. if (reexport) {
  130. localData.delete(localName);
  131. reexport.names.forEach(name => {
  132. data.reexportNamespace.add(name);
  133. });
  134. }
  135. }
  136. else if (spec.isImportSpecifier()) {
  137. const importName = spec.get('imported').node.name;
  138. const localName = spec.get('local').node.name;
  139. data.imports.set(localName, importName);
  140. const reexport = localData.get(localName);
  141. if (reexport) {
  142. localData.delete(localName);
  143. reexport.names.forEach(name => {
  144. data.reexports.set(name, importName);
  145. });
  146. }
  147. }
  148. });
  149. }
  150. else if (child.isExportAllDeclaration()) {
  151. const data = getData(child.node.source);
  152. if (!data.loc) {
  153. data.loc = child.node.loc;
  154. }
  155. data.reexportAll = {
  156. loc: child.node.loc,
  157. };
  158. }
  159. else if (child.isExportNamedDeclaration() && child.node.source) {
  160. const data = getData(child.node.source);
  161. if (!data.loc) {
  162. data.loc = child.node.loc;
  163. }
  164. child.get('specifiers').forEach(spec => {
  165. if (!spec.isExportSpecifier()) {
  166. throw spec.buildCodeFrameError('Unexpected export specifier type');
  167. }
  168. const importName = spec.get('local').node.name;
  169. const exportName = spec.get('exported').node.name;
  170. data.reexports.set(exportName, importName);
  171. if (exportName === '__esModule') {
  172. throw exportName.buildCodeFrameError('Illegal export "__esModule".');
  173. }
  174. });
  175. }
  176. });
  177. for (const metadata of sourceData.values()) {
  178. if (metadata.importsNamespace.size > 0) {
  179. metadata.interop = 'namespace';
  180. continue;
  181. }
  182. let needsDefault = false;
  183. let needsNamed = false;
  184. for (const importName of metadata.imports.values()) {
  185. if (importName === 'default') {
  186. needsDefault = true;
  187. }
  188. else {
  189. needsNamed = true;
  190. }
  191. }
  192. for (const importName of metadata.reexports.values()) {
  193. if (importName === 'default') {
  194. needsDefault = true;
  195. }
  196. else {
  197. needsNamed = true;
  198. }
  199. }
  200. if (needsDefault && needsNamed) {
  201. // TODO(logan): Using the namespace interop here is unfortunate. Revisit.
  202. metadata.interop = 'namespace';
  203. }
  204. else if (needsDefault) {
  205. metadata.interop = 'default';
  206. }
  207. }
  208. return {
  209. local: localData,
  210. source: sourceData,
  211. };
  212. }
  213. /**
  214. * Get metadata about local variables that are exported.
  215. * @return {Map<string, LocalExportMetadata>}
  216. */
  217. function getLocalExportMetadata(programPath){
  218. const bindingKindLookup = new Map();
  219. programPath.get('body').forEach(child => {
  220. let kind;
  221. if (child.isImportDeclaration()) {
  222. kind = 'import';
  223. }
  224. else {
  225. if (child.isExportDefaultDeclaration()) {
  226. child = child.get('declaration');
  227. }
  228. if (child.isExportNamedDeclaration() && child.node.declaration) {
  229. child = child.get('declaration');
  230. }
  231. if (child.isFunctionDeclaration()) {
  232. kind = 'hoisted';
  233. }
  234. else if (child.isClassDeclaration()) {
  235. kind = 'block';
  236. }
  237. else if (child.isVariableDeclaration({ kind: 'var' })) {
  238. kind = 'var';
  239. }
  240. else if (child.isVariableDeclaration()) {
  241. kind = 'block';
  242. }
  243. else {
  244. return;
  245. }
  246. }
  247. Object.keys(child.getOuterBindingIdentifiers()).forEach(name => {
  248. bindingKindLookup.set(name, kind);
  249. });
  250. });
  251. const localMetadata = new Map();
  252. const getLocalMetadata = idPath => {
  253. const localName = idPath.node.name;
  254. let metadata = localMetadata.get(localName);
  255. if (!metadata) {
  256. const kind = bindingKindLookup.get(localName);
  257. if (kind === undefined) {
  258. throw idPath.buildCodeFrameError(`Exporting local "${localName}", which is not declared.`);
  259. }
  260. metadata = {
  261. names: [],
  262. kind,
  263. };
  264. localMetadata.set(localName, metadata);
  265. }
  266. return metadata;
  267. };
  268. programPath.get('body').forEach(child => {
  269. if (child.isExportNamedDeclaration() && !child.node.source) {
  270. if (child.node.declaration) {
  271. const declaration = child.get('declaration');
  272. const ids = declaration.getOuterBindingIdentifierPaths();
  273. Object.keys(ids).forEach(name => {
  274. if (name === '__esModule') {
  275. throw declaration.buildCodeFrameError('Illegal export "__esModule".');
  276. }
  277. getLocalMetadata(ids[name]).names.push(name);
  278. });
  279. }
  280. else {
  281. child.get('specifiers').forEach(spec => {
  282. const local = spec.get('local');
  283. const exported = spec.get('exported');
  284. if (exported.node.name === '__esModule') {
  285. throw exported.buildCodeFrameError('Illegal export "__esModule".');
  286. }
  287. getLocalMetadata(local).names.push(exported.node.name);
  288. });
  289. }
  290. }
  291. else if (child.isExportDefaultDeclaration()) {
  292. const declaration = child.get('declaration');
  293. if (
  294. declaration.isFunctionDeclaration() ||
  295. declaration.isClassDeclaration()
  296. ) {
  297. getLocalMetadata(declaration.get('id')).names.push('default');
  298. }
  299. else {
  300. // These should have been removed by the nameAnonymousExports() call.
  301. throw declaration.buildCodeFrameError('Unexpected default expression export.');
  302. }
  303. }
  304. });
  305. return localMetadata;
  306. }
  307. /**
  308. * Ensure that all exported values have local binding names.
  309. */
  310. function nameAnonymousExports(programPath) {
  311. // Name anonymous exported locals.
  312. programPath.get('body').forEach(child => {
  313. if (!child.isExportDefaultDeclaration()) {
  314. return;
  315. }
  316. // export default foo;
  317. const declaration = child.get('declaration');
  318. if (declaration.isFunctionDeclaration()) {
  319. if (!declaration.node.id) {
  320. declaration.node.id = declaration.scope.generateUidIdentifier('default');
  321. }
  322. }
  323. else if (declaration.isClassDeclaration()) {
  324. if (!declaration.node.id) {
  325. declaration.node.id = declaration.scope.generateUidIdentifier('default');
  326. }
  327. }
  328. else {
  329. const id = declaration.scope.generateUidIdentifier('default');
  330. const namedDecl = babelTypes.exportNamedDeclaration(null, [
  331. babelTypes.exportSpecifier(babelTypes.identifier(id.name), babelTypes.identifier('default')),
  332. ]);
  333. namedDecl._blockHoist = child.node._blockHoist;
  334. const varDecl = babelTypes.variableDeclaration('var', [
  335. babelTypes.variableDeclarator(id, declaration.node),
  336. ]);
  337. varDecl._blockHoist = child.node._blockHoist;
  338. child.replaceWithMultiple([namedDecl, varDecl]);
  339. }
  340. });
  341. }
  342. function removeModuleDeclarations(programPath) {
  343. programPath.get('body').forEach(child => {
  344. if (child.isImportDeclaration()) {
  345. child.remove();
  346. }
  347. else if (child.isExportNamedDeclaration()) {
  348. if (child.node.declaration) {
  349. child.node.declaration._blockHoist = child.node._blockHoist;
  350. child.replaceWith(child.node.declaration);
  351. }
  352. else {
  353. child.remove();
  354. }
  355. }
  356. else if (child.isExportDefaultDeclaration()) {
  357. // export default foo;
  358. const declaration = child.get('declaration');
  359. if (
  360. declaration.isFunctionDeclaration() ||
  361. declaration.isClassDeclaration()
  362. ) {
  363. declaration._blockHoist = child.node._blockHoist;
  364. child.replaceWith(declaration);
  365. }
  366. else {
  367. // These should have been removed by the nameAnonymousExports() call.
  368. throw declaration.buildCodeFrameError('Unexpected default expression export.');
  369. }
  370. }
  371. else if (child.isExportAllDeclaration()) {
  372. child.remove();
  373. }
  374. });
  375. }
  376. /**
  377. * Perform all of the generic ES6 module rewriting needed to handle initial
  378. * module processing. This function will rewrite the majority of the given
  379. * program to reference the modules described by the returned metadata,
  380. * and returns a list of statements for use when initializing the module.
  381. */
  382. function rewriteModuleStatementsAndPrepare(path) {
  383. path.node.sourceType = 'script';
  384. const meta = normalizeModuleAndLoadMetadata(path);
  385. return meta;
  386. }
  387. /**
  388. * Create the runtime initialization statements for a given requested source.
  389. * These will initialize all of the runtime import/export logic that
  390. * can't be handled statically by the statements created by
  391. * buildExportInitializationStatements().
  392. */
  393. function buildNamespaceInitStatements(meta, metadata, checkExport) {
  394. const statements = [];
  395. const {localImportName, localImportDefaultName} = getLocalImportName(metadata);
  396. for (const exportName of metadata.reexportNamespace) {
  397. // Assign export to namespace object.
  398. checkExport(exportName);
  399. statements.push(buildExport({exportName, localName: localImportName}));
  400. }
  401. // Source code:
  402. // import {color2 as color2Alias, color3, color4, color5} from 'xxx';
  403. // export {default as b} from 'xxx';
  404. // export {color2Alias};
  405. // export {color3};
  406. // let color5Renamed = color5
  407. // export {color5Renamed};
  408. // Only two entries in metadata.reexports:
  409. // 'color2Alias' => 'color2'
  410. // 'color3' => 'color3',
  411. // 'b' => 'default'
  412. //
  413. // And consider:
  414. // export {default as defaultAsBB} from './xx/yy';
  415. // export {exportSingle} from './xx/yy';
  416. // No entries in metadata.imports, and 'default' exists in metadata.reexports.
  417. for (const entry of metadata.reexports.entries()) {
  418. const exportName = entry[0];
  419. checkExport(exportName);
  420. statements.push(
  421. (localImportDefaultName || entry[1] === 'default')
  422. ? buildExport({exportName, localName: localImportName})
  423. : buildExport({exportName, namespace: localImportName, propName: entry[1]})
  424. );
  425. }
  426. if (metadata.reexportAll) {
  427. const statement = buildNamespaceReexport(
  428. meta,
  429. metadata.name,
  430. checkExport
  431. );
  432. statement.loc = metadata.reexportAll.loc;
  433. // Iterate props creating getter for each prop.
  434. statements.push(statement);
  435. }
  436. return statements;
  437. }
  438. /**
  439. * Create a re-export initialization loop for a specific imported namespace.
  440. */
  441. function buildNamespaceReexport(meta, namespace, checkExport) {
  442. checkExport();
  443. return babelTemplate.statement(`
  444. (function() {
  445. for (var key in NAMESPACE) {
  446. if (NAMESPACE == null || !NAMESPACE.hasOwnProperty(key) || key === 'default' || key === '__esModule') return;
  447. VERIFY_NAME_LIST;
  448. exports[key] = NAMESPACE[key];
  449. }
  450. })();
  451. `)({
  452. NAMESPACE: namespace,
  453. VERIFY_NAME_LIST: meta.exportNameListName
  454. ? babelTemplate.statement(`
  455. if (Object.prototype.hasOwnProperty.call(EXPORTS_LIST, key)) return;
  456. `)({EXPORTS_LIST: meta.exportNameListName})
  457. : null
  458. });
  459. }
  460. function buildRequireStatements(types, source, metadata) {
  461. let headers = [];
  462. const loadExpr = types.callExpression(
  463. types.identifier('require'),
  464. // replace `require('./src/xxx')` to `require('./lib/xxx')`
  465. // for echarts and zrender in old npm or webpack.
  466. [types.stringLiteral(source.replace('/src/', '/lib/'))]
  467. );
  468. // side effect import: import 'xxx';
  469. if (isSideEffectImport(metadata)) {
  470. let header = types.expressionStatement(loadExpr);
  471. header.loc = metadata.loc;
  472. headers.push(header);
  473. }
  474. else {
  475. const {localImportName, localImportDefaultName} = getLocalImportName(metadata);
  476. let reqHeader = types.variableDeclaration('var', [
  477. types.variableDeclarator(
  478. types.identifier(localImportName),
  479. loadExpr
  480. )
  481. ]);
  482. reqHeader.loc = metadata.loc;
  483. headers.push(reqHeader);
  484. if (!localImportDefaultName) {
  485. // src:
  486. // import {someInZrUtil1 as someInZrUtil1Alias, zz} from 'zrender/core/util';
  487. // metadata.imports:
  488. // Map { 'someInZrUtil1Alias' => 'someInZrUtil1', 'zz' => 'zz' }
  489. for (const importEntry of metadata.imports) {
  490. headers.push(
  491. babelTemplate.statement(`var IMPORTNAME = NAMESPACE.PROPNAME;`)({
  492. NAMESPACE: localImportName,
  493. IMPORTNAME: importEntry[0],
  494. PROPNAME: importEntry[1]
  495. })
  496. );
  497. }
  498. }
  499. }
  500. return headers;
  501. }
  502. function getLocalImportName(metadata) {
  503. const localImportDefaultName = getDefaultName(metadata.imports);
  504. assert(
  505. !localImportDefaultName || metadata.imports.size === 1,
  506. 'Forbiden that both import default and others.'
  507. );
  508. return {
  509. localImportName: localImportDefaultName || metadata.name,
  510. localImportDefaultName
  511. };
  512. }
  513. function getDefaultName(map) {
  514. for (const entry of map) {
  515. if (entry[1] === 'default') {
  516. return entry[0];
  517. }
  518. }
  519. }
  520. function buildLocalExportStatements(meta, checkExport) {
  521. let tails = [];
  522. // All local export, for example:
  523. // Map {
  524. // 'localVarMame' => {
  525. // names: [ 'exportName1', 'exportName2' ],
  526. // kind: 'var'
  527. // },
  528. for (const localEntry of meta.local) {
  529. for (const exportName of localEntry[1].names) {
  530. checkExport(exportName);
  531. tails.push(buildExport({exportName, localName: localEntry[0]}));
  532. }
  533. }
  534. return tails;
  535. }
  536. function createExportChecker() {
  537. let someHasBeenExported;
  538. return function checkExport(exportName) {
  539. assert(
  540. !someHasBeenExported || exportName !== 'default',
  541. `Forbiden that both export default and others.`
  542. );
  543. someHasBeenExported = true;
  544. };
  545. }
  546. function buildExport({exportName, namespace, propName, localName}) {
  547. const exportDefault = exportName === 'default';
  548. const head = exportDefault ? 'module.exports' : `exports.${exportName}`;
  549. let opt = {};
  550. // FIXME
  551. // Does `PRIORITY`, `LOCATION_PARAMS` recognised as babel-template placeholder?
  552. // We have to do this for workaround temporarily.
  553. if (/^[A-Z0-9_]+$/.test(localName)) {
  554. opt[localName] = localName;
  555. }
  556. return babelTemplate.statement(
  557. localName
  558. ? `${head} = ${localName};`
  559. : `${head} = ${namespace}.${propName};`
  560. )(opt);
  561. }
  562. /**
  563. * Consider this case:
  564. * export var a;
  565. * function inject(b) {
  566. * a = b;
  567. * }
  568. * It will be transpiled to:
  569. * var a;
  570. * exports.a = 1;
  571. * function inject(b) {
  572. * a = b;
  573. * }
  574. * That is a wrong transpilation, because the `export.a` will not
  575. * be assigned as `b` when `inject` called.
  576. * Of course, it can be transpiled correctly as:
  577. * var _locals = {};
  578. * var a;
  579. * Object.defineProperty(exports, 'a', {
  580. * get: function () { return _locals[a]; }
  581. * };
  582. * exports.a = a;
  583. * function inject(b) {
  584. * _locals[a] = b;
  585. * }
  586. * But it is not ES3 compatible.
  587. * So we just forbiden this usage here.
  588. */
  589. function checkAssignOrUpdateExport(programPath, meta) {
  590. let visitor = {
  591. // Include:
  592. // `a++;` (no `path.get('left')`)
  593. // `x += 1212`;
  594. UpdateExpression: {
  595. exit: function exit(path, scope) {
  596. // console.log(arguments);
  597. let left = path.get('left');
  598. if (left && left.isIdentifier()) {
  599. asertNotAssign(path, left.node.name);
  600. }
  601. }
  602. },
  603. // Include:
  604. // `x = 5;` (`x` is an identifier.)
  605. // `c.d = 3;` (but `c.d` is not an identifier.)
  606. // `y = function () {}`
  607. // Exclude:
  608. // `var x = 121;`
  609. // `export var x = 121;`
  610. AssignmentExpression: {
  611. exit: function exit(path) {
  612. let left = path.get('left');
  613. if (left.isIdentifier()) {
  614. asertNotAssign(path, left.node.name);
  615. }
  616. }
  617. }
  618. };
  619. function asertNotAssign(path, localName) {
  620. // Ignore variables that is not in global scope.
  621. if (programPath.scope.getBinding(localName) !== path.scope.getBinding(localName)) {
  622. return;
  623. }
  624. for (const localEntry of meta.local) {
  625. assert(
  626. localName !== localEntry[0],
  627. `An exported variable \`${localEntry[0]}\` is forbiden to be assigned.`
  628. );
  629. }
  630. }
  631. programPath.traverse(visitor);
  632. }