import-to-globals.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. const {walk} = require("estree-walker");
  2. const isReference = require("is-reference");
  3. const {attachScopes, makeLegalIdentifier} = require("@rollup/pluginutils");
  4. function analyzeImport(node, importBindings, code, getName, globals) {
  5. const name = node.source.value && getName(node.source.value);
  6. if (!name) {
  7. return false;
  8. }
  9. globals.add(name);
  10. for (const spec of node.specifiers) {
  11. importBindings.set(spec.local.name, makeGlobalName(
  12. spec.imported ? spec.imported.name : "default",
  13. name
  14. ));
  15. }
  16. code.remove(node.start, node.end);
  17. return true;
  18. }
  19. function makeGlobalName(prop, name) {
  20. if (prop === "default") {
  21. return name;
  22. }
  23. return `${name}.${prop}`;
  24. }
  25. function writeSpecLocal(code, root, spec, name, tempNames) {
  26. if (spec.isOverwritten) return;
  27. // we always need an extra assignment for named export statement
  28. // https://github.com/eight04/rollup-plugin-external-globals/issues/19
  29. const localName = `_global_${makeLegalIdentifier(name)}`;
  30. if (!tempNames.has(localName)) {
  31. code.appendRight(root.start, `const ${localName} = ${name};\n`);
  32. tempNames.add(localName);
  33. }
  34. if (spec.local.name === localName) {
  35. return;
  36. }
  37. if (spec.local === spec.exported) {
  38. code.appendRight(spec.local.start, `${localName} as `);
  39. } else {
  40. code.overwrite(spec.local.start, spec.local.end, localName);
  41. }
  42. spec.isOverwritten = true;
  43. }
  44. function writeIdentifier(code, node, parent, name) {
  45. if (node.name === name || node.isOverwritten) {
  46. return;
  47. }
  48. // 2020/8/14, parent.key and parent.value is no longer the same object. However, the shape is the same.
  49. if (parent.type === "Property" && parent.key.start === parent.value.start) {
  50. code.appendLeft(node.end, `: ${name}`);
  51. parent.key.isOverwritten = true;
  52. parent.value.isOverwritten = true;
  53. } else {
  54. code.overwrite(node.start, node.end, name, {contentOnly: true});
  55. // FIXME: do we need this?
  56. node.isOverwritten = true;
  57. }
  58. }
  59. function analyzeExportNamed(node, code, getName, tempNames) {
  60. if (node.declaration || !node.source || !node.source.value) {
  61. return false;
  62. }
  63. const name = getName(node.source.value);
  64. if (!name) {
  65. return false;
  66. }
  67. for (const spec of node.specifiers) {
  68. const globalName = makeGlobalName(spec.local.name, name);
  69. writeSpecLocal(code, node, spec, globalName, tempNames);
  70. }
  71. if (node.specifiers.length) {
  72. code.overwrite(node.specifiers[node.specifiers.length - 1].end, node.source.end, "}");
  73. } else {
  74. code.remove(node.start, node.end);
  75. }
  76. return true;
  77. }
  78. function writeDynamicImport(code, node, content) {
  79. code.overwrite(node.start, node.end, content);
  80. }
  81. function getDynamicImportSource(node) {
  82. if (node.type === "ImportExpression") {
  83. return node.source.value;
  84. }
  85. if (node.type === "CallExpression" && node.callee.type === "Import") {
  86. return node.arguments[0].value;
  87. }
  88. }
  89. function importToGlobals({ast, code, getName, getDynamicWrapper}) {
  90. let scope = attachScopes(ast, "scope");
  91. const bindings = new Map;
  92. const globals = new Set;
  93. let isTouched = false;
  94. const tempNames = new Set;
  95. for (const node of ast.body) {
  96. if (node.type === "ImportDeclaration") {
  97. isTouched = analyzeImport(node, bindings, code, getName, globals) || isTouched;
  98. } else if (node.type === "ExportNamedDeclaration") {
  99. isTouched = analyzeExportNamed(node, code, getName, tempNames) || isTouched;
  100. }
  101. }
  102. let topStatement;
  103. walk(ast, {
  104. enter(node, parent) {
  105. if (parent && parent.type === "Program") {
  106. topStatement = node;
  107. }
  108. if (/^importdec/i.test(node.type)) {
  109. this.skip();
  110. return;
  111. }
  112. if (node.scope) {
  113. scope = node.scope;
  114. }
  115. if (isReference(node, parent)) {
  116. if (bindings.has(node.name) && !scope.contains(node.name)) {
  117. if (parent.type === "ExportSpecifier") {
  118. writeSpecLocal(code, topStatement, parent, bindings.get(node.name), tempNames);
  119. } else {
  120. writeIdentifier(code, node, parent, bindings.get(node.name));
  121. }
  122. } else if (globals.has(node.name) && scope.contains(node.name)) {
  123. writeIdentifier(code, node, parent, `_local_${node.name}`);
  124. }
  125. }
  126. const source = getDynamicImportSource(node);
  127. const name = source && getName(source);
  128. const dynamicName = name && getDynamicWrapper(name);
  129. if (dynamicName) {
  130. writeDynamicImport(code, node, dynamicName);
  131. isTouched = true;
  132. this.skip();
  133. }
  134. },
  135. leave(node) {
  136. if (node.scope) {
  137. scope = node.scope.parent;
  138. }
  139. }
  140. });
  141. return isTouched;
  142. }
  143. module.exports = importToGlobals;