create-plugin.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = createPlugin;
  6. var _pluginSyntaxJsx = require("@babel/plugin-syntax-jsx");
  7. var _helperPluginUtils = require("@babel/helper-plugin-utils");
  8. var _core = require("@babel/core");
  9. var _helperModuleImports = require("@babel/helper-module-imports");
  10. var _helperAnnotateAsPure = require("@babel/helper-annotate-as-pure");
  11. const DEFAULT = {
  12. importSource: "react",
  13. runtime: "automatic",
  14. pragma: "React.createElement",
  15. pragmaFrag: "React.Fragment"
  16. };
  17. const JSX_SOURCE_ANNOTATION_REGEX = /\*?\s*@jsxImportSource\s+([^\s]+)/;
  18. const JSX_RUNTIME_ANNOTATION_REGEX = /\*?\s*@jsxRuntime\s+([^\s]+)/;
  19. const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
  20. const JSX_FRAG_ANNOTATION_REGEX = /\*?\s*@jsxFrag\s+([^\s]+)/;
  21. const get = (pass, name) => pass.get(`@babel/plugin-react-jsx/${name}`);
  22. const set = (pass, name, v) => pass.set(`@babel/plugin-react-jsx/${name}`, v);
  23. function createPlugin({
  24. name,
  25. development
  26. }) {
  27. return (0, _helperPluginUtils.declare)((api, options) => {
  28. const {
  29. pure: PURE_ANNOTATION,
  30. throwIfNamespace = true,
  31. filter,
  32. runtime: RUNTIME_DEFAULT = development ? "automatic" : "classic",
  33. importSource: IMPORT_SOURCE_DEFAULT = DEFAULT.importSource,
  34. pragma: PRAGMA_DEFAULT = DEFAULT.pragma,
  35. pragmaFrag: PRAGMA_FRAG_DEFAULT = DEFAULT.pragmaFrag
  36. } = options;
  37. {
  38. var {
  39. useSpread = false,
  40. useBuiltIns = false
  41. } = options;
  42. if (RUNTIME_DEFAULT === "classic") {
  43. if (typeof useSpread !== "boolean") {
  44. throw new Error("transform-react-jsx currently only accepts a boolean option for " + "useSpread (defaults to false)");
  45. }
  46. if (typeof useBuiltIns !== "boolean") {
  47. throw new Error("transform-react-jsx currently only accepts a boolean option for " + "useBuiltIns (defaults to false)");
  48. }
  49. if (useSpread && useBuiltIns) {
  50. throw new Error("transform-react-jsx currently only accepts useBuiltIns or useSpread " + "but not both");
  51. }
  52. }
  53. }
  54. const injectMetaPropertiesVisitor = {
  55. JSXOpeningElement(path, state) {
  56. for (const attr of path.get("attributes")) {
  57. if (!attr.isJSXElement()) continue;
  58. const {
  59. name
  60. } = attr.node.name;
  61. if (name === "__source" || name === "__self") {
  62. throw path.buildCodeFrameError(`__source and __self should not be defined in props and are reserved for internal usage.`);
  63. }
  64. }
  65. const self = _core.types.jsxAttribute(_core.types.jsxIdentifier("__self"), _core.types.jsxExpressionContainer(_core.types.thisExpression()));
  66. const source = _core.types.jsxAttribute(_core.types.jsxIdentifier("__source"), _core.types.jsxExpressionContainer(makeSource(path, state)));
  67. path.pushContainer("attributes", [self, source]);
  68. }
  69. };
  70. return {
  71. name,
  72. inherits: _pluginSyntaxJsx.default,
  73. visitor: {
  74. JSXNamespacedName(path) {
  75. if (throwIfNamespace) {
  76. throw path.buildCodeFrameError(`Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
  77. You can set \`throwIfNamespace: false\` to bypass this warning.`);
  78. }
  79. },
  80. JSXSpreadChild(path) {
  81. throw path.buildCodeFrameError("Spread children are not supported in React.");
  82. },
  83. Program: {
  84. enter(path, state) {
  85. const {
  86. file
  87. } = state;
  88. let runtime = RUNTIME_DEFAULT;
  89. let source = IMPORT_SOURCE_DEFAULT;
  90. let pragma = PRAGMA_DEFAULT;
  91. let pragmaFrag = PRAGMA_FRAG_DEFAULT;
  92. let sourceSet = !!options.importSource;
  93. let pragmaSet = !!options.pragma;
  94. let pragmaFragSet = !!options.pragmaFrag;
  95. if (file.ast.comments) {
  96. for (const comment of file.ast.comments) {
  97. const sourceMatches = JSX_SOURCE_ANNOTATION_REGEX.exec(comment.value);
  98. if (sourceMatches) {
  99. source = sourceMatches[1];
  100. sourceSet = true;
  101. }
  102. const runtimeMatches = JSX_RUNTIME_ANNOTATION_REGEX.exec(comment.value);
  103. if (runtimeMatches) {
  104. runtime = runtimeMatches[1];
  105. }
  106. const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
  107. if (jsxMatches) {
  108. pragma = jsxMatches[1];
  109. pragmaSet = true;
  110. }
  111. const jsxFragMatches = JSX_FRAG_ANNOTATION_REGEX.exec(comment.value);
  112. if (jsxFragMatches) {
  113. pragmaFrag = jsxFragMatches[1];
  114. pragmaFragSet = true;
  115. }
  116. }
  117. }
  118. set(state, "runtime", runtime);
  119. if (runtime === "classic") {
  120. if (sourceSet) {
  121. throw path.buildCodeFrameError(`importSource cannot be set when runtime is classic.`);
  122. }
  123. const createElement = toMemberExpression(pragma);
  124. const fragment = toMemberExpression(pragmaFrag);
  125. set(state, "id/createElement", () => _core.types.cloneNode(createElement));
  126. set(state, "id/fragment", () => _core.types.cloneNode(fragment));
  127. set(state, "defaultPure", pragma === DEFAULT.pragma);
  128. } else if (runtime === "automatic") {
  129. if (pragmaSet || pragmaFragSet) {
  130. throw path.buildCodeFrameError(`pragma and pragmaFrag cannot be set when runtime is automatic.`);
  131. }
  132. const define = (name, id) => set(state, name, createImportLazily(state, path, id, source));
  133. define("id/jsx", development ? "jsxDEV" : "jsx");
  134. define("id/jsxs", development ? "jsxDEV" : "jsxs");
  135. define("id/createElement", "createElement");
  136. define("id/fragment", "Fragment");
  137. set(state, "defaultPure", source === DEFAULT.importSource);
  138. } else {
  139. throw path.buildCodeFrameError(`Runtime must be either "classic" or "automatic".`);
  140. }
  141. if (development) {
  142. path.traverse(injectMetaPropertiesVisitor, state);
  143. }
  144. }
  145. },
  146. JSXElement: {
  147. exit(path, file) {
  148. let callExpr;
  149. if (get(file, "runtime") === "classic" || shouldUseCreateElement(path)) {
  150. callExpr = buildCreateElementCall(path, file);
  151. } else {
  152. callExpr = buildJSXElementCall(path, file);
  153. }
  154. path.replaceWith(_core.types.inherits(callExpr, path.node));
  155. }
  156. },
  157. JSXFragment: {
  158. exit(path, file) {
  159. let callExpr;
  160. if (get(file, "runtime") === "classic") {
  161. callExpr = buildCreateElementFragmentCall(path, file);
  162. } else {
  163. callExpr = buildJSXFragmentCall(path, file);
  164. }
  165. path.replaceWith(_core.types.inherits(callExpr, path.node));
  166. }
  167. },
  168. JSXAttribute(path) {
  169. if (_core.types.isJSXElement(path.node.value)) {
  170. path.node.value = _core.types.jsxExpressionContainer(path.node.value);
  171. }
  172. }
  173. }
  174. };
  175. function call(pass, name, args) {
  176. const node = _core.types.callExpression(get(pass, `id/${name}`)(), args);
  177. if (PURE_ANNOTATION != null ? PURE_ANNOTATION : get(pass, "defaultPure")) (0, _helperAnnotateAsPure.default)(node);
  178. return node;
  179. }
  180. function shouldUseCreateElement(path) {
  181. const openingPath = path.get("openingElement");
  182. const attributes = openingPath.node.attributes;
  183. let seenPropsSpread = false;
  184. for (let i = 0; i < attributes.length; i++) {
  185. const attr = attributes[i];
  186. if (seenPropsSpread && _core.types.isJSXAttribute(attr) && attr.name.name === "key") {
  187. return true;
  188. } else if (_core.types.isJSXSpreadAttribute(attr)) {
  189. seenPropsSpread = true;
  190. }
  191. }
  192. return false;
  193. }
  194. function convertJSXIdentifier(node, parent) {
  195. if (_core.types.isJSXIdentifier(node)) {
  196. if (node.name === "this" && _core.types.isReferenced(node, parent)) {
  197. return _core.types.thisExpression();
  198. } else if (_core.types.isValidIdentifier(node.name, false)) {
  199. node.type = "Identifier";
  200. } else {
  201. return _core.types.stringLiteral(node.name);
  202. }
  203. } else if (_core.types.isJSXMemberExpression(node)) {
  204. return _core.types.memberExpression(convertJSXIdentifier(node.object, node), convertJSXIdentifier(node.property, node));
  205. } else if (_core.types.isJSXNamespacedName(node)) {
  206. return _core.types.stringLiteral(`${node.namespace.name}:${node.name.name}`);
  207. }
  208. return node;
  209. }
  210. function convertAttributeValue(node) {
  211. if (_core.types.isJSXExpressionContainer(node)) {
  212. return node.expression;
  213. } else {
  214. return node;
  215. }
  216. }
  217. function accumulateAttribute(array, attribute) {
  218. if (_core.types.isJSXSpreadAttribute(attribute.node)) {
  219. const arg = attribute.node.argument;
  220. if (_core.types.isObjectExpression(arg)) {
  221. array.push(...arg.properties);
  222. } else {
  223. array.push(_core.types.spreadElement(arg));
  224. }
  225. return array;
  226. }
  227. const value = convertAttributeValue(attribute.node.name.name !== "key" ? attribute.node.value || _core.types.booleanLiteral(true) : attribute.node.value);
  228. if (attribute.node.name.name === "key" && value === null) {
  229. throw attribute.buildCodeFrameError('Please provide an explicit key value. Using "key" as a shorthand for "key={true}" is not allowed.');
  230. }
  231. if (_core.types.isStringLiteral(value) && !_core.types.isJSXExpressionContainer(attribute.node.value)) {
  232. var _value$extra;
  233. value.value = value.value.replace(/\n\s+/g, " ");
  234. (_value$extra = value.extra) == null ? true : delete _value$extra.raw;
  235. }
  236. if (_core.types.isJSXNamespacedName(attribute.node.name)) {
  237. attribute.node.name = _core.types.stringLiteral(attribute.node.name.namespace.name + ":" + attribute.node.name.name.name);
  238. } else if (_core.types.isValidIdentifier(attribute.node.name.name, false)) {
  239. attribute.node.name.type = "Identifier";
  240. } else {
  241. attribute.node.name = _core.types.stringLiteral(attribute.node.name.name);
  242. }
  243. array.push(_core.types.inherits(_core.types.objectProperty(attribute.node.name, value), attribute.node));
  244. return array;
  245. }
  246. function buildChildrenProperty(children) {
  247. let childrenNode;
  248. if (children.length === 1) {
  249. childrenNode = children[0];
  250. } else if (children.length > 1) {
  251. childrenNode = _core.types.arrayExpression(children);
  252. } else {
  253. return undefined;
  254. }
  255. return _core.types.objectProperty(_core.types.identifier("children"), childrenNode);
  256. }
  257. function buildJSXElementCall(path, file) {
  258. const openingPath = path.get("openingElement");
  259. const args = [getTag(openingPath)];
  260. const attribsArray = [];
  261. const extracted = Object.create(null);
  262. for (const attr of openingPath.get("attributes")) {
  263. if (attr.isJSXAttribute() && _core.types.isJSXIdentifier(attr.node.name)) {
  264. const {
  265. name
  266. } = attr.node.name;
  267. switch (name) {
  268. case "__source":
  269. case "__self":
  270. if (extracted[name]) throw sourceSelfError(path, name);
  271. case "key":
  272. {
  273. const keyValue = convertAttributeValue(attr.node.value);
  274. if (keyValue === null) {
  275. throw attr.buildCodeFrameError('Please provide an explicit key value. Using "key" as a shorthand for "key={true}" is not allowed.');
  276. }
  277. extracted[name] = keyValue;
  278. break;
  279. }
  280. default:
  281. attribsArray.push(attr);
  282. }
  283. } else {
  284. attribsArray.push(attr);
  285. }
  286. }
  287. const children = _core.types.react.buildChildren(path.node);
  288. let attribs;
  289. if (attribsArray.length || children.length) {
  290. attribs = buildJSXOpeningElementAttributes(attribsArray, file, children);
  291. } else {
  292. attribs = _core.types.objectExpression([]);
  293. }
  294. args.push(attribs);
  295. if (development) {
  296. var _extracted$key, _extracted$__source, _extracted$__self;
  297. args.push((_extracted$key = extracted.key) != null ? _extracted$key : path.scope.buildUndefinedNode(), _core.types.booleanLiteral(children.length > 1), (_extracted$__source = extracted.__source) != null ? _extracted$__source : path.scope.buildUndefinedNode(), (_extracted$__self = extracted.__self) != null ? _extracted$__self : _core.types.thisExpression());
  298. } else if (extracted.key !== undefined) {
  299. args.push(extracted.key);
  300. }
  301. return call(file, children.length > 1 ? "jsxs" : "jsx", args);
  302. }
  303. function buildJSXOpeningElementAttributes(attribs, file, children) {
  304. const props = attribs.reduce(accumulateAttribute, []);
  305. if ((children == null ? void 0 : children.length) > 0) {
  306. props.push(buildChildrenProperty(children));
  307. }
  308. return _core.types.objectExpression(props);
  309. }
  310. function buildJSXFragmentCall(path, file) {
  311. const args = [get(file, "id/fragment")()];
  312. const children = _core.types.react.buildChildren(path.node);
  313. args.push(_core.types.objectExpression(children.length > 0 ? [buildChildrenProperty(children)] : []));
  314. if (development) {
  315. args.push(path.scope.buildUndefinedNode(), _core.types.booleanLiteral(children.length > 1));
  316. }
  317. return call(file, children.length > 1 ? "jsxs" : "jsx", args);
  318. }
  319. function buildCreateElementFragmentCall(path, file) {
  320. if (filter && !filter(path.node, file)) return;
  321. return call(file, "createElement", [get(file, "id/fragment")(), _core.types.nullLiteral(), ..._core.types.react.buildChildren(path.node)]);
  322. }
  323. function buildCreateElementCall(path, file) {
  324. const openingPath = path.get("openingElement");
  325. return call(file, "createElement", [getTag(openingPath), buildCreateElementOpeningElementAttributes(file, path, openingPath.get("attributes")), ..._core.types.react.buildChildren(path.node)]);
  326. }
  327. function getTag(openingPath) {
  328. const tagExpr = convertJSXIdentifier(openingPath.node.name, openingPath.node);
  329. let tagName;
  330. if (_core.types.isIdentifier(tagExpr)) {
  331. tagName = tagExpr.name;
  332. } else if (_core.types.isLiteral(tagExpr)) {
  333. tagName = tagExpr.value;
  334. }
  335. if (_core.types.react.isCompatTag(tagName)) {
  336. return _core.types.stringLiteral(tagName);
  337. } else {
  338. return tagExpr;
  339. }
  340. }
  341. function buildCreateElementOpeningElementAttributes(file, path, attribs) {
  342. const runtime = get(file, "runtime");
  343. {
  344. if (runtime !== "automatic") {
  345. const objs = [];
  346. const props = attribs.reduce(accumulateAttribute, []);
  347. if (!useSpread) {
  348. let start = 0;
  349. props.forEach((prop, i) => {
  350. if (_core.types.isSpreadElement(prop)) {
  351. if (i > start) {
  352. objs.push(_core.types.objectExpression(props.slice(start, i)));
  353. }
  354. objs.push(prop.argument);
  355. start = i + 1;
  356. }
  357. });
  358. if (props.length > start) {
  359. objs.push(_core.types.objectExpression(props.slice(start)));
  360. }
  361. } else if (props.length) {
  362. objs.push(_core.types.objectExpression(props));
  363. }
  364. if (!objs.length) {
  365. return _core.types.nullLiteral();
  366. }
  367. if (objs.length === 1) {
  368. return objs[0];
  369. }
  370. if (!_core.types.isObjectExpression(objs[0])) {
  371. objs.unshift(_core.types.objectExpression([]));
  372. }
  373. const helper = useBuiltIns ? _core.types.memberExpression(_core.types.identifier("Object"), _core.types.identifier("assign")) : file.addHelper("extends");
  374. return _core.types.callExpression(helper, objs);
  375. }
  376. }
  377. const props = [];
  378. const found = Object.create(null);
  379. for (const attr of attribs) {
  380. const name = _core.types.isJSXAttribute(attr) && _core.types.isJSXIdentifier(attr.name) && attr.name.name;
  381. if (runtime === "automatic" && (name === "__source" || name === "__self")) {
  382. if (found[name]) throw sourceSelfError(path, name);
  383. found[name] = true;
  384. }
  385. accumulateAttribute(props, attr);
  386. }
  387. return props.length === 1 && _core.types.isSpreadElement(props[0]) ? props[0].argument : props.length > 0 ? _core.types.objectExpression(props) : _core.types.nullLiteral();
  388. }
  389. });
  390. function getSource(source, importName) {
  391. switch (importName) {
  392. case "Fragment":
  393. return `${source}/${development ? "jsx-dev-runtime" : "jsx-runtime"}`;
  394. case "jsxDEV":
  395. return `${source}/jsx-dev-runtime`;
  396. case "jsx":
  397. case "jsxs":
  398. return `${source}/jsx-runtime`;
  399. case "createElement":
  400. return source;
  401. }
  402. }
  403. function createImportLazily(pass, path, importName, source) {
  404. return () => {
  405. const actualSource = getSource(source, importName);
  406. if ((0, _helperModuleImports.isModule)(path)) {
  407. let reference = get(pass, `imports/${importName}`);
  408. if (reference) return _core.types.cloneNode(reference);
  409. reference = (0, _helperModuleImports.addNamed)(path, importName, actualSource, {
  410. importedInterop: "uncompiled",
  411. importPosition: "after"
  412. });
  413. set(pass, `imports/${importName}`, reference);
  414. return reference;
  415. } else {
  416. let reference = get(pass, `requires/${actualSource}`);
  417. if (reference) {
  418. reference = _core.types.cloneNode(reference);
  419. } else {
  420. reference = (0, _helperModuleImports.addNamespace)(path, actualSource, {
  421. importedInterop: "uncompiled"
  422. });
  423. set(pass, `requires/${actualSource}`, reference);
  424. }
  425. return _core.types.memberExpression(reference, _core.types.identifier(importName));
  426. }
  427. };
  428. }
  429. }
  430. function toMemberExpression(id) {
  431. return id.split(".").map(name => _core.types.identifier(name)).reduce((object, property) => _core.types.memberExpression(object, property));
  432. }
  433. function makeSource(path, state) {
  434. const location = path.node.loc;
  435. if (!location) {
  436. return path.scope.buildUndefinedNode();
  437. }
  438. if (!state.fileNameIdentifier) {
  439. const {
  440. filename = ""
  441. } = state;
  442. const fileNameIdentifier = path.scope.generateUidIdentifier("_jsxFileName");
  443. const scope = path.hub.getScope();
  444. if (scope) {
  445. scope.push({
  446. id: fileNameIdentifier,
  447. init: _core.types.stringLiteral(filename)
  448. });
  449. }
  450. state.fileNameIdentifier = fileNameIdentifier;
  451. }
  452. return makeTrace(_core.types.cloneNode(state.fileNameIdentifier), location.start.line, location.start.column);
  453. }
  454. function makeTrace(fileNameIdentifier, lineNumber, column0Based) {
  455. const fileLineLiteral = lineNumber != null ? _core.types.numericLiteral(lineNumber) : _core.types.nullLiteral();
  456. const fileColumnLiteral = column0Based != null ? _core.types.numericLiteral(column0Based + 1) : _core.types.nullLiteral();
  457. const fileNameProperty = _core.types.objectProperty(_core.types.identifier("fileName"), fileNameIdentifier);
  458. const lineNumberProperty = _core.types.objectProperty(_core.types.identifier("lineNumber"), fileLineLiteral);
  459. const columnNumberProperty = _core.types.objectProperty(_core.types.identifier("columnNumber"), fileColumnLiteral);
  460. return _core.types.objectExpression([fileNameProperty, lineNumberProperty, columnNumberProperty]);
  461. }
  462. function sourceSelfError(path, name) {
  463. const pluginName = `transform-react-jsx-${name.slice(2)}`;
  464. return path.buildCodeFrameError(`Duplicate ${name} prop found. You are most likely using the deprecated ${pluginName} Babel plugin. Both __source and __self are automatically set when using the automatic runtime. Please remove transform-react-jsx-source and transform-react-jsx-self from your Babel config.`);
  465. }