vite.config.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import fs from 'node:fs'
  2. import { defineConfig, type Plugin } from 'vite'
  3. import react from '@vitejs/plugin-react-swc'
  4. import tailwindcss from '@tailwindcss/vite'
  5. import path from 'node:path'
  6. function inlineEntryAssets(): Plugin {
  7. let resolvedOutDir = ''
  8. return {
  9. name: 'inline-entry-assets',
  10. apply: 'build',
  11. configResolved(config) {
  12. resolvedOutDir = path.resolve(config.root, config.build.outDir)
  13. },
  14. closeBundle() {
  15. const indexHtmlPath = path.join(resolvedOutDir, 'index.html')
  16. if (!fs.existsSync(indexHtmlPath)) {
  17. return
  18. }
  19. const filesToDelete = new Set<string>()
  20. const escapeInlineScript = (code: string) => code.replace(/<\/script/gi, '<\\/script')
  21. const escapeInlineStyle = (code: string) => code.replace(/<\/style/gi, '<\\/style')
  22. const normalizeFileName = (assetPath: string) =>
  23. assetPath.replace(/^\//, '').replace(/^\.\//, '')
  24. const readBuiltAsset = (assetPath: string) => {
  25. const fileName = normalizeFileName(assetPath)
  26. const absolutePath = path.join(resolvedOutDir, fileName)
  27. if (!fs.existsSync(absolutePath)) {
  28. return null
  29. }
  30. filesToDelete.add(absolutePath)
  31. return fs.readFileSync(absolutePath, 'utf8')
  32. }
  33. let html = fs.readFileSync(indexHtmlPath, 'utf8')
  34. html = html.replace(
  35. /<link rel="modulepreload"[^>]+href="([^"]+)"[^>]*>/g,
  36. (_fullMatch, href: string) => {
  37. const absolutePath = path.join(resolvedOutDir, normalizeFileName(href))
  38. if (fs.existsSync(absolutePath)) {
  39. filesToDelete.add(absolutePath)
  40. }
  41. return ''
  42. },
  43. )
  44. html = html.replace(
  45. /<link rel="stylesheet"[^>]+href="([^"]+)"[^>]*>/g,
  46. (fullMatch, href: string) => {
  47. const assetSource = readBuiltAsset(href)
  48. if (!assetSource) {
  49. return fullMatch
  50. }
  51. return `<style>${escapeInlineStyle(assetSource)}</style>`
  52. },
  53. )
  54. html = html.replace(
  55. /<script type="module"[^>]+src="([^"]+)"[^>]*><\/script>/g,
  56. (fullMatch, src: string) => {
  57. const chunkCode = readBuiltAsset(src)
  58. if (!chunkCode) {
  59. return fullMatch
  60. }
  61. return `<script type="module">${escapeInlineScript(chunkCode)}</script>`
  62. },
  63. )
  64. fs.writeFileSync(indexHtmlPath, html)
  65. for (const filePath of filesToDelete) {
  66. fs.rmSync(filePath, { force: true })
  67. }
  68. fs.rmSync(path.join(resolvedOutDir, 'vite.svg'), { force: true })
  69. fs.rmSync(path.join(resolvedOutDir, 'assets'), { recursive: true, force: true })
  70. },
  71. }
  72. }
  73. // https://vite.dev/config/
  74. export default defineConfig({
  75. plugins: [react(), tailwindcss(), inlineEntryAssets()],
  76. publicDir: false,
  77. resolve: {
  78. alias: {
  79. '@': path.resolve(__dirname, './src'),
  80. },
  81. },
  82. build: {
  83. assetsInlineLimit: Number.MAX_SAFE_INTEGER,
  84. cssCodeSplit: false,
  85. modulePreload: false,
  86. rollupOptions: {
  87. output: {
  88. inlineDynamicImports: true,
  89. },
  90. },
  91. },
  92. server: {
  93. proxy: {
  94. '/v1': 'http://localhost:8888',
  95. '/v2': 'http://localhost:8888',
  96. '/health': 'http://localhost:8888',
  97. },
  98. },
  99. })