210 lines
8.3 KiB
JavaScript
210 lines
8.3 KiB
JavaScript
import crypto from "node:crypto";
|
|
import path from "node:path";
|
|
import * as babel from "@babel/core";
|
|
import { types } from "@babel/core";
|
|
import jsx from "@vue/babel-plugin-jsx";
|
|
import { createFilter, normalizePath } from "vite";
|
|
import { exactRegex, makeIdFiltersToMatchWithQuery } from "@rolldown/pluginutils";
|
|
|
|
//#region src/index.ts
|
|
const ssrRegisterHelperId = "/__vue-jsx-ssr-register-helper";
|
|
const ssrRegisterHelperCode = `import { useSSRContext } from "vue"\nexport const ssrRegisterHelper = ${ssrRegisterHelper.toString()}`;
|
|
/**
|
|
* This function is serialized with toString() and evaluated as a virtual
|
|
* module during SSR
|
|
*/
|
|
function ssrRegisterHelper(comp, filename) {
|
|
const setup = comp.setup;
|
|
comp.setup = (props, ctx) => {
|
|
const ssrContext = useSSRContext();
|
|
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add(filename);
|
|
if (setup) return setup(props, ctx);
|
|
};
|
|
}
|
|
function vueJsxPlugin(options = {}) {
|
|
let root = "";
|
|
let needHmr = false;
|
|
let needSourceMap = true;
|
|
const { include = /\.[jt]sx$/, exclude, babelPlugins = [], defineComponentName = ["defineComponent"], tsPluginOptions = {}, tsTransform,...babelPluginOptions } = options;
|
|
const filter = createFilter(include, exclude);
|
|
return {
|
|
name: "vite:vue-jsx",
|
|
config(config) {
|
|
const parseDefine = (v) => {
|
|
try {
|
|
return typeof v === "string" ? JSON.parse(v) : v;
|
|
} catch (err) {
|
|
return v;
|
|
}
|
|
};
|
|
return {
|
|
esbuild: tsTransform === "built-in" ? { exclude: /\.jsx?$/ } : { include: /\.ts$/ },
|
|
define: {
|
|
__VUE_OPTIONS_API__: parseDefine(config.define?.__VUE_OPTIONS_API__) ?? true,
|
|
__VUE_PROD_DEVTOOLS__: parseDefine(config.define?.__VUE_PROD_DEVTOOLS__) ?? false,
|
|
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: parseDefine(config.define?.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__) ?? false
|
|
},
|
|
optimizeDeps: this && "rolldownVersion" in this.meta ? { rolldownOptions: { transform: { jsx: "preserve" } } } : {}
|
|
};
|
|
},
|
|
configResolved(config) {
|
|
needHmr = config.command === "serve" && !config.isProduction;
|
|
needSourceMap = config.command === "serve" || !!config.build.sourcemap;
|
|
root = config.root;
|
|
},
|
|
resolveId: {
|
|
filter: { id: exactRegex(ssrRegisterHelperId) },
|
|
handler(id) {
|
|
if (id === ssrRegisterHelperId) return id;
|
|
}
|
|
},
|
|
load: {
|
|
filter: { id: exactRegex(ssrRegisterHelperId) },
|
|
handler(id) {
|
|
if (id === ssrRegisterHelperId) return ssrRegisterHelperCode;
|
|
}
|
|
},
|
|
transform: {
|
|
order: tsTransform === "built-in" ? "pre" : void 0,
|
|
filter: { id: {
|
|
include: include ? makeIdFiltersToMatchWithQuery(include) : void 0,
|
|
exclude: exclude ? makeIdFiltersToMatchWithQuery(exclude) : void 0
|
|
} },
|
|
async handler(code, id, opt) {
|
|
const ssr = opt?.ssr === true;
|
|
const [filepath] = id.split("?");
|
|
if (filter(id) || filter(filepath)) {
|
|
const plugins = [[jsx, babelPluginOptions], ...babelPlugins];
|
|
if (id.endsWith(".tsx") || filepath.endsWith(".tsx")) if (tsTransform === "built-in") plugins.push([await import("@babel/plugin-syntax-typescript").then((r) => r.default), { isTSX: true }]);
|
|
else plugins.push([await import("@babel/plugin-transform-typescript").then((r) => r.default), {
|
|
...tsPluginOptions,
|
|
isTSX: true,
|
|
allowExtensions: true
|
|
}]);
|
|
if (!ssr && !needHmr) plugins.push(() => {
|
|
return { visitor: { CallExpression: { enter(_path) {
|
|
if (isDefineComponentCall(_path.node, defineComponentName)) {
|
|
const callee = _path.node.callee;
|
|
callee.name = `/* @__PURE__ */ ${callee.name}`;
|
|
}
|
|
} } } };
|
|
});
|
|
else plugins.push(() => {
|
|
return { visitor: { ExportDefaultDeclaration: { enter(_path) {
|
|
const unwrappedDeclaration = unwrapTypeAssertion(_path.node.declaration);
|
|
if (isDefineComponentCall(unwrappedDeclaration, defineComponentName)) {
|
|
const declaration = unwrappedDeclaration;
|
|
const nodesPath = _path.replaceWithMultiple([types.variableDeclaration("const", [types.variableDeclarator(types.identifier("__default__"), types.callExpression(declaration.callee, declaration.arguments))]), types.exportDefaultDeclaration(types.identifier("__default__"))]);
|
|
_path.scope.registerDeclaration(nodesPath[0]);
|
|
}
|
|
} } } };
|
|
});
|
|
const result = babel.transformSync(code, {
|
|
babelrc: false,
|
|
ast: true,
|
|
plugins,
|
|
sourceMaps: needSourceMap,
|
|
sourceFileName: id,
|
|
configFile: false
|
|
});
|
|
if (!ssr && !needHmr) {
|
|
if (!result.code) return;
|
|
return {
|
|
code: result.code,
|
|
map: result.map
|
|
};
|
|
}
|
|
const declaredComponents = [];
|
|
const hotComponents = [];
|
|
for (const node of result.ast.program.body) {
|
|
if (node.type === "VariableDeclaration") {
|
|
const names = parseComponentDecls(node, defineComponentName);
|
|
if (names.length) declaredComponents.push(...names);
|
|
}
|
|
if (node.type === "ExportNamedDeclaration") {
|
|
if (node.declaration && node.declaration.type === "VariableDeclaration") hotComponents.push(...parseComponentDecls(node.declaration, defineComponentName).map((name) => ({
|
|
local: name,
|
|
exported: name,
|
|
id: getHash(id + name)
|
|
})));
|
|
else if (node.specifiers.length) {
|
|
for (const spec of node.specifiers) if (spec.type === "ExportSpecifier" && spec.exported.type === "Identifier") {
|
|
const matched = declaredComponents.find((name) => name === spec.local.name);
|
|
if (matched) hotComponents.push({
|
|
local: spec.local.name,
|
|
exported: spec.exported.name,
|
|
id: getHash(id + spec.exported.name)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (node.type === "ExportDefaultDeclaration") {
|
|
if (node.declaration.type === "Identifier") {
|
|
const _name = node.declaration.name;
|
|
const matched = declaredComponents.find((name) => name === _name);
|
|
if (matched) hotComponents.push({
|
|
local: _name,
|
|
exported: "default",
|
|
id: getHash(id + "default")
|
|
});
|
|
} else if (isDefineComponentCall(unwrapTypeAssertion(node.declaration), defineComponentName)) hotComponents.push({
|
|
local: "__default__",
|
|
exported: "default",
|
|
id: getHash(id + "default")
|
|
});
|
|
}
|
|
}
|
|
if (hotComponents.length) {
|
|
if (needHmr && !ssr && !/\?vue&type=script/.test(id)) {
|
|
let code$1 = result.code;
|
|
let callbackCode = ``;
|
|
for (const { local, exported, id: id$1 } of hotComponents) {
|
|
code$1 += `\n${local}.__hmrId = "${id$1}"\n__VUE_HMR_RUNTIME__.createRecord("${id$1}", ${local})`;
|
|
callbackCode += `\n__VUE_HMR_RUNTIME__.reload("${id$1}", __${exported})`;
|
|
}
|
|
const newCompNames = hotComponents.map((c) => `${c.exported}: __${c.exported}`).join(",");
|
|
code$1 += `\nimport.meta.hot.accept(({${newCompNames}}) => {${callbackCode}\n})`;
|
|
result.code = code$1;
|
|
}
|
|
if (ssr) {
|
|
const normalizedId = normalizePath(path.relative(root, id));
|
|
let ssrInjectCode = `\nimport { ssrRegisterHelper } from "${ssrRegisterHelperId}"\nconst __moduleId = ${JSON.stringify(normalizedId)}`;
|
|
for (const { local } of hotComponents) ssrInjectCode += `\nssrRegisterHelper(${local}, __moduleId)`;
|
|
result.code += ssrInjectCode;
|
|
}
|
|
}
|
|
if (!result.code) return;
|
|
return {
|
|
code: result.code,
|
|
map: result.map
|
|
};
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function parseComponentDecls(node, fnNames) {
|
|
const names = [];
|
|
for (const decl of node.declarations) if (decl.id.type === "Identifier" && isDefineComponentCall(unwrapTypeAssertion(decl.init), fnNames)) names.push(decl.id.name);
|
|
return names;
|
|
}
|
|
function isDefineComponentCall(node, names) {
|
|
return node && node.type === "CallExpression" && node.callee.type === "Identifier" && names.includes(node.callee.name);
|
|
}
|
|
function unwrapTypeAssertion(node) {
|
|
if (!node) return node;
|
|
let current = node;
|
|
while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion") current = current.expression;
|
|
return current;
|
|
}
|
|
function getHash(text) {
|
|
return crypto.hash("sha256", text, "hex").substring(0, 8);
|
|
}
|
|
var src_default = vueJsxPlugin;
|
|
function vueJsxPluginCjs(options) {
|
|
return vueJsxPlugin.call(this, options);
|
|
}
|
|
Object.assign(vueJsxPluginCjs, { default: vueJsxPluginCjs });
|
|
|
|
//#endregion
|
|
export { src_default as default, vueJsxPluginCjs as "module.exports" }; |