index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. exports.ensure = ensure;
  7. exports.get = get;
  8. exports.getDependencies = getDependencies;
  9. exports.list = void 0;
  10. exports.minVersion = minVersion;
  11. var _traverse = require("@babel/traverse");
  12. var _t = require("@babel/types");
  13. var _helpers = require("./helpers");
  14. const {
  15. assignmentExpression,
  16. cloneNode,
  17. expressionStatement,
  18. file,
  19. identifier
  20. } = _t;
  21. function makePath(path) {
  22. const parts = [];
  23. for (; path.parentPath; path = path.parentPath) {
  24. parts.push(path.key);
  25. if (path.inList) parts.push(path.listKey);
  26. }
  27. return parts.reverse().join(".");
  28. }
  29. let FileClass = undefined;
  30. function getHelperMetadata(file) {
  31. const globals = new Set();
  32. const localBindingNames = new Set();
  33. const dependencies = new Map();
  34. let exportName;
  35. let exportPath;
  36. const exportBindingAssignments = [];
  37. const importPaths = [];
  38. const importBindingsReferences = [];
  39. const dependencyVisitor = {
  40. ImportDeclaration(child) {
  41. const name = child.node.source.value;
  42. if (!_helpers.default[name]) {
  43. throw child.buildCodeFrameError(`Unknown helper ${name}`);
  44. }
  45. if (child.get("specifiers").length !== 1 ||
  46. !child.get("specifiers.0").isImportDefaultSpecifier()) {
  47. throw child.buildCodeFrameError("Helpers can only import a default value");
  48. }
  49. const bindingIdentifier = child.node.specifiers[0].local;
  50. dependencies.set(bindingIdentifier, name);
  51. importPaths.push(makePath(child));
  52. },
  53. ExportDefaultDeclaration(child) {
  54. const decl = child.get("declaration");
  55. if (!decl.isFunctionDeclaration() || !decl.node.id) {
  56. throw decl.buildCodeFrameError("Helpers can only export named function declarations");
  57. }
  58. exportName = decl.node.id.name;
  59. exportPath = makePath(child);
  60. },
  61. ExportAllDeclaration(child) {
  62. throw child.buildCodeFrameError("Helpers can only export default");
  63. },
  64. ExportNamedDeclaration(child) {
  65. throw child.buildCodeFrameError("Helpers can only export default");
  66. },
  67. Statement(child) {
  68. if (child.isImportDeclaration() || child.isExportDeclaration()) return;
  69. child.skip();
  70. }
  71. };
  72. const referenceVisitor = {
  73. Program(path) {
  74. const bindings = path.scope.getAllBindings();
  75. Object.keys(bindings).forEach(name => {
  76. if (name === exportName) return;
  77. if (dependencies.has(bindings[name].identifier)) return;
  78. localBindingNames.add(name);
  79. });
  80. },
  81. ReferencedIdentifier(child) {
  82. const name = child.node.name;
  83. const binding = child.scope.getBinding(name);
  84. if (!binding) {
  85. globals.add(name);
  86. } else if (dependencies.has(binding.identifier)) {
  87. importBindingsReferences.push(makePath(child));
  88. }
  89. },
  90. AssignmentExpression(child) {
  91. const left = child.get("left");
  92. if (!(exportName in left.getBindingIdentifiers())) return;
  93. if (!left.isIdentifier()) {
  94. throw left.buildCodeFrameError("Only simple assignments to exports are allowed in helpers");
  95. }
  96. const binding = child.scope.getBinding(exportName);
  97. if (binding != null && binding.scope.path.isProgram()) {
  98. exportBindingAssignments.push(makePath(child));
  99. }
  100. }
  101. };
  102. (0, _traverse.default)(file.ast, dependencyVisitor, file.scope);
  103. (0, _traverse.default)(file.ast, referenceVisitor, file.scope);
  104. if (!exportPath) throw new Error("Helpers must have a default export.");
  105. exportBindingAssignments.reverse();
  106. return {
  107. globals: Array.from(globals),
  108. localBindingNames: Array.from(localBindingNames),
  109. dependencies,
  110. exportBindingAssignments,
  111. exportPath,
  112. exportName,
  113. importBindingsReferences,
  114. importPaths
  115. };
  116. }
  117. function permuteHelperAST(file, metadata, id, localBindings, getDependency) {
  118. if (localBindings && !id) {
  119. throw new Error("Unexpected local bindings for module-based helpers.");
  120. }
  121. if (!id) return;
  122. const {
  123. localBindingNames,
  124. dependencies,
  125. exportBindingAssignments,
  126. exportPath,
  127. exportName,
  128. importBindingsReferences,
  129. importPaths
  130. } = metadata;
  131. const dependenciesRefs = {};
  132. dependencies.forEach((name, id) => {
  133. dependenciesRefs[id.name] = typeof getDependency === "function" && getDependency(name) || id;
  134. });
  135. const toRename = {};
  136. const bindings = new Set(localBindings || []);
  137. localBindingNames.forEach(name => {
  138. let newName = name;
  139. while (bindings.has(newName)) newName = "_" + newName;
  140. if (newName !== name) toRename[name] = newName;
  141. });
  142. if (id.type === "Identifier" && exportName !== id.name) {
  143. toRename[exportName] = id.name;
  144. }
  145. const {
  146. path
  147. } = file;
  148. const exp = path.get(exportPath);
  149. const imps = importPaths.map(p => path.get(p));
  150. const impsBindingRefs = importBindingsReferences.map(p => path.get(p));
  151. const decl = exp.get("declaration");
  152. if (id.type === "Identifier") {
  153. exp.replaceWith(decl);
  154. } else if (id.type === "MemberExpression") {
  155. exportBindingAssignments.forEach(assignPath => {
  156. const assign = path.get(assignPath);
  157. assign.replaceWith(assignmentExpression("=", id, assign.node));
  158. });
  159. exp.replaceWith(decl);
  160. path.pushContainer("body", expressionStatement(assignmentExpression("=", id, identifier(exportName))));
  161. } else {
  162. throw new Error("Unexpected helper format.");
  163. }
  164. Object.keys(toRename).forEach(name => {
  165. path.scope.rename(name, toRename[name]);
  166. });
  167. for (const path of imps) path.remove();
  168. for (const path of impsBindingRefs) {
  169. const node = cloneNode(dependenciesRefs[path.node.name]);
  170. path.replaceWith(node);
  171. }
  172. }
  173. const helperData = Object.create(null);
  174. function loadHelper(name) {
  175. if (!helperData[name]) {
  176. const helper = _helpers.default[name];
  177. if (!helper) {
  178. throw Object.assign(new ReferenceError(`Unknown helper ${name}`), {
  179. code: "BABEL_HELPER_UNKNOWN",
  180. helper: name
  181. });
  182. }
  183. const fn = () => {
  184. {
  185. if (!FileClass) {
  186. const fakeFile = {
  187. ast: file(helper.ast()),
  188. path: null
  189. };
  190. (0, _traverse.default)(fakeFile.ast, {
  191. Program: path => (fakeFile.path = path).stop()
  192. });
  193. return fakeFile;
  194. }
  195. }
  196. return new FileClass({
  197. filename: `babel-helper://${name}`
  198. }, {
  199. ast: file(helper.ast()),
  200. code: "[internal Babel helper code]",
  201. inputMap: null
  202. });
  203. };
  204. let metadata = null;
  205. helperData[name] = {
  206. minVersion: helper.minVersion,
  207. build(getDependency, id, localBindings) {
  208. const file = fn();
  209. metadata || (metadata = getHelperMetadata(file));
  210. permuteHelperAST(file, metadata, id, localBindings, getDependency);
  211. return {
  212. nodes: file.ast.program.body,
  213. globals: metadata.globals
  214. };
  215. },
  216. getDependencies() {
  217. metadata || (metadata = getHelperMetadata(fn()));
  218. return Array.from(metadata.dependencies.values());
  219. }
  220. };
  221. }
  222. return helperData[name];
  223. }
  224. function get(name, getDependency, id, localBindings) {
  225. return loadHelper(name).build(getDependency, id, localBindings);
  226. }
  227. function minVersion(name) {
  228. return loadHelper(name).minVersion;
  229. }
  230. function getDependencies(name) {
  231. return loadHelper(name).getDependencies();
  232. }
  233. function ensure(name, newFileClass) {
  234. FileClass || (FileClass = newFileClass);
  235. loadHelper(name);
  236. }
  237. const list = Object.keys(_helpers.default).map(name => name.replace(/^_/, ""));
  238. exports.list = list;
  239. var _default = get;
  240. exports.default = _default;
  241. //# sourceMappingURL=index.js.map