@file-viewer/vite-plugin 3.2.2 → 3.2.3

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,5 +1,7 @@
1
1
  # @file-viewer/vite-plugin
2
2
 
3
+ [English](https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.md) · [简体中文](https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.zh-CN.md)
4
+
3
5
  <!-- FILE_VIEWER_PACKAGE_HEADER -->
4
6
  <p>
5
7
  <a href="https://www.npmjs.com/package/@file-viewer/vite-plugin"><img src="https://img.shields.io/npm/v/@file-viewer%2Fvite-plugin?logo=npm" alt="npm version"></a>
@@ -9,7 +11,7 @@
9
11
  </p>
10
12
 
11
13
  <p>
12
- <a href="https://github.com/flyfish-dev/file-viewer">GitHub</a> · <a href="https://doc.file-viewer.app/">Docs</a> · <a href="https://demo.file-viewer.app/">Live demo</a> · <a href="https://doc.file-viewer.app/guide/formats">Format matrix</a> · <a href="https://github.com/flyfish-dev/file-viewer/issues">Issues</a> · <a href="https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.md">简体中文</a>
14
+ <a href="https://github.com/flyfish-dev/file-viewer">GitHub</a> · <a href="https://doc.file-viewer.app/">Docs</a> · <a href="https://demo.file-viewer.app/">Live demo</a> · <a href="https://doc.file-viewer.app/guide/formats">Format matrix</a> · <a href="https://github.com/flyfish-dev/file-viewer/issues">Issues</a> · <a href="https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.zh-CN.md">简体中文</a>
13
15
  </p>
14
16
 
15
17
  <p>
@@ -118,6 +120,8 @@ When host apps split `node_modules` by package name, circular dependencies betwe
118
120
 
119
121
  `copyAssets.baseDir` overrides automatic placement. It must be relative and cannot contain `..`; an empty string explicitly selects the root. The plugin now aligns the shared runtime asset base for standard packages, presets, and full packages; full packages also keep `setDefaultFullAssetBaseUrl()` synchronized. Only integrations that set `inject:false` and do not import the virtual module must call `setDefaultFileViewerAssetBaseUrl()` themselves (plus `setDefaultFullAssetBaseUrl()` for a full package). With Vite `base:'/app/'`, full-package dev URLs start with `/app/file-viewer/...`, while production files remain under `outDir/file-viewer/`.
120
122
 
123
+ Asset copying runs at dev-server startup/restart and after a production build, not on ordinary HMR. Files with identical contents are left untouched, and generated manifests omit the volatile `copiedAt` timestamp. Vite's own output cleanup and public-directory copying remain unchanged.
124
+
121
125
  For strict registry control, disable injection and pass the virtual module explicitly:
122
126
 
123
127
  ```ts
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @file-viewer/vite-plugin
2
2
 
3
+ [English](https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.md) · [简体中文](https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.zh-CN.md)
4
+
3
5
  <!-- FILE_VIEWER_PACKAGE_HEADER -->
4
6
  <p>
5
7
  <a href="https://www.npmjs.com/package/@file-viewer/vite-plugin"><img src="https://img.shields.io/npm/v/@file-viewer%2Fvite-plugin?logo=npm" alt="npm version"></a>
@@ -9,20 +11,20 @@
9
11
  </p>
10
12
 
11
13
  <p>
12
- <a href="https://github.com/flyfish-dev/file-viewer">GitHub</a> · <a href="https://doc.file-viewer.app/zh/">文档</a> · <a href="https://demo.file-viewer.app/">在线 Demo</a> · <a href="https://doc.file-viewer.app/guide/formats">格式矩阵</a> · <a href="https://github.com/flyfish-dev/file-viewer/issues">问题反馈</a> · <a href="https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.en.md">English</a>
14
+ <a href="https://github.com/flyfish-dev/file-viewer">GitHub</a> · <a href="https://doc.file-viewer.app/">Docs</a> · <a href="https://demo.file-viewer.app/">Live demo</a> · <a href="https://doc.file-viewer.app/guide/formats">Format matrix</a> · <a href="https://github.com/flyfish-dev/file-viewer/issues">Issues</a> · <a href="https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.zh-CN.md">简体中文</a>
13
15
  </p>
14
16
 
15
17
  <p>
16
- <a href="https://demo.file-viewer.app/"><img src="https://raw.githubusercontent.com/flyfish-dev/file-viewer/main/docs/public/_media/file-viewer-demo-v3-zh.webp" width="760" alt="File Viewer 在线演示:Word、Excel、PPT、3D 模型、CAD 图纸与二进制检查器"></a>
18
+ <a href="https://demo.file-viewer.app/"><img src="https://raw.githubusercontent.com/flyfish-dev/file-viewer/main/docs/public/_media/file-viewer-demo-v3-en.webp" width="760" alt="File Viewer live demo: Word, Excel, PowerPoint, 3D model, CAD drawing and binary inspector"></a>
17
19
  </p>
18
20
 
19
- > ⭐ 如果 File Viewer 对你有帮助,欢迎到 [GitHub](https://github.com/flyfish-dev/file-viewer) 点个 Star。
21
+ > ⭐ If File Viewer helps you, please [star the repo on GitHub](https://github.com/flyfish-dev/file-viewer).
20
22
 
21
- 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/字体资源复制能力。
23
+ 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.
22
24
 
23
- ## 最快开始
25
+ ## Fastest Start
24
26
 
25
- 需要完整格式支持时,最快路径是“full 包 + 注册一次 Vite 插件”。full 已内置 `preset-all`,不要再安装 preset;`copyAssets:true` 会在开发和构建时自动匹配 full 包,并把 Worker、WASM、字体和 vendor 文件发布到部署基址下的 `file-viewer/`(根部署即 `/file-viewer/`):
27
+ 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:
26
28
 
27
29
  ```bash
28
30
  pnpm add @file-viewer/vue3-full
@@ -42,29 +44,29 @@ export default defineConfig({
42
44
  })
43
45
  ```
44
46
 
45
- 只安装 full 包而不启用资源复制,PDF Worker、CAD/Typst/Archive WASM 等格式会缺少运行文件,只能获得部分格式支持。
47
+ 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.
46
48
 
47
- 需要更小体积时,改用“标准组件包 + 一个 preset”,Vite 配置保持不变。插件会自动发现已安装的 preset,无需手写 renderer import,也无需显式写 `preset:'office'`:
49
+ 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'`:
48
50
 
49
51
  ```bash
50
52
  pnpm add @file-viewer/vue3 @file-viewer/preset-office
51
53
  pnpm add -D @file-viewer/vite-plugin
52
54
  ```
53
55
 
54
- 组件默认 `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` 即可获得对应能力。
56
+ 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.
55
57
 
56
- 可选 preset:
58
+ Available presets:
57
59
 
58
- - `@file-viewer/preset-lite`: 文本、Markdown、代码、图片、音频、视频。
59
- - `@file-viewer/preset-office`: PDF、Word、Excel、PowerPoint、OFD、RTF、OpenDocument。
60
- - `@file-viewer/preset-engineering`: CAD、3D、绘图、XMind、Geo、Typst、Archive、Data、EDA。
61
- - `@file-viewer/preset-all`: 官方 demo 完整格式矩阵。
60
+ - `@file-viewer/preset-lite`: text, Markdown, code, image, audio, and video.
61
+ - `@file-viewer/preset-office`: PDF, Word, Excel, PowerPoint, OFD, RTF, and OpenDocument.
62
+ - `@file-viewer/preset-engineering`: CAD, 3D, drawing, XMind, Geo, Typst, Archive, Data, and EDA.
63
+ - `@file-viewer/preset-all`: the complete official demo format matrix.
62
64
 
63
- 如果只需要极少数格式,也可以安装对应 renderer 包,例如 `@file-viewer/renderer-pdf`、`@file-viewer/renderer-word`、`@file-viewer/renderer-ofd`、`@file-viewer/renderer-presentation`、`@file-viewer/renderer-cad`、`@file-viewer/renderer-drawing`、`@file-viewer/renderer-3d`、`@file-viewer/renderer-data`、`@file-viewer/renderer-eda`、`@file-viewer/renderer-typst`、`@file-viewer/renderer-archive`、`@file-viewer/renderer-text`,再通过 `formats` 做精确裁剪。
65
+ Install individual renderer packages only for strict cuts, such as `@file-viewer/renderer-pdf`, `@file-viewer/renderer-word`, `@file-viewer/renderer-ofd`, `@file-viewer/renderer-presentation`, `@file-viewer/renderer-cad`, `@file-viewer/renderer-drawing`, `@file-viewer/renderer-3d`, `@file-viewer/renderer-data`, `@file-viewer/renderer-eda`, `@file-viewer/renderer-typst`, `@file-viewer/renderer-archive`, or `@file-viewer/renderer-text`, then use `formats` to generate exact imports.
64
66
 
65
67
  ## vite.config.ts
66
68
 
67
- 推荐默认使用 full/preset 自动装配。只传 `copyAssets:true` 时,插件会自动发现已安装的 `@file-viewer/*-full` 或 `@file-viewer/preset-*`,无需写 `preset:'all'`:
69
+ 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:
68
70
 
69
71
  ```ts
70
72
  fileViewerRenderers({
@@ -72,7 +74,7 @@ fileViewerRenderers({
72
74
  })
73
75
  ```
74
76
 
75
- 只有在需要源码扫描、单格式裁剪、或手动 registry 管理时,再使用自定义能力:
77
+ Use customization options only when you need source scanning, single-format cuts, or manual registry control:
76
78
 
77
79
  ```ts
78
80
  import { defineConfig } from 'vite'
@@ -90,35 +92,37 @@ export default defineConfig({
90
92
  })
91
93
  ```
92
94
 
93
- `inject` 默认开启,插件会把 `virtual:file-viewer-renderers` 注入 Vite HTML 入口,preset 导入后会自动注册到 core。常规业务只需要安装组件包和对应 preset,组件会通过 `autoRenderers` 默认读取这些能力。
95
+ `inject` is enabled by default. The plugin injects `virtual:file-viewer-renderers` into Vite HTML entrypoints, preset imports register themselves in core, and framework components consume them through `autoRenderers`.
94
96
 
95
97
  ```ts
96
98
  const options = {
97
- // 默认 true;需要完全手动控制时设为 false。
99
+ // Defaults to true; set false when a product needs total manual control.
98
100
  autoRenderers: true
99
101
  }
100
102
  ```
101
103
 
102
- ### 可定制能力
104
+ ### Customization Options
103
105
 
104
- | 选项 | 说明 |
106
+ | Option | Description |
105
107
  | --- | --- |
106
- | `copyAssets` | `true` 或 `{ publicDir, outDir, mode, baseDir }`;复制匹配的 Worker、WASM、字体和 vendor 资源。检测到 full 时默认发布到 `<部署基址>/file-viewer/`,这是 full 完整格式支持的必需项;标准包/preset 保持原有根目录行为 |
107
- | `preset` | `'auto' | 'lite' | 'office' | 'engineering' | 'all'`;默认无显式格式时自动发现已安装 preset |
108
- | `autoPresets` | `true` 或 preset 列表;常用于 `scan:true` 时继续自动激活已安装 preset |
109
- | `formats` | 文件扩展名或格式 token,例如 `['pdf', 'docx', 'dwg']` |
110
- | `renderers` | renderer id,例如 `['pdf', 'word', 'cad']` |
111
- | `scan` | `true` 或 `{ roots, extensions, maxFileSize }`,扫描源码 hint 并合并格式 |
112
- | `inject` | 默认 `true`;设为 `false` 后手动导入 `virtual:file-viewer-renderers` |
113
- | `chunkStrategy` | `'renderer' | 'none'`,控制是否注入 renderer 级 chunk 分组 |
114
- | `stabilizeInteropChunks` | 默认 `true`;为默认分组和宿主 `manualChunks` 稳定 Pako / CommonJS 辅助模块及 CodeMirror / Lezer / Sandpack 的分组 |
115
- | `missingRenderer` | `'error' | 'warn' | 'ignore'`,控制尚未提取的 renderer 映射提示方式 |
108
+ | `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 |
109
+ | `preset` | `'auto' | 'lite' | 'office' | 'engineering' | 'all'`; with no explicit formats, installed presets are discovered automatically |
110
+ | `autoPresets` | `true` or a preset list; useful with `scan:true` when installed presets should stay active |
111
+ | `formats` | File extensions or format tokens, for example `['pdf', 'docx', 'dwg']` |
112
+ | `renderers` | Renderer ids, for example `['pdf', 'word', 'cad']` |
113
+ | `scan` | `true` or `{ roots, extensions, maxFileSize }`; scans source hints and merges formats |
114
+ | `inject` | Defaults to `true`; set `false` to import `virtual:file-viewer-renderers` manually |
115
+ | `chunkStrategy` | `'renderer' | 'none'`; controls renderer-level chunk grouping |
116
+ | `stabilizeInteropChunks` | Defaults to `true`; keeps Pako / shared CommonJS helpers and CodeMirror / Lezer / Sandpack in stable groups for default and host-defined chunk splitting |
117
+ | `missingRenderer` | `'error' | 'warn' | 'ignore'`; controls notices for planned renderer mappings |
118
+
119
+ 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 stabilizes default chunk splitting and wraps existing `manualChunks` functions. Pako and shared CommonJS helpers use `vendor-commonjs` to prevent compressed-format initialization cycles; CodeMirror / Lezer / Sandpack use `vendor-codemirror`. Other custom groups remain intact, and Vite 8 receives equivalent Rolldown groups. Set `stabilizeInteropChunks:false` only when the host build needs total control.
116
120
 
117
- 如果宿主项目按包名拆分 `node_modules`,`@codemirror/*`、`@lezer/*` 和 Sandpack 之间的循环依赖可能在生产构建中表现为 `codemirror-view.* Cannot access ... before initialization`。插件同时稳定默认分组和已有的 `manualChunks` 函数:Pako 与共享 CommonJS 辅助模块归入 `vendor-commonjs`,避免压缩格式的初始化循环;CodeMirror / Lezer / Sandpack 归入 `vendor-codemirror`。Vite 8 使用对应的 Rolldown 分组,其它自定义分组保持不变;确实需要完全接管时可设置 `stabilizeInteropChunks:false`。
121
+ `copyAssets.baseDir` overrides automatic placement. It must be relative and cannot contain `..`; an empty string explicitly selects the root. The plugin now aligns the shared runtime asset base for standard packages, presets, and full packages; full packages also keep `setDefaultFullAssetBaseUrl()` synchronized. Only integrations that set `inject:false` and do not import the virtual module must call `setDefaultFileViewerAssetBaseUrl()` themselves (plus `setDefaultFullAssetBaseUrl()` for a full package). With Vite `base:'/app/'`, full-package dev URLs start with `/app/file-viewer/...`, while production files remain under `outDir/file-viewer/`.
118
122
 
119
- `copyAssets.baseDir` 可以覆盖自动目录:必须是相对路径,不能包含 `..`;传空字符串表示显式复制到根目录。插件会为标准包、preset 和 full 包同步统一运行时资源根;full 包仍同时同步 `setDefaultFullAssetBaseUrl()`。只有设置 `inject:false` 且不手动导入虚拟模块时,业务才需要调用 `setDefaultFileViewerAssetBaseUrl()`(full 包同时调用 `setDefaultFullAssetBaseUrl()`)。Vite 配置了 `base:'/app/'` 时,full 包开发 URL 为 `/app/file-viewer/...`,构建文件仍位于 `outDir/file-viewer/`。
123
+ Asset copying runs at dev-server startup/restart and after a production build, not on ordinary HMR. Files with identical contents are left untouched, and generated manifests omit the volatile `copiedAt` timestamp. Vite's own output cleanup and public-directory copying remain unchanged.
120
124
 
121
- 如果你需要严格控制 registry,可以关闭注入并手动传入:
125
+ For strict registry control, disable injection and pass the virtual module explicitly:
122
126
 
123
127
  ```ts
124
128
  fileViewerRenderers({
@@ -137,7 +141,7 @@ const options = {
137
141
  }
138
142
  ```
139
143
 
140
- 也可以使用 `preset: 'auto'` 发现项目中已安装的 preset 包;当 `preset-all` 存在时会优先使用它,避免重复导入其它 preset。注意:如果同时开启 `scan:true`,请显式使用 `preset:'auto'` 或 `autoPresets:true`,否则插件会以源码 hint 为准,不再做“无配置 preset 自动发现”。
144
+ You can also use `preset: 'auto'` to discover installed preset packages. When `preset-all` is installed, it wins to avoid importing narrower presets twice. Note: when `scan:true` is enabled, use `preset:'auto'` or `autoPresets:true` explicitly; otherwise the plugin treats source hints as the explicit renderer selection and skips zero-config preset discovery.
141
145
 
142
146
  ```ts
143
147
  fileViewerRenderers({
@@ -149,7 +153,7 @@ fileViewerRenderers({
149
153
  })
150
154
  ```
151
155
 
152
- `scan: true` 会扫描常见源码目录里的轻量 hint,并把它们合并到 `formats`:
156
+ `scan: true` inspects common source folders for lightweight hints and merges them with `formats`:
153
157
 
154
158
  ```ts
155
159
  export const fileViewerFormats = ['pdf', 'docx', 'xlsx']
@@ -159,36 +163,36 @@ export const fileViewerFormats = ['pdf', 'docx', 'xlsx']
159
163
  <input accept=".pdf,.docx" data-file-viewer-formats="dwg,xmind" />
160
164
  ```
161
165
 
162
- 这适合业务把上传入口、示例矩阵或附件白名单维护在源码中时使用:开发和构建阶段插件会自动生成 renderer 装配模块,少写一份手工 import 清单。
166
+ This is useful when upload accept lists, sample matrices, or attachment allow-lists already live in source code: dev and production builds can generate the renderer assembly module without a second hand-written import list.
163
167
 
164
- ## 缺失 renderer 提示
168
+ ## Missing Renderer Guidance
165
169
 
166
- 如果用户打开的是支持矩阵内的格式,但项目没有安装或装配对应 renderer,core 会显示“需要装配预览能力”,并提示推荐安装的 preset / renderer 包,例如 `.pdf` 会引导安装 `@file-viewer/preset-office` 或 `@file-viewer/renderer-pdf`。只有真正不在支持矩阵中的扩展名才显示“不支持在线预览”。
170
+ When a file extension is in the supported matrix but the current project has not assembled its renderer, core now shows an install-oriented “renderer assembly required” state with the recommended preset / renderer package. For example, `.pdf` points to `@file-viewer/preset-office` or `@file-viewer/renderer-pdf`. Only truly unknown extensions show an unsupported-format state.
167
171
 
168
- ## 嵌套 Vite 项目
172
+ ## Nested Vite Projects
169
173
 
170
- 从 3.0.3 起,子目录只有项目元数据或 Vite 插件声明时,会沿祖先 `package.json`
171
- 查找实际可解析的 Full 依赖。子项目明确声明轻量组件或 preset 时保留其根目录资产布局,
172
- 不会继承父项目的 Full 布局。`copyAssets.baseDir` 仍具有最高优先级;`publicDir`
173
- 和 `build.outDir` 均相对于 Vite root,不相对于依赖所在目录。
174
+ From 3.0.3, a nested metadata-only project can use a resolvable Full dependency
175
+ declared in an ancestor `package.json`. A nearer light component or preset keeps
176
+ its own root asset layout. Explicit `copyAssets.baseDir` still takes precedence;
177
+ `publicDir` and `build.outDir` stay relative to the Vite root, not the dependency owner.
174
178
 
175
- ## 当前边界
179
+ ## Current Boundary
176
180
 
177
- 当前插件会为已经拆出的 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。
181
+ 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.
178
182
 
179
- ## 文档
183
+ ## Documentation
180
184
 
181
- - 按需渲染架构: <https://doc.file-viewer.app/guide/on-demand-renderers>
182
- - 支持格式: <https://doc.file-viewer.app/guide/formats>
185
+ - On-demand renderer architecture: <https://doc.file-viewer.app/guide/on-demand-renderers>
186
+ - Supported formats: <https://doc.file-viewer.app/guide/formats>
183
187
 
184
- ## 格式与相关包
188
+ ## Formats and related packages
185
189
 
186
- File Viewer 在浏览器内预览 275+ 种扩展名,覆盖 Office、PDF/OFD、CAD、3D、压缩包、邮件、图片、音视频和代码,无需服务端转码;完整清单见 [格式矩阵](https://doc.file-viewer.app/guide/formats)。
190
+ File Viewer previews 275+ file extensions in the browser, covering Office, PDF/OFD, CAD, 3D, archives, email, images, media and code, without server-side conversion. See the [format matrix](https://doc.file-viewer.app/guide/formats) for the full list.
187
191
 
188
- - 组件:`@file-viewer/web`、`@file-viewer/vue3`、`@file-viewer/vue2.7`、`@file-viewer/vue2.6`、`@file-viewer/react`、`@file-viewer/react-legacy`、`@file-viewer/svelte`、`@file-viewer/jquery`,以及对应的 `-full` 包
189
- - 能力组合:`@file-viewer/preset-lite`、`@file-viewer/preset-office`、`@file-viewer/preset-engineering`、`@file-viewer/preset-all`
190
- - 构建:`@file-viewer/vite-plugin`、`file-viewer-copy-assets`
192
+ - Components: `@file-viewer/web`, `@file-viewer/vue3`, `@file-viewer/vue2.7`, `@file-viewer/vue2.6`, `@file-viewer/react`, `@file-viewer/react-legacy`, `@file-viewer/svelte`, `@file-viewer/jquery`, plus their `-full` packages
193
+ - Presets: `@file-viewer/preset-lite`, `@file-viewer/preset-office`, `@file-viewer/preset-engineering`, `@file-viewer/preset-all`
194
+ - Build: `@file-viewer/vite-plugin`, `file-viewer-copy-assets`
191
195
 
192
- ## 许可证
196
+ ## License
193
197
 
194
- 本包采用 Apache-2.0。详见 [LICENSE](https://github.com/flyfish-dev/file-viewer/blob/main/LICENSE)。
198
+ This package is Apache-2.0. See [LICENSE](https://github.com/flyfish-dev/file-viewer/blob/main/LICENSE).
@@ -0,0 +1,198 @@
1
+ # @file-viewer/vite-plugin
2
+
3
+ [English](https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.md) · [简体中文](https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.zh-CN.md)
4
+
5
+ <!-- FILE_VIEWER_PACKAGE_HEADER -->
6
+ <p>
7
+ <a href="https://www.npmjs.com/package/@file-viewer/vite-plugin"><img src="https://img.shields.io/npm/v/@file-viewer%2Fvite-plugin?logo=npm" alt="npm version"></a>
8
+ <a href="https://www.npmjs.com/package/@file-viewer/vite-plugin"><img src="https://img.shields.io/npm/dm/@file-viewer%2Fvite-plugin?logo=npm" alt="npm downloads"></a>
9
+ <a href="https://github.com/flyfish-dev/file-viewer/stargazers"><img src="https://img.shields.io/github/stars/flyfish-dev/file-viewer?style=flat&logo=github" alt="GitHub stars"></a>
10
+ <a href="https://github.com/flyfish-dev/file-viewer/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License: Apache-2.0"></a>
11
+ </p>
12
+
13
+ <p>
14
+ <a href="https://github.com/flyfish-dev/file-viewer">GitHub</a> · <a href="https://doc.file-viewer.app/zh/">文档</a> · <a href="https://demo.file-viewer.app/">在线 Demo</a> · <a href="https://doc.file-viewer.app/guide/formats">格式矩阵</a> · <a href="https://github.com/flyfish-dev/file-viewer/issues">问题反馈</a> · <a href="https://github.com/flyfish-dev/file-viewer/blob/main/packages/presets/vite-plugin/README.md">English</a>
15
+ </p>
16
+
17
+ <p>
18
+ <a href="https://demo.file-viewer.app/"><img src="https://raw.githubusercontent.com/flyfish-dev/file-viewer/main/docs/public/_media/file-viewer-demo-v3-zh.webp" width="760" alt="File Viewer 在线演示:Word、Excel、PPT、3D 模型、CAD 图纸与二进制检查器"></a>
19
+ </p>
20
+
21
+ > ⭐ 如果 File Viewer 对你有帮助,欢迎到 [GitHub](https://github.com/flyfish-dev/file-viewer) 点个 Star。
22
+
23
+ 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/字体资源复制能力。
24
+
25
+ ## 最快开始
26
+
27
+ 需要完整格式支持时,最快路径是“full 包 + 注册一次 Vite 插件”。full 已内置 `preset-all`,不要再安装 preset;`copyAssets:true` 会在开发和构建时自动匹配 full 包,并把 Worker、WASM、字体和 vendor 文件发布到部署基址下的 `file-viewer/`(根部署即 `/file-viewer/`):
28
+
29
+ ```bash
30
+ pnpm add @file-viewer/vue3-full
31
+ pnpm add -D @file-viewer/vite-plugin
32
+ ```
33
+
34
+ ```ts
35
+ import { defineConfig } from 'vite'
36
+ import { fileViewerRenderers } from '@file-viewer/vite-plugin'
37
+
38
+ export default defineConfig({
39
+ plugins: [
40
+ fileViewerRenderers({
41
+ copyAssets: true
42
+ })
43
+ ]
44
+ })
45
+ ```
46
+
47
+ 只安装 full 包而不启用资源复制,PDF Worker、CAD/Typst/Archive WASM 等格式会缺少运行文件,只能获得部分格式支持。
48
+
49
+ 需要更小体积时,改用“标准组件包 + 一个 preset”,Vite 配置保持不变。插件会自动发现已安装的 preset,无需手写 renderer import,也无需显式写 `preset:'office'`:
50
+
51
+ ```bash
52
+ pnpm add @file-viewer/vue3 @file-viewer/preset-office
53
+ pnpm add -D @file-viewer/vite-plugin
54
+ ```
55
+
56
+ 组件默认 `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` 即可获得对应能力。
57
+
58
+ 可选 preset:
59
+
60
+ - `@file-viewer/preset-lite`: 文本、Markdown、代码、图片、音频、视频。
61
+ - `@file-viewer/preset-office`: PDF、Word、Excel、PowerPoint、OFD、RTF、OpenDocument。
62
+ - `@file-viewer/preset-engineering`: CAD、3D、绘图、XMind、Geo、Typst、Archive、Data、EDA。
63
+ - `@file-viewer/preset-all`: 官方 demo 完整格式矩阵。
64
+
65
+ 如果只需要极少数格式,也可以安装对应 renderer 包,例如 `@file-viewer/renderer-pdf`、`@file-viewer/renderer-word`、`@file-viewer/renderer-ofd`、`@file-viewer/renderer-presentation`、`@file-viewer/renderer-cad`、`@file-viewer/renderer-drawing`、`@file-viewer/renderer-3d`、`@file-viewer/renderer-data`、`@file-viewer/renderer-eda`、`@file-viewer/renderer-typst`、`@file-viewer/renderer-archive`、`@file-viewer/renderer-text`,再通过 `formats` 做精确裁剪。
66
+
67
+ ## vite.config.ts
68
+
69
+ 推荐默认使用 full/preset 自动装配。只传 `copyAssets:true` 时,插件会自动发现已安装的 `@file-viewer/*-full` 或 `@file-viewer/preset-*`,无需写 `preset:'all'`:
70
+
71
+ ```ts
72
+ fileViewerRenderers({
73
+ copyAssets: true
74
+ })
75
+ ```
76
+
77
+ 只有在需要源码扫描、单格式裁剪、或手动 registry 管理时,再使用自定义能力:
78
+
79
+ ```ts
80
+ import { defineConfig } from 'vite'
81
+ import { fileViewerRenderers } from '@file-viewer/vite-plugin'
82
+
83
+ export default defineConfig({
84
+ plugins: [
85
+ fileViewerRenderers({
86
+ formats: ['pdf', 'dwg', 'typst', 'zip', 'xmind'],
87
+ scan: true,
88
+ copyAssets: true,
89
+ chunkStrategy: 'renderer'
90
+ })
91
+ ]
92
+ })
93
+ ```
94
+
95
+ `inject` 默认开启,插件会把 `virtual:file-viewer-renderers` 注入 Vite HTML 入口,preset 导入后会自动注册到 core。常规业务只需要安装组件包和对应 preset,组件会通过 `autoRenderers` 默认读取这些能力。
96
+
97
+ ```ts
98
+ const options = {
99
+ // 默认 true;需要完全手动控制时设为 false。
100
+ autoRenderers: true
101
+ }
102
+ ```
103
+
104
+ ### 可定制能力
105
+
106
+ | 选项 | 说明 |
107
+ | --- | --- |
108
+ | `copyAssets` | `true` 或 `{ publicDir, outDir, mode, baseDir }`;复制匹配的 Worker、WASM、字体和 vendor 资源。检测到 full 时默认发布到 `<部署基址>/file-viewer/`,这是 full 完整格式支持的必需项;标准包/preset 保持原有根目录行为 |
109
+ | `preset` | `'auto' | 'lite' | 'office' | 'engineering' | 'all'`;默认无显式格式时自动发现已安装 preset |
110
+ | `autoPresets` | `true` 或 preset 列表;常用于 `scan:true` 时继续自动激活已安装 preset |
111
+ | `formats` | 文件扩展名或格式 token,例如 `['pdf', 'docx', 'dwg']` |
112
+ | `renderers` | renderer id,例如 `['pdf', 'word', 'cad']` |
113
+ | `scan` | `true` 或 `{ roots, extensions, maxFileSize }`,扫描源码 hint 并合并格式 |
114
+ | `inject` | 默认 `true`;设为 `false` 后手动导入 `virtual:file-viewer-renderers` |
115
+ | `chunkStrategy` | `'renderer' | 'none'`,控制是否注入 renderer 级 chunk 分组 |
116
+ | `stabilizeInteropChunks` | 默认 `true`;为默认分组和宿主 `manualChunks` 稳定 Pako / CommonJS 辅助模块及 CodeMirror / Lezer / Sandpack 的分组 |
117
+ | `missingRenderer` | `'error' | 'warn' | 'ignore'`,控制尚未提取的 renderer 映射提示方式 |
118
+
119
+ 如果宿主项目按包名拆分 `node_modules`,`@codemirror/*`、`@lezer/*` 和 Sandpack 之间的循环依赖可能在生产构建中表现为 `codemirror-view.* Cannot access ... before initialization`。插件同时稳定默认分组和已有的 `manualChunks` 函数:Pako 与共享 CommonJS 辅助模块归入 `vendor-commonjs`,避免压缩格式的初始化循环;CodeMirror / Lezer / Sandpack 归入 `vendor-codemirror`。Vite 8 使用对应的 Rolldown 分组,其它自定义分组保持不变;确实需要完全接管时可设置 `stabilizeInteropChunks:false`。
120
+
121
+ `copyAssets.baseDir` 可以覆盖自动目录:必须是相对路径,不能包含 `..`;传空字符串表示显式复制到根目录。插件会为标准包、preset 和 full 包同步统一运行时资源根;full 包仍同时同步 `setDefaultFullAssetBaseUrl()`。只有设置 `inject:false` 且不手动导入虚拟模块时,业务才需要调用 `setDefaultFileViewerAssetBaseUrl()`(full 包同时调用 `setDefaultFullAssetBaseUrl()`)。Vite 配置了 `base:'/app/'` 时,full 包开发 URL 为 `/app/file-viewer/...`,构建文件仍位于 `outDir/file-viewer/`。
122
+
123
+ 资源复制在开发服务器启动/重启及生产构建结束时执行,不随普通 HMR 执行。内容相同的文件不会被重复写入,生成的清单不再包含易变的 `copiedAt` 时间戳。Vite 自身清空构建目录、复制 public 目录的行为保持不变。
124
+
125
+ 如果你需要严格控制 registry,可以关闭注入并手动传入:
126
+
127
+ ```ts
128
+ fileViewerRenderers({
129
+ formats: ['pdf'],
130
+ inject: false,
131
+ copyAssets: true
132
+ })
133
+ ```
134
+
135
+ ```ts
136
+ import { configuredFileViewerRenderers } from 'virtual:file-viewer-renderers'
137
+
138
+ const options = {
139
+ rendererMode: 'replace',
140
+ renderers: configuredFileViewerRenderers
141
+ }
142
+ ```
143
+
144
+ 也可以使用 `preset: 'auto'` 发现项目中已安装的 preset 包;当 `preset-all` 存在时会优先使用它,避免重复导入其它 preset。注意:如果同时开启 `scan:true`,请显式使用 `preset:'auto'` 或 `autoPresets:true`,否则插件会以源码 hint 为准,不再做“无配置 preset 自动发现”。
145
+
146
+ ```ts
147
+ fileViewerRenderers({
148
+ preset: 'auto',
149
+ scan: true,
150
+ formats: ['pdf'],
151
+ copyAssets: true,
152
+ chunkStrategy: 'renderer'
153
+ })
154
+ ```
155
+
156
+ `scan: true` 会扫描常见源码目录里的轻量 hint,并把它们合并到 `formats`:
157
+
158
+ ```ts
159
+ export const fileViewerFormats = ['pdf', 'docx', 'xlsx']
160
+ ```
161
+
162
+ ```html
163
+ <input accept=".pdf,.docx" data-file-viewer-formats="dwg,xmind" />
164
+ ```
165
+
166
+ 这适合业务把上传入口、示例矩阵或附件白名单维护在源码中时使用:开发和构建阶段插件会自动生成 renderer 装配模块,少写一份手工 import 清单。
167
+
168
+ ## 缺失 renderer 提示
169
+
170
+ 如果用户打开的是支持矩阵内的格式,但项目没有安装或装配对应 renderer,core 会显示“需要装配预览能力”,并提示推荐安装的 preset / renderer 包,例如 `.pdf` 会引导安装 `@file-viewer/preset-office` 或 `@file-viewer/renderer-pdf`。只有真正不在支持矩阵中的扩展名才显示“不支持在线预览”。
171
+
172
+ ## 嵌套 Vite 项目
173
+
174
+ 从 3.0.3 起,子目录只有项目元数据或 Vite 插件声明时,会沿祖先 `package.json`
175
+ 查找实际可解析的 Full 依赖。子项目明确声明轻量组件或 preset 时保留其根目录资产布局,
176
+ 不会继承父项目的 Full 布局。`copyAssets.baseDir` 仍具有最高优先级;`publicDir`
177
+ 和 `build.outDir` 均相对于 Vite root,不相对于依赖所在目录。
178
+
179
+ ## 当前边界
180
+
181
+ 当前插件会为已经拆出的 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。
182
+
183
+ ## 文档
184
+
185
+ - 按需渲染架构: <https://doc.file-viewer.app/guide/on-demand-renderers>
186
+ - 支持格式: <https://doc.file-viewer.app/guide/formats>
187
+
188
+ ## 格式与相关包
189
+
190
+ File Viewer 在浏览器内预览 275+ 种扩展名,覆盖 Office、PDF/OFD、CAD、3D、压缩包、邮件、图片、音视频和代码,无需服务端转码;完整清单见 [格式矩阵](https://doc.file-viewer.app/guide/formats)。
191
+
192
+ - 组件:`@file-viewer/web`、`@file-viewer/vue3`、`@file-viewer/vue2.7`、`@file-viewer/vue2.6`、`@file-viewer/react`、`@file-viewer/react-legacy`、`@file-viewer/svelte`、`@file-viewer/jquery`,以及对应的 `-full` 包
193
+ - 能力组合:`@file-viewer/preset-lite`、`@file-viewer/preset-office`、`@file-viewer/preset-engineering`、`@file-viewer/preset-all`
194
+ - 构建:`@file-viewer/vite-plugin`、`file-viewer-copy-assets`
195
+
196
+ ## 许可证
197
+
198
+ 本包采用 Apache-2.0。详见 [LICENSE](https://github.com/flyfish-dev/file-viewer/blob/main/LICENSE)。
package/dist/index.d.ts CHANGED
@@ -4,28 +4,103 @@ export type FileViewerVitePresetMode = FileViewerVitePreset | 'auto';
4
4
  export type FileViewerMissingRendererMode = 'error' | 'warn' | 'ignore';
5
5
  export type FileViewerChunkStrategy = 'renderer' | 'none';
6
6
  export interface FileViewerRendererScanOptions {
7
+ /**
8
+ * Disable a shared scan object without branching user config.
9
+ */
7
10
  enabled?: boolean;
11
+ /**
12
+ * Source roots, relative to Vite root, that should be inspected for format hints.
13
+ * Defaults to common application source folders.
14
+ */
8
15
  roots?: readonly string[];
16
+ /**
17
+ * Text-like source extensions to inspect. Values may include or omit the dot.
18
+ */
9
19
  extensions?: readonly string[];
20
+ /**
21
+ * Large generated files are ignored by default to keep config/startup fast.
22
+ */
10
23
  maxFileSize?: number;
11
24
  }
12
25
  export interface FileViewerCopyAssetsOptions {
26
+ /**
27
+ * Directory used by Vite dev. Defaults to config.publicDir.
28
+ */
13
29
  publicDir?: string;
30
+ /**
31
+ * Directory used after production build. Defaults to build.outDir.
32
+ */
14
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. The injected runtime module
39
+ * keeps every renderer's default asset base aligned with this directory.
40
+ */
15
41
  baseDir?: string;
42
+ /**
43
+ * Copy during dev server startup, build closeBundle, or both.
44
+ */
16
45
  mode?: 'dev' | 'build' | 'both';
17
46
  }
18
47
  export interface FileViewerRenderersPluginOptions {
48
+ /**
49
+ * File extensions or renderer ids. Examples: pdf, .dwg, typst, zip, xmind.
50
+ */
19
51
  formats?: readonly string[];
52
+ /**
53
+ * Explicit renderer ids. Useful when several extensions share one renderer.
54
+ */
20
55
  renderers?: readonly string[];
56
+ /**
57
+ * Presets import their dedicated @file-viewer/preset-* package. Use `auto`
58
+ * to discover installed preset packages, and add `formats` / `renderers`
59
+ * when you need to extend a preset with extra lines.
60
+ */
21
61
  preset?: FileViewerVitePresetMode;
62
+ /**
63
+ * Auto-discovers installed `@file-viewer/preset-*` packages and registers
64
+ * them globally for framework components. Defaults to true only when no
65
+ * explicit preset/formats/renderers are configured, or when `preset: 'auto'`.
66
+ */
22
67
  autoPresets?: boolean | readonly FileViewerVitePreset[];
68
+ /**
69
+ * Injects the virtual renderer module into Vite HTML entrypoints so
70
+ * framework components can consume auto-registered renderers without
71
+ * application code importing `virtual:file-viewer-renderers`.
72
+ */
23
73
  inject?: boolean;
74
+ /**
75
+ * Virtual module id consumed by application code.
76
+ */
24
77
  moduleId?: string;
78
+ /**
79
+ * Controls how planned-but-not-yet-extracted renderer lines are reported.
80
+ */
25
81
  missingRenderer?: FileViewerMissingRendererMode;
82
+ /**
83
+ * Adds renderer-oriented manualChunks when the user did not define one.
84
+ */
26
85
  chunkStrategy?: FileViewerChunkStrategy;
86
+ /**
87
+ * Stabilizes default chunk groups and wraps existing manualChunks functions.
88
+ * This keeps CodeMirror/Lezer/Sandpack in one chunk when host
89
+ * apps split node_modules by package name, avoiding production TDZ errors.
90
+ * Pako and the shared CommonJS helpers also stay outside renderer/app chunks
91
+ * so compressed-format imports cannot form an initialization cycle.
92
+ */
27
93
  stabilizeInteropChunks?: boolean;
94
+ /**
95
+ * Copies known worker/WASM/vendor assets for selected renderer lines.
96
+ */
28
97
  copyAssets?: boolean | FileViewerCopyAssetsOptions;
98
+ /**
99
+ * Opt-in source scan. The plugin reads lightweight hints such as
100
+ * `fileViewerFormats = ['pdf', 'docx']`, `data-file-viewer-formats="pdf,docx"`,
101
+ * and upload `accept=".pdf,.docx"` declarations, then merges them with
102
+ * `formats` / `renderers` before generating the virtual module.
103
+ */
29
104
  scan?: boolean | FileViewerRendererScanOptions;
30
105
  }
31
106
  interface MissingRendererNotice {
@@ -36,8 +111,11 @@ interface MissingRendererNotice {
36
111
  export declare function extractFileViewerRendererHintTokens(source: string): string[];
37
112
  export declare function collectFileViewerRendererScanTokens(projectRoot: string, scan: FileViewerRenderersPluginOptions['scan']): string[];
38
113
  export interface FileViewerCopyAssetsTargetConfig {
114
+ /** Vite project root. Defaults to process.cwd(). */
39
115
  projectRoot?: string;
116
+ /** Resolved Vite publicDir. Defaults to `public`. */
40
117
  publicDir?: string;
118
+ /** Resolved Vite build.outDir. Defaults to `dist`. */
41
119
  outDir?: string;
42
120
  }
43
121
  export interface FileViewerCopyAssetsTarget {
@@ -48,6 +126,10 @@ export interface FileViewerCopyAssetsTarget {
48
126
  installedFullPackages: string[];
49
127
  installedAssetPackages: string[];
50
128
  }
129
+ /**
130
+ * Resolves the exact directory used by copyAssets without running a Vite build.
131
+ * This is exported so integrations can validate their deployment contract.
132
+ */
51
133
  export declare function resolveFileViewerCopyAssetsTarget(mode: 'dev' | 'build', value?: true | FileViewerCopyAssetsOptions, config?: FileViewerCopyAssetsTargetConfig): FileViewerCopyAssetsTarget;
52
134
  export declare function fileViewerRenderers(options?: FileViewerRenderersPluginOptions): Plugin;
53
135
  export declare function createFileViewerManualChunks(options?: FileViewerRenderersPluginOptions): (id: string) => string | undefined;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
- import { copyFile, cp, lstat, mkdir, readFile, readdir, realpath, rm, stat, writeFile } from 'node:fs/promises';
2
+ import { copyFile, lstat, mkdir, readFile, readdir, realpath, rm, stat, writeFile } from 'node:fs/promises';
3
3
  import { createRequire } from 'node:module';
4
4
  import { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
5
5
  const virtualModuleId = 'virtual:file-viewer-renderers';
@@ -578,6 +578,10 @@ const fileViewerFullPackages = [
578
578
  '@file-viewer/jquery-full',
579
579
  '@file-viewer/svelte-full'
580
580
  ];
581
+ // Vite dep optimization rewrites import.meta.url into node_modules/.vite/deps
582
+ // and can trigger full-preset dev-server re-optimization reloads. Keep File
583
+ // Viewer packages out of dep optimization while still optimizing their
584
+ // CommonJS/UMD helper dependencies below.
581
585
  const fileViewerOptimizationExcludedPackages = [
582
586
  '@file-viewer/core',
583
587
  '@file-viewer/ppt',
@@ -686,15 +690,24 @@ export function extractFileViewerRendererHintTokens(source) {
686
690
  }
687
691
  });
688
692
  };
693
+ // Explicit JavaScript/TypeScript hints:
694
+ // const fileViewerFormats = ['pdf', 'docx']
695
+ // fileViewerRenderers: ['pdf', 'cad']
689
696
  for (const match of source.matchAll(/\bfileViewer(?:Formats?|Renderers?)\b\s*[:=]\s*\[([\s\S]*?)\]/g)) {
690
697
  push(collectQuotedTokens(match[1]));
691
698
  }
699
+ // HTML / template hints:
700
+ // <div data-file-viewer-formats="pdf,docx"></div>
701
+ // <input accept=".pdf,.docx,application/vnd.ms-excel">
692
702
  for (const match of source.matchAll(/\bdata-file-viewer-(?:formats?|renderers?)\s*=\s*["']([^"']+)["']/g)) {
693
703
  push(collectDelimitedTokens(match[1]));
694
704
  }
695
705
  for (const match of source.matchAll(/\baccept\s*=\s*["']([^"']+)["']/g)) {
696
706
  push(collectDelimitedTokens(match[1]));
697
707
  }
708
+ // Comment hints are useful in non-framework projects where the upload UI is
709
+ // assembled dynamically:
710
+ // // file-viewer-formats: pdf,docx,dwg
698
711
  for (const match of source.matchAll(/file-viewer-(?:formats?|renderers?)\s*:\s*([^\n\r<]+)/g)) {
699
712
  push(collectDelimitedTokens(match[1]));
700
713
  }
@@ -923,6 +936,7 @@ function resolveProjectViteMajor(projectRoot) {
923
936
  return major;
924
937
  }
925
938
  catch {
939
+ // Try the plugin's own Vite installation when the project does not expose one.
926
940
  }
927
941
  }
928
942
  return 7;
@@ -952,6 +966,9 @@ function getNodeModulePackageName(id) {
952
966
  : parts[0];
953
967
  }
954
968
  function getStableInteropChunkName(id) {
969
+ // Rollup's virtual helper is shared by every converted CommonJS package.
970
+ // Leaving it in an app/renderer chunk makes Pako depend back on that chunk
971
+ // before its exported utility object has been initialized (issue #311).
955
972
  if (id.startsWith('\0') && /(?:^|\/)commonjsHelpers\.js$/.test(id.slice(1))) {
956
973
  return 'vendor-commonjs';
957
974
  }
@@ -1137,6 +1154,7 @@ function collectWorkspacePackageJsons(root) {
1137
1154
  }
1138
1155
  }
1139
1156
  catch {
1157
+ // Ignore malformed package.json files in unrelated workspace folders.
1140
1158
  }
1141
1159
  }
1142
1160
  for (const entry of readdirSync(directory)) {
@@ -1334,6 +1352,7 @@ function resolvePackageEntry(packageName, anchorPackages = []) {
1334
1352
  return requireFn.resolve(packageName);
1335
1353
  }
1336
1354
  catch {
1355
+ // Continue probing alternate anchors.
1337
1356
  }
1338
1357
  }
1339
1358
  return null;
@@ -1351,12 +1370,14 @@ function resolvePackageEntry(packageName, anchorPackages = []) {
1351
1370
  }
1352
1371
  }
1353
1372
  catch {
1373
+ // Fall back to Node resolution below.
1354
1374
  }
1355
1375
  for (const requireFn of requireFns) {
1356
1376
  try {
1357
1377
  return requireFn.resolve(packageName);
1358
1378
  }
1359
1379
  catch {
1380
+ // Continue probing alternate anchors.
1360
1381
  }
1361
1382
  }
1362
1383
  return null;
@@ -1440,6 +1461,42 @@ function createFileViewerResolveAliases(anchorPackages) {
1440
1461
  .filter((alias) => Boolean(alias)));
1441
1462
  return aliases;
1442
1463
  }
1464
+ async function assetPathInfo(path) {
1465
+ try {
1466
+ return await lstat(path);
1467
+ }
1468
+ catch (error) {
1469
+ if (error.code === 'ENOENT')
1470
+ return null;
1471
+ throw error;
1472
+ }
1473
+ }
1474
+ async function ensureAssetCopyDirectory(path, replace) {
1475
+ const info = await assetPathInfo(path);
1476
+ if (info?.isDirectory() && !info.isSymbolicLink())
1477
+ return;
1478
+ if (info) {
1479
+ if (!replace)
1480
+ throw new Error(`[file-viewer:vite-plugin] Unsafe asset directory ${path}.`);
1481
+ await rm(path, { recursive: info.isDirectory(), force: true });
1482
+ }
1483
+ await mkdir(path, { recursive: true });
1484
+ }
1485
+ async function writeAssetJsonIfChanged(path, value) {
1486
+ const manifest = { ...value };
1487
+ delete manifest.copiedAt;
1488
+ const content = `${JSON.stringify(manifest, null, 2)}\n`;
1489
+ const info = await assetPathInfo(path);
1490
+ if (info) {
1491
+ if (!info.isFile() || info.isSymbolicLink()) {
1492
+ throw new Error(`[file-viewer:vite-plugin] Unsafe asset manifest ${path}.`);
1493
+ }
1494
+ if (await readFile(path, 'utf8') === content)
1495
+ return;
1496
+ }
1497
+ await mkdir(dirname(path), { recursive: true });
1498
+ await writeFile(path, content);
1499
+ }
1443
1500
  async function copyFileIfPresent(from, to) {
1444
1501
  if (!from || !existsSync(from)) {
1445
1502
  return false;
@@ -1448,21 +1505,66 @@ async function copyFileIfPresent(from, to) {
1448
1505
  if (!info.isFile()) {
1449
1506
  return false;
1450
1507
  }
1508
+ const destination = await assetPathInfo(to);
1509
+ if (destination) {
1510
+ if (!destination.isFile() || destination.isSymbolicLink()) {
1511
+ throw new Error(`[file-viewer:vite-plugin] Unsafe asset file ${to}.`);
1512
+ }
1513
+ if (info.size === destination.size) {
1514
+ const [sourceBytes, destinationBytes] = await Promise.all([readFile(from), readFile(to)]);
1515
+ if (sourceBytes.equals(destinationBytes))
1516
+ return true;
1517
+ }
1518
+ }
1451
1519
  await mkdir(dirname(to), { recursive: true });
1452
1520
  await copyFile(from, to);
1453
1521
  return true;
1454
1522
  }
1455
- async function copyDirectoryIfPresent(from, to) {
1523
+ async function copyDirectoryIfPresent(from, to, options = {}) {
1456
1524
  if (!from || !existsSync(from)) {
1457
1525
  return false;
1458
1526
  }
1459
- const info = await stat(from);
1527
+ const info = await lstat(from);
1528
+ if (info.isSymbolicLink()) {
1529
+ throw new Error(`[file-viewer:vite-plugin] Asset source is a symbolic link: ${from}.`);
1530
+ }
1460
1531
  if (!info.isDirectory()) {
1461
1532
  return false;
1462
1533
  }
1463
- await rm(to, { recursive: true, force: true });
1464
- await mkdir(dirname(to), { recursive: true });
1465
- await cp(from, to, { recursive: true, force: true });
1534
+ if (isPathWithin(from, to) || isPathWithin(to, from)) {
1535
+ throw new Error(`[file-viewer:vite-plugin] Asset source and destination overlap: ${from}, ${to}.`);
1536
+ }
1537
+ const entries = await readdir(from, { withFileTypes: true });
1538
+ if (entries.some(entry => entry.isSymbolicLink())) {
1539
+ throw new Error(`[file-viewer:vite-plugin] Asset source contains a symbolic link: ${from}.`);
1540
+ }
1541
+ await ensureAssetCopyDirectory(to, !options.merge);
1542
+ const excluded = new Set(options.exclude || []);
1543
+ if (!options.merge) {
1544
+ const names = new Set([...entries.map(entry => entry.name), ...excluded]);
1545
+ for (const entry of await readdir(to)) {
1546
+ if (!names.has(entry))
1547
+ await rm(join(to, entry), { recursive: true, force: true });
1548
+ }
1549
+ }
1550
+ for (const entry of entries) {
1551
+ if (excluded.has(entry.name))
1552
+ continue;
1553
+ const source = join(from, entry.name), destination = join(to, entry.name);
1554
+ if (entry.isDirectory()) {
1555
+ await copyDirectoryIfPresent(source, destination, { merge: options.merge });
1556
+ }
1557
+ else if (entry.isFile()) {
1558
+ const destinationInfo = await assetPathInfo(destination);
1559
+ if (!options.merge && destinationInfo && (!destinationInfo.isFile() || destinationInfo.isSymbolicLink())) {
1560
+ await rm(destination, { recursive: destinationInfo.isDirectory(), force: true });
1561
+ }
1562
+ await copyFileIfPresent(source, destination);
1563
+ }
1564
+ else {
1565
+ throw new Error(`[file-viewer:vite-plugin] Asset source is not a regular file: ${source}.`);
1566
+ }
1567
+ }
1466
1568
  return true;
1467
1569
  }
1468
1570
  async function copyVersionedCadRuntimeDirectoryIfPresent(from, to, runtimeVersion) {
@@ -1472,12 +1574,13 @@ async function copyVersionedCadRuntimeDirectoryIfPresent(from, to, runtimeVersio
1472
1574
  if (!runtimeVersion || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(runtimeVersion)) {
1473
1575
  throw new Error(`[file-viewer:vite-plugin] Cannot publish CAD runtime assets with invalid version ${JSON.stringify(runtimeVersion)}.`);
1474
1576
  }
1475
- const copied = await copyDirectoryIfPresent(from, to);
1577
+ const copied = await copyDirectoryIfPresent(from, to, { exclude: [runtimeVersion] });
1476
1578
  if (!copied) {
1477
1579
  return false;
1478
1580
  }
1479
1581
  const versionedTarget = join(to, runtimeVersion);
1480
- return copyDirectoryIfPresent(from, versionedTarget);
1582
+ // These files are copied from the package root after its runtime directory.
1583
+ return copyDirectoryIfPresent(from, versionedTarget, { exclude: ['LICENSE', 'NOTICE'] });
1481
1584
  }
1482
1585
  async function copyPdfCjkFontAssets(from, to) {
1483
1586
  if (!from) {
@@ -1489,11 +1592,15 @@ async function copyPdfCjkFontAssets(from, to) {
1489
1592
  if (![files, stylesheet, license].every(existsSync) || !statSync(files).isDirectory()) {
1490
1593
  return false;
1491
1594
  }
1492
- await rm(to, { recursive: true, force: true });
1493
- await mkdir(to, { recursive: true });
1494
- await cp(files, join(to, 'files'), { recursive: true, force: true });
1495
- await copyFile(stylesheet, join(to, 'noto-sans-sc.css'));
1496
- await copyFile(license, join(to, 'OFL-1.1.txt'));
1595
+ await ensureAssetCopyDirectory(to, true);
1596
+ const retained = new Set(['files', 'noto-sans-sc.css', 'OFL-1.1.txt']);
1597
+ for (const name of await readdir(to)) {
1598
+ if (!retained.has(name))
1599
+ await rm(join(to, name), { recursive: true, force: true });
1600
+ }
1601
+ await copyDirectoryIfPresent(files, join(to, 'files'));
1602
+ await copyFileIfPresent(stylesheet, join(to, 'noto-sans-sc.css'));
1603
+ await copyFileIfPresent(license, join(to, 'OFL-1.1.txt'));
1497
1604
  return true;
1498
1605
  }
1499
1606
  function hasPdfCjkFontAssets(root, stylesheet, license) {
@@ -1579,6 +1686,9 @@ async function copyKnownRendererAssets(targetRoot, rendererIds) {
1579
1686
  '@file-viewer/renderer-pdf'
1580
1687
  ]);
1581
1688
  const pdfCjkFontTarget = join(targetRoot, 'vendor/pdf/fonts');
1689
+ // The PDF renderer owns the runtime font dependency so direct renderer installs retain
1690
+ // CJK fallback support. The standard profile pack is only a pre-staged copy source.
1691
+ // A missing source means an incomplete install and must not silently drop the fallback.
1582
1692
  const hasStandardPdfCjkFont = hasPdfCjkFontAssets(standardPdfCjkFontRoot, 'noto-sans-sc.css', 'OFL-1.1.txt');
1583
1693
  const hasStagedPdfCjkFont = hasPdfCjkFontAssets(stagedPdfCjkFontRoot, 'wght.css', 'LICENSE');
1584
1694
  const hasDependencyPdfCjkFont = hasPdfCjkFontAssets(dependencyPdfCjkFontRoot, 'wght.css', 'LICENSE');
@@ -1749,13 +1859,7 @@ async function copyKnownRendererAssets(targetRoot, rendererIds) {
1749
1859
  await push('indesign-idml-design', 'indesign-idml-license', join(targetRoot, 'vendor/design/LICENSE.introspect-wasm-MPL-2.0.txt'), () => copyFileIfPresent(designRendererRoot ? join(designRendererRoot, 'LICENSES/MPL-2.0.txt') : null, join(targetRoot, 'vendor/design/LICENSE.introspect-wasm-MPL-2.0.txt')));
1750
1860
  await push('data-asset', 'data-sql-wasm', join(targetRoot, 'wasm/data/sql-wasm.wasm'), () => copyFileIfPresent(sqlJsRoot ? join(sqlJsRoot, 'dist/sql-wasm.wasm') : null, join(targetRoot, 'wasm/data/sql-wasm.wasm')));
1751
1861
  await mkdir(targetRoot, { recursive: true });
1752
- await writeFile(join(targetRoot, 'flyfish-viewer-assets.json'), `${JSON.stringify({
1753
- schemaVersion: 1,
1754
- generatedBy: '@file-viewer/vite-plugin',
1755
- rendererIds,
1756
- copiedAt: new Date().toISOString(),
1757
- assets: results
1758
- }, null, 2)}\n`);
1862
+ await writeAssetJsonIfChanged(join(targetRoot, 'flyfish-viewer-assets.json'), { schemaVersion: 1, generatedBy: '@file-viewer/vite-plugin', rendererIds, assets: results });
1759
1863
  return results;
1760
1864
  }
1761
1865
  const independentlyOwnedAssetRendererIds = new Map([
@@ -1899,17 +2003,17 @@ async function copyInstalledCapabilityAssetPacks(targetRoot, rendererIds, instal
1899
2003
  const manifests = [...rendererOwners]
1900
2004
  .sort(([left], [right]) => left.localeCompare(right))
1901
2005
  .map(([, value]) => value.manifest);
1902
- await writeFile(join(targetRoot, 'flyfish-viewer-assets.json'), `${JSON.stringify({
2006
+ await writeAssetJsonIfChanged(join(targetRoot, 'flyfish-viewer-assets.json'), {
1903
2007
  schemaVersion: 1,
1904
2008
  generatedBy: '@file-viewer/vite-plugin',
1905
2009
  rendererAssetManifests: manifests
1906
- }, null, 2)}\n`);
1907
- await writeFile(join(targetRoot, 'flyfish-viewer-manifest.json'), `${JSON.stringify({
2010
+ });
2011
+ await writeAssetJsonIfChanged(join(targetRoot, 'flyfish-viewer-manifest.json'), {
1908
2012
  schemaVersion: 1,
1909
2013
  kind: 'file-viewer-capability-assets',
1910
2014
  owners: sources.map(source => ({ packageName: source.packageName, packageVersion: source.packageVersion }))
1911
2015
  .sort((left, right) => left.packageName.localeCompare(right.packageName))
1912
- }, null, 2)}\n`);
2016
+ });
1913
2017
  return { results, rendererIds: [...rendererOwners.keys()].sort() };
1914
2018
  }
1915
2019
  function matchesBundledAssetKind(kind, info) {
@@ -1960,6 +2064,8 @@ function resolveBundledFullAssetSource(installedFullPackages) {
1960
2064
  };
1961
2065
  }
1962
2066
  }
2067
+ // Source workspaces may not have staged file-viewer-copy-assets/viewer yet.
2068
+ // @file-viewer/web keeps the same tracked payload for local development.
1963
2069
  const webPackageRoot = resolvePackageRoot('@file-viewer/web', installedFullPackages);
1964
2070
  const webPayloadRoot = webPackageRoot ? join(webPackageRoot, 'viewer') : null;
1965
2071
  const sourceVersion = readPackageVersion(webPackageRoot);
@@ -2007,6 +2113,8 @@ async function copyBundledFullRendererAssets(targetRoot, rendererIds, installedF
2007
2113
  throw new Error(`[file-viewer:vite-plugin] Unsupported full asset manifest in ${sourceManifestPath}.`);
2008
2114
  }
2009
2115
  const packageJson = JSON.parse(readFileSync(join(source.packageRoot, 'package.json'), 'utf8'));
2116
+ // Full may split large asset families into version-paired dependencies.
2117
+ // Resolve only its declared owners, never an unrelated project-level pack.
2010
2118
  const dependencies = packageJson.dependencies || {};
2011
2119
  const splitPackageNames = Object.keys(dependencies)
2012
2120
  .filter(name => name.startsWith('@file-viewer/assets-'));
@@ -2040,7 +2148,12 @@ async function copyBundledFullRendererAssets(targetRoot, rendererIds, installedF
2040
2148
  }
2041
2149
  }
2042
2150
  await mkdir(targetRoot, { recursive: true });
2043
- await cp(source.payloadRoot, targetRoot, { recursive: true, force: true });
2151
+ // The runtime manifest is generated below; never overwrite it with the bundled
2152
+ // source first, otherwise an unchanged copy still writes it twice.
2153
+ await copyDirectoryIfPresent(source.payloadRoot, targetRoot, {
2154
+ merge: true,
2155
+ exclude: ['flyfish-viewer-assets.json']
2156
+ });
2044
2157
  const results = [];
2045
2158
  for (const asset of manifest.rendererAssetManifests.flatMap((rendererManifest) => rendererManifest.assets)) {
2046
2159
  if (asset.target !== 'public' || !asset.defaultPath) {
@@ -2079,13 +2192,7 @@ async function copyBundledFullRendererAssets(targetRoot, rendererIds, installedF
2079
2192
  sourceVersion: owner?.packageVersion || source.sourceVersion
2080
2193
  });
2081
2194
  }
2082
- await writeFile(join(targetRoot, 'flyfish-viewer-assets.json'), `${JSON.stringify({
2083
- ...manifest,
2084
- generatedBy: '@file-viewer/vite-plugin',
2085
- copiedAt: new Date().toISOString(),
2086
- rendererIds,
2087
- assets: results
2088
- }, null, 2)}\n`);
2195
+ await writeAssetJsonIfChanged(join(targetRoot, 'flyfish-viewer-assets.json'), { ...manifest, generatedBy: '@file-viewer/vite-plugin', rendererIds, assets: results });
2089
2196
  return results;
2090
2197
  }
2091
2198
  async function copyRendererAssets(target, rendererIds) {
@@ -2106,6 +2213,7 @@ async function copyRendererAssets(target, rendererIds) {
2106
2213
  id: 'missing-independent-asset-owner',
2107
2214
  to: target.targetRoot,
2108
2215
  copied: false,
2216
+ // Specialist offline payloads stay opt-in; warn instead of failing the build.
2109
2217
  required: false,
2110
2218
  reason: `install ${requiredOwner} for this capability`
2111
2219
  });
@@ -2164,6 +2272,8 @@ function resolveInstalledFullPackages(projectRoot) {
2164
2272
  return fileViewerFullPackages.filter(packageName => declaredPackages.has(packageName) &&
2165
2273
  Boolean(tryResolvePackageJson(packageName, projectPackageRequire)));
2166
2274
  }
2275
+ // Vite roots can contain a metadata-only package.json while runtime
2276
+ // dependencies live in an ancestor. A nearer light runtime is a boundary.
2167
2277
  const directory = dirname(packageJsonPath);
2168
2278
  const parent = dirname(directory);
2169
2279
  packageJsonPath = parent === directory ? null : findNearestProjectPackageJson(parent);
@@ -2235,6 +2345,10 @@ function resolveOutputRoot(value, fallback, projectRoot) {
2235
2345
  const output = value || fallback;
2236
2346
  return isAbsolute(output) ? resolve(output) : resolve(projectRoot, output);
2237
2347
  }
2348
+ /**
2349
+ * Resolves the exact directory used by copyAssets without running a Vite build.
2350
+ * This is exported so integrations can validate their deployment contract.
2351
+ */
2238
2352
  export function resolveFileViewerCopyAssetsTarget(mode, value = true, config = {}) {
2239
2353
  const projectRoot = resolve(config.projectRoot || process.cwd());
2240
2354
  const options = copyOptions(value);
@@ -2294,6 +2408,10 @@ function resolveViteFullAssetBase(base, baseDir) {
2294
2408
  function resolveDevAssetRequestPath(targetRoot, baseDir, requestPathname, viteBasePath) {
2295
2409
  let requestRelativePath;
2296
2410
  try {
2411
+ // Depending on the Vite major and middleware ordering, configureServer can
2412
+ // observe either the original base-prefixed URL or the pathname after
2413
+ // Vite's base middleware has stripped it. Accept both shapes, then keep the
2414
+ // strict viewer-asset allowlist and target-root boundary checks below.
2297
2415
  const encodedRelativePath = viteBasePath === '/' || requestPathname.startsWith(viteBasePath)
2298
2416
  ? requestPathname.slice(viteBasePath === '/' ? 1 : viteBasePath.length)
2299
2417
  : requestPathname.replace(/^\/+/, '');
@@ -2562,6 +2680,7 @@ export function fileViewerRenderers(options = {}) {
2562
2680
  }
2563
2681
  }
2564
2682
  catch {
2683
+ // Let Vite return its normal 404/fallback response.
2565
2684
  }
2566
2685
  }
2567
2686
  next();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/vite-plugin",
3
- "version": "3.2.2",
3
+ "version": "3.2.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Vite plugin for File Viewer on-demand renderer assembly, chunk planning, and offline asset deployment.",
@@ -47,12 +47,13 @@
47
47
  "./package.json": "./package.json"
48
48
  },
49
49
  "files": [
50
+ "LICENSE",
50
51
  "client.d.ts",
51
- "dist/index.d.ts",
52
52
  "dist/index.js",
53
- "LICENSE",
53
+ "dist/index.d.ts",
54
+ "README.md",
54
55
  "README.en.md",
55
- "README.md"
56
+ "README.zh-CN.md"
56
57
  ],
57
58
  "peerDependencies": {
58
59
  "vite": ">=5 <9"