UNPKG

insert-refs-promise-loader

Version:

A Webpack loader to handle this.$refs usage in Vue components

195 lines (185 loc) 7.21 kB
const { parseComponent, compile } = require("vue-template-compiler"); const babelParser = require("@babel/parser"); const babelTraverse = require("@babel/traverse").default; const babelGenerator = require("@babel/generator").default; const t = require("@babel/types"); const fs = require("fs"); const path = require("path"); function addPropToProps(propsNode, newProp) { const newPropNode = babelParser.parseExpression(newProp).properties[0]; if (propsNode.value.properties.find((p) => p.key.name === "asyncLoadProp")) { return; // 已经存在,不处理 } propsNode.value.properties.unshift(newPropNode); } module.exports = function (source) { try { const options = this.query; const subPacks = options.subPacks || []; const pages = options.pages || []; // 是否在subPacks中 const isInSubPacks = subPacks.length === 0 || subPacks.some((includePath) => { const absoluteIncludePath = path.resolve(includePath); return resourcePath.startsWith(absoluteIncludePath); }); // 是否在pages中 const isInPages = pages.length === 0 || pages.some((includePath) => { const absoluteIncludePath = path.resolve(includePath); return resourcePath.startsWith(absoluteIncludePath); }); const component = parseComponent(source); const script = component.script ? component.script.content : null; const template = component.template ? component.template.content : null; if (!script && !template) { return source; // 没有脚本内容,直接返回原始代码 } const ast = babelParser.parse(script, { sourceType: "module", plugins: ["jsx"], attachComment: true, }); if (isInPages) { const visited = new Set(); babelTraverse(ast, { MemberExpression(path) { if (path.node.property.name === "$refs") { const functionPath = path.getFunctionParent(); if (functionPath && !functionPath.node.async) { functionPath.node.async = true; } const blockPath = path.findParent((p) => p.isBlockStatement()); if (blockPath && !visited.has(blockPath)) { visited.add(blockPath); // 标记节点为已访问 const timestamp = new Date().getTime(); let promiseCode = ` let promise_${timestamp} = new Promise((resolve, reject) => { this.asyncLoad = resolve; }); await promise_${timestamp}; `; const promiseNode = babelParser.parse(promiseCode, { sourceType: "module", }).program.body; blockPath.node.body.unshift(...promiseNode); } } }, }); // var modifiedTemplate = template?.replace(/(<[a-zA-Z]+[^>]*ref="[\w]+"[^>]*)(>)/g, (match, p1, p2) => { // return p1 + ` asyncLoadProp="asyncLoad"${p2}`; // }); ast.program.body.forEach((node) => { if (node.type === "ExportDefaultDeclaration") { const dataNode = node.declaration.properties.find((p) => p.key.name === "data"); if (dataNode) { // const data = dataNode.value.properties[0]; const newData = babelParser.parseExpression(`{ asyncLoad:()=>{} }`).properties[0]; dataNode.body.body.find((item) => item.type == "ReturnStatement").argument.properties.unshift(newData); } else { const dataCode = `{ data(){ return { asyncLoad:()=>{} } } }`; const dataNode = babelParser.parseExpression(dataCode).properties[0]; node.declaration.properties.unshift(dataNode); } } }); } if (isInSubPacks) { const newProp = `{ asyncLoadProp:{ type:Function, default:()=>{} } }`; const newWatch = `{ asyncLoadProp:{ handler(val){ val() } } }`; ast.program.body.forEach((node) => { if (node.type === "ExportDefaultDeclaration") { let props = node.declaration.properties.find((p) => p.key.name === "props"); if (props) { addPropToProps(props, newProp); } else { const propsCode = `{ props:{ asyncLoadProp:{ type:Function, default:()=>{} } } }`; const propsNode = babelParser.parseExpression(propsCode).properties[0]; node.declaration.properties.unshift(propsNode); } let watch = node.declaration.properties.find((p) => p.key.name === "watch"); if (watch) { watch.value.properties.unshift(...babelParser.parseExpression(newWatch).properties); } else { const watchCode = `{ watch:{ asyncLoadProp:{ handler(val){ val() } } } }`; const watchNode = babelParser.parseExpression(watchCode).properties[0]; node.declaration.properties.unshift(watchNode); } let mounted = node.declaration.properties.find((p) => p.key.name === "mounted"); if (mounted) { const mountedCode = `this.asyncLoadProp&&this.asyncLoadProp();`; const mountedNode = babelParser.parse(mountedCode, { sourceType: "module", }).program.body; mounted.value ? mounted.value.body.body.unshift(...mountedCode) : mounted.body.body.unshift(...mountedNode); } else { const mountedCode = `{ mounted(){ this.asyncLoadProp&&this.asyncLoadProp(); } }`; const mountedNode = babelParser.parseExpression(mountedCode).properties[0]; node.declaration.properties.unshift(mountedNode); } } }); } const { code: modifiedScript } = babelGenerator(ast, { comments: true, retainLines: true, compact: false, }); let modifiedSource = source.replace(script, modifiedScript); if (isInPages) { modifiedSource = modifiedSource.replace(/(<[a-zA-Z]+[^>]*ref="[\w]+"[^>]*)(>)/g, (match, p1, p2) => { if (p1.includes("asyncLoadProp")) { return match; } return p1 + ` :asyncLoadProp="asyncLoad"${p2}`; }); } // fs.appendFileSync("console.log", modifiedSource, "utf8"); return modifiedSource; } catch (error) { console.log(this.resourcePath); console.log(error); return source; } };