├── src ├── index.js ├── babel-sugar-v-model-v1.1.2-patch.js ├── babel-sugar-inject-h.js ├── babel-sugar-setup-functional.js └── babel-sugar-setup-ref.js ├── package.json ├── .gitignore ├── README.zh-CN.md └── README.md /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return { 3 | plugins: [ 4 | require('./babel-sugar-inject-h'), 5 | require('./babel-sugar-setup-functional'), 6 | require('./babel-sugar-setup-ref'), 7 | require('./babel-sugar-v-model-v1.1.2-patch') 8 | ] 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-preset-vca-jsx", 3 | "version": "0.3.5", 4 | "description": "Support for automatic import of createElement as h and setup functional component syntax and setup template refs", 5 | "main": "./src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/luwanquan/babel-preset-vca-jsx.git" 12 | }, 13 | "keywords": [ 14 | "vue", 15 | "@vue/composition-api", 16 | "jsx", 17 | "babel-preset", 18 | "auto-import", 19 | "createElement", 20 | "h", 21 | "template refs" 22 | ], 23 | "author": "luwanquan", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/luwanquan/babel-preset-vca-jsx/issues" 27 | }, 28 | "homepage": "https://github.com/luwanquan/babel-preset-vca-jsx#readme", 29 | "dependencies": { 30 | "@babel/plugin-syntax-jsx": "^7.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /src/babel-sugar-v-model-v1.1.2-patch.js: -------------------------------------------------------------------------------- 1 | 2 | const autoImportVue = (t, path) => { 3 | const importSource = "vue"; 4 | const imports = path.node.body.filter(t.isImportDeclaration); 5 | const imported = imports.find(node => node.source.value === importSource); 6 | const autoImport = () => 7 | path.unshiftContainer( 8 | "body", 9 | t.importDeclaration( 10 | [t.importDefaultSpecifier(t.identifier("Vue"))], 11 | t.stringLiteral(importSource) 12 | ) 13 | ); 14 | if (!imported) { 15 | autoImport(); 16 | return; 17 | } 18 | const defaultSpecifier = imported.specifiers.find(t.isImportDefaultSpecifier); 19 | if (!defaultSpecifier) { 20 | imported.specifiers.push(t.importDefaultSpecifier(t.identifier("Vue"))); 21 | } else if (defaultSpecifier.local.name !== "Vue") { 22 | autoImport(); 23 | } 24 | }; 25 | 26 | module.exports = ({ types: t }) => { 27 | return { 28 | visitor: { 29 | Program(p) { 30 | p.traverse({ 31 | "ObjectMethod|ObjectProperty"(path) { 32 | if (path.node.key.name !== "setup") return; 33 | path.traverse({ 34 | JSXAttribute(path) { 35 | const n = path.get("name"); 36 | const isInputOrModel = n.node.name === "on-input" || n.node.name === "on-change" || n.node.name === "model"; 37 | if (!isInputOrModel) return; 38 | path.traverse({ 39 | MemberExpression(path) { 40 | const obj = path.get("object"); 41 | const prop = path.get("property"); 42 | if (t.isThisExpression(obj) && t.isIdentifier(prop) && prop.node.name === "$set") { 43 | autoImportVue(t, p); 44 | obj.replaceWith(t.identifier("Vue")); 45 | prop.replaceWith(t.identifier("set")); 46 | } 47 | } 48 | }); 49 | } 50 | }); 51 | } 52 | }); 53 | } 54 | } 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /src/babel-sugar-inject-h.js: -------------------------------------------------------------------------------- 1 | const syntaxJsx = require('@babel/plugin-syntax-jsx').default; 2 | 3 | const importSource = '@vue/composition-api'; 4 | 5 | const hasJSX = (t, path) => { 6 | const JSXChecker = { 7 | hasJSX: false 8 | }; 9 | path.traverse({ 10 | JSXElement() { 11 | this.hasJSX = true 12 | } 13 | }, JSXChecker); 14 | return JSXChecker.hasJSX; 15 | }; 16 | 17 | // remove `var h = this.$createElement;` in `setup()` 18 | const remove$createElement = (t, path) => { 19 | path.traverse({ 20 | ObjectMethod(p) { 21 | const isSetup = p.node.key.name === 'setup'; 22 | if (!isSetup) return; 23 | p.traverse({ 24 | VariableDeclaration(varPath) { 25 | varPath.traverse({ 26 | MemberExpression(p) { 27 | if (t.isThisExpression(p.node.object) && t.isIdentifier(p.node.property) && p.node.property.name === '$createElement') { 28 | varPath.remove(); 29 | } 30 | } 31 | }) 32 | } 33 | }); 34 | } 35 | }); 36 | }; 37 | 38 | // auto import `createElement as h` from `@vue/composition-api` 39 | const autoImportH = (t, path) => { 40 | if (hasJSX(t, path)) { 41 | const importNodes = path.get('body').filter(p => p.isImportDeclaration()).map(p => p.node); 42 | const vcaImportNodes = importNodes.filter(p => p.source.value === importSource); 43 | const hasH = vcaImportNodes.some(p => ( 44 | p.specifiers.some(s => ( 45 | t.isImportSpecifier(s) && s.imported.name === 'createElement' && s.local.name === 'h' 46 | )) 47 | )); 48 | if (!hasH) { 49 | const vcaImportSpecifier = t.importSpecifier(t.identifier('h'), t.identifier('createElement')); 50 | if (vcaImportNodes.length > 0) { 51 | vcaImportNodes[0].specifiers.push(vcaImportSpecifier); 52 | } else { 53 | path.unshiftContainer('body', t.importDeclaration([vcaImportSpecifier], t.stringLiteral(importSource))); 54 | } 55 | } 56 | } 57 | } 58 | 59 | module.exports = ({ types: t }) => { 60 | return { 61 | inherits: syntaxJsx, 62 | visitor: { 63 | Program(path) { 64 | remove$createElement(t, path); 65 | autoImportH(t, path); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # babel-preset-vca-jsx 2 | > 支持自动导入`createElement as h`以及`setup`函数式组件语法和`setup`中的模板refs引用 3 | 4 | ## 功能点 5 | 6 | 1. 写`JSX`时自动导入`createElement as h` 7 | 1. 默认只有`setup()`的函数式组件语法 8 | ```javascript 9 | const Hello = (prop, ctx) => { 10 | const state = ref('hello world'); 11 | return () =>

{state.value}

; 12 | }; 13 | ``` 14 | 1. 在`setup()`返回的渲染函数上使用`JSX`分配模板引用 15 | ```javascript 16 | const Hello = createComponent({ 17 | setup() { 18 | const root = ref(null); 19 | watch(() => console.log(root.value)); //

...

20 | /* 21 | return () => h('h1', { 22 | ref: root 23 | }, 'hello world!'); 24 | */ 25 | return () =>

hello world!

26 | } 27 | }); 28 | ``` 29 | 1. 修复 [@vue/babel-sugar-v-model@1.1.2](https://github.com/vuejs/jsx/tree/dev/packages/babel-sugar-v-model) 在 `setup()` 中调用 `this` 的问题 30 | 31 | 32 | ## [案例](https://codesandbox.io/s/babel-preset-vca-jsx-example-7k5xs) 33 | 34 | 编译前 35 | ```javascript 36 | import { ref } from '@vue/composition-api'; 37 | 38 | const Hello = (prop, ctx) => { 39 | const state = ref('Hello World!'); 40 | return () => ( 41 |

{state.value}

42 | ); 43 | }; 44 | ``` 45 | 46 | 编译后 47 | ```javascript 48 | import { ref, createElement as h } from '@vue/composition-api'; 49 | 50 | const Hello = { 51 | setup: (prop, ctx) => { 52 | const state = ref('Hello World!'); 53 | return () => { 54 | return h('h1', state.value); 55 | }; 56 | } 57 | }; 58 | ``` 59 | 60 | ## 使用前提 61 | 62 | 已安装`@vue/composition-api`和`@vue/cli-plugin-babel`的项目 63 | 64 | 65 | 66 | ## 如何使用? 67 | 68 | 1. 安装 69 | 70 | ```shell 71 | npm install babel-preset-vca-jsx --save-dev 72 | ``` 73 | 74 | 2. 配置 `babel.config.js` 75 | 76 | ```javascript 77 | module.exports = { 78 | presets: [ 79 | "vca-jsx", 80 | "@vue/cli-plugin-babel/preset" 81 | ] 82 | }; 83 | ``` 84 | 85 | 86 | ## 注意 87 | 88 | - 这里需要区分默认的 `functional`组件和基于 `composition-api` 的 `functional`组件的概念 89 | 90 | - 默认的 `functional`组件实质是 `render`函数,`jsx`中的简写写法如下 91 | ``` javascript 92 | const Test = ({ props, children, data, ... }) => { 93 | return

Hello World!

; 94 | }; 95 | ``` 96 | **注:变量名首字母必须为大写,具体回调参数见[详情](https://cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6)** 97 | 98 | - 基于本插件的 `composition-api functional`实质是 `setup`函数,`jsx`中的简写写法如下 99 | ``` javascript 100 | const Test = (props, { refs, emit, ... }) => { 101 | return () =>

Hello World!

; 102 | }; 103 | ``` 104 | **注:与默认`functional`的区别是返回了一个`render`函数** 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-preset-vca-jsx 2 | > Support for automatic import of `createElement as h` and `setup` functional component syntax and `setup` template refs 3 | 4 | ## Feature 5 | 6 | 1. Automatically import `createElement as h` when writing `JSX` 7 | 1. The functional component syntax of the `setup()` by default 8 | ```javascript 9 | const Hello = (prop, ctx) => { 10 | const state = ref('hello world'); 11 | return () =>

{state.value}

; 12 | }; 13 | ``` 14 | 1. Allocating template refs with `JSX` on the render function returned by `setup()` 15 | ```javascript 16 | const Hello = createComponent({ 17 | setup() { 18 | const root = ref(null); 19 | watch(() => console.log(root.value)); //

...

20 | /* 21 | return () => h('h1', { 22 | ref: root 23 | }, 'hello world!'); 24 | */ 25 | return () =>

hello world!

26 | } 27 | }); 28 | ``` 29 | 1. Fixed [@vue/babel-sugar-v-model@1.1.2](https://github.com/vuejs/jsx/tree/dev/packages/babel-sugar-v-model) calling `this` in `setup()` 30 | 31 | 32 | ## [Example](https://codesandbox.io/s/babel-preset-vca-jsx-example-7k5xs) 33 | 34 | Before compiling 35 | ```javascript 36 | import { ref } from '@vue/composition-api'; 37 | 38 | const Hello = (prop, ctx) => { 39 | const state = ref('Hello World!'); 40 | return () => ( 41 |

{state.value}

42 | ); 43 | }; 44 | ``` 45 | 46 | After compilation 47 | ```javascript 48 | import { ref, createElement as h } from '@vue/composition-api'; 49 | 50 | const Hello = { 51 | setup: (prop, ctx) => { 52 | const state = ref('Hello World!'); 53 | return () => { 54 | return h('h1', state.value); 55 | }; 56 | } 57 | }; 58 | ``` 59 | 60 | ## Prerequisite 61 | 62 | Project with `@vue/composition-api` and `@vue/cli-plugin-babel` installed 63 | 64 | 65 | 66 | ## How to use? 67 | 68 | 1. Install 69 | 70 | ```shell 71 | npm install babel-preset-vca-jsx --save-dev 72 | ``` 73 | 74 | 1. Config `babel.config.js` 75 | 76 | ```javascript 77 | module.exports = { 78 | presets: [ 79 | "vca-jsx", 80 | "@vue/cli-plugin-babel/preset" 81 | ] 82 | }; 83 | ``` 84 | 85 | ## Note 86 | 87 | - Here we need to distinguish between the default `functional` component and the `composition-api-based` `functional` component. 88 | 89 | - The default `functional` component is essentially the` render` function. The shorthand in `jsx` is as follows 90 | ``` javascript 91 | const Test = ({ props, children, data, ... }) => { 92 | return

Hello World!

; 93 | }; 94 | ``` 95 | **Tips:The first letter of the variable name must be capitalized. For details of the callback parameters, see [Detail](https://vuejs.org/v2/guide/render-function.html)** 96 | 97 | - The `composition-api functional` component based on this plugin is essentially a `setup` function, and the shorthand in `jsx` is as follows 98 | ``` javascript 99 | const Test = (props, { refs, emit, ... }) => { 100 | return () =>

Hello World!

; 101 | }; 102 | ``` 103 | **Tips:The difference from the default `functional` is that a` render` function is returned** 104 | -------------------------------------------------------------------------------- /src/babel-sugar-setup-functional.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert object to functional component 3 | * @param t 4 | * @param path 5 | */ 6 | const convertSetupFunctionalComponent = (t, path) => { 7 | const properties = path.node.properties; 8 | const nameProp = properties.find(node => node.key.name === 'name'); 9 | const renderProp = properties.find(node => node.key.name === 'render'); 10 | const renderBody = renderProp.value.body; 11 | const renderParams = renderProp.value.params; 12 | const name = nameProp && nameProp.value.value; 13 | const params = [...renderParams.slice(renderParams.length && renderParams[0].name === 'h' ? 1 : 0)]; 14 | 15 | const props = [ 16 | t.objectProperty(t.identifier('setup'), t.arrowFunctionExpression(params, renderBody)) 17 | ]; 18 | if (name) { 19 | props.unshift(t.objectProperty(t.identifier('name'), t.stringLiteral(name))); 20 | } 21 | path.replaceWith(t.objectExpression(props)); 22 | } 23 | 24 | /** 25 | * Check if it's a setup functional componet declarator 26 | * @param t 27 | * @param path 28 | * @returns boolean 29 | */ 30 | const isSetupFunctionalComponentDeclarator = (t, path) => { 31 | const properties = path.node.properties; 32 | 33 | const isFunctional = properties.filter(node => { 34 | return ( 35 | t.isObjectProperty(node) && 36 | ((node.key.name === 'functional' && node.value.value === true) || node.key.name === 'render') 37 | ); 38 | }).length === 2; 39 | 40 | if (!isFunctional) return false; 41 | 42 | const renderBody = properties.find(node => node.key.name === 'render').value.body; 43 | 44 | const returnStatement = t.isBlockStatement(renderBody) && renderBody.body.find(node => t.isReturnStatement(node)); 45 | 46 | return ( 47 | t.isArrowFunctionExpression(renderBody) || 48 | t.isFunctionExpression(renderBody) || 49 | (returnStatement && t.isArrowFunctionExpression(returnStatement.argument)) || 50 | (returnStatement && t.isFunctionExpression(returnStatement.argument)) 51 | ); 52 | } 53 | 54 | module.exports = ({ types: t }) => { 55 | return { 56 | visitor: { 57 | Program(path) { 58 | path.traverse({ 59 | ExportDefaultDeclaration(path) { 60 | const declaration = path.get('declaration'); 61 | if ( 62 | !t.isObjectExpression(declaration) || 63 | !isSetupFunctionalComponentDeclarator(t, declaration) 64 | ) { 65 | return; 66 | } 67 | convertSetupFunctionalComponent(t, declaration); 68 | }, 69 | VariableDeclaration(path) { 70 | if ( 71 | path.node.declarations.length !== 1 || 72 | !t.isVariableDeclarator(path.node.declarations[0]) || 73 | !t.isObjectExpression(path.node.declarations[0].init) 74 | ) { 75 | return; 76 | } 77 | const declarator = path.get('declarations')[0]; 78 | const init = declarator.get('init'); 79 | if (!isSetupFunctionalComponentDeclarator(t, init)) { 80 | return; 81 | } 82 | convertSetupFunctionalComponent(t, init); 83 | } 84 | }); 85 | } 86 | } 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /src/babel-sugar-setup-ref.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition like `const root = ref(null);`. 3 | * @param {*} t 4 | * @param {*} path 5 | * @returns {boolean} 6 | */ 7 | const isRefCallee = (t, path) => { 8 | if (!t.isVariableDeclaration(path)) return; 9 | const declarations = path.get("declarations"); 10 | if (declarations.length !== 1) return; 11 | const init = declarations[0].get("init"); 12 | if (!t.isCallExpression(init)) return; 13 | return ( 14 | init.node.callee.name === "ref" && t.isNullLiteral(init.get("arguments")[0]) 15 | ); 16 | }; 17 | 18 | /** 19 | * Match definedRefs in JSX 20 | * @param {*} t 21 | * @param {*} path ObjectMethod 22 | * @returns {*} `refs` to be processed 23 | */ 24 | const matchRefsInJSX = (t, path, definedRefs = []) => { 25 | const refs = []; 26 | path.traverse({ 27 | // Definition like `h('h1', { ref: ... })`. 28 | CallExpression(path) { 29 | if (path.node.callee.name !== "h") return; 30 | const [, obj] = path.get("arguments"); 31 | if (!obj || !t.isObjectExpression(obj)) return; 32 | const props = obj.get("properties"); 33 | const refProp = props.find(path => path.node.key.name === "ref"); 34 | if (!refProp) return; 35 | const value = refProp.get("value"); 36 | if (t.isIdentifier(value)) { 37 | const ref = value.node.name; 38 | if (definedRefs.indexOf(ref) > -1) { 39 | refs.push(ref); 40 | value.replaceWith(t.stringLiteral(ref)); 41 | } 42 | } else if (t.isStringLiteral(value)) { 43 | const ref = value.node.value; 44 | if (definedRefs.indexOf(ref) > -1) { 45 | refs.push(ref); 46 | } 47 | } 48 | }, 49 | // Definition like `

`. 50 | JSXAttribute(path) { 51 | if (path.node.name.name !== "ref") return; 52 | const value = path.get("value"); 53 | if (t.isJSXExpressionContainer(value)) { 54 | const exp = value.get("expression"); 55 | if (!exp || !t.isIdentifier(exp)) return; 56 | const ref = exp.node.name; 57 | if (definedRefs.indexOf(ref) > -1) { 58 | refs.push(ref); 59 | value.replaceWith(t.stringLiteral(ref)); 60 | } 61 | } else if (t.isStringLiteral(value)) { 62 | const ref = value.node.value; 63 | if (definedRefs.indexOf(ref) > -1) { 64 | refs.push(ref); 65 | } 66 | } 67 | } 68 | }); 69 | return refs; 70 | }; 71 | 72 | const autoImportVue = (t, path) => { 73 | const importSource = "vue"; 74 | const imports = path.node.body.filter(t.isImportDeclaration); 75 | const imported = imports.find(node => node.source.value === importSource); 76 | const autoImport = () => 77 | path.unshiftContainer( 78 | "body", 79 | t.importDeclaration( 80 | [t.importDefaultSpecifier(t.identifier("Vue"))], 81 | t.stringLiteral(importSource) 82 | ) 83 | ); 84 | if (!imported) { 85 | autoImport(); 86 | return; 87 | } 88 | const defaultSpecifier = imported.specifiers.find(t.isImportDefaultSpecifier); 89 | if (!defaultSpecifier) { 90 | imported.specifiers.push(t.importDefaultSpecifier(t.identifier("Vue"))); 91 | } else if (defaultSpecifier.local.name !== "Vue") { 92 | autoImport(); 93 | } 94 | }; 95 | 96 | /** 97 | * auto import lifeCycle 98 | * @param {*} t 99 | * @param {*} path Program 100 | * @param {*} lifeCycles 101 | */ 102 | const autoImportLifeCycle = (t, path, lifeCycles = []) => { 103 | const importSource = "@vue/composition-api"; 104 | const imports = path.node.body.filter(t.isImportDeclaration); 105 | const imported = imports.find(node => node.source.value === importSource); 106 | const genImportSpecifiers = list => 107 | list.map(name => t.importSpecifier(t.identifier(name), t.identifier(name))); 108 | if (!imported) { 109 | path.unshiftContainer( 110 | "body", 111 | t.importDeclaration( 112 | genImportSpecifiers(lifeCycles), 113 | t.stringLiteral(importSource) 114 | ) 115 | ); 116 | return lifeCycles; 117 | } 118 | const specifiers = imported.specifiers.filter(t.isImportSpecifier); 119 | const names = lifeCycles.map(name => { 120 | const node = specifiers.find(node => name === node.imported.name); 121 | if (node) { 122 | return node.local.name; 123 | } else { 124 | imported.specifiers.push(...genImportSpecifiers([name])); 125 | return name; 126 | } 127 | }); 128 | return names; 129 | }; 130 | 131 | /** 132 | * inject onMounted(() => { 133 | * Vue.$nextTick(() => { 134 | * ... 135 | * }); 136 | * }); 137 | * @param {*} t 138 | * @param {*} path ObjectMethod 139 | * @param {*} lifeCycles 140 | * @param {*} refs 141 | */ 142 | const injectLifeCycle = (t, path, lifeCycles = [], refs = []) => { 143 | const [prop, ctx] = path.get("params"); 144 | const props = ["refs"]; 145 | const getProperties = () => 146 | props.map(val => 147 | t.objectProperty(t.identifier(val), t.identifier(val), false, true) 148 | ); 149 | if (!prop) { 150 | path.node.params.push(t.identifier("_")); 151 | } 152 | if (!ctx) { 153 | path.node.params.push(t.objectPattern(getProperties())); 154 | } else { 155 | if (t.isObjectPattern(ctx)) { 156 | const refsProp = ctx.node.properties.find( 157 | node => node.key.name === props[0] 158 | ); 159 | if (!refsProp) { 160 | ctx.node.properties.push(...getProperties()); 161 | } else { 162 | props[0] = refsProp.value.name; 163 | } 164 | } else { 165 | props.unshift(ctx.node.name); 166 | } 167 | } 168 | 169 | const identifiers = props.map(val => t.identifier(val)); 170 | const statements = refs.map(val => { 171 | return t.expressionStatement( 172 | t.assignmentExpression( 173 | "=", 174 | t.memberExpression(t.identifier(val), t.identifier("value")), 175 | t.logicalExpression( 176 | "||", 177 | t.memberExpression( 178 | identifiers.length > 1 179 | ? t.memberExpression(...identifiers) 180 | : identifiers[0], 181 | t.identifier(val) 182 | ), 183 | t.nullLiteral() 184 | ) 185 | ) 186 | ); 187 | }); 188 | 189 | const nextTick = t.expressionStatement( 190 | t.callExpression( 191 | t.memberExpression(t.identifier("Vue"), t.identifier("nextTick")), 192 | [t.arrowFunctionExpression([], t.blockStatement(statements))] 193 | ) 194 | ); 195 | 196 | const content = lifeCycles.map(val => 197 | t.expressionStatement( 198 | t.callExpression(t.identifier(val), [ 199 | t.arrowFunctionExpression( 200 | [], 201 | t.blockStatement([...statements, nextTick]) 202 | ) 203 | ]) 204 | ) 205 | ); 206 | 207 | path.get("body").unshiftContainer("body", content); 208 | }; 209 | 210 | module.exports = ({ types: t }) => { 211 | return { 212 | visitor: { 213 | Program(p) { 214 | p.traverse({ 215 | "ObjectMethod|ObjectProperty"(path) { 216 | if (path.node.key.name !== "setup") return; 217 | let container = path; 218 | if (t.isObjectProperty(path)) { 219 | container = path.get("value"); 220 | } 221 | if (!t.isBlockStatement(container.get("body"))) return; 222 | const body = container.get("body").get("body"); 223 | const definedRefs = body 224 | .filter(path => isRefCallee(t, path)) 225 | .map(path => path.get("declarations")[0].node.id.name); 226 | if (!definedRefs.length) return; 227 | 228 | const refs = matchRefsInJSX(t, container, definedRefs); 229 | 230 | if (!refs.length) return; 231 | autoImportVue(t, p); 232 | const lifeCycles = autoImportLifeCycle(t, p, [ 233 | "onMounted", 234 | "onUpdated" 235 | ]); 236 | 237 | injectLifeCycle(t, container, lifeCycles, refs); 238 | } 239 | }); 240 | } 241 | } 242 | }; 243 | }; 244 | --------------------------------------------------------------------------------