@vitejs/plugin-vue-jsx
Version:
Provides Vue 3 JSX & TSX support with HMR.
290 lines (287 loc) • 10.2 kB
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 { makeIdFiltersToMatchWithQuery, exactRegex } from '@rolldown/pluginutils';
const ssrRegisterHelperId = "/__vue-jsx-ssr-register-helper";
const ssrRegisterHelperCode = `import { useSSRContext } from "vue"
export const ssrRegisterHelper = ${ssrRegisterHelper.toString()}`;
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 = {},
...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 {
// only apply esbuild to ts files
// since we are handling jsx and tsx now
esbuild: {
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
}
};
},
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: {
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")) {
plugins.push([
// @ts-ignore missing type
await import('@babel/plugin-transform-typescript').then(
(r) => r.default
),
// @ts-ignore
{ ...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) {
if (isDefineComponentCall(
_path.node.declaration,
defineComponentName
)) {
const declaration = _path.node.declaration;
const nodesPath = _path.replaceWithMultiple([
// const __default__ = defineComponent(...)
types.variableDeclaration("const", [
types.variableDeclarator(
types.identifier("__default__"),
types.callExpression(
declaration.callee,
declaration.arguments
)
)
]),
// export default __default__
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(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 code2 = result.code;
let callbackCode = ``;
for (const { local, exported, id: id2 } of hotComponents) {
code2 += `
${local}.__hmrId = "${id2}"
__VUE_HMR_RUNTIME__.createRecord("${id2}", ${local})`;
callbackCode += `
__VUE_HMR_RUNTIME__.reload("${id2}", __${exported})`;
}
const newCompNames = hotComponents.map((c) => `${c.exported}: __${c.exported}`).join(",");
code2 += `
import.meta.hot.accept(({${newCompNames}}) => {${callbackCode}
})`;
result.code = code2;
}
if (ssr) {
const normalizedId = normalizePath(path.relative(root, id));
let ssrInjectCode = `
import { ssrRegisterHelper } from "${ssrRegisterHelperId}"
const __moduleId = ${JSON.stringify(normalizedId)}`;
for (const { local } of hotComponents) {
ssrInjectCode += `
ssrRegisterHelper(${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(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 getHash(text) {
return crypto.hash("sha256", text, "hex").substring(0, 8);
}
function vueJsxPluginCjs(options) {
return vueJsxPlugin.call(this, options);
}
Object.assign(vueJsxPluginCjs, {
default: vueJsxPluginCjs
});
export { vueJsxPlugin as default, vueJsxPluginCjs as "module.exports" };