@vitejs/plugin-vue2-jsx
Version:
Provides Vue 2 JSX & TSX support with HMR.
468 lines (447 loc) • 14.3 kB
JavaScript
'use strict';
const node_crypto = require('node:crypto');
const path = require('node:path');
const babel = require('@babel/core');
const jsx = require('@vue/babel-preset-jsx');
const importMeta = require('@babel/plugin-syntax-import-meta');
const pluginutils = require('@rollup/pluginutils');
const vite = require('vite');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
const n = Object.create(null);
if (e) {
for (const k in e) {
n[k] = e[k];
}
}
n["default"] = e;
return n;
}
const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
const babel__namespace = /*#__PURE__*/_interopNamespace(babel);
const jsx__default = /*#__PURE__*/_interopDefaultLegacy(jsx);
const importMeta__default = /*#__PURE__*/_interopDefaultLegacy(importMeta);
const HMR_RUNTIME_ID = "plugin-vue2-jsx:hmr-runtime";
const hmrRuntimeCode = `
var __VUE_HMR_RUNTIME__ = Object.create(null)
var map = Object.create(null)
__VUE_HMR_RUNTIME__.createRecord = function (id, options) {
if(map[id]) { return }
var Ctor = null
if (typeof options === 'function') {
Ctor = options
options = Ctor.options
}
makeOptionsHot(id, options)
map[id] = {
Ctor: Ctor,
options: options,
instances: []
}
}
__VUE_HMR_RUNTIME__.isRecorded = function (id) {
return typeof map[id] !== 'undefined'
}
function makeOptionsHot(id, options) {
if (options.functional) {
var render = options.render
options.render = function (h, ctx) {
var instances = map[id].instances
if (ctx && instances.indexOf(ctx.parent) < 0) {
instances.push(ctx.parent)
}
return render(h, ctx)
}
} else {
injectHook(options, 'beforeCreate', function() {
var record = map[id]
if (!record.Ctor) {
record.Ctor = this.constructor
}
record.instances.push(this)
})
injectHook(options, 'beforeDestroy', function() {
var instances = map[id].instances
instances.splice(instances.indexOf(this), 1)
})
}
}
function injectHook(options, name, hook) {
var existing = options[name]
options[name] = existing
? Array.isArray(existing) ? existing.concat(hook) : [existing, hook]
: [hook]
}
function tryWrap(fn) {
return function (id, arg) {
try {
fn(id, arg)
} catch (e) {
console.error(e)
console.warn(
'Something went wrong during Vue component hot-reload. Full reload required.'
)
}
}
}
function updateOptions (oldOptions, newOptions) {
for (var key in oldOptions) {
if (!(key in newOptions)) {
delete oldOptions[key]
}
}
for (var key$1 in newOptions) {
oldOptions[key$1] = newOptions[key$1]
}
}
__VUE_HMR_RUNTIME__.rerender = tryWrap(function (id, options) {
var record = map[id]
if (!options) {
record.instances.slice().forEach(function (instance) {
instance.$forceUpdate()
})
return
}
if (typeof options === 'function') {
options = options.options
}
if(record.functional){
record.render = options.render
record.staticRenderFns = options.staticRenderFns
__VUE_HMR_RUNTIME__.reload(id, record)
return
}
if (record.Ctor) {
record.Ctor.options.render = options.render
record.Ctor.options.staticRenderFns = options.staticRenderFns
record.instances.slice().forEach(function (instance) {
instance.$options.render = options.render
instance.$options.staticRenderFns = options.staticRenderFns
// reset static trees
// pre 2.5, all static trees are cached together on the instance
if (instance._staticTrees) {
instance._staticTrees = []
}
// 2.5.0
if (Array.isArray(record.Ctor.options.cached)) {
record.Ctor.options.cached = []
}
// 2.5.3
if (Array.isArray(instance.$options.cached)) {
instance.$options.cached = []
}
// post 2.5.4: v-once trees are cached on instance._staticTrees.
// Pure static trees are cached on the staticRenderFns array
// (both already reset above)
// 2.6: temporarily mark rendered scoped slots as unstable so that
// child components can be forced to update
var restore = patchScopedSlots(instance)
instance.$forceUpdate()
instance.$nextTick(restore)
})
} else {
// functional or no instance created yet
record.options.render = options.render
record.options.staticRenderFns = options.staticRenderFns
// handle functional component re-render
if (record.options.functional) {
// rerender with full options
if (Object.keys(options).length > 2) {
updateOptions(record.options, options)
} else {
// template-only rerender.
// need to inject the style injection code for CSS modules
// to work properly.
var injectStyles = record.options._injectStyles
if (injectStyles) {
var render = options.render
record.options.render = function (h, ctx) {
injectStyles.call(ctx)
return render(h, ctx)
}
}
}
record.options._Ctor = null
// 2.5.3
if (Array.isArray(record.options.cached)) {
record.options.cached = []
}
record.instances.slice().forEach(function (instance) {
instance.$forceUpdate()
})
}
}
})
__VUE_HMR_RUNTIME__.reload = tryWrap(function (id, options) {
var record = map[id]
if (options) {
if (typeof options === 'function') {
options = options.options
}
makeOptionsHot(id, options)
if (record.Ctor) {
var newCtor = record.Ctor.super.extend(options)
// prevent record.options._Ctor from being overwritten accidentally
newCtor.options._Ctor = record.options._Ctor
record.Ctor.options = newCtor.options
record.Ctor.cid = newCtor.cid
record.Ctor.prototype = newCtor.prototype
if (newCtor.release) {
// temporary global mixin strategy used in < 2.0.0-alpha.6
newCtor.release()
}
} else {
updateOptions(record.options, options)
}
}
record.instances.slice().forEach(function (instance) {
if (instance.$vnode && instance.$vnode.context) {
instance.$vnode.context.$forceUpdate()
} else {
console.warn(
'Root or manually mounted instance modified. Full reload required.'
)
}
})
})
// 2.6 optimizes template-compiled scoped slots and skips updates if child
// only uses scoped slots. We need to patch the scoped slots resolving helper
// to temporarily mark all scoped slots as unstable in order to force child
// updates.
function patchScopedSlots (instance) {
if (!instance._u) { return }
// https://github.com/vuejs/vue/blob/dev/src/core/instance/render-helpers/resolve-scoped-slots.js
var original = instance._u
instance._u = function (slots) {
try {
// 2.6.4 ~ 2.6.6
return original(slots, true)
} catch (e) {
// 2.5 / >= 2.6.7
return original(slots, null, true)
}
}
return function () {
instance._u = original
}
}
export default __VUE_HMR_RUNTIME__
`;
const ssrRegisterHelperId = "/__vue2-jsx-ssr-register-helper";
const ssrRegisterHelperCode = `export ${ssrRegisterHelper.toString()}`;
function ssrRegisterHelper(comp, filename) {
const created = comp.created;
comp.created = function() {
const ssrContext = this.$ssrContext;
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add(filename);
if (created) {
created.call(this);
}
};
}
function vue2JsxPlugin(options = {}) {
let root = "";
let needHmr = false;
let needSourceMap = true;
return {
name: "vite:vue2-jsx",
config(config) {
return {
esbuild: {
include: /\.ts$/
}
};
},
configResolved(config) {
needHmr = config.command === "serve" && !config.isProduction;
needSourceMap = config.command === "serve" || !!config.build.sourcemap;
root = config.root;
},
resolveId(id) {
if (id === ssrRegisterHelperId) {
return id;
}
if (id === HMR_RUNTIME_ID) {
return id;
}
},
load(id) {
if (id === ssrRegisterHelperId) {
return ssrRegisterHelperCode;
}
if (id === HMR_RUNTIME_ID) {
return hmrRuntimeCode;
}
},
async transform(code, id, opt) {
const ssr = opt?.ssr === true;
const {
include,
exclude,
babelPlugins = [],
...babelPresetOptions
} = options;
const filter = pluginutils.createFilter(include || /\.[jt]sx$/, exclude);
const [filepath] = id.split("?");
if (filter(id) || filter(filepath)) {
const plugins = [importMeta__default];
const presets = [
[jsx__default, {
compositionAPI: "native",
...babelPresetOptions
}]
];
if (id.endsWith(".tsx") || filepath.endsWith(".tsx")) {
plugins.push([
await import('@babel/plugin-transform-typescript').then(
(r) => r.default
),
{ isTSX: true, allowExtensions: true, allowDeclareFields: true }
]);
}
plugins.push(...babelPlugins);
const result = babel__namespace.transformSync(code, {
babelrc: false,
ast: true,
plugins,
presets,
sourceMaps: needSourceMap,
sourceFileName: id,
configFile: false
});
if (!ssr && !needHmr) {
if (!result.code)
return;
return {
code: result.code,
map: result.map
};
}
const declaredComponents = [];
const hotComponents = [];
let hasDefault = false;
for (const node of result.ast.program.body) {
if (node.type === "VariableDeclaration") {
const names = parseComponentDecls(node);
if (names.length) {
declaredComponents.push(...names);
}
}
if (node.type === "ExportNamedDeclaration") {
if (node.declaration && node.declaration.type === "VariableDeclaration") {
hotComponents.push(
...parseComponentDecls(node.declaration).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: node.declaration.name,
exported: "default",
id: getHash(id + "default")
});
}
} else if (isDefineComponentCall(node.declaration)) {
hasDefault = true;
hotComponents.push({
local: "__default__",
exported: "default",
id: getHash(id + "default")
});
}
}
}
if (hotComponents.length) {
if (hasDefault && (needHmr || ssr)) {
result.code = result.code.replace(
/export default defineComponent/g,
`const __default__ = defineComponent`
) + `
export default __default__`;
}
if (needHmr && !ssr && !/\?vue&type=script/.test(id)) {
let code2 = result.code;
let callbackCode = ``;
code2 += `
import __VUE_HMR_RUNTIME__ from "${HMR_RUNTIME_ID}"`;
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})`;
}
code2 += `
import.meta.hot.accept(({${hotComponents.map((c) => `${c.exported}: __${c.exported}`).join(",")}}) => {${callbackCode}
})`;
result.code = code2;
}
if (ssr) {
const normalizedId = vite.normalizePath(path__default.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, source) {
const names = [];
for (const decl of node.declarations) {
if (decl.id.type === "Identifier" && isDefineComponentCall(decl.init)) {
names.push({
name: decl.id.name
});
}
}
return names;
}
function isDefineComponentCall(node) {
return node && node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "defineComponent";
}
function getHash(text) {
return node_crypto.createHash("sha256").update(text).digest("hex").substring(0, 8);
}
module.exports = vue2JsxPlugin;
module.exports["default"] = vue2JsxPlugin;