You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1395 lines
39 KiB
1395 lines
39 KiB
import process from 'node:process'; |
|
import sirv from 'sirv'; |
|
import c from 'picocolors'; |
|
import { debounce } from 'perfect-debounce'; |
|
import { dirname, resolve, isAbsolute, join } from 'node:path'; |
|
import { fileURLToPath } from 'node:url'; |
|
import { Buffer as Buffer$1 } from 'node:buffer'; |
|
import { createFilter } from '@rollup/pluginutils'; |
|
import Debug from 'debug'; |
|
import { parse } from 'error-stack-parser-es'; |
|
import fs from 'fs-extra'; |
|
import { createServer } from 'node:http'; |
|
|
|
const DEFAULT_TIMEOUT = 6e4; |
|
function defaultSerialize(i) { |
|
return i; |
|
} |
|
const defaultDeserialize = defaultSerialize; |
|
const { clearTimeout, setTimeout: setTimeout$1 } = globalThis; |
|
const random = Math.random.bind(Math); |
|
function createBirpc(functions, options) { |
|
const { |
|
post, |
|
on, |
|
eventNames = [], |
|
serialize = defaultSerialize, |
|
deserialize = defaultDeserialize, |
|
resolver, |
|
timeout = DEFAULT_TIMEOUT |
|
} = options; |
|
const rpcPromiseMap = /* @__PURE__ */ new Map(); |
|
let _promise; |
|
const rpc = new Proxy({}, { |
|
get(_, method) { |
|
if (method === "$functions") |
|
return functions; |
|
const sendEvent = (...args) => { |
|
post(serialize({ m: method, a: args, t: "q" })); |
|
}; |
|
if (eventNames.includes(method)) { |
|
sendEvent.asEvent = sendEvent; |
|
return sendEvent; |
|
} |
|
const sendCall = async (...args) => { |
|
await _promise; |
|
return new Promise((resolve, reject) => { |
|
const id = nanoid(); |
|
let timeoutId; |
|
if (timeout >= 0) { |
|
timeoutId = setTimeout$1(() => { |
|
reject(new Error(`[birpc] timeout on calling "${method}"`)); |
|
rpcPromiseMap.delete(id); |
|
}, timeout).unref?.(); |
|
} |
|
rpcPromiseMap.set(id, { resolve, reject, timeoutId }); |
|
post(serialize({ m: method, a: args, i: id, t: "q" })); |
|
}); |
|
}; |
|
sendCall.asEvent = sendEvent; |
|
return sendCall; |
|
} |
|
}); |
|
_promise = on(async (data, ...extra) => { |
|
const msg = deserialize(data); |
|
if (msg.t === "q") { |
|
const { m: method, a: args } = msg; |
|
let result, error; |
|
const fn = resolver ? resolver(method, functions[method]) : functions[method]; |
|
if (!fn) { |
|
error = new Error(`[birpc] function "${method}" not found`); |
|
} else { |
|
try { |
|
result = await fn.apply(rpc, args); |
|
} catch (e) { |
|
error = e; |
|
} |
|
} |
|
if (msg.i) { |
|
if (error && options.onError) |
|
options.onError(error, method, args); |
|
post(serialize({ t: "s", i: msg.i, r: result, e: error }), ...extra); |
|
} |
|
} else { |
|
const { i: ack, r: result, e: error } = msg; |
|
const promise = rpcPromiseMap.get(ack); |
|
if (promise) { |
|
clearTimeout(promise.timeoutId); |
|
if (error) |
|
promise.reject(error); |
|
else |
|
promise.resolve(result); |
|
} |
|
rpcPromiseMap.delete(ack); |
|
} |
|
}); |
|
return rpc; |
|
} |
|
const cacheMap = /* @__PURE__ */ new WeakMap(); |
|
function cachedMap(items, fn) { |
|
return items.map((i) => { |
|
let r = cacheMap.get(i); |
|
if (!r) { |
|
r = fn(i); |
|
cacheMap.set(i, r); |
|
} |
|
return r; |
|
}); |
|
} |
|
function createBirpcGroup(functions, channels, options = {}) { |
|
const getChannels = () => typeof channels === "function" ? channels() : channels; |
|
const getClients = (channels2 = getChannels()) => cachedMap(channels2, (s) => createBirpc(functions, { ...options, ...s })); |
|
const broadcastProxy = new Proxy({}, { |
|
get(_, method) { |
|
const client = getClients(); |
|
const callbacks = client.map((c) => c[method]); |
|
const sendCall = (...args) => { |
|
return Promise.all(callbacks.map((i) => i(...args))); |
|
}; |
|
sendCall.asEvent = (...args) => { |
|
callbacks.map((i) => i.asEvent(...args)); |
|
}; |
|
return sendCall; |
|
} |
|
}); |
|
function updateChannels(fn) { |
|
const channels2 = getChannels(); |
|
fn?.(channels2); |
|
return getClients(channels2); |
|
} |
|
getClients(); |
|
return { |
|
get clients() { |
|
return getClients(); |
|
}, |
|
functions, |
|
updateChannels, |
|
broadcast: broadcastProxy, |
|
/** |
|
* @deprecated use `broadcast` |
|
*/ |
|
// @ts-expect-error deprecated |
|
boardcast: broadcastProxy |
|
}; |
|
} |
|
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; |
|
function nanoid(size = 21) { |
|
let id = ""; |
|
let i = size; |
|
while (i--) |
|
id += urlAlphabet[random() * 64 | 0]; |
|
return id; |
|
} |
|
|
|
function createRPCServer(name, ws, functions, options = {}) { |
|
const event = `${name}:rpc`; |
|
const group = createBirpcGroup( |
|
functions, |
|
() => cachedMap( |
|
Array.from(ws?.clients || []), |
|
(socket) => { |
|
return { |
|
on: (fn) => { |
|
ws.on(event, (data, source) => { |
|
if (socket === source) |
|
fn(data); |
|
}); |
|
}, |
|
post: (data) => { |
|
socket.send(event, data); |
|
} |
|
}; |
|
} |
|
), |
|
options |
|
); |
|
ws.on("connection", () => { |
|
group.updateChannels(); |
|
}); |
|
return group.broadcast; |
|
} |
|
|
|
const DIR_DIST = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url)); |
|
const DIR_CLIENT = resolve(DIR_DIST, "../dist/client"); |
|
|
|
const DUMMY_LOAD_PLUGIN_NAME = "__load__"; |
|
|
|
var __defProp$1 = Object.defineProperty; |
|
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; |
|
var __publicField$1 = (obj, key, value) => { |
|
__defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value); |
|
return value; |
|
}; |
|
class Recorder { |
|
constructor(context) { |
|
this.context = context; |
|
__publicField$1(this, "transform", {}); |
|
__publicField$1(this, "resolveId", {}); |
|
__publicField$1(this, "transformCounter", {}); |
|
this.context = context; |
|
} |
|
recordTransform(id, info, preTransformCode) { |
|
id = this.context.normalizeId(id); |
|
if (!this.transform[id] || !this.transform[id].some((tr) => tr.result)) { |
|
this.transform[id] = [{ |
|
name: DUMMY_LOAD_PLUGIN_NAME, |
|
result: preTransformCode, |
|
start: info.start, |
|
end: info.start, |
|
sourcemaps: info.sourcemaps |
|
}]; |
|
this.transformCounter[id] = (this.transformCounter[id] || 0) + 1; |
|
} |
|
this.transform[id].push(info); |
|
} |
|
recordLoad(id, info) { |
|
id = this.context.normalizeId(id); |
|
this.transform[id] = [info]; |
|
this.transformCounter[id] = (this.transformCounter[id] || 0) + 1; |
|
} |
|
recordResolveId(id, info) { |
|
id = this.context.normalizeId(id); |
|
if (!this.resolveId[id]) |
|
this.resolveId[id] = []; |
|
this.resolveId[id].push(info); |
|
} |
|
invalidate(id) { |
|
id = this.context.normalizeId(id); |
|
delete this.transform[id]; |
|
} |
|
} |
|
|
|
async function openBrowser(address) { |
|
await import('open').then((r) => r.default(address, { newInstance: true })).catch(() => { |
|
}); |
|
} |
|
function removeVersionQuery(url) { |
|
if (url.includes("v=")) { |
|
return url.replace(/&v=\w+/, "").replace(/\?v=\w+/, "?").replace(/\?$/, ""); |
|
} |
|
return url; |
|
} |
|
|
|
var __defProp = Object.defineProperty; |
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; |
|
var __publicField = (obj, key, value) => { |
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); |
|
return value; |
|
}; |
|
class ViteInspectContext { |
|
constructor(options) { |
|
this.options = options; |
|
__publicField(this, "filter"); |
|
__publicField(this, "config"); |
|
__publicField(this, "recorderClient"); |
|
__publicField(this, "recorderServer"); |
|
this.filter = createFilter(options.include, options.exclude); |
|
this.recorderClient = new Recorder(this); |
|
this.recorderServer = new Recorder(this); |
|
} |
|
normalizeId(id) { |
|
if (this.options.removeVersionQuery !== false) |
|
return removeVersionQuery(id); |
|
return id; |
|
} |
|
getRecorder(ssr) { |
|
return ssr ? this.recorderServer : this.recorderClient; |
|
} |
|
resolveId(id = "", ssr = false) { |
|
if (id.startsWith("./")) |
|
id = resolve(this.config.root, id).replace(/\\/g, "/"); |
|
return this.resolveIdRecursive(id, ssr); |
|
} |
|
resolveIdRecursive(id, ssr = false) { |
|
const rec = this.getRecorder(ssr); |
|
const resolved = rec.resolveId[id]?.[0]?.result; |
|
return resolved ? this.resolveIdRecursive(resolved, ssr) : id; |
|
} |
|
getList(server) { |
|
const isVirtual = (pluginName) => pluginName !== DUMMY_LOAD_PLUGIN_NAME; |
|
const getDeps = (id) => Array.from(server.moduleGraph.getModuleById(id)?.importedModules || []).map((i) => i.id || "").filter(Boolean); |
|
return { |
|
root: this.config.root, |
|
modules: this.getModulesInfo(this.recorderClient, getDeps, isVirtual), |
|
ssrModules: this.getModulesInfo(this.recorderServer, getDeps, isVirtual) |
|
}; |
|
} |
|
getModulesInfo(recorder, getDeps, isVirtual) { |
|
function transformIdMap(recorder2) { |
|
return Object.values(recorder2.resolveId).reduce((map, ids2) => { |
|
ids2.forEach((id) => { |
|
var _a; |
|
map[_a = id.result] ?? (map[_a] = []); |
|
map[id.result].push(id); |
|
}); |
|
return map; |
|
}, {}); |
|
} |
|
const transformedIdMap = transformIdMap(recorder); |
|
const ids = new Set(Object.keys(recorder.transform).concat(Object.keys(transformedIdMap))); |
|
return Array.from(ids).sort().map((id) => { |
|
let totalTime = 0; |
|
const plugins = (recorder.transform[id] || []).filter((tr) => tr.result).map((transItem) => { |
|
const delta = transItem.end - transItem.start; |
|
totalTime += delta; |
|
return { name: transItem.name, transform: delta }; |
|
}).concat( |
|
// @ts-expect-error transform is optional |
|
(transformedIdMap[id] || []).map((idItem) => { |
|
return { name: idItem.name, resolveId: idItem.end - idItem.start }; |
|
}) |
|
); |
|
function getSize(str) { |
|
if (!str) |
|
return 0; |
|
return Buffer$1.byteLength(str, "utf8"); |
|
} |
|
return { |
|
id, |
|
deps: getDeps ? getDeps(id) : [], |
|
plugins, |
|
virtual: isVirtual(plugins[0]?.name || "", recorder.transform[id]?.[0].name || ""), |
|
totalTime, |
|
invokeCount: recorder.transformCounter?.[id] || 0, |
|
sourceSize: getSize(recorder.transform[id]?.[0]?.result), |
|
distSize: getSize(recorder.transform[id]?.[recorder.transform[id].length - 1]?.result) |
|
}; |
|
}); |
|
} |
|
getPluginMetrics(ssr = false) { |
|
const map = {}; |
|
const defaultMetricInfo = () => ({ |
|
transform: { invokeCount: 0, totalTime: 0 }, |
|
resolveId: { invokeCount: 0, totalTime: 0 } |
|
}); |
|
this.config.plugins.forEach((i) => { |
|
map[i.name] = { |
|
...defaultMetricInfo(), |
|
name: i.name, |
|
enforce: i.enforce |
|
}; |
|
}); |
|
const recorder = this.getRecorder(ssr); |
|
Object.values(recorder.transform).forEach((transformInfos) => { |
|
transformInfos.forEach(({ name, start, end }) => { |
|
if (name === DUMMY_LOAD_PLUGIN_NAME) |
|
return; |
|
if (!map[name]) |
|
map[name] = { ...defaultMetricInfo(), name }; |
|
map[name].transform.totalTime += end - start; |
|
map[name].transform.invokeCount += 1; |
|
}); |
|
}); |
|
Object.values(recorder.resolveId).forEach((resolveIdInfos) => { |
|
resolveIdInfos.forEach(({ name, start, end }) => { |
|
if (!map[name]) |
|
map[name] = { ...defaultMetricInfo(), name }; |
|
map[name].resolveId.totalTime += end - start; |
|
map[name].resolveId.invokeCount += 1; |
|
}); |
|
}); |
|
const metrics = Object.values(map).filter(Boolean).sort((a, b) => a.name.localeCompare(b.name)); |
|
return metrics; |
|
} |
|
} |
|
|
|
const debug = Debug("vite-plugin-inspect"); |
|
function hijackHook(plugin, name, wrapper) { |
|
if (!plugin[name]) |
|
return; |
|
debug(`hijack plugin "${name}"`, plugin.name); |
|
let order = plugin.order || plugin.enforce || "normal"; |
|
const hook = plugin[name]; |
|
if ("handler" in hook) { |
|
const oldFn = hook.handler; |
|
order += `-${hook.order || hook.enforce || "normal"}`; |
|
hook.handler = function(...args) { |
|
return wrapper(oldFn, this, args, order); |
|
}; |
|
} else if ("transform" in hook) { |
|
const oldFn = hook.transform; |
|
order += `-${hook.order || hook.enforce || "normal"}`; |
|
hook.transform = function(...args) { |
|
return wrapper(oldFn, this, args, order); |
|
}; |
|
} else { |
|
const oldFn = hook; |
|
plugin[name] = function(...args) { |
|
return wrapper(oldFn, this, args, order); |
|
}; |
|
} |
|
} |
|
function hijackPlugin(plugin, ctx) { |
|
hijackHook(plugin, "transform", async (fn, context, args, order) => { |
|
const code = args[0]; |
|
const id = args[1]; |
|
const ssr = args[2]?.ssr; |
|
let _result; |
|
let error; |
|
const start = Date.now(); |
|
try { |
|
_result = await fn.apply(context, args); |
|
} catch (_err) { |
|
error = _err; |
|
} |
|
const end = Date.now(); |
|
const result = error ? "[Error]" : typeof _result === "string" ? _result : _result?.code; |
|
if (ctx.filter(id)) { |
|
const sourcemaps = typeof _result === "string" ? null : _result?.map; |
|
const rec = ctx.getRecorder(ssr); |
|
rec.recordTransform(id, { |
|
name: plugin.name, |
|
result, |
|
start, |
|
end, |
|
order, |
|
sourcemaps, |
|
error: error ? parseError(error) : void 0 |
|
}, code); |
|
} |
|
if (error) |
|
throw error; |
|
return _result; |
|
}); |
|
hijackHook(plugin, "load", async (fn, context, args) => { |
|
const id = args[0]; |
|
const ssr = args[1]?.ssr; |
|
let _result; |
|
let error; |
|
const start = Date.now(); |
|
try { |
|
_result = await fn.apply(context, args); |
|
} catch (err) { |
|
error = err; |
|
} |
|
const end = Date.now(); |
|
const result = error ? "[Error]" : typeof _result === "string" ? _result : _result?.code; |
|
const sourcemaps = typeof _result === "string" ? null : _result?.map; |
|
if (result) { |
|
ctx.getRecorder(ssr).recordLoad(id, { |
|
name: plugin.name, |
|
result, |
|
start, |
|
end, |
|
sourcemaps, |
|
error: error ? parseError(error) : void 0 |
|
}); |
|
} |
|
if (error) |
|
throw error; |
|
return _result; |
|
}); |
|
hijackHook(plugin, "resolveId", async (fn, context, args) => { |
|
const id = args[0]; |
|
const ssr = args[2]?.ssr; |
|
let _result; |
|
let error; |
|
const start = Date.now(); |
|
try { |
|
_result = await fn.apply(context, args); |
|
} catch (err) { |
|
error = err; |
|
} |
|
const end = Date.now(); |
|
if (!ctx.filter(id)) { |
|
if (error) |
|
throw error; |
|
return _result; |
|
} |
|
const result = error ? stringifyError(error) : typeof _result === "object" ? _result?.id : _result; |
|
if (result && result !== id) { |
|
ctx.getRecorder(ssr).recordResolveId(id, { |
|
name: plugin.name, |
|
result, |
|
start, |
|
end, |
|
error |
|
}); |
|
} |
|
if (error) |
|
throw error; |
|
return _result; |
|
}); |
|
} |
|
function parseError(error) { |
|
const stack = parse(error, { allowEmpty: true }); |
|
const message = error.message || String(error); |
|
return { |
|
message, |
|
stack, |
|
raw: error |
|
}; |
|
} |
|
function stringifyError(err) { |
|
return String(err.stack ? err.stack : err); |
|
} |
|
|
|
const defaults = Object.freeze({ |
|
ignoreUnknown: false, |
|
respectType: false, |
|
respectFunctionNames: false, |
|
respectFunctionProperties: false, |
|
unorderedObjects: true, |
|
unorderedArrays: false, |
|
unorderedSets: false, |
|
excludeKeys: void 0, |
|
excludeValues: void 0, |
|
replacer: void 0 |
|
}); |
|
function objectHash(object, options) { |
|
if (options) { |
|
options = { ...defaults, ...options }; |
|
} else { |
|
options = defaults; |
|
} |
|
const hasher = createHasher(options); |
|
hasher.dispatch(object); |
|
return hasher.toString(); |
|
} |
|
const defaultPrototypesKeys = Object.freeze([ |
|
"prototype", |
|
"__proto__", |
|
"constructor" |
|
]); |
|
function createHasher(options) { |
|
let buff = ""; |
|
let context = /* @__PURE__ */ new Map(); |
|
const write = (str) => { |
|
buff += str; |
|
}; |
|
return { |
|
toString() { |
|
return buff; |
|
}, |
|
getContext() { |
|
return context; |
|
}, |
|
dispatch(value) { |
|
if (options.replacer) { |
|
value = options.replacer(value); |
|
} |
|
const type = value === null ? "null" : typeof value; |
|
return this[type](value); |
|
}, |
|
object(object) { |
|
if (object && typeof object.toJSON === "function") { |
|
return this.object(object.toJSON()); |
|
} |
|
const objString = Object.prototype.toString.call(object); |
|
let objType = ""; |
|
const objectLength = objString.length; |
|
if (objectLength < 10) { |
|
objType = "unknown:[" + objString + "]"; |
|
} else { |
|
objType = objString.slice(8, objectLength - 1); |
|
} |
|
objType = objType.toLowerCase(); |
|
let objectNumber = null; |
|
if ((objectNumber = context.get(object)) === void 0) { |
|
context.set(object, context.size); |
|
} else { |
|
return this.dispatch("[CIRCULAR:" + objectNumber + "]"); |
|
} |
|
if (typeof Buffer !== "undefined" && Buffer.isBuffer && Buffer.isBuffer(object)) { |
|
write("buffer:"); |
|
return write(object.toString("utf8")); |
|
} |
|
if (objType !== "object" && objType !== "function" && objType !== "asyncfunction") { |
|
if (this[objType]) { |
|
this[objType](object); |
|
} else if (!options.ignoreUnknown) { |
|
this.unkown(object, objType); |
|
} |
|
} else { |
|
let keys = Object.keys(object); |
|
if (options.unorderedObjects) { |
|
keys = keys.sort(); |
|
} |
|
let extraKeys = []; |
|
if (options.respectType !== false && !isNativeFunction(object)) { |
|
extraKeys = defaultPrototypesKeys; |
|
} |
|
if (options.excludeKeys) { |
|
keys = keys.filter((key) => { |
|
return !options.excludeKeys(key); |
|
}); |
|
extraKeys = extraKeys.filter((key) => { |
|
return !options.excludeKeys(key); |
|
}); |
|
} |
|
write("object:" + (keys.length + extraKeys.length) + ":"); |
|
const dispatchForKey = (key) => { |
|
this.dispatch(key); |
|
write(":"); |
|
if (!options.excludeValues) { |
|
this.dispatch(object[key]); |
|
} |
|
write(","); |
|
}; |
|
for (const key of keys) { |
|
dispatchForKey(key); |
|
} |
|
for (const key of extraKeys) { |
|
dispatchForKey(key); |
|
} |
|
} |
|
}, |
|
array(arr, unordered) { |
|
unordered = unordered === void 0 ? options.unorderedArrays !== false : unordered; |
|
write("array:" + arr.length + ":"); |
|
if (!unordered || arr.length <= 1) { |
|
for (const entry of arr) { |
|
this.dispatch(entry); |
|
} |
|
return; |
|
} |
|
const contextAdditions = /* @__PURE__ */ new Map(); |
|
const entries = arr.map((entry) => { |
|
const hasher = createHasher(options); |
|
hasher.dispatch(entry); |
|
for (const [key, value] of hasher.getContext()) { |
|
contextAdditions.set(key, value); |
|
} |
|
return hasher.toString(); |
|
}); |
|
context = contextAdditions; |
|
entries.sort(); |
|
return this.array(entries, false); |
|
}, |
|
date(date) { |
|
return write("date:" + date.toJSON()); |
|
}, |
|
symbol(sym) { |
|
return write("symbol:" + sym.toString()); |
|
}, |
|
unkown(value, type) { |
|
write(type); |
|
if (!value) { |
|
return; |
|
} |
|
write(":"); |
|
if (value && typeof value.entries === "function") { |
|
return this.array( |
|
Array.from(value.entries()), |
|
true |
|
/* ordered */ |
|
); |
|
} |
|
}, |
|
error(err) { |
|
return write("error:" + err.toString()); |
|
}, |
|
boolean(bool) { |
|
return write("bool:" + bool); |
|
}, |
|
string(string) { |
|
write("string:" + string.length + ":"); |
|
write(string); |
|
}, |
|
function(fn) { |
|
write("fn:"); |
|
if (isNativeFunction(fn)) { |
|
this.dispatch("[native]"); |
|
} else { |
|
this.dispatch(fn.toString()); |
|
} |
|
if (options.respectFunctionNames !== false) { |
|
this.dispatch("function-name:" + String(fn.name)); |
|
} |
|
if (options.respectFunctionProperties) { |
|
this.object(fn); |
|
} |
|
}, |
|
number(number) { |
|
return write("number:" + number); |
|
}, |
|
xml(xml) { |
|
return write("xml:" + xml.toString()); |
|
}, |
|
null() { |
|
return write("Null"); |
|
}, |
|
undefined() { |
|
return write("Undefined"); |
|
}, |
|
regexp(regex) { |
|
return write("regex:" + regex.toString()); |
|
}, |
|
uint8array(arr) { |
|
write("uint8array:"); |
|
return this.dispatch(Array.prototype.slice.call(arr)); |
|
}, |
|
uint8clampedarray(arr) { |
|
write("uint8clampedarray:"); |
|
return this.dispatch(Array.prototype.slice.call(arr)); |
|
}, |
|
int8array(arr) { |
|
write("int8array:"); |
|
return this.dispatch(Array.prototype.slice.call(arr)); |
|
}, |
|
uint16array(arr) { |
|
write("uint16array:"); |
|
return this.dispatch(Array.prototype.slice.call(arr)); |
|
}, |
|
int16array(arr) { |
|
write("int16array:"); |
|
return this.dispatch(Array.prototype.slice.call(arr)); |
|
}, |
|
uint32array(arr) { |
|
write("uint32array:"); |
|
return this.dispatch(Array.prototype.slice.call(arr)); |
|
}, |
|
int32array(arr) { |
|
write("int32array:"); |
|
return this.dispatch(Array.prototype.slice.call(arr)); |
|
}, |
|
float32array(arr) { |
|
write("float32array:"); |
|
return this.dispatch(Array.prototype.slice.call(arr)); |
|
}, |
|
float64array(arr) { |
|
write("float64array:"); |
|
return this.dispatch(Array.prototype.slice.call(arr)); |
|
}, |
|
arraybuffer(arr) { |
|
write("arraybuffer:"); |
|
return this.dispatch(new Uint8Array(arr)); |
|
}, |
|
url(url) { |
|
return write("url:" + url.toString()); |
|
}, |
|
map(map) { |
|
write("map:"); |
|
const arr = [...map]; |
|
return this.array(arr, options.unorderedSets !== false); |
|
}, |
|
set(set) { |
|
write("set:"); |
|
const arr = [...set]; |
|
return this.array(arr, options.unorderedSets !== false); |
|
}, |
|
file(file) { |
|
write("file:"); |
|
return this.dispatch([file.name, file.size, file.type, file.lastModfied]); |
|
}, |
|
blob() { |
|
if (options.ignoreUnknown) { |
|
return write("[blob]"); |
|
} |
|
throw new Error( |
|
'Hashing Blob objects is currently not supported\nUse "options.replacer" or "options.ignoreUnknown"\n' |
|
); |
|
}, |
|
domwindow() { |
|
return write("domwindow"); |
|
}, |
|
bigint(number) { |
|
return write("bigint:" + number.toString()); |
|
}, |
|
/* Node.js standard native objects */ |
|
process() { |
|
return write("process"); |
|
}, |
|
timer() { |
|
return write("timer"); |
|
}, |
|
pipe() { |
|
return write("pipe"); |
|
}, |
|
tcp() { |
|
return write("tcp"); |
|
}, |
|
udp() { |
|
return write("udp"); |
|
}, |
|
tty() { |
|
return write("tty"); |
|
}, |
|
statwatcher() { |
|
return write("statwatcher"); |
|
}, |
|
securecontext() { |
|
return write("securecontext"); |
|
}, |
|
connection() { |
|
return write("connection"); |
|
}, |
|
zlib() { |
|
return write("zlib"); |
|
}, |
|
context() { |
|
return write("context"); |
|
}, |
|
nodescript() { |
|
return write("nodescript"); |
|
}, |
|
httpparser() { |
|
return write("httpparser"); |
|
}, |
|
dataview() { |
|
return write("dataview"); |
|
}, |
|
signal() { |
|
return write("signal"); |
|
}, |
|
fsevent() { |
|
return write("fsevent"); |
|
}, |
|
tlswrap() { |
|
return write("tlswrap"); |
|
} |
|
}; |
|
} |
|
const nativeFunc = "[native code] }"; |
|
const nativeFuncLength = nativeFunc.length; |
|
function isNativeFunction(f) { |
|
if (typeof f !== "function") { |
|
return false; |
|
} |
|
return Function.prototype.toString.call(f).slice(-nativeFuncLength) === nativeFunc; |
|
} |
|
|
|
class WordArray { |
|
constructor(words, sigBytes) { |
|
words = this.words = words || []; |
|
this.sigBytes = sigBytes === void 0 ? words.length * 4 : sigBytes; |
|
} |
|
toString(encoder) { |
|
return (encoder || Hex).stringify(this); |
|
} |
|
concat(wordArray) { |
|
this.clamp(); |
|
if (this.sigBytes % 4) { |
|
for (let i = 0; i < wordArray.sigBytes; i++) { |
|
const thatByte = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255; |
|
this.words[this.sigBytes + i >>> 2] |= thatByte << 24 - (this.sigBytes + i) % 4 * 8; |
|
} |
|
} else { |
|
for (let j = 0; j < wordArray.sigBytes; j += 4) { |
|
this.words[this.sigBytes + j >>> 2] = wordArray.words[j >>> 2]; |
|
} |
|
} |
|
this.sigBytes += wordArray.sigBytes; |
|
return this; |
|
} |
|
clamp() { |
|
this.words[this.sigBytes >>> 2] &= 4294967295 << 32 - this.sigBytes % 4 * 8; |
|
this.words.length = Math.ceil(this.sigBytes / 4); |
|
} |
|
clone() { |
|
return new WordArray([...this.words]); |
|
} |
|
} |
|
const Hex = { |
|
stringify(wordArray) { |
|
const hexChars = []; |
|
for (let i = 0; i < wordArray.sigBytes; i++) { |
|
const bite = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255; |
|
hexChars.push((bite >>> 4).toString(16), (bite & 15).toString(16)); |
|
} |
|
return hexChars.join(""); |
|
} |
|
}; |
|
const Base64 = { |
|
stringify(wordArray) { |
|
const keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; |
|
const base64Chars = []; |
|
for (let i = 0; i < wordArray.sigBytes; i += 3) { |
|
const byte1 = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255; |
|
const byte2 = wordArray.words[i + 1 >>> 2] >>> 24 - (i + 1) % 4 * 8 & 255; |
|
const byte3 = wordArray.words[i + 2 >>> 2] >>> 24 - (i + 2) % 4 * 8 & 255; |
|
const triplet = byte1 << 16 | byte2 << 8 | byte3; |
|
for (let j = 0; j < 4 && i * 8 + j * 6 < wordArray.sigBytes * 8; j++) { |
|
base64Chars.push(keyStr.charAt(triplet >>> 6 * (3 - j) & 63)); |
|
} |
|
} |
|
return base64Chars.join(""); |
|
} |
|
}; |
|
const Latin1 = { |
|
parse(latin1Str) { |
|
const latin1StrLength = latin1Str.length; |
|
const words = []; |
|
for (let i = 0; i < latin1StrLength; i++) { |
|
words[i >>> 2] |= (latin1Str.charCodeAt(i) & 255) << 24 - i % 4 * 8; |
|
} |
|
return new WordArray(words, latin1StrLength); |
|
} |
|
}; |
|
const Utf8 = { |
|
parse(utf8Str) { |
|
return Latin1.parse(unescape(encodeURIComponent(utf8Str))); |
|
} |
|
}; |
|
class BufferedBlockAlgorithm { |
|
constructor() { |
|
this._data = new WordArray(); |
|
this._nDataBytes = 0; |
|
this._minBufferSize = 0; |
|
this.blockSize = 512 / 32; |
|
} |
|
reset() { |
|
this._data = new WordArray(); |
|
this._nDataBytes = 0; |
|
} |
|
_append(data) { |
|
if (typeof data === "string") { |
|
data = Utf8.parse(data); |
|
} |
|
this._data.concat(data); |
|
this._nDataBytes += data.sigBytes; |
|
} |
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars |
|
_doProcessBlock(_dataWords, _offset) { |
|
} |
|
_process(doFlush) { |
|
let processedWords; |
|
let nBlocksReady = this._data.sigBytes / (this.blockSize * 4); |
|
if (doFlush) { |
|
nBlocksReady = Math.ceil(nBlocksReady); |
|
} else { |
|
nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); |
|
} |
|
const nWordsReady = nBlocksReady * this.blockSize; |
|
const nBytesReady = Math.min(nWordsReady * 4, this._data.sigBytes); |
|
if (nWordsReady) { |
|
for (let offset = 0; offset < nWordsReady; offset += this.blockSize) { |
|
this._doProcessBlock(this._data.words, offset); |
|
} |
|
processedWords = this._data.words.splice(0, nWordsReady); |
|
this._data.sigBytes -= nBytesReady; |
|
} |
|
return new WordArray(processedWords, nBytesReady); |
|
} |
|
} |
|
class Hasher extends BufferedBlockAlgorithm { |
|
update(messageUpdate) { |
|
this._append(messageUpdate); |
|
this._process(); |
|
return this; |
|
} |
|
finalize(messageUpdate) { |
|
if (messageUpdate) { |
|
this._append(messageUpdate); |
|
} |
|
} |
|
} |
|
|
|
const H = [ |
|
1779033703, |
|
-1150833019, |
|
1013904242, |
|
-1521486534, |
|
1359893119, |
|
-1694144372, |
|
528734635, |
|
1541459225 |
|
]; |
|
const K = [ |
|
1116352408, |
|
1899447441, |
|
-1245643825, |
|
-373957723, |
|
961987163, |
|
1508970993, |
|
-1841331548, |
|
-1424204075, |
|
-670586216, |
|
310598401, |
|
607225278, |
|
1426881987, |
|
1925078388, |
|
-2132889090, |
|
-1680079193, |
|
-1046744716, |
|
-459576895, |
|
-272742522, |
|
264347078, |
|
604807628, |
|
770255983, |
|
1249150122, |
|
1555081692, |
|
1996064986, |
|
-1740746414, |
|
-1473132947, |
|
-1341970488, |
|
-1084653625, |
|
-958395405, |
|
-710438585, |
|
113926993, |
|
338241895, |
|
666307205, |
|
773529912, |
|
1294757372, |
|
1396182291, |
|
1695183700, |
|
1986661051, |
|
-2117940946, |
|
-1838011259, |
|
-1564481375, |
|
-1474664885, |
|
-1035236496, |
|
-949202525, |
|
-778901479, |
|
-694614492, |
|
-200395387, |
|
275423344, |
|
430227734, |
|
506948616, |
|
659060556, |
|
883997877, |
|
958139571, |
|
1322822218, |
|
1537002063, |
|
1747873779, |
|
1955562222, |
|
2024104815, |
|
-2067236844, |
|
-1933114872, |
|
-1866530822, |
|
-1538233109, |
|
-1090935817, |
|
-965641998 |
|
]; |
|
const W = []; |
|
class SHA256 extends Hasher { |
|
constructor() { |
|
super(...arguments); |
|
this._hash = new WordArray([...H]); |
|
} |
|
reset() { |
|
super.reset(); |
|
this._hash = new WordArray([...H]); |
|
} |
|
_doProcessBlock(M, offset) { |
|
const H2 = this._hash.words; |
|
let a = H2[0]; |
|
let b = H2[1]; |
|
let c = H2[2]; |
|
let d = H2[3]; |
|
let e = H2[4]; |
|
let f = H2[5]; |
|
let g = H2[6]; |
|
let h = H2[7]; |
|
for (let i = 0; i < 64; i++) { |
|
if (i < 16) { |
|
W[i] = M[offset + i] | 0; |
|
} else { |
|
const gamma0x = W[i - 15]; |
|
const gamma0 = (gamma0x << 25 | gamma0x >>> 7) ^ (gamma0x << 14 | gamma0x >>> 18) ^ gamma0x >>> 3; |
|
const gamma1x = W[i - 2]; |
|
const gamma1 = (gamma1x << 15 | gamma1x >>> 17) ^ (gamma1x << 13 | gamma1x >>> 19) ^ gamma1x >>> 10; |
|
W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; |
|
} |
|
const ch = e & f ^ ~e & g; |
|
const maj = a & b ^ a & c ^ b & c; |
|
const sigma0 = (a << 30 | a >>> 2) ^ (a << 19 | a >>> 13) ^ (a << 10 | a >>> 22); |
|
const sigma1 = (e << 26 | e >>> 6) ^ (e << 21 | e >>> 11) ^ (e << 7 | e >>> 25); |
|
const t1 = h + sigma1 + ch + K[i] + W[i]; |
|
const t2 = sigma0 + maj; |
|
h = g; |
|
g = f; |
|
f = e; |
|
e = d + t1 | 0; |
|
d = c; |
|
c = b; |
|
b = a; |
|
a = t1 + t2 | 0; |
|
} |
|
H2[0] = H2[0] + a | 0; |
|
H2[1] = H2[1] + b | 0; |
|
H2[2] = H2[2] + c | 0; |
|
H2[3] = H2[3] + d | 0; |
|
H2[4] = H2[4] + e | 0; |
|
H2[5] = H2[5] + f | 0; |
|
H2[6] = H2[6] + g | 0; |
|
H2[7] = H2[7] + h | 0; |
|
} |
|
finalize(messageUpdate) { |
|
super.finalize(messageUpdate); |
|
const nBitsTotal = this._nDataBytes * 8; |
|
const nBitsLeft = this._data.sigBytes * 8; |
|
this._data.words[nBitsLeft >>> 5] |= 128 << 24 - nBitsLeft % 32; |
|
this._data.words[(nBitsLeft + 64 >>> 9 << 4) + 14] = Math.floor( |
|
nBitsTotal / 4294967296 |
|
); |
|
this._data.words[(nBitsLeft + 64 >>> 9 << 4) + 15] = nBitsTotal; |
|
this._data.sigBytes = this._data.words.length * 4; |
|
this._process(); |
|
return this._hash; |
|
} |
|
} |
|
function sha256base64(message) { |
|
return new SHA256().finalize(message).toString(Base64); |
|
} |
|
|
|
function hash(object, options = {}) { |
|
const hashed = typeof object === "string" ? object : objectHash(object, options); |
|
return sha256base64(hashed).slice(0, 10); |
|
} |
|
|
|
async function generateBuild(ctx, config) { |
|
const { |
|
outputDir = ".vite-inspect" |
|
} = ctx.options; |
|
const targetDir = isAbsolute(outputDir) ? outputDir : resolve(config.root, outputDir); |
|
const reportsDir = join(targetDir, "reports"); |
|
await fs.emptyDir(targetDir); |
|
await fs.ensureDir(reportsDir); |
|
await fs.copy(DIR_CLIENT, targetDir); |
|
const isVirtual = (pluginName, transformName) => pluginName !== DUMMY_LOAD_PLUGIN_NAME && transformName !== "vite:load-fallback"; |
|
function list() { |
|
return { |
|
root: config.root, |
|
modules: ctx.getModulesInfo(ctx.recorderClient, null, isVirtual), |
|
ssrModules: ctx.getModulesInfo(ctx.recorderServer, null, isVirtual) |
|
}; |
|
} |
|
async function dumpModuleInfo(dir, recorder, ssr = false) { |
|
await fs.ensureDir(dir); |
|
return Promise.all( |
|
Object.entries(recorder.transform).map( |
|
([id, info]) => fs.writeJSON( |
|
join(dir, `${hash(id)}.json`), |
|
{ |
|
resolvedId: ctx.resolveId(id, ssr), |
|
transforms: info |
|
}, |
|
{ spaces: 2 } |
|
) |
|
) |
|
); |
|
} |
|
await Promise.all([ |
|
fs.writeFile( |
|
join(targetDir, "index.html"), |
|
(await fs.readFile(join(targetDir, "index.html"), "utf-8")).replace( |
|
'data-vite-inspect-mode="DEV"', |
|
'data-vite-inspect-mode="BUILD"' |
|
) |
|
), |
|
fs.writeJSON( |
|
join(reportsDir, "list.json"), |
|
list(), |
|
{ spaces: 2 } |
|
), |
|
fs.writeJSON( |
|
join(reportsDir, "metrics.json"), |
|
ctx.getPluginMetrics(false), |
|
{ spaces: 2 } |
|
), |
|
fs.writeJSON( |
|
join(reportsDir, "metrics-ssr.json"), |
|
ctx.getPluginMetrics(true), |
|
{ spaces: 2 } |
|
), |
|
dumpModuleInfo(join(reportsDir, "transform"), ctx.recorderClient), |
|
dumpModuleInfo(join(reportsDir, "transform-ssr"), ctx.recorderServer, true) |
|
]); |
|
return targetDir; |
|
} |
|
|
|
function createPreviewServer(staticPath) { |
|
const server = createServer(); |
|
const statics = sirv(staticPath); |
|
server.on("request", (req, res) => { |
|
statics(req, res, () => { |
|
res.statusCode = 404; |
|
res.end("File not found"); |
|
}); |
|
}); |
|
server.listen(0, () => { |
|
const { port } = server.address(); |
|
const url = `http://localhost:${port}`; |
|
console.log(` ${c.green("\u279C")} ${c.bold("Inspect Preview Started")}: ${url}`); |
|
openBrowser(url); |
|
}); |
|
} |
|
|
|
const NAME = "vite-plugin-inspect"; |
|
const isCI = !!process.env.CI; |
|
function PluginInspect(options = {}) { |
|
const { |
|
dev = true, |
|
build = false, |
|
silent = false, |
|
open: _open = false |
|
} = options; |
|
if (!dev && !build) { |
|
return { |
|
name: NAME |
|
}; |
|
} |
|
const ctx = new ViteInspectContext(options); |
|
const timestampRE = /\bt=\d{13}&?\b/; |
|
const trailingSeparatorRE = /[?&]$/; |
|
let config; |
|
const serverPerf = { |
|
middleware: {} |
|
}; |
|
function setupMiddlewarePerf(middlewares) { |
|
let firstMiddlewareIndex = -1; |
|
middlewares.forEach((middleware, index) => { |
|
const { handle: originalHandle } = middleware; |
|
if (typeof originalHandle !== "function" || !originalHandle.name) |
|
return middleware; |
|
middleware.handle = (...middlewareArgs) => { |
|
var _a; |
|
let req; |
|
if (middlewareArgs.length === 4) |
|
[, req] = middlewareArgs; |
|
else |
|
[req] = middlewareArgs; |
|
const start = Date.now(); |
|
const url = req.url?.replace(timestampRE, "").replace(trailingSeparatorRE, ""); |
|
(_a = serverPerf.middleware)[url] ?? (_a[url] = []); |
|
if (firstMiddlewareIndex < 0) |
|
firstMiddlewareIndex = index; |
|
if (index === firstMiddlewareIndex) |
|
serverPerf.middleware[url] = []; |
|
const result = originalHandle(...middlewareArgs); |
|
Promise.resolve(result).then(() => { |
|
const total = Date.now() - start; |
|
const metrics = serverPerf.middleware[url]; |
|
serverPerf.middleware[url].push({ |
|
self: metrics.length ? Math.max(total - metrics[metrics.length - 1].total, 0) : total, |
|
total, |
|
name: originalHandle.name |
|
}); |
|
}); |
|
return result; |
|
}; |
|
Object.defineProperty(middleware.handle, "name", { |
|
value: originalHandle.name, |
|
configurable: true, |
|
enumerable: true |
|
}); |
|
return middleware; |
|
}); |
|
} |
|
function configureServer(server) { |
|
const _invalidateModule = server.moduleGraph.invalidateModule; |
|
server.moduleGraph.invalidateModule = function(...args) { |
|
const mod = args[0]; |
|
if (mod?.id) { |
|
ctx.recorderClient.invalidate(mod.id); |
|
ctx.recorderServer.invalidate(mod.id); |
|
} |
|
return _invalidateModule.apply(this, args); |
|
}; |
|
const base = (options.base ?? server.config.base) || "/"; |
|
server.middlewares.use(`${base}__inspect`, sirv(DIR_CLIENT, { |
|
single: true, |
|
dev: true |
|
})); |
|
const rpcFunctions = { |
|
list: () => ctx.getList(server), |
|
getIdInfo, |
|
getPluginMetrics: (ssr = false) => ctx.getPluginMetrics(ssr), |
|
getServerMetrics, |
|
resolveId: (id, ssr = false) => ctx.resolveId(id, ssr), |
|
clear: clearId, |
|
moduleUpdated: () => { |
|
} |
|
}; |
|
const rpcServer = createRPCServer("vite-plugin-inspect", server.ws, rpcFunctions); |
|
const debouncedModuleUpdated = debounce(() => { |
|
rpcServer.moduleUpdated.asEvent(); |
|
}, 100); |
|
server.middlewares.use((req, res, next) => { |
|
debouncedModuleUpdated(); |
|
next(); |
|
}); |
|
function getServerMetrics() { |
|
return serverPerf || {}; |
|
} |
|
async function getIdInfo(id, ssr = false, clear = false) { |
|
if (clear) { |
|
clearId(id, ssr); |
|
try { |
|
await server.transformRequest(id, { ssr }); |
|
} catch { |
|
} |
|
} |
|
const resolvedId = ctx.resolveId(id, ssr); |
|
const recorder = ctx.getRecorder(ssr); |
|
return { |
|
resolvedId, |
|
transforms: recorder.transform[resolvedId] || [] |
|
}; |
|
} |
|
function clearId(_id, ssr = false) { |
|
const id = ctx.resolveId(_id); |
|
if (id) { |
|
const mod = server.moduleGraph.getModuleById(id); |
|
if (mod) |
|
server.moduleGraph.invalidateModule(mod); |
|
ctx.getRecorder(ssr).invalidate(id); |
|
} |
|
} |
|
const _print = server.printUrls; |
|
server.printUrls = () => { |
|
let host = `${config.server.https ? "https" : "http"}://localhost:${config.server.port || "80"}`; |
|
const url = server.resolvedUrls?.local[0]; |
|
if (url) { |
|
try { |
|
const u = new URL(url); |
|
host = `${u.protocol}//${u.host}`; |
|
} catch (error) { |
|
config.logger.warn(`Parse resolved url failed: ${error}`); |
|
} |
|
} |
|
_print(); |
|
if (!silent) { |
|
const colorUrl = (url2) => c.green(url2.replace(/:(\d+)\//, (_, port) => `:${c.bold(port)}/`)); |
|
config.logger.info(` ${c.green("\u279C")} ${c.bold("Inspect")}: ${colorUrl(`${host}${base}__inspect/`)}`); |
|
} |
|
if (_open && !isCI) { |
|
setTimeout(() => { |
|
openBrowser(`${host}${base}__inspect/`); |
|
}, 500); |
|
} |
|
}; |
|
return rpcFunctions; |
|
} |
|
const plugin = { |
|
name: NAME, |
|
enforce: "pre", |
|
apply(_, { command }) { |
|
if (command === "serve" && dev) |
|
return true; |
|
if (command === "build" && build) |
|
return true; |
|
return false; |
|
}, |
|
configResolved(_config) { |
|
config = ctx.config = _config; |
|
config.plugins.forEach((plugin2) => hijackPlugin(plugin2, ctx)); |
|
const _createResolver = config.createResolver; |
|
config.createResolver = function(...args) { |
|
const _resolver = _createResolver.apply(this, args); |
|
return async function(...args2) { |
|
const id = args2[0]; |
|
const aliasOnly = args2[2]; |
|
const ssr = args2[3]; |
|
const start = Date.now(); |
|
const result = await _resolver.apply(this, args2); |
|
const end = Date.now(); |
|
if (result && result !== id) { |
|
const pluginName = aliasOnly ? "alias" : "vite:resolve (+alias)"; |
|
ctx.getRecorder(ssr).recordResolveId(id, { name: pluginName, result, start, end }); |
|
} |
|
return result; |
|
}; |
|
}; |
|
}, |
|
configureServer(server) { |
|
const rpc = configureServer(server); |
|
plugin.api = { |
|
rpc |
|
}; |
|
return () => { |
|
setupMiddlewarePerf(server.middlewares.stack); |
|
}; |
|
}, |
|
load: { |
|
order: "pre", |
|
handler(id, { ssr } = {}) { |
|
ctx.getRecorder(ssr).invalidate(id); |
|
return null; |
|
} |
|
}, |
|
handleHotUpdate({ modules, server }) { |
|
const ids = modules.map((module) => module.id); |
|
server.ws.send({ |
|
type: "custom", |
|
event: "vite-plugin-inspect:update", |
|
data: { ids } |
|
}); |
|
}, |
|
async buildEnd() { |
|
if (!build) |
|
return; |
|
const dir = await generateBuild(ctx, config); |
|
config.logger.info(`${c.green("Inspect report generated at")} ${c.dim(`${dir}`)}`); |
|
if (_open && !isCI) |
|
createPreviewServer(dir); |
|
} |
|
}; |
|
return plugin; |
|
} |
|
PluginInspect.getViteInspectAPI = function(plugins) { |
|
return plugins.find((p) => p.name === NAME)?.api; |
|
}; |
|
|
|
export { PluginInspect as default };
|
|
|