create-plugin.js 19 KB

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