reactivity-transform.cjs.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var MagicString = require('magic-string');
  4. var estreeWalker = require('estree-walker');
  5. var compilerCore = require('@vue/compiler-core');
  6. var parser = require('@babel/parser');
  7. var shared = require('@vue/shared');
  8. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e['default'] : e; }
  9. var MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString);
  10. const CONVERT_SYMBOL = '$';
  11. const ESCAPE_SYMBOL = '$$';
  12. const IMPORT_SOURCE = 'vue/macros';
  13. const shorthands = ['ref', 'computed', 'shallowRef', 'toRef', 'customRef'];
  14. const transformCheckRE = /[^\w]\$(?:\$|ref|computed|shallowRef)?\s*(\(|\<)/;
  15. function shouldTransform(src) {
  16. return transformCheckRE.test(src);
  17. }
  18. function transform(src, { filename, sourceMap, parserPlugins, importHelpersFrom = 'vue' } = {}) {
  19. const plugins = parserPlugins || [];
  20. if (filename) {
  21. if (/\.tsx?$/.test(filename)) {
  22. plugins.push('typescript');
  23. }
  24. if (filename.endsWith('x')) {
  25. plugins.push('jsx');
  26. }
  27. }
  28. const ast = parser.parse(src, {
  29. sourceType: 'module',
  30. plugins
  31. });
  32. const s = new MagicString__default(src);
  33. const res = transformAST(ast.program, s, 0);
  34. // inject helper imports
  35. if (res.importedHelpers.length) {
  36. s.prepend(`import { ${res.importedHelpers
  37. .map(h => `${h} as _${h}`)
  38. .join(', ')} } from '${importHelpersFrom}'\n`);
  39. }
  40. return {
  41. ...res,
  42. code: s.toString(),
  43. map: sourceMap
  44. ? s.generateMap({
  45. source: filename,
  46. hires: true,
  47. includeContent: true
  48. })
  49. : null
  50. };
  51. }
  52. function transformAST(ast, s, offset = 0, knownRefs, knownProps) {
  53. // TODO remove when out of experimental
  54. warnExperimental();
  55. const userImports = Object.create(null);
  56. for (const node of ast.body) {
  57. if (node.type !== 'ImportDeclaration')
  58. continue;
  59. walkImportDeclaration(node);
  60. }
  61. // macro import handling
  62. let convertSymbol;
  63. let escapeSymbol;
  64. for (const { local, imported, source, specifier } of Object.values(userImports)) {
  65. if (source === IMPORT_SOURCE) {
  66. if (imported === ESCAPE_SYMBOL) {
  67. escapeSymbol = local;
  68. }
  69. else if (imported === CONVERT_SYMBOL) {
  70. convertSymbol = local;
  71. }
  72. else if (imported !== local) {
  73. error(`macro imports for ref-creating methods do not support aliasing.`, specifier);
  74. }
  75. }
  76. }
  77. // default symbol
  78. if (!convertSymbol && !userImports[CONVERT_SYMBOL]) {
  79. convertSymbol = CONVERT_SYMBOL;
  80. }
  81. if (!escapeSymbol && !userImports[ESCAPE_SYMBOL]) {
  82. escapeSymbol = ESCAPE_SYMBOL;
  83. }
  84. const importedHelpers = new Set();
  85. const rootScope = {};
  86. const scopeStack = [rootScope];
  87. let currentScope = rootScope;
  88. let escapeScope; // inside $$()
  89. const excludedIds = new WeakSet();
  90. const parentStack = [];
  91. const propsLocalToPublicMap = Object.create(null);
  92. if (knownRefs) {
  93. for (const key of knownRefs) {
  94. rootScope[key] = true;
  95. }
  96. }
  97. if (knownProps) {
  98. for (const key in knownProps) {
  99. const { local } = knownProps[key];
  100. rootScope[local] = 'prop';
  101. propsLocalToPublicMap[local] = key;
  102. }
  103. }
  104. function walkImportDeclaration(node) {
  105. const source = node.source.value;
  106. if (source === IMPORT_SOURCE) {
  107. s.remove(node.start + offset, node.end + offset);
  108. }
  109. for (const specifier of node.specifiers) {
  110. const local = specifier.local.name;
  111. const imported = (specifier.type === 'ImportSpecifier' &&
  112. specifier.imported.type === 'Identifier' &&
  113. specifier.imported.name) ||
  114. 'default';
  115. userImports[local] = {
  116. source,
  117. local,
  118. imported,
  119. specifier
  120. };
  121. }
  122. }
  123. function isRefCreationCall(callee) {
  124. if (!convertSymbol || currentScope[convertSymbol] !== undefined) {
  125. return false;
  126. }
  127. if (callee === convertSymbol) {
  128. return convertSymbol;
  129. }
  130. if (callee[0] === '$' && shorthands.includes(callee.slice(1))) {
  131. return callee;
  132. }
  133. return false;
  134. }
  135. function error(msg, node) {
  136. const e = new Error(msg);
  137. e.node = node;
  138. throw e;
  139. }
  140. function helper(msg) {
  141. importedHelpers.add(msg);
  142. return `_${msg}`;
  143. }
  144. function registerBinding(id, isRef = false) {
  145. excludedIds.add(id);
  146. if (currentScope) {
  147. currentScope[id.name] = isRef;
  148. }
  149. else {
  150. error('registerBinding called without active scope, something is wrong.', id);
  151. }
  152. }
  153. const registerRefBinding = (id) => registerBinding(id, true);
  154. let tempVarCount = 0;
  155. function genTempVar() {
  156. return `__$temp_${++tempVarCount}`;
  157. }
  158. function snip(node) {
  159. return s.original.slice(node.start + offset, node.end + offset);
  160. }
  161. function walkScope(node, isRoot = false) {
  162. for (const stmt of node.body) {
  163. if (stmt.type === 'VariableDeclaration') {
  164. walkVariableDeclaration(stmt, isRoot);
  165. }
  166. else if (stmt.type === 'FunctionDeclaration' ||
  167. stmt.type === 'ClassDeclaration') {
  168. if (stmt.declare || !stmt.id)
  169. continue;
  170. registerBinding(stmt.id);
  171. }
  172. else if ((stmt.type === 'ForOfStatement' || stmt.type === 'ForInStatement') &&
  173. stmt.left.type === 'VariableDeclaration') {
  174. walkVariableDeclaration(stmt.left);
  175. }
  176. else if (stmt.type === 'ExportNamedDeclaration' &&
  177. stmt.declaration &&
  178. stmt.declaration.type === 'VariableDeclaration') {
  179. walkVariableDeclaration(stmt.declaration, isRoot);
  180. }
  181. else if (stmt.type === 'LabeledStatement' &&
  182. stmt.body.type === 'VariableDeclaration') {
  183. walkVariableDeclaration(stmt.body, isRoot);
  184. }
  185. }
  186. }
  187. function walkVariableDeclaration(stmt, isRoot = false) {
  188. if (stmt.declare) {
  189. return;
  190. }
  191. for (const decl of stmt.declarations) {
  192. let refCall;
  193. const isCall = decl.init &&
  194. decl.init.type === 'CallExpression' &&
  195. decl.init.callee.type === 'Identifier';
  196. if (isCall &&
  197. (refCall = isRefCreationCall(decl.init.callee.name))) {
  198. processRefDeclaration(refCall, decl.id, decl.init);
  199. }
  200. else {
  201. const isProps = isRoot && isCall && decl.init.callee.name === 'defineProps';
  202. for (const id of compilerCore.extractIdentifiers(decl.id)) {
  203. if (isProps) {
  204. // for defineProps destructure, only exclude them since they
  205. // are already passed in as knownProps
  206. excludedIds.add(id);
  207. }
  208. else {
  209. registerBinding(id);
  210. }
  211. }
  212. }
  213. }
  214. }
  215. function processRefDeclaration(method, id, call) {
  216. excludedIds.add(call.callee);
  217. if (method === convertSymbol) {
  218. // $
  219. // remove macro
  220. s.remove(call.callee.start + offset, call.callee.end + offset);
  221. if (id.type === 'Identifier') {
  222. // single variable
  223. registerRefBinding(id);
  224. }
  225. else if (id.type === 'ObjectPattern') {
  226. processRefObjectPattern(id, call);
  227. }
  228. else if (id.type === 'ArrayPattern') {
  229. processRefArrayPattern(id, call);
  230. }
  231. }
  232. else {
  233. // shorthands
  234. if (id.type === 'Identifier') {
  235. registerRefBinding(id);
  236. // replace call
  237. s.overwrite(call.start + offset, call.start + method.length + offset, helper(method.slice(1)));
  238. }
  239. else {
  240. error(`${method}() cannot be used with destructure patterns.`, call);
  241. }
  242. }
  243. }
  244. function processRefObjectPattern(pattern, call, tempVar, path = []) {
  245. if (!tempVar) {
  246. tempVar = genTempVar();
  247. // const { x } = $(useFoo()) --> const __$temp_1 = useFoo()
  248. s.overwrite(pattern.start + offset, pattern.end + offset, tempVar);
  249. }
  250. let nameId;
  251. for (const p of pattern.properties) {
  252. let key;
  253. let defaultValue;
  254. if (p.type === 'ObjectProperty') {
  255. if (p.key.start === p.value.start) {
  256. // shorthand { foo }
  257. nameId = p.key;
  258. if (p.value.type === 'Identifier') {
  259. // avoid shorthand value identifier from being processed
  260. excludedIds.add(p.value);
  261. }
  262. else if (p.value.type === 'AssignmentPattern' &&
  263. p.value.left.type === 'Identifier') {
  264. // { foo = 1 }
  265. excludedIds.add(p.value.left);
  266. defaultValue = p.value.right;
  267. }
  268. }
  269. else {
  270. key = p.computed ? p.key : p.key.name;
  271. if (p.value.type === 'Identifier') {
  272. // { foo: bar }
  273. nameId = p.value;
  274. }
  275. else if (p.value.type === 'ObjectPattern') {
  276. processRefObjectPattern(p.value, call, tempVar, [...path, key]);
  277. }
  278. else if (p.value.type === 'ArrayPattern') {
  279. processRefArrayPattern(p.value, call, tempVar, [...path, key]);
  280. }
  281. else if (p.value.type === 'AssignmentPattern') {
  282. if (p.value.left.type === 'Identifier') {
  283. // { foo: bar = 1 }
  284. nameId = p.value.left;
  285. defaultValue = p.value.right;
  286. }
  287. else if (p.value.left.type === 'ObjectPattern') {
  288. processRefObjectPattern(p.value.left, call, tempVar, [
  289. ...path,
  290. [key, p.value.right]
  291. ]);
  292. }
  293. else if (p.value.left.type === 'ArrayPattern') {
  294. processRefArrayPattern(p.value.left, call, tempVar, [
  295. ...path,
  296. [key, p.value.right]
  297. ]);
  298. }
  299. else ;
  300. }
  301. }
  302. }
  303. else {
  304. // rest element { ...foo }
  305. error(`reactivity destructure does not support rest elements.`, p);
  306. }
  307. if (nameId) {
  308. registerRefBinding(nameId);
  309. // inject toRef() after original replaced pattern
  310. const source = pathToString(tempVar, path);
  311. const keyStr = shared.isString(key)
  312. ? `'${key}'`
  313. : key
  314. ? snip(key)
  315. : `'${nameId.name}'`;
  316. const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``;
  317. s.appendLeft(call.end + offset, `,\n ${nameId.name} = ${helper('toRef')}(${source}, ${keyStr}${defaultStr})`);
  318. }
  319. }
  320. if (nameId) {
  321. s.appendLeft(call.end + offset, ';');
  322. }
  323. }
  324. function processRefArrayPattern(pattern, call, tempVar, path = []) {
  325. if (!tempVar) {
  326. // const [x] = $(useFoo()) --> const __$temp_1 = useFoo()
  327. tempVar = genTempVar();
  328. s.overwrite(pattern.start + offset, pattern.end + offset, tempVar);
  329. }
  330. let nameId;
  331. for (let i = 0; i < pattern.elements.length; i++) {
  332. const e = pattern.elements[i];
  333. if (!e)
  334. continue;
  335. let defaultValue;
  336. if (e.type === 'Identifier') {
  337. // [a] --> [__a]
  338. nameId = e;
  339. }
  340. else if (e.type === 'AssignmentPattern') {
  341. // [a = 1]
  342. nameId = e.left;
  343. defaultValue = e.right;
  344. }
  345. else if (e.type === 'RestElement') {
  346. // [...a]
  347. error(`reactivity destructure does not support rest elements.`, e);
  348. }
  349. else if (e.type === 'ObjectPattern') {
  350. processRefObjectPattern(e, call, tempVar, [...path, i]);
  351. }
  352. else if (e.type === 'ArrayPattern') {
  353. processRefArrayPattern(e, call, tempVar, [...path, i]);
  354. }
  355. if (nameId) {
  356. registerRefBinding(nameId);
  357. // inject toRef() after original replaced pattern
  358. const source = pathToString(tempVar, path);
  359. const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``;
  360. s.appendLeft(call.end + offset, `,\n ${nameId.name} = ${helper('toRef')}(${source}, ${i}${defaultStr})`);
  361. }
  362. }
  363. if (nameId) {
  364. s.appendLeft(call.end + offset, ';');
  365. }
  366. }
  367. function pathToString(source, path) {
  368. if (path.length) {
  369. for (const seg of path) {
  370. if (shared.isArray(seg)) {
  371. source = `(${source}${segToString(seg[0])} || ${snip(seg[1])})`;
  372. }
  373. else {
  374. source += segToString(seg);
  375. }
  376. }
  377. }
  378. return source;
  379. }
  380. function segToString(seg) {
  381. if (typeof seg === 'number') {
  382. return `[${seg}]`;
  383. }
  384. else if (typeof seg === 'string') {
  385. return `.${seg}`;
  386. }
  387. else {
  388. return snip(seg);
  389. }
  390. }
  391. function rewriteId(scope, id, parent, parentStack) {
  392. if (shared.hasOwn(scope, id.name)) {
  393. const bindingType = scope[id.name];
  394. if (bindingType) {
  395. const isProp = bindingType === 'prop';
  396. if (compilerCore.isStaticProperty(parent) && parent.shorthand) {
  397. // let binding used in a property shorthand
  398. // skip for destructure patterns
  399. if (!parent.inPattern ||
  400. compilerCore.isInDestructureAssignment(parent, parentStack)) {
  401. if (isProp) {
  402. if (escapeScope) {
  403. // prop binding in $$()
  404. // { prop } -> { prop: __props_prop }
  405. registerEscapedPropBinding(id);
  406. s.appendLeft(id.end + offset, `: __props_${propsLocalToPublicMap[id.name]}`);
  407. }
  408. else {
  409. // { prop } -> { prop: __props.prop }
  410. s.appendLeft(id.end + offset, `: ${shared.genPropsAccessExp(propsLocalToPublicMap[id.name])}`);
  411. }
  412. }
  413. else {
  414. // { foo } -> { foo: foo.value }
  415. s.appendLeft(id.end + offset, `: ${id.name}.value`);
  416. }
  417. }
  418. }
  419. else {
  420. if (isProp) {
  421. if (escapeScope) {
  422. // x --> __props_x
  423. registerEscapedPropBinding(id);
  424. s.overwrite(id.start + offset, id.end + offset, `__props_${propsLocalToPublicMap[id.name]}`);
  425. }
  426. else {
  427. // x --> __props.x
  428. s.overwrite(id.start + offset, id.end + offset, shared.genPropsAccessExp(propsLocalToPublicMap[id.name]));
  429. }
  430. }
  431. else {
  432. // x --> x.value
  433. s.appendLeft(id.end + offset, '.value');
  434. }
  435. }
  436. }
  437. return true;
  438. }
  439. return false;
  440. }
  441. const propBindingRefs = {};
  442. function registerEscapedPropBinding(id) {
  443. if (!propBindingRefs.hasOwnProperty(id.name)) {
  444. propBindingRefs[id.name] = true;
  445. const publicKey = propsLocalToPublicMap[id.name];
  446. s.prependRight(offset, `const __props_${publicKey} = ${helper(`toRef`)}(__props, '${publicKey}');\n`);
  447. }
  448. }
  449. // check root scope first
  450. walkScope(ast, true);
  451. estreeWalker.walk(ast, {
  452. enter(node, parent) {
  453. parent && parentStack.push(parent);
  454. // function scopes
  455. if (compilerCore.isFunctionType(node)) {
  456. scopeStack.push((currentScope = {}));
  457. compilerCore.walkFunctionParams(node, registerBinding);
  458. if (node.body.type === 'BlockStatement') {
  459. walkScope(node.body);
  460. }
  461. return;
  462. }
  463. // catch param
  464. if (node.type === 'CatchClause') {
  465. scopeStack.push((currentScope = {}));
  466. if (node.param && node.param.type === 'Identifier') {
  467. registerBinding(node.param);
  468. }
  469. walkScope(node.body);
  470. return;
  471. }
  472. // non-function block scopes
  473. if (node.type === 'BlockStatement' && !compilerCore.isFunctionType(parent)) {
  474. scopeStack.push((currentScope = {}));
  475. walkScope(node);
  476. return;
  477. }
  478. // skip type nodes
  479. if (parent &&
  480. parent.type.startsWith('TS') &&
  481. parent.type !== 'TSAsExpression' &&
  482. parent.type !== 'TSNonNullExpression' &&
  483. parent.type !== 'TSTypeAssertion') {
  484. return this.skip();
  485. }
  486. if (node.type === 'Identifier' &&
  487. // if inside $$(), skip unless this is a destructured prop binding
  488. !(escapeScope && rootScope[node.name] !== 'prop') &&
  489. compilerCore.isReferencedIdentifier(node, parent, parentStack) &&
  490. !excludedIds.has(node)) {
  491. // walk up the scope chain to check if id should be appended .value
  492. let i = scopeStack.length;
  493. while (i--) {
  494. if (rewriteId(scopeStack[i], node, parent, parentStack)) {
  495. return;
  496. }
  497. }
  498. }
  499. if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
  500. const callee = node.callee.name;
  501. const refCall = isRefCreationCall(callee);
  502. if (refCall && (!parent || parent.type !== 'VariableDeclarator')) {
  503. return error(`${refCall} can only be used as the initializer of ` +
  504. `a variable declaration.`, node);
  505. }
  506. if (escapeSymbol &&
  507. currentScope[escapeSymbol] === undefined &&
  508. callee === escapeSymbol) {
  509. s.remove(node.callee.start + offset, node.callee.end + offset);
  510. escapeScope = node;
  511. }
  512. // TODO remove when out of experimental
  513. if (callee === '$raw') {
  514. error(`$raw() has been replaced by $$(). ` +
  515. `See ${RFC_LINK} for latest updates.`, node);
  516. }
  517. if (callee === '$fromRef') {
  518. error(`$fromRef() has been replaced by $(). ` +
  519. `See ${RFC_LINK} for latest updates.`, node);
  520. }
  521. }
  522. },
  523. leave(node, parent) {
  524. parent && parentStack.pop();
  525. if ((node.type === 'BlockStatement' && !compilerCore.isFunctionType(parent)) ||
  526. compilerCore.isFunctionType(node)) {
  527. scopeStack.pop();
  528. currentScope = scopeStack[scopeStack.length - 1] || null;
  529. }
  530. if (node === escapeScope) {
  531. escapeScope = undefined;
  532. }
  533. }
  534. });
  535. return {
  536. rootRefs: Object.keys(rootScope).filter(key => rootScope[key] === true),
  537. importedHelpers: [...importedHelpers]
  538. };
  539. }
  540. const RFC_LINK = `https://github.com/vuejs/rfcs/discussions/369`;
  541. const hasWarned = {};
  542. function warnExperimental() {
  543. // eslint-disable-next-line
  544. if (typeof window !== 'undefined') {
  545. return;
  546. }
  547. warnOnce(`Reactivity transform is an experimental feature.\n` +
  548. `Experimental features may change behavior between patch versions.\n` +
  549. `It is recommended to pin your vue dependencies to exact versions to avoid breakage.\n` +
  550. `You can follow the proposal's status at ${RFC_LINK}.`);
  551. }
  552. function warnOnce(msg) {
  553. const isNodeProd = typeof process !== 'undefined' && process.env.NODE_ENV === 'production';
  554. if (!isNodeProd && !false && !hasWarned[msg]) {
  555. hasWarned[msg] = true;
  556. warn(msg);
  557. }
  558. }
  559. function warn(msg) {
  560. console.warn(`\x1b[1m\x1b[33m[@vue/reactivity-transform]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`);
  561. }
  562. exports.shouldTransform = shouldTransform;
  563. exports.transform = transform;
  564. exports.transformAST = transformAST;