insert-refs-polling-loader
Version:
A Webpack loader to handle this.$refs usage in Vue components
122 lines (105 loc) • 4.45 kB
JavaScript
const { parseComponent } = require("vue-template-compiler");
const babelParser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generate = require("@babel/generator").default;
const t = require("@babel/types");
const fs = require("fs");
const path = require("path");
module.exports = function (source) {
const _this = this;
try {
const options = _this.query;
const inclusion = options.inclusion || [];
const resourcePath = _this.resourcePath;
// 检查是否需要处理当前文件
const shouldProcess =
inclusion.length === 0 ||
inclusion.some((includePath) => {
const absoluteIncludePath = path.resolve(includePath);
return resourcePath.startsWith(absoluteIncludePath);
});
if (!shouldProcess) {
return source;
}
const parsed = parseComponent(source);
let modified = false; // 标记是否有修改
if (parsed.script) {
const scriptContent = parsed.script.content;
// 解析脚本内容为 AST
const ast = babelParser.parse(scriptContent, {
sourceType: "module",
plugins: ["jsx", "typescript"],
attachComment: true, // 保留注释
});
// 使用 Set 来标记已经处理过的节点,防止重复处理
const visited = new Set();
// 遍历 AST,查找并修改 this.$refs 使用语句
traverse(ast, {
MemberExpression(path) {
if (
(t.isThisExpression(path.node.object) || t.isIdentifier(path.node.object)) &&
t.isIdentifier(path.node.property, { name: "$refs" }) &&
(t.isMemberExpression(path.parent) || t.isOptionalMemberExpression(path.parent))
) {
// 修改函数为异步函数
const functionPath = path.getFunctionParent();
if (functionPath && !functionPath.node.async) {
functionPath.node.async = true;
}
const parentPath = path.findParent((p) => p.isBlockStatement());
if (parentPath && !visited.has(parentPath)) {
visited.add(parentPath); // 标记节点为已访问
const refs = generate(path.node).code;
const refPath = generate(path.parentPath.node).code;
const fullRefPath = t.isMemberExpression(path.parentPath.parent) ? generate(path.parentPath.parent).code : "true";
const timestamp = new Date().getTime();
// 创建轮询代码
const pollingCode = `
let resolveF_${timestamp};
let promise_${timestamp} = new Promise((resolve, reject) => {
resolveF_${timestamp} = resolve;
});
let countdown = 5 * 1000;
let timer_${timestamp} = setInterval(() => {
countdown -= 200;
if ((${refPath} && ${fullRefPath}) || countdown <= 0) {
clearInterval(timer_${timestamp});
console.log("countdown",countdown);
resolveF_${timestamp}();
}
}, 200);
await promise_${timestamp};
`;
// 创建 AST 节点
const pollingNodes = babelParser.parse(pollingCode, {
sourceType: "module",
}).program.body;
// 在 this.$refs 使用语句前插入轮询代码
parentPath.node.body.unshift(...pollingNodes);
modified = true;
}
}
},
});
if (modified) {
// 生成修改后的脚本内容
const { code } = generate(ast, {
retainLines: true, // 保持行号
compact: false, // 不压缩
comments: true, // 保留注释
});
// 将处理后的脚本内容替换到源代码中
const scriptRegex = /(<script[^>]*>)([\s\S]*?)(<\/script>)/i;
const modifiedSource = source.replace(scriptRegex, (match, p1, p2, p3) => {
return `${p1}\n${code}\n${p3}`;
});
// fs.appendFileSync("console.log", modifiedSource, "utf8");
console.log(_this.resourcePath, "完成$refs轮询插入");
return modifiedSource;
}
}
} catch (error) {
console.log(this.resourcePath, error);
}
return source;
};