@file-viewer/vite-plugin 2.1.29 → 2.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # @file-viewer/vite-plugin
2
2
 
3
- Vite plugin for Flyfish File Viewer on-demand renderer assembly. It can auto-discover installed `@file-viewer/preset-*` packages and inject them into the page, so Vue, React, Svelte, jQuery, and Vanilla JS components receive matching preview capability without application code passing `renderers` manually. It can also generate `virtual:file-viewer-renderers` from declared formats, import only matching renderer packages, and provide renderer-oriented chunk planning plus offline worker/WASM/font asset copying.
3
+ Vite plugin for Flyfish File Viewer on-demand renderer assembly. It can auto-discover installed `@file-viewer/preset-*` and `@file-viewer/*-full` packages, so Vue, React, Svelte, jQuery, and Vanilla JS components receive matching preview capability without application code passing `renderers` manually. It can also generate `virtual:file-viewer-renderers` from declared formats, import only matching renderer packages, and provide renderer-oriented chunk planning plus offline worker/WASM/font asset copying.
4
4
 
5
5
  ## Fastest Start
6
6
 
7
- The fastest path is a standard component package, one preset, and one Vite plugin registration. The plugin auto-discovers installed `@file-viewer/preset-*` packages, so application code does not hand-write renderer imports and the plugin does not need `preset:'office'`. Installing the npm package alone does not make Vite run the plugin; register it once in `vite.config.ts`:
7
+ For complete format support, the fastest path is one full package and one Vite plugin registration. Full packages already include `preset-all`; do not install another preset. `copyAssets:true` matches the installed full package and publishes its Worker, WASM, font, and vendor files under `file-viewer/` at the deployment base (`/file-viewer/` for a root deployment) in both dev and build:
8
8
 
9
9
  ```bash
10
- pnpm add @file-viewer/vue3 @file-viewer/preset-office
10
+ pnpm add @file-viewer/vue3-full
11
11
  pnpm add -D @file-viewer/vite-plugin
12
12
  ```
13
13
 
@@ -24,15 +24,17 @@ export default defineConfig({
24
24
  })
25
25
  ```
26
26
 
27
- Components keep `autoRenderers:true` by default, so `@file-viewer/vue3`, `@file-viewer/react`, `@file-viewer/web`, `@file-viewer/svelte`, `@file-viewer/jquery`, `@file-viewer/vue2.7`, and `@file-viewer/vue2.6` automatically consume the injected preset / renderer registry.
27
+ Installing a full package without publishing these assets leaves PDF Worker, CAD/Typst/Archive WASM, and other formats without runtime files. That is partial support, not a complete setup.
28
28
 
29
- Heavy users that want the fastest full-capability setup can install the full preset and keep the same Vite config:
29
+ For a smaller install, use a standard component with one preset and keep the same Vite config. The plugin auto-discovers installed presets, so application code does not hand-write renderer imports and the plugin does not need `preset:'office'`:
30
30
 
31
31
  ```bash
32
- pnpm add @file-viewer/vue3 @file-viewer/preset-all
32
+ pnpm add @file-viewer/vue3 @file-viewer/preset-office
33
33
  pnpm add -D @file-viewer/vite-plugin
34
34
  ```
35
35
 
36
+ Components keep `autoRenderers:true` by default, so `@file-viewer/vue3`, `@file-viewer/react`, `@file-viewer/web`, `@file-viewer/svelte`, `@file-viewer/jquery`, `@file-viewer/vue2.7`, and `@file-viewer/vue2.6` automatically consume the injected preset / renderer registry.
37
+
36
38
  Available presets:
37
39
 
38
40
  - `@file-viewer/preset-lite`: text, Markdown, code, image, audio, and video.
@@ -44,7 +46,7 @@ Install individual renderer packages only for strict cuts, such as `@file-viewer
44
46
 
45
47
  ## vite.config.ts
46
48
 
47
- Prefer preset auto assembly for normal products. With no explicit `preset` option, or only `copyAssets:true`, the plugin auto-discovers installed `@file-viewer/preset-*` packages:
49
+ Prefer full/preset auto assembly for normal products. With only `copyAssets:true`, the plugin auto-discovers installed `@file-viewer/*-full` or `@file-viewer/preset-*` packages:
48
50
 
49
51
  ```ts
50
52
  fileViewerRenderers({
@@ -83,7 +85,7 @@ const options = {
83
85
 
84
86
  | Option | Description |
85
87
  | --- | --- |
86
- | `copyAssets` | `true` or `{ publicDir, outDir, mode }`; copies matched renderer Worker, WASM, font, and vendor assets |
88
+ | `copyAssets` | `true` or `{ publicDir, outDir, mode, baseDir }`; copies matching Worker, WASM, font, and vendor assets. Installed full packages default to `<deployment-base>/file-viewer/`, which is required for complete full support; standard packages/presets keep the existing root behavior |
87
89
  | `preset` | `'auto' | 'lite' | 'office' | 'engineering' | 'all'`; with no explicit formats, installed presets are discovered automatically |
88
90
  | `autoPresets` | `true` or a preset list; useful with `scan:true` when installed presets should stay active |
89
91
  | `formats` | File extensions or format tokens, for example `['pdf', 'docx', 'dwg']` |
@@ -96,6 +98,8 @@ const options = {
96
98
 
97
99
  When host apps split `node_modules` by package name, circular dependencies between `@codemirror/*`, `@lezer/*`, and Sandpack can show up in production as `codemirror-view.* Cannot access ... before initialization`. The plugin wraps existing `manualChunks` functions by default, stabilizes only those known interop chunks, and preserves the rest of the app's custom grouping. Set `stabilizeInteropChunks:false` only when the host build needs total control.
98
100
 
101
+ `copyAssets.baseDir` overrides automatic placement. It must be relative and cannot contain `..`; an empty string explicitly selects the root. The plugin also updates the full package runtime asset base. A manual `setDefaultFullAssetBaseUrl()` call is only needed when a custom `baseDir` is combined with `inject:false`. With Vite `base:'/app/'`, full-package dev URLs start with `/app/file-viewer/...`, while production files remain under `outDir/file-viewer/`.
102
+
99
103
  For strict registry control, disable injection and pass the virtual module explicitly:
100
104
 
101
105
  ```ts
@@ -145,7 +149,7 @@ When a file extension is in the supported matrix but the current project has not
145
149
 
146
150
  ## Current Boundary
147
151
 
148
- The plugin currently generates imports for extracted renderer packages: Word, Spreadsheet, PDF, OFD, Presentation, CAD, Draw.io/Excalidraw/Mermaid/PlantUML, 3D, Data, EDA, Typst, archives, email, EPUB, code/Markdown/Patch/Git Bundle, image, media, XMind, and Geo. Declare them explicitly with `formats`, or let `scan: true` discover source hints automatically; core-supported extensions such as `.zipx`, `.cbz`, `.tiff`, `.mjs`, `.gv`, `.patch`, `.bundle`, `.mermaid`, `.puml`, and `.mpeg` also resolve to their renderer packages. With `copyAssets:true`, the plugin also copies the Typst compiler / renderer WASM files and the `wasm/typst/fonts/` default-font directory. `preset: 'auto' | 'lite' | 'office' | 'engineering' | 'all'` imports matching `@file-viewer/preset-*` packages; when `formats` are also present, the plugin adds extra renderers outside the preset.
152
+ The plugin currently generates imports for extracted renderer packages: Word, Spreadsheet, PDF, OFD, Presentation, CAD, Draw.io/Excalidraw/Mermaid/PlantUML, 3D, Data, EDA, Typst, archives, email, EPUB, code/Markdown/Patch/Git Bundle, image, media, XMind, and Geo. Declare them explicitly with `formats`, or let `scan: true` discover source hints automatically; core-supported extensions such as `.zipx`, `.cbz`, `.tiff`, `.mjs`, `.gv`, `.patch`, `.bundle`, `.mermaid`, `.puml`, and `.mpeg` also resolve to their renderer packages. With `copyAssets:true`, the plugin detects installed capability packages. Full packages copy matching assets into dev `publicDir/file-viewer/` and production `outDir/file-viewer/`; standard packages/presets keep the existing root placement, including the Typst compiler / renderer WASM files and default fonts. `preset: 'auto' | 'lite' | 'office' | 'engineering' | 'all'` imports matching `@file-viewer/preset-*` packages; when `formats` are also present, the plugin adds extra renderers outside the preset.
149
153
 
150
154
  ## Documentation
151
155
 
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # @file-viewer/vite-plugin
2
2
 
3
- Flyfish File Viewer 的 Vite 按需 renderer 自动装配插件。它可以自动发现已安装的 `@file-viewer/preset-*` 包并注入到页面,让 Vue、React、Svelte、jQuery 和 Vanilla JS 组件无需手动传 `renderers` 就获得对应预览能力;也可以根据业务声明的文件格式生成 `virtual:file-viewer-renderers`,只 import 命中的 renderer 包,并提供 chunk 分组和离线 worker/WASM/字体资源复制能力。
3
+ Flyfish File Viewer 的 Vite 按需 renderer 自动装配插件。它可以自动发现已安装的 `@file-viewer/preset-*` 和 `@file-viewer/*-full` 包,让 Vue、React、Svelte、jQuery 和 Vanilla JS 组件无需手动传 `renderers` 就获得对应预览能力;也可以根据业务声明的文件格式生成 `virtual:file-viewer-renderers`,只 import 命中的 renderer 包,并提供 chunk 分组和离线 worker/WASM/字体资源复制能力。
4
4
 
5
5
  ## 最快开始
6
6
 
7
- 最快接入路径是“标准组件包 + 一个 preset + 注册一次 Vite 插件”。插件会自动发现当前项目已安装的 `@file-viewer/preset-*`,无需手写 renderer import,也无需给插件显式写 `preset:'office'`。注意:只安装 npm 包不会让 Vite 自动运行插件,仍需要在 `vite.config.ts` 中注册一次:
7
+ 需要完整格式支持时,最快路径是“full 包 + 注册一次 Vite 插件”。full 已内置 `preset-all`,不要再安装 preset;`copyAssets:true` 会在开发和构建时自动匹配 full 包,并把 Worker、WASM、字体和 vendor 文件发布到部署基址下的 `file-viewer/`(根部署即 `/file-viewer/`):
8
8
 
9
9
  ```bash
10
- pnpm add @file-viewer/vue3 @file-viewer/preset-office
10
+ pnpm add @file-viewer/vue3-full
11
11
  pnpm add -D @file-viewer/vite-plugin
12
12
  ```
13
13
 
@@ -24,15 +24,17 @@ export default defineConfig({
24
24
  })
25
25
  ```
26
26
 
27
- 组件默认 `autoRenderers:true`,会读取 Vite 插件注入的 preset / renderer registry。常规业务代码只要使用 `@file-viewer/vue3`、`@file-viewer/react`、`@file-viewer/web`、`@file-viewer/svelte`、`@file-viewer/jquery`、`@file-viewer/vue2.7` 或 `@file-viewer/vue2.6` 即可获得对应能力。
27
+ 只安装 full 包而不启用资源复制,PDF Worker、CAD/Typst/Archive WASM 等格式会缺少运行文件,只能获得部分格式支持。
28
28
 
29
- 重度用户需要最快拥有全部预览能力时,直接安装全量 preset,Vite 配置保持不变:
29
+ 需要更小体积时,改用“标准组件包 + 一个 preset”,Vite 配置保持不变。插件会自动发现已安装的 preset,无需手写 renderer import,也无需显式写 `preset:'office'`:
30
30
 
31
31
  ```bash
32
- pnpm add @file-viewer/vue3 @file-viewer/preset-all
32
+ pnpm add @file-viewer/vue3 @file-viewer/preset-office
33
33
  pnpm add -D @file-viewer/vite-plugin
34
34
  ```
35
35
 
36
+ 组件默认 `autoRenderers:true`,会读取 Vite 插件注入的 preset / renderer registry。常规业务代码只要使用 `@file-viewer/vue3`、`@file-viewer/react`、`@file-viewer/web`、`@file-viewer/svelte`、`@file-viewer/jquery`、`@file-viewer/vue2.7` 或 `@file-viewer/vue2.6` 即可获得对应能力。
37
+
36
38
  可选 preset:
37
39
 
38
40
  - `@file-viewer/preset-lite`: 文本、Markdown、代码、图片、音频、视频。
@@ -44,7 +46,7 @@ pnpm add -D @file-viewer/vite-plugin
44
46
 
45
47
  ## vite.config.ts
46
48
 
47
- 推荐默认使用 preset 自动装配。无参或只传 `copyAssets:true` 时,插件会自动发现已安装的 `@file-viewer/preset-*`,无需写 `preset:'office'`:
49
+ 推荐默认使用 full/preset 自动装配。只传 `copyAssets:true` 时,插件会自动发现已安装的 `@file-viewer/*-full` 或 `@file-viewer/preset-*`,无需写 `preset:'all'`:
48
50
 
49
51
  ```ts
50
52
  fileViewerRenderers({
@@ -83,7 +85,7 @@ const options = {
83
85
 
84
86
  | 选项 | 说明 |
85
87
  | --- | --- |
86
- | `copyAssets` | `true` 或 `{ publicDir, outDir, mode }`,复制匹配 renderer 的 Worker、WASM、字体和 vendor 资源 |
88
+ | `copyAssets` | `true` 或 `{ publicDir, outDir, mode, baseDir }`;复制匹配的 Worker、WASM、字体和 vendor 资源。检测到 full 时默认发布到 `<部署基址>/file-viewer/`,这是 full 完整格式支持的必需项;标准包/preset 保持原有根目录行为 |
87
89
  | `preset` | `'auto' | 'lite' | 'office' | 'engineering' | 'all'`;默认无显式格式时自动发现已安装 preset |
88
90
  | `autoPresets` | `true` 或 preset 列表;常用于 `scan:true` 时继续自动激活已安装 preset |
89
91
  | `formats` | 文件扩展名或格式 token,例如 `['pdf', 'docx', 'dwg']` |
@@ -96,6 +98,8 @@ const options = {
96
98
 
97
99
  如果宿主项目按包名拆分 `node_modules`,`@codemirror/*`、`@lezer/*` 和 Sandpack 之间的循环依赖可能在生产构建中表现为 `codemirror-view.* Cannot access ... before initialization`。插件默认会包裹已有的 `manualChunks` 函数,只稳定这些已知互操作 chunk,保留其它自定义分组;确实需要完全接管时可设置 `stabilizeInteropChunks:false`。
98
100
 
101
+ `copyAssets.baseDir` 可以覆盖自动目录:必须是相对路径,不能包含 `..`;传空字符串表示显式复制到根目录。插件会同步 full 包的运行时资源根,无需再手动调用 `setDefaultFullAssetBaseUrl()`;同时自定义 `baseDir` 并设置 `inject:false` 时,才需由业务自行设置。Vite 配置了 `base:'/app/'` 时,full 包开发 URL 为 `/app/file-viewer/...`,构建文件仍位于 `outDir/file-viewer/`。
102
+
99
103
  如果你需要严格控制 registry,可以关闭注入并手动传入:
100
104
 
101
105
  ```ts
@@ -145,7 +149,7 @@ export const fileViewerFormats = ['pdf', 'docx', 'xlsx']
145
149
 
146
150
  ## 当前边界
147
151
 
148
- 当前插件会为已经拆出的 renderer 包生成导入:Word、Spreadsheet、PDF、OFD、Presentation、CAD、Draw.io/Excalidraw/Mermaid/PlantUML、3D、Data、EDA、Typst、压缩包、邮件、EPUB、代码/Markdown/Patch/Git Bundle、图片、媒体、XMind 和 Geo。可以通过 `formats` 显式声明,也可以通过 `scan: true` 从源码 hint 自动发现;`.zipx`、`.cbz`、`.tiff`、`.mjs`、`.gv`、`.patch`、`.bundle`、`.mermaid`、`.puml`、`.mpeg` 等 core 支持的扩展也会映射到对应 renderer。开启 `copyAssets:true` 时会同时复制 Typst compiler / renderer WASM 与 `wasm/typst/fonts/` 默认字体目录。`preset: 'auto' | 'lite' | 'office' | 'engineering' | 'all'` 会导入对应 `@file-viewer/preset-*` 包;如果同时声明 `formats`,插件会在 preset 之外补充额外 renderer。
152
+ 当前插件会为已经拆出的 renderer 包生成导入:Word、Spreadsheet、PDF、OFD、Presentation、CAD、Draw.io/Excalidraw/Mermaid/PlantUML、3D、Data、EDA、Typst、压缩包、邮件、EPUB、代码/Markdown/Patch/Git Bundle、图片、媒体、XMind 和 Geo。可以通过 `formats` 显式声明,也可以通过 `scan: true` 从源码 hint 自动发现;`.zipx`、`.cbz`、`.tiff`、`.mjs`、`.gv`、`.patch`、`.bundle`、`.mermaid`、`.puml`、`.mpeg` 等 core 支持的扩展也会映射到对应 renderer。开启 `copyAssets:true` 时会识别已安装能力;full 包把匹配资源复制到开发 `publicDir/file-viewer/` 与构建 `outDir/file-viewer/`,标准包/preset 维持根目录行为,包括 Typst compiler / renderer WASM 与默认字体目录。`preset: 'auto' | 'lite' | 'office' | 'engineering' | 'all'` 会导入对应 `@file-viewer/preset-*` 包;如果同时声明 `formats`,插件会在 preset 之外补充额外 renderer。
149
153
 
150
154
  ## 文档
151
155
 
package/dist/index.d.ts CHANGED
@@ -31,6 +31,14 @@ export interface FileViewerCopyAssetsOptions {
31
31
  * Directory used after production build. Defaults to build.outDir.
32
32
  */
33
33
  outDir?: string;
34
+ /**
35
+ * Directory below publicDir/outDir where assets are published. When omitted,
36
+ * projects with an installed @file-viewer/*-full package use `file-viewer`;
37
+ * other projects keep the existing output-root layout. Use an empty string
38
+ * to explicitly publish at the output root. For full packages, the injected
39
+ * runtime module keeps the default asset base aligned with this directory.
40
+ */
41
+ baseDir?: string;
34
42
  /**
35
43
  * Copy during dev server startup, build closeBundle, or both.
36
44
  */
@@ -100,6 +108,26 @@ interface MissingRendererNotice {
100
108
  }
101
109
  export declare function extractFileViewerRendererHintTokens(source: string): string[];
102
110
  export declare function collectFileViewerRendererScanTokens(projectRoot: string, scan: FileViewerRenderersPluginOptions['scan']): string[];
111
+ export interface FileViewerCopyAssetsTargetConfig {
112
+ /** Vite project root. Defaults to process.cwd(). */
113
+ projectRoot?: string;
114
+ /** Resolved Vite publicDir. Defaults to `public`. */
115
+ publicDir?: string;
116
+ /** Resolved Vite build.outDir. Defaults to `dist`. */
117
+ outDir?: string;
118
+ }
119
+ export interface FileViewerCopyAssetsTarget {
120
+ mode: 'dev' | 'build';
121
+ outputRoot: string;
122
+ targetRoot: string;
123
+ baseDir: string;
124
+ installedFullPackages: string[];
125
+ }
126
+ /**
127
+ * Resolves the exact directory used by copyAssets without running a Vite build.
128
+ * This is exported so integrations can validate their deployment contract.
129
+ */
130
+ export declare function resolveFileViewerCopyAssetsTarget(mode: 'dev' | 'build', value?: true | FileViewerCopyAssetsOptions, config?: FileViewerCopyAssetsTargetConfig): FileViewerCopyAssetsTarget;
103
131
  export declare function fileViewerRenderers(options?: FileViewerRenderersPluginOptions): Plugin;
104
132
  export declare function createFileViewerManualChunks(options?: FileViewerRenderersPluginOptions): (id: string) => string | undefined;
105
133
  export declare function resolveFileViewerRendererSelection(options?: FileViewerRenderersPluginOptions, projectRoot?: string): {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
- import { copyFile, cp, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
2
+ import { copyFile, cp, mkdir, readFile, realpath, rm, stat, writeFile } from 'node:fs/promises';
3
3
  import { createRequire } from 'node:module';
4
- import { dirname, extname, isAbsolute, join, resolve, sep } from 'node:path';
4
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
5
5
  const virtualModuleId = 'virtual:file-viewer-renderers';
6
6
  const resolvedVirtualModuleId = `\0${virtualModuleId}`;
7
7
  const pluginRequire = createRequire(import.meta.url);
@@ -384,6 +384,16 @@ const presetModules = {
384
384
  chunkName: 'file-viewer-preset-engineering'
385
385
  }
386
386
  };
387
+ const fileViewerFullPackages = [
388
+ '@file-viewer/react-full',
389
+ '@file-viewer/react-legacy-full',
390
+ '@file-viewer/vue3-full',
391
+ '@file-viewer/vue2.7-full',
392
+ '@file-viewer/vue2.6-full',
393
+ '@file-viewer/web-full',
394
+ '@file-viewer/jquery-full',
395
+ '@file-viewer/svelte-full'
396
+ ];
387
397
  // Vite dep optimization rewrites import.meta.url into node_modules/.vite/deps
388
398
  // and can trigger full-preset dev-server re-optimization reloads. Keep File
389
399
  // Viewer packages out of dep optimization while still optimizing their
@@ -393,14 +403,7 @@ const fileViewerOptimizationExcludedPackages = [
393
403
  '@file-viewer/pptx',
394
404
  ...rendererModules.map((descriptor) => descriptor.packageName),
395
405
  ...Object.values(presetModules).map((preset) => preset.packageName),
396
- '@file-viewer/react-full',
397
- '@file-viewer/react-legacy-full',
398
- '@file-viewer/vue3-full',
399
- '@file-viewer/vue2.7-full',
400
- '@file-viewer/vue2.6-full',
401
- '@file-viewer/web-full',
402
- '@file-viewer/jquery-full',
403
- '@file-viewer/svelte-full'
406
+ ...fileViewerFullPackages
404
407
  ];
405
408
  const cjsInteropPackages = [
406
409
  '@file-viewer/docx',
@@ -632,7 +635,7 @@ function expandDescriptorRendererIds(ids) {
632
635
  return descriptor ? [...descriptor.rendererIds] : [id];
633
636
  }));
634
637
  }
635
- function renderVirtualModule(selection, formats, autoPresetIds = []) {
638
+ function renderVirtualModule(selection, formats, autoPresetIds = [], fullAssetBase = null) {
636
639
  const presetModule = selection.preset ? presetModules[selection.preset] : null;
637
640
  const autoPresetModules = autoPresetIds
638
641
  .filter((id) => id !== selection.preset)
@@ -672,15 +675,38 @@ function renderVirtualModule(selection, formats, autoPresetIds = []) {
672
675
  : !selection.preset && autoPresetModules.length === 1
673
676
  ? autoPresetModules[0].id
674
677
  : '@file-viewer/vite-plugin:configured';
678
+ const fullAssetBaseImport = fullAssetBase
679
+ ? fullAssetBase.kind === 'deployment'
680
+ ? `import { DEFAULT_FULL_ASSET_BASE_PATH, resolveDefaultFullAssetBaseUrl, setDefaultFullAssetBaseUrl } from '@file-viewer/preset-all';`
681
+ : `import { setDefaultFullAssetBaseUrl } from '@file-viewer/preset-all';`
682
+ : null;
683
+ const autoRegistrationImport = rendererNames.length
684
+ ? `import { registerFileViewerAutoRendererPreset } from '@file-viewer/core';`
685
+ : null;
686
+ const fullAssetBaseSetup = !fullAssetBase
687
+ ? []
688
+ : fullAssetBase.kind === 'literal'
689
+ ? [`setDefaultFullAssetBaseUrl(${JSON.stringify(fullAssetBase.url)});`, '']
690
+ : [
691
+ 'const fileViewerDefaultFullAssetBaseUrl = resolveDefaultFullAssetBaseUrl();',
692
+ 'const fileViewerDeploymentBaseUrl = fileViewerDefaultFullAssetBaseUrl.endsWith(DEFAULT_FULL_ASSET_BASE_PATH)',
693
+ ' ? fileViewerDefaultFullAssetBaseUrl.slice(0, -DEFAULT_FULL_ASSET_BASE_PATH.length)',
694
+ ' : fileViewerDefaultFullAssetBaseUrl;',
695
+ `setDefaultFullAssetBaseUrl(fileViewerDeploymentBaseUrl + ${JSON.stringify(fullAssetBase.encodedBaseDir)});`,
696
+ ''
697
+ ];
675
698
  return [
676
- `import { registerFileViewerAutoRendererPreset } from '@file-viewer/core';`,
699
+ ...[autoRegistrationImport, fullAssetBaseImport].filter(Boolean),
677
700
  ...[presetImport].filter(Boolean),
678
701
  ...autoPresetImports,
679
702
  ...rendererImports,
680
703
  '',
704
+ ...fullAssetBaseSetup,
681
705
  `export const configuredFileViewerRenderers = [${rendererNames.join(', ')}];`,
682
706
  `export const fileViewerRendererPlan = ${JSON.stringify(plan, null, 2)};`,
683
- `registerFileViewerAutoRendererPreset(configuredFileViewerRenderers, { id: ${JSON.stringify(autoRegistrationId)} });`,
707
+ ...(rendererNames.length
708
+ ? [`registerFileViewerAutoRendererPreset(configuredFileViewerRenderers, { id: ${JSON.stringify(autoRegistrationId)} });`]
709
+ : []),
684
710
  'export default configuredFileViewerRenderers;',
685
711
  ''
686
712
  ].join('\n');
@@ -963,7 +989,10 @@ function assertOwnedPackageVersion(packageName, packageRoot, ownerPackageName) {
963
989
  return readPackageVersion(packageRoot);
964
990
  }
965
991
  const ownerPackageJson = JSON.parse(readFileSync(ownerPackageJsonPath, 'utf8'));
966
- const expectedVersion = ownerPackageJson.dependencies?.[packageName];
992
+ const configuredVersion = ownerPackageJson.dependencies?.[packageName];
993
+ const expectedVersion = configuredVersion?.startsWith('workspace:')
994
+ ? configuredVersion.slice('workspace:'.length)
995
+ : configuredVersion;
967
996
  const actualVersion = readPackageVersion(packageRoot);
968
997
  if (expectedVersion &&
969
998
  /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(expectedVersion) &&
@@ -1199,14 +1228,339 @@ async function copyKnownRendererAssets(targetRoot, rendererIds) {
1199
1228
  }, null, 2)}\n`);
1200
1229
  return results;
1201
1230
  }
1202
- function resolveTargetDir(value, fallback, projectRoot) {
1203
- const target = value || fallback;
1204
- return isAbsolute(target) ? target : resolve(projectRoot || process.cwd(), target);
1231
+ function matchesBundledAssetKind(kind, info) {
1232
+ return kind === 'directory' || kind === 'wasm-directory'
1233
+ ? info.isDirectory()
1234
+ : info.isFile();
1235
+ }
1236
+ function isBundledFullAssetPayload(payloadRoot) {
1237
+ return Boolean(payloadRoot &&
1238
+ existsSync(payloadRoot) &&
1239
+ statSync(payloadRoot).isDirectory() &&
1240
+ existsSync(join(payloadRoot, 'flyfish-viewer-assets.json')));
1241
+ }
1242
+ function resolveBundledFullAssetSource(installedFullPackages) {
1243
+ const copyAssetsPackageRoot = resolvePackageRoot('file-viewer-copy-assets', installedFullPackages);
1244
+ const copyAssetsPayloadRoot = copyAssetsPackageRoot
1245
+ ? join(copyAssetsPackageRoot, 'viewer')
1246
+ : null;
1247
+ if (copyAssetsPackageRoot && copyAssetsPayloadRoot && isBundledFullAssetPayload(copyAssetsPayloadRoot)) {
1248
+ installedFullPackages.forEach((ownerPackageName) => {
1249
+ assertOwnedPackageVersion('file-viewer-copy-assets', copyAssetsPackageRoot, ownerPackageName);
1250
+ });
1251
+ const sourceVersion = readPackageVersion(copyAssetsPackageRoot);
1252
+ if (sourceVersion) {
1253
+ return {
1254
+ packageName: 'file-viewer-copy-assets',
1255
+ packageRoot: copyAssetsPackageRoot,
1256
+ payloadRoot: copyAssetsPayloadRoot,
1257
+ sourceVersion
1258
+ };
1259
+ }
1260
+ }
1261
+ if (installedFullPackages.includes('@file-viewer/web-full')) {
1262
+ const webFullPackageRoot = resolvePackageRoot('@file-viewer/web-full', installedFullPackages);
1263
+ const webFullPayloadRoot = webFullPackageRoot
1264
+ ? join(webFullPackageRoot, 'dist')
1265
+ : null;
1266
+ const sourceVersion = readPackageVersion(webFullPackageRoot);
1267
+ if (webFullPackageRoot &&
1268
+ webFullPayloadRoot &&
1269
+ sourceVersion &&
1270
+ isBundledFullAssetPayload(webFullPayloadRoot)) {
1271
+ return {
1272
+ packageName: '@file-viewer/web-full',
1273
+ packageRoot: webFullPackageRoot,
1274
+ payloadRoot: webFullPayloadRoot,
1275
+ sourceVersion
1276
+ };
1277
+ }
1278
+ }
1279
+ // Source workspaces may not have staged file-viewer-copy-assets/viewer yet.
1280
+ // @file-viewer/web keeps the same tracked payload for local development.
1281
+ const webPackageRoot = resolvePackageRoot('@file-viewer/web', installedFullPackages);
1282
+ const webPayloadRoot = webPackageRoot ? join(webPackageRoot, 'viewer') : null;
1283
+ const sourceVersion = readPackageVersion(webPackageRoot);
1284
+ if (webPackageRoot &&
1285
+ webPayloadRoot &&
1286
+ sourceVersion &&
1287
+ isBundledFullAssetPayload(webPayloadRoot)) {
1288
+ return {
1289
+ packageName: '@file-viewer/web',
1290
+ packageRoot: webPackageRoot,
1291
+ payloadRoot: webPayloadRoot,
1292
+ sourceVersion
1293
+ };
1294
+ }
1295
+ return null;
1296
+ }
1297
+ function assertBundledFullAssetSourceVersion(source, installedFullPackages) {
1298
+ const payloadManifestPath = join(source.payloadRoot, 'flyfish-viewer-manifest.json');
1299
+ if (!existsSync(payloadManifestPath)) {
1300
+ throw new Error(`[file-viewer:vite-plugin] Missing version manifest in ${source.packageName} payload: ${payloadManifestPath}.`);
1301
+ }
1302
+ const payloadManifest = JSON.parse(readFileSync(payloadManifestPath, 'utf8'));
1303
+ if (!payloadManifest.version || payloadManifest.version !== source.sourceVersion) {
1304
+ throw new Error(`[file-viewer:vite-plugin] ${source.packageName}@${source.sourceVersion} contains ` +
1305
+ `viewer assets for ${payloadManifest.version || 'an unknown version'}.`);
1306
+ }
1307
+ installedFullPackages.forEach((packageName) => {
1308
+ const packageRoot = resolvePackageRoot(packageName, installedFullPackages);
1309
+ const packageVersion = readPackageVersion(packageRoot);
1310
+ if (packageVersion && packageVersion !== payloadManifest.version) {
1311
+ throw new Error(`[file-viewer:vite-plugin] ${packageName}@${packageVersion} cannot use ` +
1312
+ `${source.packageName} viewer assets at ${payloadManifest.version}.`);
1313
+ }
1314
+ });
1315
+ }
1316
+ async function copyBundledFullRendererAssets(targetRoot, rendererIds, installedFullPackages) {
1317
+ const source = resolveBundledFullAssetSource(installedFullPackages);
1318
+ if (!source) {
1319
+ return null;
1320
+ }
1321
+ assertBundledFullAssetSourceVersion(source, installedFullPackages);
1322
+ const sourceManifestPath = join(source.payloadRoot, 'flyfish-viewer-assets.json');
1323
+ const manifest = JSON.parse(readFileSync(sourceManifestPath, 'utf8'));
1324
+ if (manifest.schemaVersion !== 1 || !Array.isArray(manifest.rendererAssetManifests)) {
1325
+ throw new Error(`[file-viewer:vite-plugin] Unsupported full asset manifest in ${sourceManifestPath}.`);
1326
+ }
1327
+ await mkdir(targetRoot, { recursive: true });
1328
+ await cp(source.payloadRoot, targetRoot, { recursive: true, force: true });
1329
+ const results = [];
1330
+ for (const asset of manifest.rendererAssetManifests.flatMap((rendererManifest) => rendererManifest.assets)) {
1331
+ if (asset.target !== 'public' || !asset.defaultPath) {
1332
+ continue;
1333
+ }
1334
+ const relativePath = asset.defaultPath.replace(/\\/g, '/').replace(/^\/+/, '');
1335
+ const destination = resolve(targetRoot, ...relativePath.split('/'));
1336
+ let copied = false;
1337
+ let reason;
1338
+ if (!isPathWithin(targetRoot, destination)) {
1339
+ reason = 'asset manifest path escapes the copy target';
1340
+ }
1341
+ else {
1342
+ try {
1343
+ copied = matchesBundledAssetKind(asset.kind, await stat(destination));
1344
+ if (!copied) {
1345
+ reason = `copied path does not match asset kind ${asset.kind}`;
1346
+ }
1347
+ }
1348
+ catch {
1349
+ reason = 'bundled full asset not found after copy';
1350
+ }
1351
+ }
1352
+ results.push({
1353
+ rendererId: asset.rendererId,
1354
+ id: asset.id,
1355
+ to: destination,
1356
+ copied,
1357
+ required: asset.required,
1358
+ reason,
1359
+ sourcePackage: source.packageName,
1360
+ sourceVersion: source.sourceVersion
1361
+ });
1362
+ }
1363
+ await writeFile(join(targetRoot, 'flyfish-viewer-assets.json'), `${JSON.stringify({
1364
+ ...manifest,
1365
+ generatedBy: '@file-viewer/vite-plugin',
1366
+ copiedAt: new Date().toISOString(),
1367
+ rendererIds,
1368
+ assets: results
1369
+ }, null, 2)}\n`);
1370
+ return results;
1371
+ }
1372
+ async function copyRendererAssets(target, rendererIds) {
1373
+ if (target.installedFullPackages.length) {
1374
+ const bundledResults = await copyBundledFullRendererAssets(target.targetRoot, rendererIds, target.installedFullPackages);
1375
+ if (bundledResults) {
1376
+ return bundledResults;
1377
+ }
1378
+ }
1379
+ const results = await copyKnownRendererAssets(target.targetRoot, rendererIds);
1380
+ if (target.installedFullPackages.length) {
1381
+ results.push({
1382
+ rendererId: 'full',
1383
+ id: 'full-asset-payload',
1384
+ to: target.targetRoot,
1385
+ copied: false,
1386
+ required: true,
1387
+ reason: 'no version-matched full viewer payload was found in file-viewer-copy-assets, @file-viewer/web-full, or @file-viewer/web'
1388
+ });
1389
+ }
1390
+ return results;
1205
1391
  }
1206
1392
  function copyOptions(value) {
1207
1393
  return typeof value === 'object' ? value : {};
1208
1394
  }
1209
- function collectAssetRendererIds(selection, autoPresetIds = []) {
1395
+ function findNearestProjectPackageJson(projectRoot) {
1396
+ let current = resolve(projectRoot);
1397
+ while (true) {
1398
+ const packageJsonPath = join(current, 'package.json');
1399
+ if (existsSync(packageJsonPath) && statSyncSafe(packageJsonPath)?.isFile()) {
1400
+ return packageJsonPath;
1401
+ }
1402
+ const parent = dirname(current);
1403
+ if (parent === current) {
1404
+ return null;
1405
+ }
1406
+ current = parent;
1407
+ }
1408
+ }
1409
+ function resolveInstalledFullPackages(projectRoot) {
1410
+ const packageJsonPath = findNearestProjectPackageJson(projectRoot);
1411
+ if (!packageJsonPath) {
1412
+ return [];
1413
+ }
1414
+ try {
1415
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
1416
+ const declaredPackages = new Set([
1417
+ ...Object.keys(packageJson.dependencies || {}),
1418
+ ...Object.keys(packageJson.devDependencies || {}),
1419
+ ...Object.keys(packageJson.optionalDependencies || {}),
1420
+ ...Object.keys(packageJson.peerDependencies || {})
1421
+ ]);
1422
+ const projectPackageRequire = createRequire(packageJsonPath);
1423
+ return fileViewerFullPackages.filter((packageName) => declaredPackages.has(packageName) &&
1424
+ Boolean(tryResolvePackageJson(packageName, projectPackageRequire)));
1425
+ }
1426
+ catch {
1427
+ return [];
1428
+ }
1429
+ }
1430
+ function normalizeCopyAssetsBaseDir(value) {
1431
+ const normalized = value.trim().replace(/\\/g, '/');
1432
+ if (!normalized) {
1433
+ return '';
1434
+ }
1435
+ if (normalized.startsWith('/') ||
1436
+ isAbsolute(value) ||
1437
+ /^[A-Za-z]:\//.test(normalized) ||
1438
+ normalized.includes('\0')) {
1439
+ throw new Error(`[file-viewer:vite-plugin] copyAssets.baseDir must be a relative directory, received ${JSON.stringify(value)}.`);
1440
+ }
1441
+ const segments = normalized.split('/').filter(Boolean);
1442
+ const decodedSegments = [];
1443
+ for (const segment of segments) {
1444
+ let decodedSegment = segment;
1445
+ try {
1446
+ decodedSegment = decodeURIComponent(segment);
1447
+ }
1448
+ catch {
1449
+ throw new Error(`[file-viewer:vite-plugin] copyAssets.baseDir contains invalid URL encoding: ${JSON.stringify(value)}.`);
1450
+ }
1451
+ if (decodedSegment === '.' ||
1452
+ decodedSegment === '..' ||
1453
+ decodedSegment.includes('/') ||
1454
+ decodedSegment.includes('\\') ||
1455
+ decodedSegment.includes('\0')) {
1456
+ throw new Error(`[file-viewer:vite-plugin] copyAssets.baseDir cannot escape its output directory: ${JSON.stringify(value)}.`);
1457
+ }
1458
+ decodedSegments.push(decodedSegment);
1459
+ }
1460
+ return decodedSegments.join('/');
1461
+ }
1462
+ function isPathWithin(parent, candidate) {
1463
+ const relativePath = relative(parent, candidate);
1464
+ return relativePath === '' || (relativePath !== '..' &&
1465
+ !relativePath.startsWith(`..${sep}`) &&
1466
+ !isAbsolute(relativePath));
1467
+ }
1468
+ function resolveOutputRoot(value, fallback, projectRoot) {
1469
+ const output = value || fallback;
1470
+ return isAbsolute(output) ? resolve(output) : resolve(projectRoot, output);
1471
+ }
1472
+ /**
1473
+ * Resolves the exact directory used by copyAssets without running a Vite build.
1474
+ * This is exported so integrations can validate their deployment contract.
1475
+ */
1476
+ export function resolveFileViewerCopyAssetsTarget(mode, value = true, config = {}) {
1477
+ const projectRoot = resolve(config.projectRoot || process.cwd());
1478
+ const options = copyOptions(value);
1479
+ const installedFullPackages = resolveInstalledFullPackages(projectRoot);
1480
+ const baseDir = normalizeCopyAssetsBaseDir(typeof options.baseDir === 'string'
1481
+ ? options.baseDir
1482
+ : installedFullPackages.length
1483
+ ? 'file-viewer'
1484
+ : '');
1485
+ const outputRoot = resolveOutputRoot(mode === 'dev' ? options.publicDir : options.outDir, mode === 'dev' ? config.publicDir || 'public' : config.outDir || 'dist', projectRoot);
1486
+ const targetRoot = baseDir
1487
+ ? resolve(outputRoot, ...baseDir.split('/'))
1488
+ : outputRoot;
1489
+ if (!isPathWithin(outputRoot, targetRoot)) {
1490
+ throw new Error(`[file-viewer:vite-plugin] Refusing to copy assets outside ${outputRoot}: ${targetRoot}.`);
1491
+ }
1492
+ return {
1493
+ mode,
1494
+ outputRoot,
1495
+ targetRoot,
1496
+ baseDir,
1497
+ installedFullPackages: [...installedFullPackages]
1498
+ };
1499
+ }
1500
+ function normalizeViteDevBasePath(base) {
1501
+ let pathname = base;
1502
+ if (/^[A-Za-z][A-Za-z\d+.-]*:\/\//.test(base)) {
1503
+ try {
1504
+ pathname = new URL(base).pathname;
1505
+ }
1506
+ catch {
1507
+ return '/';
1508
+ }
1509
+ }
1510
+ if (!pathname.startsWith('/')) {
1511
+ return '/';
1512
+ }
1513
+ return pathname.endsWith('/') ? pathname : `${pathname}/`;
1514
+ }
1515
+ function resolveViteFullAssetBase(base, baseDir) {
1516
+ const encodedBaseDir = baseDir
1517
+ .split('/')
1518
+ .filter(Boolean)
1519
+ .map((segment) => encodeURIComponent(segment))
1520
+ .join('/');
1521
+ const encodedBaseDirWithSlash = encodedBaseDir ? `${encodedBaseDir}/` : '';
1522
+ const resolvedBase = base === undefined ? '/' : base.trim();
1523
+ const isAbsoluteBase = resolvedBase.startsWith('/') || /^[A-Za-z][A-Za-z\d+.-]*:\/\//.test(resolvedBase);
1524
+ if (!isAbsoluteBase) {
1525
+ return { kind: 'deployment', encodedBaseDir: encodedBaseDirWithSlash };
1526
+ }
1527
+ const normalizedBase = resolvedBase.endsWith('/') ? resolvedBase : `${resolvedBase}/`;
1528
+ return { kind: 'literal', url: `${normalizedBase}${encodedBaseDirWithSlash}` };
1529
+ }
1530
+ function resolveDevAssetRequestPath(targetRoot, baseDir, requestPathname, viteBasePath) {
1531
+ if (!requestPathname.startsWith(viteBasePath)) {
1532
+ return null;
1533
+ }
1534
+ let requestRelativePath;
1535
+ try {
1536
+ requestRelativePath = decodeURIComponent(requestPathname.slice(viteBasePath.length));
1537
+ }
1538
+ catch {
1539
+ return null;
1540
+ }
1541
+ requestRelativePath = requestRelativePath.replace(/\\/g, '/');
1542
+ if (requestRelativePath.includes('\0')) {
1543
+ return null;
1544
+ }
1545
+ const basePrefix = baseDir ? `${baseDir}/` : '';
1546
+ if (basePrefix && !requestRelativePath.startsWith(basePrefix)) {
1547
+ return null;
1548
+ }
1549
+ const assetRelativePath = requestRelativePath.slice(basePrefix.length);
1550
+ const isViewerManifest = assetRelativePath === 'flyfish-viewer-assets.json' ||
1551
+ assetRelativePath === 'flyfish-viewer-manifest.json';
1552
+ if (!isViewerManifest &&
1553
+ !assetRelativePath.startsWith('vendor/') &&
1554
+ !assetRelativePath.startsWith('wasm/')) {
1555
+ return null;
1556
+ }
1557
+ const assetPath = resolve(targetRoot, ...assetRelativePath.split('/'));
1558
+ return isPathWithin(targetRoot, assetPath) ? assetPath : null;
1559
+ }
1560
+ function collectAssetRendererIds(selection, autoPresetIds = [], includeFullPreset = false) {
1561
+ if (includeFullPreset) {
1562
+ return expandDescriptorRendererIds(presetModules.all.rendererIds);
1563
+ }
1210
1564
  const presetIds = [
1211
1565
  ...(selection.preset ? expandDescriptorRendererIds(presetModules[selection.preset].rendererIds) : []),
1212
1566
  ...autoPresetIds.flatMap((presetId) => expandDescriptorRendererIds(presetModules[presetId].rendererIds))
@@ -1217,14 +1571,14 @@ function collectAssetRendererIds(selection, autoPresetIds = []) {
1217
1571
  ]);
1218
1572
  }
1219
1573
  function reportAssetCopy(results, targetRoot, mode) {
1220
- const failedRequired = results.filter((result) => !result.copied && [
1574
+ const failedRequired = results.filter((result) => !result.copied && (result.required ?? [
1221
1575
  'pdf',
1222
1576
  'office-word-openxml',
1223
1577
  'office-presentation',
1224
1578
  'archive',
1225
1579
  'cad',
1226
1580
  'typst'
1227
- ].includes(result.rendererId));
1581
+ ].includes(result.rendererId)));
1228
1582
  if (!results.length) {
1229
1583
  return;
1230
1584
  }
@@ -1258,6 +1612,8 @@ export function fileViewerRenderers(options = {}) {
1258
1612
  });
1259
1613
  let autoPresetIds = resolveAutoPresetIds(options);
1260
1614
  let resolvedConfig = null;
1615
+ let installedFullPackages = [];
1616
+ let fullAssetBase = null;
1261
1617
  const refreshSelection = (projectRoot) => {
1262
1618
  if (projectRoot) {
1263
1619
  scanFormats = collectFileViewerRendererScanTokens(projectRoot, options.scan);
@@ -1269,6 +1625,20 @@ export function fileViewerRenderers(options = {}) {
1269
1625
  });
1270
1626
  autoPresetIds = resolveAutoPresetIds(options);
1271
1627
  };
1628
+ const refreshFullAssetRuntime = (projectRoot, viteBase) => {
1629
+ if (!options.copyAssets) {
1630
+ installedFullPackages = [];
1631
+ fullAssetBase = null;
1632
+ return;
1633
+ }
1634
+ const target = resolveFileViewerCopyAssetsTarget('dev', options.copyAssets, {
1635
+ projectRoot
1636
+ });
1637
+ installedFullPackages = target.installedFullPackages;
1638
+ fullAssetBase = installedFullPackages.length
1639
+ ? resolveViteFullAssetBase(viteBase, target.baseDir)
1640
+ : null;
1641
+ };
1272
1642
  const hasConfiguredRenderers = () => Boolean(selection.preset || selection.descriptors.length || autoPresetIds.length);
1273
1643
  return {
1274
1644
  name: 'file-viewer-renderers',
@@ -1276,19 +1646,27 @@ export function fileViewerRenderers(options = {}) {
1276
1646
  config(userConfig) {
1277
1647
  const projectRoot = resolve(process.cwd(), userConfig.root || '.');
1278
1648
  refreshSelection(projectRoot);
1649
+ refreshFullAssetRuntime(projectRoot, userConfig.base);
1279
1650
  const selectedPackages = collectSelectedPackages(selection, autoPresetIds);
1280
1651
  const dependencyAnchorPackages = collectDependencyAnchorPackages(selection, autoPresetIds);
1652
+ const resolvedDependencyAnchorPackages = fullAssetBase
1653
+ ? unique([
1654
+ ...dependencyAnchorPackages,
1655
+ ...installedFullPackages,
1656
+ '@file-viewer/preset-all'
1657
+ ])
1658
+ : dependencyAnchorPackages;
1281
1659
  const optimizeDepsExclude = createOptimizeDepsExclude(userConfig);
1282
1660
  const manualChunksFunction = getManualChunksFunction(userConfig);
1283
1661
  const nextConfig = {
1284
1662
  optimizeDeps: {
1285
1663
  exclude: optimizeDepsExclude,
1286
- include: createOptimizeDepsInclude(userConfig, optimizeDepsExclude, dependencyAnchorPackages)
1664
+ include: createOptimizeDepsInclude(userConfig, optimizeDepsExclude, resolvedDependencyAnchorPackages)
1287
1665
  },
1288
1666
  resolve: {
1289
1667
  alias: [
1290
1668
  ...normalizeViteAlias(userConfig.resolve?.alias),
1291
- ...createFileViewerResolveAliases(dependencyAnchorPackages)
1669
+ ...createFileViewerResolveAliases(resolvedDependencyAnchorPackages)
1292
1670
  ]
1293
1671
  }
1294
1672
  };
@@ -1321,6 +1699,7 @@ export function fileViewerRenderers(options = {}) {
1321
1699
  configResolved(config) {
1322
1700
  resolvedConfig = config;
1323
1701
  refreshSelection(config.root);
1702
+ refreshFullAssetRuntime(config.root, config.base);
1324
1703
  },
1325
1704
  buildStart() {
1326
1705
  assertMissingRendererPolicy(selection, missingMode);
@@ -1340,58 +1719,68 @@ export function fileViewerRenderers(options = {}) {
1340
1719
  if (!options.copyAssets || copyOptions(options.copyAssets).mode === 'build') {
1341
1720
  return;
1342
1721
  }
1343
- const targetRoot = resolveTargetDir(copyOptions(options.copyAssets).publicDir, resolvedConfig?.publicDir || 'public', resolvedConfig?.root);
1344
- const results = await copyKnownRendererAssets(targetRoot, collectAssetRendererIds(selection, autoPresetIds));
1722
+ const copyTarget = resolveFileViewerCopyAssetsTarget('dev', options.copyAssets, {
1723
+ projectRoot: resolvedConfig?.root,
1724
+ publicDir: resolvedConfig?.publicDir || 'public',
1725
+ outDir: resolvedConfig?.build.outDir || 'dist'
1726
+ });
1727
+ const { targetRoot } = copyTarget;
1728
+ const results = await copyRendererAssets(copyTarget, collectAssetRendererIds(selection, autoPresetIds, copyTarget.installedFullPackages.length > 0));
1345
1729
  reportAssetCopy(results, targetRoot, missingMode);
1346
- const base = resolvedConfig?.base || '/';
1347
- if (base.startsWith('/') && base !== '/') {
1348
- const normalizedBase = base.endsWith('/') ? base : `${base}/`;
1730
+ const viteBasePath = normalizeViteDevBasePath(resolvedConfig?.base || '/');
1731
+ if (server?.middlewares?.use) {
1732
+ const realTargetRoot = await realpath(targetRoot).catch(() => targetRoot);
1349
1733
  server.middlewares.use(async (request, response, next) => {
1350
1734
  const requestUrl = request.url || '';
1351
1735
  const queryIndex = requestUrl.indexOf('?');
1352
1736
  const pathname = queryIndex >= 0 ? requestUrl.slice(0, queryIndex) : requestUrl;
1353
- if (pathname.startsWith(normalizedBase)) {
1354
- let relativePath = pathname.slice(normalizedBase.length);
1737
+ const assetPath = resolveDevAssetRequestPath(targetRoot, copyTarget.baseDir, pathname, viteBasePath);
1738
+ if (assetPath) {
1355
1739
  try {
1356
- relativePath = decodeURIComponent(relativePath);
1357
- }
1358
- catch {
1359
- next();
1360
- return;
1361
- }
1362
- if (relativePath.startsWith('vendor/') || relativePath.startsWith('wasm/')) {
1363
- const assetPath = resolve(targetRoot, relativePath);
1364
- if (assetPath.startsWith(`${targetRoot}${sep}`)) {
1365
- try {
1366
- const info = await stat(assetPath);
1367
- if (info.isFile()) {
1368
- const contentTypes = {
1369
- '.bcmap': 'application/octet-stream',
1370
- '.css': 'text/css; charset=utf-8',
1371
- '.js': 'text/javascript; charset=utf-8',
1372
- '.json': 'application/json; charset=utf-8',
1373
- '.mjs': 'text/javascript; charset=utf-8',
1374
- '.ttf': 'font/ttf',
1375
- '.wasm': 'application/wasm',
1376
- '.woff': 'font/woff',
1377
- '.woff2': 'font/woff2'
1378
- };
1379
- response.statusCode = 200;
1380
- response.setHeader('Content-Type', contentTypes[extname(assetPath).toLowerCase()] || 'application/octet-stream');
1381
- if (request.method === 'HEAD') {
1382
- response.end();
1383
- }
1384
- else {
1385
- response.end(await readFile(assetPath));
1386
- }
1387
- return;
1740
+ const resolvedAssetPath = await realpath(assetPath);
1741
+ if (isPathWithin(realTargetRoot, resolvedAssetPath)) {
1742
+ const info = await stat(resolvedAssetPath);
1743
+ if (info.isFile()) {
1744
+ const contentTypes = {
1745
+ '.avif': 'image/avif',
1746
+ '.bcmap': 'application/octet-stream',
1747
+ '.css': 'text/css; charset=utf-8',
1748
+ '.gif': 'image/gif',
1749
+ '.ico': 'image/x-icon',
1750
+ '.jpeg': 'image/jpeg',
1751
+ '.jpg': 'image/jpeg',
1752
+ '.js': 'text/javascript; charset=utf-8',
1753
+ '.json': 'application/json; charset=utf-8',
1754
+ '.map': 'application/json; charset=utf-8',
1755
+ '.md': 'text/markdown; charset=utf-8',
1756
+ '.mjs': 'text/javascript; charset=utf-8',
1757
+ '.otf': 'font/otf',
1758
+ '.pfb': 'application/x-font-type1',
1759
+ '.png': 'image/png',
1760
+ '.svg': 'image/svg+xml',
1761
+ '.ttf': 'font/ttf',
1762
+ '.txt': 'text/plain; charset=utf-8',
1763
+ '.wasm': 'application/wasm',
1764
+ '.webp': 'image/webp',
1765
+ '.woff': 'font/woff',
1766
+ '.woff2': 'font/woff2',
1767
+ '.xml': 'application/xml; charset=utf-8'
1768
+ };
1769
+ response.statusCode = 200;
1770
+ response.setHeader('Content-Type', contentTypes[extname(resolvedAssetPath).toLowerCase()] || 'application/octet-stream');
1771
+ if (request.method === 'HEAD') {
1772
+ response.end();
1388
1773
  }
1389
- }
1390
- catch {
1391
- // Let Vite return its normal 404/fallback response.
1774
+ else {
1775
+ response.end(await readFile(resolvedAssetPath));
1776
+ }
1777
+ return;
1392
1778
  }
1393
1779
  }
1394
1780
  }
1781
+ catch {
1782
+ // Let Vite return its normal 404/fallback response.
1783
+ }
1395
1784
  }
1396
1785
  next();
1397
1786
  });
@@ -1400,7 +1789,7 @@ export function fileViewerRenderers(options = {}) {
1400
1789
  transformIndexHtml: {
1401
1790
  order: 'pre',
1402
1791
  handler() {
1403
- if (options.inject === false || !hasConfiguredRenderers()) {
1792
+ if (options.inject === false || (!hasConfiguredRenderers() && !fullAssetBase)) {
1404
1793
  return undefined;
1405
1794
  }
1406
1795
  return [
@@ -1439,9 +1828,13 @@ export function fileViewerRenderers(options = {}) {
1439
1828
  if (!options.copyAssets || copyOptions(options.copyAssets).mode === 'dev') {
1440
1829
  return;
1441
1830
  }
1442
- const outDir = resolvedConfig?.build.outDir || 'dist';
1443
- const targetRoot = resolveTargetDir(copyOptions(options.copyAssets).outDir, outDir, resolvedConfig?.root);
1444
- const results = await copyKnownRendererAssets(targetRoot, collectAssetRendererIds(selection, autoPresetIds));
1831
+ const copyTarget = resolveFileViewerCopyAssetsTarget('build', options.copyAssets, {
1832
+ projectRoot: resolvedConfig?.root,
1833
+ publicDir: resolvedConfig?.publicDir || 'public',
1834
+ outDir: resolvedConfig?.build.outDir || 'dist'
1835
+ });
1836
+ const { targetRoot } = copyTarget;
1837
+ const results = await copyRendererAssets(copyTarget, collectAssetRendererIds(selection, autoPresetIds, copyTarget.installedFullPackages.length > 0));
1445
1838
  reportAssetCopy(results, targetRoot, missingMode);
1446
1839
  },
1447
1840
  resolveId(id) {
@@ -1452,7 +1845,7 @@ export function fileViewerRenderers(options = {}) {
1452
1845
  },
1453
1846
  load(id) {
1454
1847
  if (id === resolvedModuleId || id === resolvedVirtualModuleId) {
1455
- return renderVirtualModule(selection, requestedFormats, autoPresetIds);
1848
+ return renderVirtualModule(selection, requestedFormats, autoPresetIds, fullAssetBase);
1456
1849
  }
1457
1850
  return undefined;
1458
1851
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/vite-plugin",
3
- "version": "2.1.29",
3
+ "version": "2.1.30",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Vite plugin for Flyfish File Viewer on-demand renderer assembly, chunk planning, and offline asset deployment.",