98 lines
3.0 KiB
JavaScript
98 lines
3.0 KiB
JavaScript
|
|
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 };
|