verify-vite-plugin-auto-scan.mjs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import { existsSync } from 'node:fs'
  2. import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
  3. import { tmpdir } from 'node:os'
  4. import { dirname, join, resolve } from 'node:path'
  5. import { fileURLToPath, pathToFileURL } from 'node:url'
  6. const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
  7. const pluginDist = resolve(root, 'packages/presets/vite-plugin/dist/index.js')
  8. function assert(condition, message) {
  9. if (!condition) {
  10. throw new Error(message)
  11. }
  12. }
  13. assert(
  14. existsSync(pluginDist),
  15. 'packages/presets/vite-plugin/dist/index.js is missing; run pnpm --filter @file-viewer/vite-plugin build first.'
  16. )
  17. const plugin = await import(`${pathToFileURL(pluginDist).href}?t=${Date.now()}`)
  18. const {
  19. collectFileViewerRendererScanTokens,
  20. extractFileViewerRendererHintTokens,
  21. fileViewerRenderers,
  22. resolveFileViewerRendererSelection
  23. } = plugin
  24. const inlineTokens = extractFileViewerRendererHintTokens(`
  25. export const fileViewerFormats = ['pdf', 'docx', 'dwg']
  26. const upload = '<input accept=".xlsx,image/*,application/vnd.ms-powerpoint">'
  27. // file-viewer-formats: typst zip xmind
  28. <div data-file-viewer-renderers="geo,eda"></div>
  29. `)
  30. for (const token of ['pdf', 'docx', 'dwg', 'xlsx', 'image', 'ppt', 'typst', 'zip', 'xmind', 'geo', 'eda']) {
  31. assert(inlineTokens.includes(token), `inline hint extraction missed ${token}`)
  32. }
  33. const tempRoot = await mkdtemp(join(tmpdir(), 'file-viewer-vite-scan-'))
  34. try {
  35. await mkdir(join(tempRoot, 'src'), { recursive: true })
  36. await writeFile(
  37. join(tempRoot, 'src/App.vue'),
  38. [
  39. '<template>',
  40. ' <input data-file-viewer-formats="pdf,docx,dwf,step" accept=".xlsx,.xmind,.brep" />',
  41. '</template>',
  42. '<script setup lang="ts">',
  43. "const fileViewerRenderers = ['cad', 'typst']",
  44. '</script>',
  45. ''
  46. ].join('\n')
  47. )
  48. await writeFile(
  49. join(tempRoot, 'src/upload.ts'),
  50. [
  51. "export const fileViewerFormats = ['eml', 'epub', 'geojson']",
  52. '// file-viewer-formats: gds oas parquet',
  53. ''
  54. ].join('\n')
  55. )
  56. const scanTokens = collectFileViewerRendererScanTokens(tempRoot, true)
  57. for (const token of ['pdf', 'docx', 'dwf', 'step', 'xlsx', 'xmind', 'brep', 'cad', 'typst', 'eml', 'epub', 'geojson', 'gds', 'oas', 'parquet']) {
  58. assert(scanTokens.includes(token), `source scan missed ${token}`)
  59. }
  60. const selection = resolveFileViewerRendererSelection({ scan: true }, tempRoot)
  61. const packages = new Set(selection.renderers.map(renderer => renderer.packageName))
  62. for (const packageName of [
  63. '@file-viewer/renderer-pdf',
  64. '@file-viewer/renderer-word',
  65. '@file-viewer/renderer-spreadsheet',
  66. '@file-viewer/renderer-cad',
  67. '@file-viewer/renderer-3d',
  68. '@file-viewer/renderer-typst',
  69. '@file-viewer/renderer-mindmap',
  70. '@file-viewer/renderer-email',
  71. '@file-viewer/renderer-epub',
  72. '@file-viewer/renderer-geo',
  73. '@file-viewer/renderer-eda',
  74. '@file-viewer/renderer-data'
  75. ]) {
  76. assert(packages.has(packageName), `auto scan did not select ${packageName}`)
  77. }
  78. const officePresetSelection = resolveFileViewerRendererSelection(
  79. { preset: 'office', formats: ['dwg'] },
  80. tempRoot
  81. )
  82. assert(
  83. officePresetSelection.presetPackage === '@file-viewer/preset-office',
  84. 'office preset did not resolve to @file-viewer/preset-office'
  85. )
  86. assert(
  87. officePresetSelection.packages.includes('@file-viewer/preset-office'),
  88. 'office preset package is missing from the renderer plan'
  89. )
  90. assert(
  91. officePresetSelection.packages.includes('@file-viewer/renderer-cad'),
  92. 'office preset should allow additional explicit renderer packages'
  93. )
  94. for (const rendererId of ['pdf', 'office-word-openxml', 'spreadsheet-openxml', 'cad']) {
  95. assert(
  96. officePresetSelection.rendererIds.includes(rendererId),
  97. `office preset selection missed renderer id ${rendererId}`
  98. )
  99. }
  100. const officePlugin = fileViewerRenderers({ preset: 'office', formats: ['dwg'] })
  101. await officePlugin.buildStart?.()
  102. const officeModuleId = officePlugin.resolveId?.('virtual:file-viewer-renderers')
  103. assert(typeof officeModuleId === 'string', 'office preset virtual module did not resolve')
  104. const officeVirtualCode = await officePlugin.load?.(officeModuleId)
  105. assert(
  106. officeVirtualCode.includes("@file-viewer/preset-office"),
  107. 'office preset virtual module must import @file-viewer/preset-office'
  108. )
  109. assert(
  110. officeVirtualCode.includes("@file-viewer/renderer-cad"),
  111. 'office preset virtual module must import explicitly added CAD renderer'
  112. )
  113. assert(
  114. !officeVirtualCode.includes("import { pdfRenderer"),
  115. 'office preset virtual module must not re-import renderer packages covered by the preset'
  116. )
  117. assert(
  118. officeVirtualCode.includes('registerFileViewerAutoRendererPreset'),
  119. 'virtual module must register selected renderers for framework auto consumption'
  120. )
  121. const officeHtmlTags = officePlugin.transformIndexHtml?.()
  122. assert(
  123. Array.isArray(officeHtmlTags) &&
  124. officeHtmlTags.some(tag => tag?.children?.includes('virtual:file-viewer-renderers')),
  125. 'configured plugin should inject the virtual renderer module into Vite HTML'
  126. )
  127. const manualOnlyPlugin = fileViewerRenderers({ preset: 'office', inject: false })
  128. assert(
  129. manualOnlyPlugin.transformIndexHtml?.() === undefined,
  130. 'inject:false should keep the virtual renderer module manual-only'
  131. )
  132. const strictRoot = await mkdtemp(join(tmpdir(), 'file-viewer-vite-strict-'))
  133. const previousCwd = process.cwd()
  134. try {
  135. const fakePdfRoot = join(
  136. strictRoot,
  137. 'node_modules/@file-viewer/preset-office/node_modules/@file-viewer/renderer-pdf/node_modules/pdfjs-dist'
  138. )
  139. await mkdir(join(strictRoot, 'node_modules/@file-viewer/preset-office'), { recursive: true })
  140. await mkdir(join(strictRoot, 'node_modules/@file-viewer/preset-office/node_modules/@file-viewer/renderer-pdf'), { recursive: true })
  141. await mkdir(join(fakePdfRoot, 'legacy/build'), { recursive: true })
  142. await mkdir(join(fakePdfRoot, 'cmaps'), { recursive: true })
  143. await mkdir(join(fakePdfRoot, 'wasm'), { recursive: true })
  144. await mkdir(join(fakePdfRoot, 'standard_fonts'), { recursive: true })
  145. await writeFile(join(strictRoot, 'package.json'), '{"name":"strict-react-app","type":"module"}\n')
  146. await writeFile(
  147. join(strictRoot, 'node_modules/@file-viewer/preset-office/package.json'),
  148. '{"name":"@file-viewer/preset-office","version":"0.0.0","dependencies":{"@file-viewer/renderer-pdf":"0.0.0"}}\n'
  149. )
  150. await writeFile(
  151. join(strictRoot, 'node_modules/@file-viewer/preset-office/node_modules/@file-viewer/renderer-pdf/package.json'),
  152. '{"name":"@file-viewer/renderer-pdf","version":"0.0.0","dependencies":{"pdfjs-dist":"0.0.0"}}\n'
  153. )
  154. await writeFile(join(fakePdfRoot, 'package.json'), '{"name":"pdfjs-dist","version":"0.0.0"}\n')
  155. await writeFile(join(fakePdfRoot, 'legacy/build/pdf.worker.mjs'), 'export default null\n')
  156. await writeFile(join(fakePdfRoot, 'cmaps/LICENSE'), 'fake cmap\n')
  157. await writeFile(join(fakePdfRoot, 'wasm/pdf.wasm'), 'fake wasm\n')
  158. await writeFile(join(fakePdfRoot, 'standard_fonts/LICENSE'), 'fake font\n')
  159. process.chdir(strictRoot)
  160. const strictPlugin = fileViewerRenderers({
  161. preset: 'office',
  162. copyAssets: { publicDir: 'public', mode: 'dev' }
  163. })
  164. strictPlugin.config?.({ root: '.' })
  165. strictPlugin.configResolved?.({
  166. root: strictRoot,
  167. publicDir: join(strictRoot, 'public'),
  168. build: { outDir: join(strictRoot, 'dist') }
  169. })
  170. await strictPlugin.buildStart?.()
  171. await strictPlugin.configureServer?.({})
  172. for (const assetPath of [
  173. 'vendor/pdf/pdf.worker.mjs',
  174. 'vendor/pdf/cmaps/LICENSE',
  175. 'vendor/pdf/wasm/pdf.wasm',
  176. 'vendor/pdf/standard_fonts/LICENSE'
  177. ]) {
  178. assert(
  179. existsSync(join(strictRoot, 'public', assetPath)),
  180. `strict preset dependency asset copy missed ${assetPath}`
  181. )
  182. }
  183. } finally {
  184. process.chdir(previousCwd)
  185. await rm(strictRoot, { recursive: true, force: true })
  186. }
  187. const autoPresetSelection = resolveFileViewerRendererSelection({ preset: 'auto' }, tempRoot)
  188. assert(
  189. autoPresetSelection.autoPresetPackages.length > 0,
  190. 'preset:auto should discover installed @file-viewer/preset-* packages'
  191. )
  192. const autoPresetPlugin = fileViewerRenderers({ preset: 'auto' })
  193. await autoPresetPlugin.buildStart?.()
  194. const autoPresetModuleId = autoPresetPlugin.resolveId?.('virtual:file-viewer-renderers')
  195. assert(typeof autoPresetModuleId === 'string', 'preset:auto virtual module did not resolve')
  196. const autoPresetVirtualCode = await autoPresetPlugin.load?.(autoPresetModuleId)
  197. assert(typeof autoPresetVirtualCode === 'string', 'preset:auto virtual module did not load')
  198. assert(
  199. autoPresetSelection.autoPresetPackages.some(packageName =>
  200. autoPresetVirtualCode.includes(packageName)
  201. ),
  202. 'preset:auto virtual module should import discovered preset packages'
  203. )
  204. } finally {
  205. await rm(tempRoot, { recursive: true, force: true })
  206. }
  207. console.log('[vite-plugin-auto-scan] Verified source hint scanning and renderer selection.')