reactivity-transform.cjs.js 24 KB

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