284 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict';
 | 
						|
 | 
						|
const crypto = require('node:crypto');
 | 
						|
const path = require('node:path');
 | 
						|
const babel = require('@babel/core');
 | 
						|
const jsx = require('@vue/babel-plugin-jsx');
 | 
						|
const vite = require('vite');
 | 
						|
const pluginutils = require('@rolldown/pluginutils');
 | 
						|
 | 
						|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
 | 
						|
 | 
						|
function _interopNamespaceCompat(e) {
 | 
						|
  if (e && typeof e === 'object' && 'default' in e) return e;
 | 
						|
  const n = Object.create(null);
 | 
						|
  if (e) {
 | 
						|
    for (const k in e) {
 | 
						|
      n[k] = e[k];
 | 
						|
    }
 | 
						|
  }
 | 
						|
  n.default = e;
 | 
						|
  return n;
 | 
						|
}
 | 
						|
 | 
						|
const crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
 | 
						|
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
 | 
						|
const babel__namespace = /*#__PURE__*/_interopNamespaceCompat(babel);
 | 
						|
const jsx__default = /*#__PURE__*/_interopDefaultCompat(jsx);
 | 
						|
 | 
						|
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 = vite.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: pluginutils.exactRegex(ssrRegisterHelperId) },
 | 
						|
      handler(id) {
 | 
						|
        if (id === ssrRegisterHelperId) {
 | 
						|
          return id;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
    load: {
 | 
						|
      filter: { id: pluginutils.exactRegex(ssrRegisterHelperId) },
 | 
						|
      handler(id) {
 | 
						|
        if (id === ssrRegisterHelperId) {
 | 
						|
          return ssrRegisterHelperCode;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
    transform: {
 | 
						|
      filter: {
 | 
						|
        id: {
 | 
						|
          include: include ? pluginutils.makeIdFiltersToMatchWithQuery(include) : void 0,
 | 
						|
          exclude: exclude ? pluginutils.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__default, 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}`;
 | 
						|
                      }
 | 
						|
                    }
 | 
						|
                  }
 | 
						|
                }
 | 
						|
              };
 | 
						|
            });
 | 
						|
          }
 | 
						|
          const result = babel__namespace.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 = [];
 | 
						|
          let hasDefault = false;
 | 
						|
          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)) {
 | 
						|
                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 = ``;
 | 
						|
              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 = 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, 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);
 | 
						|
}
 | 
						|
const hash = (
 | 
						|
  // eslint-disable-next-line n/no-unsupported-features/node-builtins -- crypto.hash is supported in Node 21.7.0+, 20.12.0+
 | 
						|
  crypto__default.hash ?? ((algorithm, data, outputEncoding) => crypto__default.createHash(algorithm).update(data).digest(outputEncoding))
 | 
						|
);
 | 
						|
function getHash(text) {
 | 
						|
  return hash("sha256", text, "hex").substring(0, 8);
 | 
						|
}
 | 
						|
 | 
						|
module.exports = vueJsxPlugin;
 | 
						|
module.exports.default = vueJsxPlugin;
 |