index.mjs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import { createHash } from 'node:crypto';
  2. import path from 'node:path';
  3. import * as babel from '@babel/core';
  4. import jsx from '@vue/babel-plugin-jsx';
  5. import { createFilter, normalizePath } from 'vite';
  6. const ssrRegisterHelperId = "/__vue-jsx-ssr-register-helper";
  7. const ssrRegisterHelperCode = `import { useSSRContext } from "vue"
  8. export ${ssrRegisterHelper.toString()}`;
  9. function ssrRegisterHelper(comp, filename) {
  10. const setup = comp.setup;
  11. comp.setup = (props, ctx) => {
  12. const ssrContext = useSSRContext();
  13. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add(filename);
  14. if (setup) {
  15. return setup(props, ctx);
  16. }
  17. };
  18. }
  19. function vueJsxPlugin(options = {}) {
  20. let root = "";
  21. let needHmr = false;
  22. let needSourceMap = true;
  23. const { include, exclude, babelPlugins = [], ...babelPluginOptions } = options;
  24. const filter = createFilter(include || /\.[jt]sx$/, exclude);
  25. return {
  26. name: "vite:vue-jsx",
  27. config(config) {
  28. return {
  29. // only apply esbuild to ts files
  30. // since we are handling jsx and tsx now
  31. esbuild: {
  32. include: /\.ts$/
  33. },
  34. define: {
  35. __VUE_OPTIONS_API__: config.define?.__VUE_OPTIONS_API__ ?? true,
  36. __VUE_PROD_DEVTOOLS__: config.define?.__VUE_PROD_DEVTOOLS__ ?? false
  37. }
  38. };
  39. },
  40. configResolved(config) {
  41. needHmr = config.command === "serve" && !config.isProduction;
  42. needSourceMap = config.command === "serve" || !!config.build.sourcemap;
  43. root = config.root;
  44. },
  45. resolveId(id) {
  46. if (id === ssrRegisterHelperId) {
  47. return id;
  48. }
  49. },
  50. load(id) {
  51. if (id === ssrRegisterHelperId) {
  52. return ssrRegisterHelperCode;
  53. }
  54. },
  55. async transform(code, id, opt) {
  56. const ssr = opt?.ssr === true;
  57. const [filepath] = id.split("?");
  58. if (filter(id) || filter(filepath)) {
  59. const plugins = [[jsx, babelPluginOptions], ...babelPlugins];
  60. if (id.endsWith(".tsx") || filepath.endsWith(".tsx")) {
  61. plugins.push([
  62. // @ts-ignore missing type
  63. await import('@babel/plugin-transform-typescript').then(
  64. (r) => r.default
  65. ),
  66. // @ts-ignore
  67. { isTSX: true, allowExtensions: true }
  68. ]);
  69. }
  70. if (!ssr && !needHmr) {
  71. plugins.push(() => {
  72. return {
  73. visitor: {
  74. CallExpression: {
  75. enter(_path) {
  76. if (isDefineComponentCall(_path.node)) {
  77. const callee = _path.node.callee;
  78. callee.name = `/* @__PURE__ */ ${callee.name}`;
  79. }
  80. }
  81. }
  82. }
  83. };
  84. });
  85. }
  86. const result = babel.transformSync(code, {
  87. babelrc: false,
  88. ast: true,
  89. plugins,
  90. sourceMaps: needSourceMap,
  91. sourceFileName: id,
  92. configFile: false
  93. });
  94. if (!ssr && !needHmr) {
  95. if (!result.code)
  96. return;
  97. return {
  98. code: result.code,
  99. map: result.map
  100. };
  101. }
  102. const declaredComponents = [];
  103. const hotComponents = [];
  104. let hasDefault = false;
  105. for (const node of result.ast.program.body) {
  106. if (node.type === "VariableDeclaration") {
  107. const names = parseComponentDecls(node);
  108. if (names.length) {
  109. declaredComponents.push(...names);
  110. }
  111. }
  112. if (node.type === "ExportNamedDeclaration") {
  113. if (node.declaration && node.declaration.type === "VariableDeclaration") {
  114. hotComponents.push(
  115. ...parseComponentDecls(node.declaration).map(
  116. ({ name }) => ({
  117. local: name,
  118. exported: name,
  119. id: getHash(id + name)
  120. })
  121. )
  122. );
  123. } else if (node.specifiers.length) {
  124. for (const spec of node.specifiers) {
  125. if (spec.type === "ExportSpecifier" && spec.exported.type === "Identifier") {
  126. const matched = declaredComponents.find(
  127. ({ name }) => name === spec.local.name
  128. );
  129. if (matched) {
  130. hotComponents.push({
  131. local: spec.local.name,
  132. exported: spec.exported.name,
  133. id: getHash(id + spec.exported.name)
  134. });
  135. }
  136. }
  137. }
  138. }
  139. }
  140. if (node.type === "ExportDefaultDeclaration") {
  141. if (node.declaration.type === "Identifier") {
  142. const _name = node.declaration.name;
  143. const matched = declaredComponents.find(
  144. ({ name }) => name === _name
  145. );
  146. if (matched) {
  147. hotComponents.push({
  148. local: node.declaration.name,
  149. exported: "default",
  150. id: getHash(id + "default")
  151. });
  152. }
  153. } else if (isDefineComponentCall(node.declaration)) {
  154. hasDefault = true;
  155. hotComponents.push({
  156. local: "__default__",
  157. exported: "default",
  158. id: getHash(id + "default")
  159. });
  160. }
  161. }
  162. }
  163. if (hotComponents.length) {
  164. if (hasDefault && (needHmr || ssr)) {
  165. result.code = result.code.replace(
  166. /export default defineComponent/g,
  167. `const __default__ = defineComponent`
  168. ) + `
  169. export default __default__`;
  170. }
  171. if (needHmr && !ssr && !/\?vue&type=script/.test(id)) {
  172. let code2 = result.code;
  173. let callbackCode = ``;
  174. for (const { local, exported, id: id2 } of hotComponents) {
  175. code2 += `
  176. ${local}.__hmrId = "${id2}"
  177. __VUE_HMR_RUNTIME__.createRecord("${id2}", ${local})`;
  178. callbackCode += `
  179. __VUE_HMR_RUNTIME__.reload("${id2}", __${exported})`;
  180. }
  181. code2 += `
  182. import.meta.hot.accept(({${hotComponents.map((c) => `${c.exported}: __${c.exported}`).join(",")}}) => {${callbackCode}
  183. })`;
  184. result.code = code2;
  185. }
  186. if (ssr) {
  187. const normalizedId = normalizePath(path.relative(root, id));
  188. let ssrInjectCode = `
  189. import { ssrRegisterHelper } from "${ssrRegisterHelperId}"
  190. const __moduleId = ${JSON.stringify(normalizedId)}`;
  191. for (const { local } of hotComponents) {
  192. ssrInjectCode += `
  193. ssrRegisterHelper(${local}, __moduleId)`;
  194. }
  195. result.code += ssrInjectCode;
  196. }
  197. }
  198. if (!result.code)
  199. return;
  200. return {
  201. code: result.code,
  202. map: result.map
  203. };
  204. }
  205. }
  206. };
  207. }
  208. function parseComponentDecls(node, source) {
  209. const names = [];
  210. for (const decl of node.declarations) {
  211. if (decl.id.type === "Identifier" && isDefineComponentCall(decl.init)) {
  212. names.push({
  213. name: decl.id.name
  214. });
  215. }
  216. }
  217. return names;
  218. }
  219. function isDefineComponentCall(node) {
  220. return node && node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "defineComponent";
  221. }
  222. function getHash(text) {
  223. return createHash("sha256").update(text).digest("hex").substring(0, 8);
  224. }
  225. export { vueJsxPlugin as default };