UNPKG

@vitejs/plugin-vue2-jsx

Version:

Provides Vue 2 JSX & TSX support with HMR.

468 lines (447 loc) 14.3 kB
'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;