98 lines
3.0 KiB
JavaScript
Raw Permalink Normal View History

2025-09-05 14:59:21 +08:00
import process from 'node:process';
import { walk } from 'estree-walker';
import { resolveModulePath } from 'exsolve';
import MagicString from 'magic-string';
import { relative, dirname, isAbsolute } from 'pathe';
import { SourceMapConsumer } from 'source-map-js';
const functions = [
"h",
"_createElementVNode",
"_createElementBlock",
"_createBlock",
"_createVNode",
"_createStaticVNode"
];
const testRe = new RegExp(`\\b(?:${functions.join("|")})\\(`);
function VueTracer(options) {
let {
enabled = "dev",
resolveRecordEntryPath = true
} = options || {};
if (enabled === false)
return;
const pathRecordDist = resolveModulePath("vite-plugin-vue-tracer/client/record", { from: import.meta.url });
const getRecordPath = (id) => {
if (!resolveRecordEntryPath)
return "vite-plugin-vue-tracer/client/record";
let related = relative(dirname(id), pathRecordDist);
if (!related.startsWith("./") && !isAbsolute(related))
related = `./${related}`;
return related;
};
return {
name: "vite-plugin-vue-tracer",
enforce: "post",
configResolved(config) {
if (enabled === "dev")
enabled = config.command === "serve";
else if (enabled === "prod")
enabled = config.command === "build";
},
transform(code, id) {
if (!enabled)
return;
if (this.environment.name !== "client")
return;
if (!code.includes("_sfc_render("))
return;
if (!code.match(testRe))
return;
if (code.includes("_tracer("))
return;
function offsetToPos(index) {
const lines = code.slice(0, index).split("\n");
return {
line: lines.length,
column: lines[lines.length - 1].length
};
}
const map = this.getCombinedSourcemap();
const consumer = new SourceMapConsumer(map);
const s = new MagicString(code);
const ast = this.parse(code);
let hit = false;
walk(ast, {
enter(node) {
if (node.type !== "CallExpression" || node.callee.type !== "Identifier")
return;
if (!functions.includes(node.callee.name))
return;
const { start, end } = node;
const pos = offsetToPos(start);
const original = consumer.originalPositionFor(pos);
if (original.source === null)
return;
hit = true;
s.appendLeft(start, `_tracer(${original.line},${original.column},`);
s.appendRight(end, `)`);
}
});
if (!hit)
return;
const related = relative(process.cwd(), id);
s.prepend(`import { recordPosition as _tracerRecordPosition } from ${JSON.stringify(getRecordPath(id))}
`);
s.append(`
function _tracer(line, column, vnode) { return _tracerRecordPosition(${JSON.stringify(related)}, line, column, vnode) }
`);
return {
code: s.toString(),
map: s.generateMap({ hires: true })
};
}
};
}
export { VueTracer, VueTracer as default };