@ubean/integrations 0.2.0

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.
@@ -0,0 +1,349 @@
1
+ import { hash } from "ohash";
2
+ //#region src/fonts/core.ts
3
+ const defaultFontWeights = [400];
4
+ const defaultFontStyles = ["normal"];
5
+ const defaultFontSubsets = ["latin"];
6
+ const defaultFontDisplay = "swap";
7
+ const fallbackFonts = {
8
+ sans: [
9
+ "system-ui",
10
+ "-apple-system",
11
+ "BlinkMacSystemFont",
12
+ "\"Segoe UI\"",
13
+ "Roboto",
14
+ "\"Helvetica Neue\"",
15
+ "Arial",
16
+ "sans-serif"
17
+ ],
18
+ serif: [
19
+ "Georgia",
20
+ "Cambria",
21
+ "\"Times New Roman\"",
22
+ "Times",
23
+ "serif"
24
+ ],
25
+ mono: [
26
+ "Menlo",
27
+ "Monaco",
28
+ "Consolas",
29
+ "\"Liberation Mono\"",
30
+ "\"Courier New\"",
31
+ "monospace"
32
+ ],
33
+ cursive: ["cursive"],
34
+ fantasy: ["fantasy"]
35
+ };
36
+ function normalizeFontName(name) {
37
+ return name.replace(/\s+/g, "+");
38
+ }
39
+ function weightsToString(weights) {
40
+ return weights.map((w) => String(w)).sort((a, b) => Number(a) - Number(b)).join(";");
41
+ }
42
+ function getGoogleFontsUrl(options) {
43
+ const { family, weights = defaultFontWeights, styles = defaultFontStyles, display = defaultFontDisplay } = options;
44
+ const familyName = normalizeFontName(family);
45
+ const weightList = weights.map((w) => String(w)).sort((a, b) => Number(a) - Number(b));
46
+ const italParams = [];
47
+ for (const style of styles) {
48
+ const ital = style === "italic" ? "1" : "0";
49
+ for (const w of weightList) italParams.push(`${ital},${w}`);
50
+ }
51
+ const variants = italParams.length > 0 ? italParams.join(";") : weightList.join(";");
52
+ let url = `https://fonts.googleapis.com/css2?family=${familyName}`;
53
+ if (variants) url += `:ital,wght@${variants}`;
54
+ const paramParts = [];
55
+ if (options.subsets && options.subsets.length > 0) paramParts.push(`subset=${options.subsets.join(",")}`);
56
+ paramParts.push(`display=${display}`);
57
+ if (paramParts.length > 0) url += `&${paramParts.join("&")}`;
58
+ return url;
59
+ }
60
+ function getBunnyFontsUrl(options) {
61
+ const { family, weights = defaultFontWeights, styles = defaultFontStyles, display = defaultFontDisplay } = options;
62
+ const familyName = normalizeFontName(family);
63
+ const weightList = weights.map((w) => String(w)).sort((a, b) => Number(a) - Number(b));
64
+ const italParams = [];
65
+ for (const style of styles) {
66
+ const ital = style === "italic" ? "1" : "0";
67
+ for (const w of weightList) italParams.push(`${ital},${w}`);
68
+ }
69
+ const variants = italParams.length > 0 ? italParams.join(";") : weightList.join(";");
70
+ let url = `https://fonts.bunny.net/css?family=${familyName.toLowerCase()}`;
71
+ if (variants) url += `:ital,wght@${variants}`;
72
+ const params = new URLSearchParams();
73
+ if (options.subsets && options.subsets.length > 0) params.set("subset", options.subsets.join(","));
74
+ params.set("display", display);
75
+ const paramStr = params.toString();
76
+ if (paramStr) url += `&${paramStr}`;
77
+ return url;
78
+ }
79
+ function getFontshareUrl(family, _format = "css") {
80
+ return `https://api.fontshare.com/v2/css?f[]=${family.toLowerCase().replace(/\s+/g, "-")}@400,500,600,700&display=swap`;
81
+ }
82
+ function generateFontFace(face) {
83
+ const props = [];
84
+ props.push(`font-family: '${face.fontFamily}'`);
85
+ props.push(`src: ${face.src}`);
86
+ if (face.weight) props.push(`font-weight: ${face.weight}`);
87
+ if (face.style) props.push(`font-style: ${face.style}`);
88
+ if (face.display) props.push(`font-display: ${face.display}`);
89
+ if (face.unicodeRange) props.push(`unicode-range: ${face.unicodeRange}`);
90
+ if (face.fontStretch) props.push(`font-stretch: ${face.fontStretch}`);
91
+ if (face.ascentOverride) props.push(`ascent-override: ${face.ascentOverride}`);
92
+ if (face.descentOverride) props.push(`descent-override: ${face.descentOverride}`);
93
+ if (face.lineGapOverride) props.push(`line-gap-override: ${face.lineGapOverride}`);
94
+ if (face.sizeAdjust) props.push(`size-adjust: ${face.sizeAdjust}`);
95
+ return `@font-face {\n ${props.join(";\n ")};\n}`;
96
+ }
97
+ function generateFontFallbackCSS(familyName, metric, fallbackName, isItalic = false) {
98
+ return `@font-face {
99
+ font-family: '${familyName} fallback';
100
+ src: local('${fallbackName}');
101
+ ascent-override: ${`${metric.ascent / metric.unitsPerEm * 100}%`};
102
+ descent-override: ${`${Math.abs(metric.descent) / metric.unitsPerEm * 100}%`};
103
+ line-gap-override: ${`${metric.lineGap / metric.unitsPerEm * 100}%`};
104
+ size-adjust: 100%;
105
+ font-style: ${isItalic ? "italic" : "normal"};
106
+ }`;
107
+ }
108
+ function generatePreloadLink(url, type = "font", crossorigin = true) {
109
+ const attrs = [
110
+ "rel=\"preload\"",
111
+ `href="${url}"`,
112
+ "as=\"font\"",
113
+ `type="${type}"`
114
+ ];
115
+ if (crossorigin) attrs.push("crossorigin");
116
+ return `<link ${attrs.join(" ")}>`;
117
+ }
118
+ function generatePreconnectLink(href, crossorigin = true) {
119
+ const attrs = ["rel=\"preconnect\"", `href="${href}"`];
120
+ if (crossorigin) attrs.push("crossorigin");
121
+ return `<link ${attrs.join(" ")}>`;
122
+ }
123
+ function generateFontCSSVariable(name, family) {
124
+ return `${name.startsWith("--") ? name : `--font-${name.toLowerCase().replace(/\s+/g, "-")}`}: '${family}', ${fallbackFonts.sans.join(", ")};`;
125
+ }
126
+ function parseFontFamilyCSS(css) {
127
+ const faces = [];
128
+ const regex = /@font-face\s*\{([^}]+)\}/g;
129
+ let match;
130
+ while ((match = regex.exec(css)) !== null) {
131
+ const body = match[1];
132
+ const face = {
133
+ fontFamily: "",
134
+ src: ""
135
+ };
136
+ const familyMatch = body.match(/font-family:\s*['"]?([^'";]+)['"]?/);
137
+ if (familyMatch) face.fontFamily = familyMatch[1].trim();
138
+ const srcMatch = body.match(/src:\s*([^;]+)/);
139
+ if (srcMatch) face.src = srcMatch[1].trim();
140
+ const weightMatch = body.match(/font-weight:\s*([^;]+)/);
141
+ if (weightMatch) face.weight = weightMatch[1].trim();
142
+ const styleMatch = body.match(/font-style:\s*([^;]+)/);
143
+ if (styleMatch) face.style = styleMatch[1].trim();
144
+ const displayMatch = body.match(/font-display:\s*([^;]+)/);
145
+ if (displayMatch) face.display = displayMatch[1].trim();
146
+ const unicodeMatch = body.match(/unicode-range:\s*([^;]+)/);
147
+ if (unicodeMatch) face.unicodeRange = unicodeMatch[1].trim();
148
+ faces.push(face);
149
+ }
150
+ return faces;
151
+ }
152
+ function extractFontUrlsFromCSS(css) {
153
+ const urls = [];
154
+ const regex = /url\(['"]?([^'")]+\.(?:woff2?|ttf|otf|eot))['"]?\)/g;
155
+ let match;
156
+ while ((match = regex.exec(css)) !== null) urls.push(match[1]);
157
+ return [...new Set(urls)];
158
+ }
159
+ function createFontHash(config) {
160
+ return hash({
161
+ name: config.name,
162
+ weights: config.weights,
163
+ styles: config.styles,
164
+ subsets: config.subsets,
165
+ provider: config.provider
166
+ }).slice(0, 10);
167
+ }
168
+ function normalizeFontFamily(family, defaults) {
169
+ return {
170
+ name: family.name,
171
+ provider: family.provider || "google",
172
+ weights: family.weights || defaults.weights,
173
+ styles: family.styles || defaults.styles,
174
+ subsets: family.subsets || defaults.subsets,
175
+ display: family.display || defaults.display,
176
+ preload: family.preload ?? true,
177
+ fallback: family.fallback || [],
178
+ unicodeRange: family.unicodeRange || "",
179
+ selector: family.selector || "",
180
+ src: family.src || "",
181
+ cssVariable: family.cssVariable ?? false
182
+ };
183
+ }
184
+ const googleFontMetrics = {
185
+ Inter: {
186
+ familyName: "Inter",
187
+ category: "sans-serif",
188
+ capHeight: 2048,
189
+ ascent: 2728,
190
+ descent: -680,
191
+ lineGap: 0,
192
+ unitsPerEm: 2816,
193
+ xHeight: 1536,
194
+ xWidthAvg: 1352,
195
+ subsets: { latin: { xWidthAvg: 1352 } }
196
+ },
197
+ Roboto: {
198
+ familyName: "Roboto",
199
+ category: "sans-serif",
200
+ capHeight: 1456,
201
+ ascent: 1900,
202
+ descent: -500,
203
+ lineGap: 0,
204
+ unitsPerEm: 2048,
205
+ xHeight: 1082,
206
+ xWidthAvg: 1061,
207
+ subsets: { latin: { xWidthAvg: 1061 } }
208
+ }
209
+ };
210
+ //#endregion
211
+ //#region src/fonts/runtime.ts
212
+ let fontsConfig;
213
+ function configureFontsRuntime(options = {}) {
214
+ fontsConfig = {
215
+ families: options.families || [],
216
+ google: options.google || {},
217
+ local: options.local || {},
218
+ custom: options.custom || {},
219
+ defaults: {
220
+ weights: options.defaults?.weights || defaultFontWeights,
221
+ styles: options.defaults?.styles || defaultFontStyles,
222
+ subsets: options.defaults?.subsets || defaultFontSubsets,
223
+ display: options.defaults?.display || "swap",
224
+ preload: options.defaults?.preload ?? true,
225
+ preconnect: options.defaults?.preconnect ?? true,
226
+ download: options.defaults?.download ?? false,
227
+ fallback: options.defaults?.fallback || fallbackFonts
228
+ },
229
+ assets: {
230
+ baseURL: options.assets?.baseURL || "/_fonts",
231
+ prefix: options.assets?.prefix || "_fonts"
232
+ },
233
+ metrics: options.metrics ?? true,
234
+ experimental: { inline: options.experimental?.inline ?? false }
235
+ };
236
+ return fontsConfig;
237
+ }
238
+ function getFontsConfig() {
239
+ if (!fontsConfig) configureFontsRuntime();
240
+ return fontsConfig;
241
+ }
242
+ function defineFontFamily(options) {
243
+ if (!fontsConfig) configureFontsRuntime();
244
+ const normalized = normalizeFontFamily(options, fontsConfig.defaults);
245
+ fontsConfig.families.push(normalized);
246
+ return normalized;
247
+ }
248
+ function defineLocalFont(options) {
249
+ if (!fontsConfig) configureFontsRuntime();
250
+ fontsConfig.local[options.name] = options;
251
+ const family = {
252
+ name: options.name,
253
+ provider: "local",
254
+ src: options.src,
255
+ weights: options.weights?.length ? options.weights : [400],
256
+ styles: options.styles?.length ? options.styles : ["normal"],
257
+ preload: options.preload ?? true
258
+ };
259
+ fontsConfig.families.push(family);
260
+ return options;
261
+ }
262
+ function defineGoogleFont(options) {
263
+ if (!fontsConfig) configureFontsRuntime();
264
+ fontsConfig.google = {
265
+ ...fontsConfig.google,
266
+ ...options
267
+ };
268
+ return options;
269
+ }
270
+ function generateFontLinks() {
271
+ const config = getFontsConfig();
272
+ const preconnect = [];
273
+ const styles = [];
274
+ const preload = [];
275
+ if (config.defaults.preconnect && (config.families.some((f) => f.provider === "google") || Object.keys(config.google?.families || {}).length > 0)) {
276
+ preconnect.push(generatePreconnectLink("https://fonts.googleapis.com"));
277
+ preconnect.push(generatePreconnectLink("https://fonts.gstatic.com"));
278
+ }
279
+ for (const family of config.families) {
280
+ const normalized = normalizeFontFamily(family, config.defaults);
281
+ let url = "";
282
+ switch (normalized.provider) {
283
+ case "google":
284
+ url = getGoogleFontsUrl({
285
+ family: normalized.name,
286
+ weights: normalized.weights,
287
+ styles: normalized.styles,
288
+ subsets: normalized.subsets,
289
+ display: normalized.display
290
+ });
291
+ if (config.defaults.preconnect && !preconnect.some((l) => l.includes("fonts.googleapis"))) {
292
+ preconnect.push(generatePreconnectLink("https://fonts.googleapis.com"));
293
+ preconnect.push(generatePreconnectLink("https://fonts.gstatic.com"));
294
+ }
295
+ styles.push(`<link rel="stylesheet" href="${url}">`);
296
+ break;
297
+ case "bunny":
298
+ url = getBunnyFontsUrl({
299
+ family: normalized.name,
300
+ weights: normalized.weights,
301
+ styles: normalized.styles,
302
+ subsets: normalized.subsets,
303
+ display: normalized.display
304
+ });
305
+ if (config.defaults.preconnect) preconnect.push(generatePreconnectLink("https://fonts.bunny.net"));
306
+ styles.push(`<link rel="stylesheet" href="${url}">`);
307
+ break;
308
+ case "local": if (normalized.src) {
309
+ const srcs = Array.isArray(normalized.src) ? normalized.src : [normalized.src];
310
+ for (const src of srcs) {
311
+ const face = {
312
+ fontFamily: normalized.name,
313
+ src: `url('${src}') format('woff2')`,
314
+ weight: normalized.weights[0],
315
+ style: normalized.styles[0],
316
+ display: normalized.display
317
+ };
318
+ styles.push(`<style>${generateFontFace(face)}</style>`);
319
+ if (normalized.preload) preload.push(generatePreloadLink(src));
320
+ }
321
+ }
322
+ }
323
+ }
324
+ return {
325
+ preconnect,
326
+ styles,
327
+ preload
328
+ };
329
+ }
330
+ function generateFontCSS() {
331
+ const config = getFontsConfig();
332
+ const css = [];
333
+ const variables = [];
334
+ for (const family of config.families) if (family.cssVariable) {
335
+ const varName = typeof family.cssVariable === "string" ? family.cssVariable : family.name;
336
+ variables.push(generateFontCSSVariable(varName, family.name));
337
+ }
338
+ if (variables.length > 0) css.push(`:root { ${variables.join(" ")} }`);
339
+ return css.join("\n");
340
+ }
341
+ function getFontFamilies() {
342
+ const config = getFontsConfig();
343
+ return config.families.map((f) => normalizeFontFamily(f, config.defaults));
344
+ }
345
+ function getFontHash(family) {
346
+ return createFontHash(family);
347
+ }
348
+ //#endregion
349
+ export { getFontshareUrl as C, normalizeFontName as D, normalizeFontFamily as E, parseFontFamilyCSS as O, getBunnyFontsUrl as S, googleFontMetrics as T, generateFontCSSVariable as _, generateFontCSS as a, generatePreconnectLink as b, getFontHash as c, defaultFontDisplay as d, defaultFontStyles as f, fallbackFonts as g, extractFontUrlsFromCSS as h, defineLocalFont as i, weightsToString as k, getFontsConfig as l, defaultFontWeights as m, defineFontFamily as n, generateFontLinks as o, defaultFontSubsets as p, defineGoogleFont as r, getFontFamilies as s, configureFontsRuntime as t, createFontHash as u, generateFontFace as v, getGoogleFontsUrl as w, generatePreloadLink as x, generateFontFallbackCSS as y };
@@ -0,0 +1,37 @@
1
+ //#region src/ui/types.d.ts
2
+ /**
3
+ * @ubean/integrations/ui 类型定义
4
+ *
5
+ * ubean UI 模块的配置选项。
6
+ * 底层组件库为 @soybeanjs/ui,本模块负责:
7
+ * 1. 注入 UiResolver 到 unplugin-vue-components(组件自动导入)
8
+ * 2. 可选注入 @soybeanjs/ui/styles.css(预构建样式)
9
+ */
10
+ /**
11
+ * UI 模块配置选项
12
+ *
13
+ * 用户在 ubean.config.ts 中通过 `ui: true | UiOptions` 配置。
14
+ * `ui: true` 即可启用,默认自动注入 styles.css。
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * // 最简启用(含 styles.css)
19
+ * export default defineConfig({ ui: true });
20
+ *
21
+ * // 使用 UnoCSS 模式(不注入 styles.css,由用户自行配置 @soybeanjs/unocss-shadcn)
22
+ * export default defineConfig({ ui: { css: false } });
23
+ * ```
24
+ */
25
+ interface UiOptions {
26
+ /** 是否启用,默认 true */
27
+ enabled?: boolean;
28
+ /**
29
+ * 是否自动注入 `@soybeanjs/ui/styles.css`,默认 true。
30
+ *
31
+ * - `true`(默认):自动注入预构建样式,零配置开箱即用
32
+ * - `false`:不注入样式,用户需自行配置 UnoCSS + `@soybeanjs/unocss-shadcn` preset
33
+ */
34
+ css?: boolean;
35
+ }
36
+ //#endregion
37
+ export { UiOptions as t };
@@ -0,0 +1,40 @@
1
+ import { InlineConfig } from "vite";
2
+ //#region src/electron/types.d.ts
3
+ /** main 进程配置 */
4
+ interface ElectronMainOptions {
5
+ /** main 进程入口文件路径,默认 'electron/main.ts' */
6
+ entry?: string;
7
+ /** 传递给 Vite 的额外配置 */
8
+ vite?: InlineConfig;
9
+ }
10
+ /** preload 脚本配置 */
11
+ interface ElectronPreloadOptions {
12
+ /** preload 脚本入口文件路径,默认 'electron/preload.ts' */
13
+ input?: string;
14
+ /** 传递给 Vite 的额外配置 */
15
+ vite?: InlineConfig;
16
+ }
17
+ /** renderer 进程配置(可选,用于在 renderer 中使用 Node.js API) */
18
+ interface ElectronRendererOptions {
19
+ /** 是否在 renderer 中启用 Node.js API */
20
+ nodeIntegration?: boolean;
21
+ }
22
+ /**
23
+ * ubean Electron 配置选项
24
+ *
25
+ * 用户在 ubean.config.ts 中通过 `electron: true | ElectronOptions` 配置。
26
+ * main/preload 有默认值,`electron: true` 即可启用。
27
+ * 内部由 ubeanElectronPlugin 转换为 vite-plugin-electron/simple 的配置。
28
+ */
29
+ interface ElectronOptions {
30
+ /** 是否启用,默认 true */
31
+ enabled?: boolean;
32
+ /** main 进程配置(默认 entry: 'electron/main.ts') */
33
+ main?: ElectronMainOptions;
34
+ /** preload 脚本配置(默认 input: 'electron/preload.ts') */
35
+ preload?: ElectronPreloadOptions;
36
+ /** renderer 进程配置(可选) */
37
+ renderer?: ElectronRendererOptions;
38
+ }
39
+ //#endregion
40
+ export { ElectronRendererOptions as i, ElectronOptions as n, ElectronPreloadOptions as r, ElectronMainOptions as t };
@@ -0,0 +1,106 @@
1
+ //#region src/fonts/types.d.ts
2
+ type FontProvider = 'google' | 'local' | 'bunny' | 'fontshare' | 'custom';
3
+ type FontDisplay = 'auto' | 'block' | 'swap' | 'fallback' | 'optional';
4
+ type FontSubset = 'cyrillic' | 'cyrillic-ext' | 'greek' | 'greek-ext' | 'latin' | 'latin-ext' | 'vietnamese' | 'hebrew' | 'arabic' | 'devanagari' | 'thai' | 'chinese-simplified' | 'chinese-traditional' | 'japanese' | 'korean';
5
+ type FontStyle = 'normal' | 'italic' | 'oblique';
6
+ type FontWeight = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | 'normal' | 'bold';
7
+ interface FontFamilyOptions {
8
+ name: string;
9
+ provider?: FontProvider;
10
+ weights?: (FontWeight | number)[];
11
+ styles?: FontStyle[];
12
+ subsets?: FontSubset[];
13
+ display?: FontDisplay;
14
+ preload?: boolean;
15
+ fallback?: string[];
16
+ unicodeRange?: string;
17
+ selector?: string;
18
+ src?: string | string[];
19
+ cssVariable?: string | boolean;
20
+ }
21
+ interface ResolvedFontFace {
22
+ fontFamily: string;
23
+ src: string;
24
+ weight?: string | number;
25
+ style?: FontStyle;
26
+ display?: FontDisplay;
27
+ unicodeRange?: string;
28
+ fontStretch?: string;
29
+ fontVariant?: string;
30
+ fontFeatureSettings?: string;
31
+ fontVariationSettings?: string;
32
+ ascentOverride?: string;
33
+ descentOverride?: string;
34
+ lineGapOverride?: string;
35
+ sizeAdjust?: string;
36
+ }
37
+ interface FontMetric {
38
+ familyName: string;
39
+ category: string;
40
+ capHeight: number;
41
+ ascent: number;
42
+ descent: number;
43
+ lineGap: number;
44
+ unitsPerEm: number;
45
+ xHeight: number;
46
+ xWidthAvg: number;
47
+ subsets: Record<string, {
48
+ xWidthAvg: number;
49
+ }>;
50
+ weights?: Record<string, any>;
51
+ }
52
+ interface FontFaceDeclaration {
53
+ css: string;
54
+ url?: string;
55
+ preload?: boolean;
56
+ }
57
+ interface FontProviderOptions {
58
+ name: FontProvider | string;
59
+ prefix?: string;
60
+ baseUrl?: string;
61
+ }
62
+ interface GoogleFontsOptions {
63
+ families: Record<string, FontFamilyOptions>;
64
+ display?: FontDisplay;
65
+ subsets?: FontSubset[];
66
+ preload?: boolean;
67
+ preconnect?: boolean;
68
+ download?: boolean;
69
+ baseUrl?: string;
70
+ inject?: boolean;
71
+ }
72
+ interface LocalFontOptions {
73
+ name: string;
74
+ src: string | string[];
75
+ weights?: (FontWeight | number)[];
76
+ styles?: FontStyle[];
77
+ display?: FontDisplay;
78
+ preload?: boolean;
79
+ fallback?: string[];
80
+ }
81
+ interface FontModuleOptions {
82
+ google?: Partial<GoogleFontsOptions>;
83
+ local?: Record<string, LocalFontOptions>;
84
+ custom?: Record<string, FontFamilyOptions>;
85
+ families: FontFamilyOptions[];
86
+ defaults: {
87
+ weights: number[];
88
+ styles: FontStyle[];
89
+ subsets: FontSubset[];
90
+ display: FontDisplay;
91
+ preload: boolean;
92
+ preconnect: boolean;
93
+ download: boolean;
94
+ fallback: Record<string, string[]>;
95
+ };
96
+ assets: {
97
+ baseURL: string;
98
+ prefix: string;
99
+ };
100
+ metrics: boolean;
101
+ experimental: {
102
+ inline: boolean;
103
+ };
104
+ }
105
+ //#endregion
106
+ export { FontModuleOptions as a, FontStyle as c, GoogleFontsOptions as d, LocalFontOptions as f, FontMetric as i, FontSubset as l, FontFaceDeclaration as n, FontProvider as o, ResolvedFontFace as p, FontFamilyOptions as r, FontProviderOptions as s, FontDisplay as t, FontWeight as u };
@@ -0,0 +1,130 @@
1
+ //#region src/pwa/types.d.ts
2
+ /**
3
+ * PWA 类型定义
4
+ *
5
+ * 这些类型面向 ubean 用户配置,PWA 底层实现由 vite-plugin-pwa 提供。
6
+ * ubean PwaOptions 在 ubeanPwaPlugin 内部转换为 vite-plugin-pwa 的 VitePluginPWOptions。
7
+ */
8
+ type DisplayMode = 'fullscreen' | 'standalone' | 'minimal-ui' | 'browser';
9
+ type ManifestOrientation = 'any' | 'natural' | 'landscape' | 'portrait' | 'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary';
10
+ interface ManifestIcon {
11
+ src: string;
12
+ sizes: string;
13
+ type?: string;
14
+ purpose?: string;
15
+ }
16
+ interface ManifestScreenshot {
17
+ src: string;
18
+ sizes: string;
19
+ type?: string;
20
+ form_factor?: 'narrow' | 'wide';
21
+ }
22
+ interface WebAppManifest {
23
+ name: string;
24
+ short_name?: string;
25
+ description?: string;
26
+ start_url?: string;
27
+ scope?: string;
28
+ display?: DisplayMode;
29
+ orientation?: ManifestOrientation;
30
+ background_color?: string;
31
+ theme_color?: string;
32
+ lang?: string;
33
+ dir?: 'ltr' | 'rtl' | 'auto';
34
+ icons?: ManifestIcon[];
35
+ screenshots?: ManifestScreenshot[];
36
+ categories?: string[];
37
+ shortcuts?: Array<{
38
+ name: string;
39
+ short_name?: string;
40
+ description?: string;
41
+ url: string;
42
+ icons?: ManifestIcon[];
43
+ }>;
44
+ }
45
+ type CachingStrategy = 'cache-first' | 'network-first' | 'stale-while-revalidate' | 'network-only' | 'cache-only';
46
+ interface RuntimeCachingRule {
47
+ urlPattern: string | RegExp | ((ctx: {
48
+ request: Request;
49
+ }) => boolean);
50
+ handler: CachingStrategy;
51
+ method?: string;
52
+ options?: {
53
+ cacheName?: string;
54
+ expiration?: {
55
+ maxEntries?: number;
56
+ maxAgeSeconds?: number;
57
+ };
58
+ cacheableResponse?: {
59
+ statuses?: number[];
60
+ headers?: Record<string, string>;
61
+ };
62
+ };
63
+ }
64
+ /**
65
+ * ubean PWA 配置选项
66
+ *
67
+ * 用户在 ubean.config.ts 中通过 `pwa: true | PwaOptions` 配置。
68
+ * 内部由 ubeanPwaPlugin 转换为 vite-plugin-pwa 的 VitePluginPWOptions。
69
+ */
70
+ interface PwaOptions {
71
+ /** 是否启用 PWA,默认 true */
72
+ enabled?: boolean;
73
+ /** Web App Manifest 配置 */
74
+ manifest?: Partial<WebAppManifest> & {
75
+ name: string;
76
+ };
77
+ /** 注册类型:autoUpdate(自动更新)| prompt(提示用户)| manual(手动),默认 autoUpdate */
78
+ registerType?: 'autoUpdate' | 'prompt' | 'manual';
79
+ /** 自动注入 SW 注册脚本:auto | script | inline | null(禁用),默认 auto */
80
+ injectRegister?: 'auto' | 'script' | 'inline' | null;
81
+ /** dev 模式选项 */
82
+ devOptions?: {
83
+ enabled?: boolean;
84
+ suppressWarnings?: boolean;
85
+ };
86
+ /**
87
+ * 按资源类型配置默认缓存策略,会转换为 workbox runtimeCaching 规则。
88
+ * 与 runtimeCaching 合并:默认规则在前,用户自定义规则在后。
89
+ */
90
+ strategies?: {
91
+ assets?: CachingStrategy;
92
+ pages?: CachingStrategy;
93
+ images?: CachingStrategy;
94
+ fonts?: CachingStrategy;
95
+ crossOrigin?: CachingStrategy;
96
+ };
97
+ /** 自定义 workbox runtimeCaching 规则(追加到默认规则之后) */
98
+ runtimeCaching?: RuntimeCachingRule[];
99
+ /** workbox.skipWaiting,默认 true */
100
+ skipWaiting?: boolean;
101
+ /** workbox.clientsClaim,默认 true */
102
+ clientsClaim?: boolean;
103
+ /** workbox.cleanupOutdatedCaches,默认 true */
104
+ cleanupOutdatedCaches?: boolean;
105
+ /**
106
+ * 是否使用 injectManifest 策略(自定义 SW 源文件)。
107
+ * - false(默认):使用 generateSW,vite-plugin-pwa 自动生成 sw.js
108
+ * - true:使用 injectManifest,需提供 swSrc 自定义 SW 源
109
+ */
110
+ injectManifest?: boolean;
111
+ /** injectManifest 模式下的自定义 SW 源文件路径 */
112
+ swSrc?: string;
113
+ }
114
+ interface UsePwaOptions {
115
+ immediate?: boolean;
116
+ onNeedRefresh?: () => void;
117
+ onOfflineReady?: () => void;
118
+ onRegistered?: (swUrl: string) => void;
119
+ onRegisterError?: (error: Error) => void;
120
+ onUpdateFound?: () => void;
121
+ }
122
+ interface PwaState {
123
+ isInstalled: boolean;
124
+ isUpdateAvailable: boolean;
125
+ isOfflineReady: boolean;
126
+ needRefresh: boolean;
127
+ registration: ServiceWorkerRegistration | null;
128
+ }
129
+ //#endregion
130
+ export { ManifestScreenshot as a, RuntimeCachingRule as c, ManifestOrientation as i, UsePwaOptions as l, DisplayMode as n, PwaOptions as o, ManifestIcon as r, PwaState as s, CachingStrategy as t, WebAppManifest as u };