vite-plugin-preloader 1.0.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.
package/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # 🚀 vite-plugin-preloader
2
+
3
+ 智能路由预加载 Vite 插件,一行配置,极致性能!
4
+
5
+ ## ✨ 特性
6
+
7
+ - 🎯 **一行配置** - 在 `vite.config.js` 中配置即可
8
+ - ⚡ **零运行时开销** - 构建时生成,运行时高效
9
+ - 🎨 **自动注入** - 无需手动调用,全自动工作
10
+ - 🛠️ **开发友好** - 提供调试工具和状态显示
11
+ - 📦 **类型安全** - 完整的 TypeScript 支持
12
+ - 🔥 **热更新支持** - 配置变更时自动更新
13
+
14
+ ## 📦 安装
15
+
16
+ ```bash
17
+ npm install vite-plugin-preloader
18
+ # 或
19
+ yarn add vite-plugin-preloader
20
+ # 或
21
+ pnpm add vite-plugin-preloader
22
+ # 或
23
+ bun add vite-plugin-preloader
24
+ ```
25
+
26
+ ## 🚀 使用
27
+
28
+ ### 基础配置
29
+
30
+ ```js
31
+ // vite.config.js
32
+ import { defineConfig } from 'vite'
33
+ import vue from '@vitejs/plugin-vue'
34
+ import preloader from 'vite-plugin-preloader'
35
+
36
+ export default defineConfig({
37
+ plugins: [
38
+ vue(),
39
+ preloader({
40
+ routes: [
41
+ {
42
+ path: '/calendar',
43
+ reason: '日历组件包含大型库',
44
+ priority: 1
45
+ },
46
+ {
47
+ path: '/charts',
48
+ component: '@/views/charts/index.vue',
49
+ reason: '图表页面较重',
50
+ priority: 1
51
+ }
52
+ ]
53
+ })
54
+ ]
55
+ })
56
+ ```
57
+
58
+ 就这样!无需其他任何代码,插件会自动:
59
+ - 🎯 生成预加载逻辑
60
+ - 📦 注入到应用中
61
+ - 🎨 显示加载状态
62
+ - 🛠️ 提供调试工具
63
+
64
+ ### 完整配置示例
65
+
66
+ ```js
67
+ preloader({
68
+ routes: [
69
+ // 🔥 高优先级 - 核心页面
70
+ {
71
+ path: '/dashboard',
72
+ component: '@/views/dashboard/index.vue',
73
+ reason: '主仪表盘,用户访问频率最高',
74
+ priority: 1
75
+ },
76
+
77
+ // ⚡ 中优先级 - 常用页面
78
+ {
79
+ path: '/calendar',
80
+ reason: '日历组件包含 @fullcalendar 全家桶(~500KB)',
81
+ priority: 2
82
+ },
83
+
84
+ // 📊 低优先级 - 重型工具页面
85
+ {
86
+ path: '/reports',
87
+ component: '@/views/reports/index.vue',
88
+ reason: '报表页面包含 echarts + d3 图表库(~800KB)',
89
+ priority: 3
90
+ }
91
+ ],
92
+ delay: 2000, // 延迟时间
93
+ showStatus: true, // 显示状态
94
+ statusPosition: 'bottom-right', // 状态位置
95
+ debug: true // 开发调试
96
+ })
97
+ ```
98
+
99
+ ## 🎯 配置选项
100
+
101
+ | 选项 | 类型 | 默认值 | 说明 |
102
+ |------|------|--------|------|
103
+ | `routes` | `PreloadRoute[]` | `[]` | 预加载路由配置 |
104
+ | `delay` | `number` | `2000` | 延迟时间(ms) |
105
+ | `showStatus` | `boolean` | `true` | 显示状态 |
106
+ | `statusPosition` | `string` | `'bottom-right'` | 状态位置 |
107
+ | `debug` | `boolean` | `false` | 开发调试 |
108
+
109
+ ### PreloadRoute 配置
110
+
111
+ | 字段 | 类型 | 必填 | 说明 |
112
+ |------|------|------|------|
113
+ | `path` | `string` | ✅ | 路由路径 |
114
+ | `component` | `string` | ❌ | 组件路径(不填自动推断) |
115
+ | `reason` | `string` | ❌ | 预加载原因说明 |
116
+ | `priority` | `number` | ❌ | 优先级 1-5(数字越小越先加载) |
117
+
118
+ ## 🛠️ 开发调试
119
+
120
+ 开发环境下,浏览器控制台可用:
121
+
122
+ ```js
123
+ // 查看预加载统计
124
+ window.preloaderDebug.stats()
125
+
126
+ // 检查指定路由状态
127
+ window.preloaderDebug.check('/calendar')
128
+
129
+ // 重新开始预加载
130
+ window.preloaderDebug.restart()
131
+
132
+ // 显示帮助信息
133
+ window.preloaderDebug.help()
134
+ ```
135
+
136
+ ## 📊 控制台输出示例
137
+
138
+ ```
139
+ 🚀 预加载插件已启用,配置了 3 个路由
140
+ 🚀 [预加载] 开始预加载 3 个页面
141
+ ✅ [预加载] /calendar (234ms) - 日历组件包含 @fullcalendar 全家桶
142
+ ✅ [预加载] /charts (456ms) - 图表页面包含 echarts、@antv/x6
143
+ ✅ [预加载] /reports (678ms) - 报表页面包含 echarts + d3 图表库
144
+ 🎉 [预加载] 完成! 耗时 1234ms
145
+ ```
146
+
147
+ ## 🎨 状态显示
148
+
149
+ 插件会在页面右下角显示预加载状态:
150
+
151
+ ```
152
+ 🔄 正在优化页面... 2/3
153
+ ```
154
+
155
+ 状态位置可通过 `statusPosition` 配置:
156
+ - `'top-left'` - 左上角
157
+ - `'top-right'` - 右上角
158
+ - `'bottom-left'` - 左下角
159
+ - `'bottom-right'` - 右下角(默认)
160
+
161
+ ## 🔧 最佳实践
162
+
163
+ ### 1. 优先级设置建议
164
+
165
+ ```js
166
+ // 优先级 1: 核心业务页面(高频访问 + 大体积)
167
+ { path: '/dashboard', priority: 1 }
168
+
169
+ // 优先级 2: 常用功能页面(中频访问 + 中体积)
170
+ { path: '/calendar', priority: 2 }
171
+
172
+ // 优先级 3: 专业工具页面(低频访问 + 大体积)
173
+ { path: '/reports', priority: 3 }
174
+ ```
175
+
176
+ ### 2. 组件路径自动推断
177
+
178
+ 如果不指定 `component`,插件会自动推断:
179
+
180
+ ```js
181
+ { path: '/user-profile' }
182
+ // 自动推断为: @/views/user-profile/index.vue
183
+
184
+ { path: '/admin/users' }
185
+ // 自动推断为: @/views/admin-users/index.vue
186
+ ```
187
+
188
+ ### 3. 预加载原因说明
189
+
190
+ 建议在 `reason` 中说明预加载的具体原因:
191
+
192
+ ```js
193
+ {
194
+ path: '/calendar',
195
+ reason: '日历组件包含 @fullcalendar 全家桶(~500KB)',
196
+ priority: 2
197
+ }
198
+ ```
199
+
200
+ ## 🚀 工作原理
201
+
202
+ 1. **构建时分析** - 插件在构建时解析配置
203
+ 2. **代码生成** - 自动生成预加载逻辑代码
204
+ 3. **虚拟模块** - 创建 `virtual:preloader` 模块
205
+ 4. **自动注入** - 将预加载代码注入到应用中
206
+ 5. **智能调度** - 按优先级串行预加载,避免阻塞
207
+
208
+ ## 🤝 贡献
209
+
210
+ 欢迎提交 Issue 和 Pull Request!
211
+
212
+ ## 📄 License
213
+
214
+ MIT © ChenYu
215
+
216
+ ---
217
+
218
+ **🎯 一行配置,极致性能!让你的 Vue 应用飞起来!** 🚀
@@ -0,0 +1,35 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface PreloadRoute {
4
+ /** 路由路径 */
5
+ path: string;
6
+ /** 组件导入路径,不填则自动推断 */
7
+ component?: string;
8
+ /** 预加载原因说明 */
9
+ reason?: string;
10
+ /** 优先级 1-5,数字越小优先级越高 */
11
+ priority?: number;
12
+ }
13
+ interface PreloaderOptions {
14
+ /** 预加载路由配置 */
15
+ routes: PreloadRoute[];
16
+ /** 延迟时间(毫秒),默认 2000 */
17
+ delay?: number;
18
+ /** 是否显示预加载状态,默认 true */
19
+ showStatus?: boolean;
20
+ /** 状态显示位置,默认 'bottom-right' */
21
+ statusPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
22
+ /** 是否启用开发时调试,默认 false */
23
+ debug?: boolean;
24
+ /** 自动检测大文件并预加载(未来功能) */
25
+ autoDetect?: {
26
+ /** 最小文件大小阈值 */
27
+ minSize?: string;
28
+ /** 排除的路径模式 */
29
+ exclude?: string[];
30
+ };
31
+ }
32
+
33
+ declare function preloaderPlugin(options: PreloaderOptions): Plugin;
34
+
35
+ export { type PreloadRoute, type PreloaderOptions, preloaderPlugin as default };
@@ -0,0 +1,35 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface PreloadRoute {
4
+ /** 路由路径 */
5
+ path: string;
6
+ /** 组件导入路径,不填则自动推断 */
7
+ component?: string;
8
+ /** 预加载原因说明 */
9
+ reason?: string;
10
+ /** 优先级 1-5,数字越小优先级越高 */
11
+ priority?: number;
12
+ }
13
+ interface PreloaderOptions {
14
+ /** 预加载路由配置 */
15
+ routes: PreloadRoute[];
16
+ /** 延迟时间(毫秒),默认 2000 */
17
+ delay?: number;
18
+ /** 是否显示预加载状态,默认 true */
19
+ showStatus?: boolean;
20
+ /** 状态显示位置,默认 'bottom-right' */
21
+ statusPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
22
+ /** 是否启用开发时调试,默认 false */
23
+ debug?: boolean;
24
+ /** 自动检测大文件并预加载(未来功能) */
25
+ autoDetect?: {
26
+ /** 最小文件大小阈值 */
27
+ minSize?: string;
28
+ /** 排除的路径模式 */
29
+ exclude?: string[];
30
+ };
31
+ }
32
+
33
+ declare function preloaderPlugin(options: PreloaderOptions): Plugin;
34
+
35
+ export { type PreloadRoute, type PreloaderOptions, preloaderPlugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,282 @@
1
+ // vite-plugin-preloader - 智能路由预加载插件
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ default: () => preloaderPlugin
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/runtime.ts
29
+ var runtimeTemplate = `// \u{1F680} Auto-generated by vite-plugin-preloader
30
+ import { ref, defineComponent, h, onMounted } from 'vue'
31
+
32
+ interface PreloadRoute {
33
+ path: string
34
+ component: () => Promise<any>
35
+ reason: string
36
+ priority: number
37
+ }
38
+
39
+ interface PreloadStats {
40
+ total: number
41
+ completed: number
42
+ failed: number
43
+ startTime: number
44
+ endTime: number
45
+ }
46
+
47
+ // \u{1F3AF} \u9884\u52A0\u8F7D\u914D\u7F6E\uFF08\u6784\u5EFA\u65F6\u6CE8\u5165\uFF09
48
+ const PRELOAD_ROUTES = __PRELOAD_ROUTES__
49
+ const PRELOAD_OPTIONS = __PRELOAD_OPTIONS__
50
+
51
+ class PreloaderManager {
52
+ private preloadedRoutes = new Set()
53
+ private isPreloading = ref(false)
54
+ private stats = ref({
55
+ total: 0, completed: 0, failed: 0, startTime: 0, endTime: 0
56
+ })
57
+
58
+ async start() {
59
+ if (this.isPreloading.value) return
60
+
61
+ this.isPreloading.value = true
62
+ this.stats.value = {
63
+ total: PRELOAD_ROUTES.length,
64
+ completed: 0, failed: 0,
65
+ startTime: Date.now(), endTime: 0
66
+ }
67
+
68
+ console.log(\`\u{1F680} [\u9884\u52A0\u8F7D] \u5F00\u59CB\u9884\u52A0\u8F7D \${PRELOAD_ROUTES.length} \u4E2A\u9875\u9762\`)
69
+
70
+ const sortedRoutes = [...PRELOAD_ROUTES].sort((a, b) => a.priority - b.priority)
71
+
72
+ for (const route of sortedRoutes) {
73
+ await this.preloadSingle(route)
74
+ await this.sleep(100)
75
+ }
76
+
77
+ this.stats.value.endTime = Date.now()
78
+ this.isPreloading.value = false
79
+
80
+ console.log(\`\u{1F389} [\u9884\u52A0\u8F7D] \u5B8C\u6210! \u8017\u65F6 \${this.stats.value.endTime - this.stats.value.startTime}ms\`)
81
+ }
82
+
83
+ async preloadSingle(route) {
84
+ if (this.preloadedRoutes.has(route.path)) return
85
+
86
+ try {
87
+ const startTime = Date.now()
88
+ await route.component()
89
+ const loadTime = Date.now() - startTime
90
+
91
+ this.preloadedRoutes.add(route.path)
92
+ this.stats.value.completed++
93
+ console.log(\`\u2705 [\u9884\u52A0\u8F7D] \${route.path} (\${loadTime}ms) - \${route.reason}\`)
94
+ } catch (error) {
95
+ this.stats.value.failed++
96
+ console.error(\`\u274C [\u9884\u52A0\u8F7D] \${route.path} \u5931\u8D25:\`, error)
97
+ }
98
+ }
99
+
100
+ sleep(ms) {
101
+ return new Promise(resolve => setTimeout(resolve, ms))
102
+ }
103
+
104
+ isPreloaded(path) {
105
+ return this.preloadedRoutes.has(path)
106
+ }
107
+
108
+ getStats() {
109
+ return {
110
+ ...this.stats.value,
111
+ preloadedPaths: Array.from(this.preloadedRoutes),
112
+ isPreloading: this.isPreloading.value
113
+ }
114
+ }
115
+
116
+ createStatusComponent() {
117
+ const self = this
118
+ return defineComponent({
119
+ name: 'PreloadStatus',
120
+ setup() {
121
+ onMounted(() => {
122
+ if (!document.getElementById('preloader-styles')) {
123
+ const style = document.createElement('style')
124
+ style.id = 'preloader-styles'
125
+ const position = PRELOAD_OPTIONS.statusPosition.replace('-', ': 20px; ') + ': 20px;'
126
+ style.textContent = \`
127
+ .preloader-status {
128
+ position: fixed; \${position}
129
+ background: rgba(0,0,0,0.8); color: white; padding: 8px 16px;
130
+ border-radius: 6px; font-size: 12px; z-index: 9999;
131
+ pointer-events: none; font-family: system-ui;
132
+ }
133
+ \`
134
+ document.head.appendChild(style)
135
+ }
136
+ })
137
+
138
+ return () => {
139
+ if (!self.isPreloading.value || !import.meta.env.DEV || !PRELOAD_OPTIONS.showStatus) {
140
+ return null
141
+ }
142
+ return h('div', { class: 'preloader-status' }, [
143
+ '\u{1F504} \u6B63\u5728\u4F18\u5316\u9875\u9762... ',
144
+ \`\${self.stats.value.completed}/\${self.stats.value.total}\`
145
+ ])
146
+ }
147
+ }
148
+ })
149
+ }
150
+ }
151
+
152
+ // \u{1F680} \u5168\u5C40\u5B9E\u4F8B
153
+ const preloader = new PreloaderManager()
154
+
155
+ // \u{1F6E0}\uFE0F \u5F00\u53D1\u73AF\u5883\u8C03\u8BD5\u5DE5\u5177
156
+ if (import.meta.env.DEV && PRELOAD_OPTIONS.debug) {
157
+ window.preloaderDebug = {
158
+ stats: () => preloader.getStats(),
159
+ restart: () => preloader.start(),
160
+ check: (path) => preloader.isPreloaded(path),
161
+ help: () => console.log('\u{1F6E0}\uFE0F \u9884\u52A0\u8F7D\u8C03\u8BD5: stats() | restart() | check(path)')
162
+ }
163
+ console.log('\u{1F6E0}\uFE0F \u9884\u52A0\u8F7D\u8C03\u8BD5\u5DE5\u5177: window.preloaderDebug')
164
+ }
165
+
166
+ // \u{1F3AF} \u5BFC\u51FA
167
+ export const usePreloader = () => ({
168
+ start: () => preloader.start(),
169
+ isPreloaded: (path) => preloader.isPreloaded(path),
170
+ StatusComponent: preloader.createStatusComponent()
171
+ })
172
+
173
+ // \u{1F680} \u81EA\u52A8\u542F\u52A8
174
+ if (typeof window !== 'undefined') {
175
+ setTimeout(() => preloader.start(), PRELOAD_OPTIONS.delay || 2000)
176
+ }`;
177
+
178
+ // src/generator.ts
179
+ var CodeGenerator = class {
180
+ constructor(options) {
181
+ this.options = options;
182
+ }
183
+ /**
184
+ * 生成运行时代码
185
+ */
186
+ generateRuntime() {
187
+ const routes = this.processRoutes();
188
+ const options = this.processOptions();
189
+ return runtimeTemplate.replace("__PRELOAD_ROUTES__", JSON.stringify(routes, null, 2)).replace("__PRELOAD_OPTIONS__", JSON.stringify(options, null, 2));
190
+ }
191
+ /**
192
+ * 处理路由配置
193
+ */
194
+ processRoutes() {
195
+ return this.options.routes.map((route) => {
196
+ const componentPath = route.component || this.inferComponentPath(route.path);
197
+ return {
198
+ path: route.path,
199
+ component: `() => import('${componentPath}')`,
200
+ reason: route.reason || "\u7528\u6237\u914D\u7F6E\u7684\u9884\u52A0\u8F7D\u9875\u9762",
201
+ priority: route.priority || 2
202
+ };
203
+ });
204
+ }
205
+ /**
206
+ * 处理选项配置
207
+ */
208
+ processOptions() {
209
+ return {
210
+ delay: this.options.delay || 2e3,
211
+ showStatus: this.options.showStatus !== false,
212
+ statusPosition: this.options.statusPosition || "bottom-right",
213
+ debug: this.options.debug || false
214
+ };
215
+ }
216
+ /**
217
+ * 推断组件路径
218
+ */
219
+ inferComponentPath(routePath) {
220
+ const cleanPath = routePath.replace(/^\//, "").replace(/\//g, "-");
221
+ return `@/views/${cleanPath}/index.vue`;
222
+ }
223
+ /**
224
+ * 生成HTML注入代码
225
+ */
226
+ generateHtmlInject() {
227
+ if (this.options.showStatus === false) return "";
228
+ return "<preloader-status></preloader-status>";
229
+ }
230
+ };
231
+
232
+ // src/index.ts
233
+ var VIRTUAL_MODULE_ID = "virtual:preloader";
234
+ var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
235
+ function preloaderPlugin(options) {
236
+ let generator;
237
+ return {
238
+ name: "vite-plugin-preloader",
239
+ // 🎯 设置插件执行顺序
240
+ enforce: "post",
241
+ configResolved() {
242
+ generator = new CodeGenerator(options);
243
+ console.log(`\u{1F680} \u9884\u52A0\u8F7D\u63D2\u4EF6\u5DF2\u542F\u7528\uFF0C\u914D\u7F6E\u4E86 ${options.routes.length} \u4E2A\u8DEF\u7531`);
244
+ },
245
+ resolveId(id) {
246
+ if (id === VIRTUAL_MODULE_ID) {
247
+ return RESOLVED_VIRTUAL_MODULE_ID;
248
+ }
249
+ return null;
250
+ },
251
+ load(id) {
252
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
253
+ return generator.generateRuntime();
254
+ }
255
+ return null;
256
+ },
257
+ // 🎨 HTML 转换(修复类型错误)
258
+ transformIndexHtml(html) {
259
+ const inject = generator.generateHtmlInject();
260
+ if (inject) {
261
+ return html.replace(
262
+ '<div id="app">',
263
+ `<div id="app">
264
+ ${inject}`
265
+ );
266
+ }
267
+ return html;
268
+ },
269
+ // 🔥 HMR 支持
270
+ handleHotUpdate(ctx) {
271
+ if (ctx.file.includes("vite.config")) {
272
+ console.log("\u{1F504} \u9884\u52A0\u8F7D\u914D\u7F6E\u5DF2\u66F4\u65B0");
273
+ ctx.server.ws.send({
274
+ type: "full-reload"
275
+ });
276
+ return [];
277
+ }
278
+ return void 0;
279
+ }
280
+ };
281
+ }
282
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/runtime.ts","../src/generator.ts"],"sourcesContent":["// ============================================================================\r\n// 🚀 src/index.ts - 主插件文件\r\n// ============================================================================\r\n\r\nimport type { Plugin } from 'vite'\r\nimport type { PreloaderOptions } from './types'\r\nimport { CodeGenerator } from './generator'\r\n\r\nconst VIRTUAL_MODULE_ID = 'virtual:preloader'\r\nconst RESOLVED_VIRTUAL_MODULE_ID = '\\0' + VIRTUAL_MODULE_ID\r\n\r\nexport default function preloaderPlugin(options: PreloaderOptions): Plugin {\r\n let generator: CodeGenerator\r\n\r\n return {\r\n name: 'vite-plugin-preloader',\r\n \r\n // 🎯 设置插件执行顺序\r\n enforce: 'post',\r\n \r\n configResolved() {\r\n generator = new CodeGenerator(options)\r\n console.log(`🚀 预加载插件已启用,配置了 ${options.routes.length} 个路由`)\r\n },\r\n\r\n resolveId(id) {\r\n if (id === VIRTUAL_MODULE_ID) {\r\n return RESOLVED_VIRTUAL_MODULE_ID\r\n }\r\n return null // 🔧 修复:明确返回 null\r\n },\r\n\r\n load(id) {\r\n if (id === RESOLVED_VIRTUAL_MODULE_ID) {\r\n return generator.generateRuntime()\r\n }\r\n return null // 🔧 修复:明确返回 null\r\n },\r\n\r\n // 🎨 HTML 转换(修复类型错误)\r\n transformIndexHtml(html) {\r\n const inject = generator.generateHtmlInject()\r\n if (inject) {\r\n return html.replace(\r\n '<div id=\"app\">',\r\n `<div id=\"app\">\\n ${inject}`\r\n )\r\n }\r\n return html // 🔧 修复:确保总是返回 html\r\n },\r\n\r\n // 🔥 HMR 支持\r\n handleHotUpdate(ctx) {\r\n if (ctx.file.includes('vite.config')) {\r\n console.log('🔄 预加载配置已更新')\r\n ctx.server.ws.send({\r\n type: 'full-reload'\r\n })\r\n return []\r\n }\r\n return undefined // 🔧 修复:明确返回 undefined\r\n }\r\n }\r\n}\r\n\r\n// 命名导出,支持多种导入方式\r\nexport { type PreloaderOptions, type PreloadRoute } from './types'","// ============================================================================\r\n// ⚡ src/runtime.ts - 运行时代码模板(字符串)\r\n// ============================================================================\r\n\r\nexport const runtimeTemplate = `// 🚀 Auto-generated by vite-plugin-preloader\r\nimport { ref, defineComponent, h, onMounted } from 'vue'\r\n\r\ninterface PreloadRoute {\r\n path: string\r\n component: () => Promise<any>\r\n reason: string\r\n priority: number\r\n}\r\n\r\ninterface PreloadStats {\r\n total: number\r\n completed: number\r\n failed: number\r\n startTime: number\r\n endTime: number\r\n}\r\n\r\n// 🎯 预加载配置(构建时注入)\r\nconst PRELOAD_ROUTES = __PRELOAD_ROUTES__\r\nconst PRELOAD_OPTIONS = __PRELOAD_OPTIONS__\r\n\r\nclass PreloaderManager {\r\n private preloadedRoutes = new Set()\r\n private isPreloading = ref(false)\r\n private stats = ref({\r\n total: 0, completed: 0, failed: 0, startTime: 0, endTime: 0\r\n })\r\n\r\n async start() {\r\n if (this.isPreloading.value) return\r\n\r\n this.isPreloading.value = true\r\n this.stats.value = {\r\n total: PRELOAD_ROUTES.length,\r\n completed: 0, failed: 0,\r\n startTime: Date.now(), endTime: 0\r\n }\r\n\r\n console.log(\\`🚀 [预加载] 开始预加载 \\${PRELOAD_ROUTES.length} 个页面\\`)\r\n\r\n const sortedRoutes = [...PRELOAD_ROUTES].sort((a, b) => a.priority - b.priority)\r\n \r\n for (const route of sortedRoutes) {\r\n await this.preloadSingle(route)\r\n await this.sleep(100)\r\n }\r\n\r\n this.stats.value.endTime = Date.now()\r\n this.isPreloading.value = false\r\n \r\n console.log(\\`🎉 [预加载] 完成! 耗时 \\${this.stats.value.endTime - this.stats.value.startTime}ms\\`)\r\n }\r\n\r\n async preloadSingle(route) {\r\n if (this.preloadedRoutes.has(route.path)) return\r\n\r\n try {\r\n const startTime = Date.now()\r\n await route.component()\r\n const loadTime = Date.now() - startTime\r\n \r\n this.preloadedRoutes.add(route.path)\r\n this.stats.value.completed++\r\n console.log(\\`✅ [预加载] \\${route.path} (\\${loadTime}ms) - \\${route.reason}\\`)\r\n } catch (error) {\r\n this.stats.value.failed++\r\n console.error(\\`❌ [预加载] \\${route.path} 失败:\\`, error)\r\n }\r\n }\r\n\r\n sleep(ms) {\r\n return new Promise(resolve => setTimeout(resolve, ms))\r\n }\r\n\r\n isPreloaded(path) {\r\n return this.preloadedRoutes.has(path)\r\n }\r\n\r\n getStats() {\r\n return {\r\n ...this.stats.value,\r\n preloadedPaths: Array.from(this.preloadedRoutes),\r\n isPreloading: this.isPreloading.value\r\n }\r\n }\r\n\r\n createStatusComponent() {\r\n const self = this\r\n return defineComponent({\r\n name: 'PreloadStatus',\r\n setup() {\r\n onMounted(() => {\r\n if (!document.getElementById('preloader-styles')) {\r\n const style = document.createElement('style')\r\n style.id = 'preloader-styles'\r\n const position = PRELOAD_OPTIONS.statusPosition.replace('-', ': 20px; ') + ': 20px;'\r\n style.textContent = \\`\r\n .preloader-status {\r\n position: fixed; \\${position}\r\n background: rgba(0,0,0,0.8); color: white; padding: 8px 16px;\r\n border-radius: 6px; font-size: 12px; z-index: 9999;\r\n pointer-events: none; font-family: system-ui;\r\n }\r\n \\`\r\n document.head.appendChild(style)\r\n }\r\n })\r\n\r\n return () => {\r\n if (!self.isPreloading.value || !import.meta.env.DEV || !PRELOAD_OPTIONS.showStatus) {\r\n return null\r\n }\r\n return h('div', { class: 'preloader-status' }, [\r\n '🔄 正在优化页面... ',\r\n \\`\\${self.stats.value.completed}/\\${self.stats.value.total}\\`\r\n ])\r\n }\r\n }\r\n })\r\n }\r\n}\r\n\r\n// 🚀 全局实例\r\nconst preloader = new PreloaderManager()\r\n\r\n// 🛠️ 开发环境调试工具\r\nif (import.meta.env.DEV && PRELOAD_OPTIONS.debug) {\r\n window.preloaderDebug = {\r\n stats: () => preloader.getStats(),\r\n restart: () => preloader.start(),\r\n check: (path) => preloader.isPreloaded(path),\r\n help: () => console.log('🛠️ 预加载调试: stats() | restart() | check(path)')\r\n }\r\n console.log('🛠️ 预加载调试工具: window.preloaderDebug')\r\n}\r\n\r\n// 🎯 导出\r\nexport const usePreloader = () => ({\r\n start: () => preloader.start(),\r\n isPreloaded: (path) => preloader.isPreloaded(path),\r\n StatusComponent: preloader.createStatusComponent()\r\n})\r\n\r\n// 🚀 自动启动\r\nif (typeof window !== 'undefined') {\r\n setTimeout(() => preloader.start(), PRELOAD_OPTIONS.delay || 2000)\r\n}`","// ============================================================================\r\n// 🛠️ src/generator.ts - 代码生成器\r\n// ============================================================================\r\n\r\nimport type { PreloaderOptions, PreloadRoute } from './types'\r\nimport { runtimeTemplate } from './runtime'\r\n\r\nexport class CodeGenerator {\r\n constructor(private options: PreloaderOptions) {}\r\n\r\n /**\r\n * 生成运行时代码\r\n */\r\n generateRuntime(): string {\r\n const routes = this.processRoutes()\r\n const options = this.processOptions()\r\n\r\n return runtimeTemplate\r\n .replace('__PRELOAD_ROUTES__', JSON.stringify(routes, null, 2))\r\n .replace('__PRELOAD_OPTIONS__', JSON.stringify(options, null, 2))\r\n }\r\n\r\n /**\r\n * 处理路由配置\r\n */\r\n private processRoutes(): any[] {\r\n return this.options.routes.map(route => {\r\n const componentPath = route.component || this.inferComponentPath(route.path)\r\n return {\r\n path: route.path,\r\n component: `() => import('${componentPath}')`,\r\n reason: route.reason || '用户配置的预加载页面',\r\n priority: route.priority || 2\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * 处理选项配置\r\n */\r\n private processOptions() {\r\n return {\r\n delay: this.options.delay || 2000,\r\n showStatus: this.options.showStatus !== false,\r\n statusPosition: this.options.statusPosition || 'bottom-right',\r\n debug: this.options.debug || false\r\n }\r\n }\r\n\r\n /**\r\n * 推断组件路径\r\n */\r\n private inferComponentPath(routePath: string): string {\r\n const cleanPath = routePath.replace(/^\\//, '').replace(/\\//g, '-')\r\n return `@/views/${cleanPath}/index.vue`\r\n }\r\n\r\n /**\r\n * 生成HTML注入代码\r\n */\r\n generateHtmlInject(): string {\r\n if (this.options.showStatus === false) return ''\r\n \r\n return '<preloader-status></preloader-status>'\r\n }\r\n}"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,260 @@
1
+ // vite-plugin-preloader - 智能路由预加载插件
2
+
3
+ // src/runtime.ts
4
+ var runtimeTemplate = `// \u{1F680} Auto-generated by vite-plugin-preloader
5
+ import { ref, defineComponent, h, onMounted } from 'vue'
6
+
7
+ interface PreloadRoute {
8
+ path: string
9
+ component: () => Promise<any>
10
+ reason: string
11
+ priority: number
12
+ }
13
+
14
+ interface PreloadStats {
15
+ total: number
16
+ completed: number
17
+ failed: number
18
+ startTime: number
19
+ endTime: number
20
+ }
21
+
22
+ // \u{1F3AF} \u9884\u52A0\u8F7D\u914D\u7F6E\uFF08\u6784\u5EFA\u65F6\u6CE8\u5165\uFF09
23
+ const PRELOAD_ROUTES = __PRELOAD_ROUTES__
24
+ const PRELOAD_OPTIONS = __PRELOAD_OPTIONS__
25
+
26
+ class PreloaderManager {
27
+ private preloadedRoutes = new Set()
28
+ private isPreloading = ref(false)
29
+ private stats = ref({
30
+ total: 0, completed: 0, failed: 0, startTime: 0, endTime: 0
31
+ })
32
+
33
+ async start() {
34
+ if (this.isPreloading.value) return
35
+
36
+ this.isPreloading.value = true
37
+ this.stats.value = {
38
+ total: PRELOAD_ROUTES.length,
39
+ completed: 0, failed: 0,
40
+ startTime: Date.now(), endTime: 0
41
+ }
42
+
43
+ console.log(\`\u{1F680} [\u9884\u52A0\u8F7D] \u5F00\u59CB\u9884\u52A0\u8F7D \${PRELOAD_ROUTES.length} \u4E2A\u9875\u9762\`)
44
+
45
+ const sortedRoutes = [...PRELOAD_ROUTES].sort((a, b) => a.priority - b.priority)
46
+
47
+ for (const route of sortedRoutes) {
48
+ await this.preloadSingle(route)
49
+ await this.sleep(100)
50
+ }
51
+
52
+ this.stats.value.endTime = Date.now()
53
+ this.isPreloading.value = false
54
+
55
+ console.log(\`\u{1F389} [\u9884\u52A0\u8F7D] \u5B8C\u6210! \u8017\u65F6 \${this.stats.value.endTime - this.stats.value.startTime}ms\`)
56
+ }
57
+
58
+ async preloadSingle(route) {
59
+ if (this.preloadedRoutes.has(route.path)) return
60
+
61
+ try {
62
+ const startTime = Date.now()
63
+ await route.component()
64
+ const loadTime = Date.now() - startTime
65
+
66
+ this.preloadedRoutes.add(route.path)
67
+ this.stats.value.completed++
68
+ console.log(\`\u2705 [\u9884\u52A0\u8F7D] \${route.path} (\${loadTime}ms) - \${route.reason}\`)
69
+ } catch (error) {
70
+ this.stats.value.failed++
71
+ console.error(\`\u274C [\u9884\u52A0\u8F7D] \${route.path} \u5931\u8D25:\`, error)
72
+ }
73
+ }
74
+
75
+ sleep(ms) {
76
+ return new Promise(resolve => setTimeout(resolve, ms))
77
+ }
78
+
79
+ isPreloaded(path) {
80
+ return this.preloadedRoutes.has(path)
81
+ }
82
+
83
+ getStats() {
84
+ return {
85
+ ...this.stats.value,
86
+ preloadedPaths: Array.from(this.preloadedRoutes),
87
+ isPreloading: this.isPreloading.value
88
+ }
89
+ }
90
+
91
+ createStatusComponent() {
92
+ const self = this
93
+ return defineComponent({
94
+ name: 'PreloadStatus',
95
+ setup() {
96
+ onMounted(() => {
97
+ if (!document.getElementById('preloader-styles')) {
98
+ const style = document.createElement('style')
99
+ style.id = 'preloader-styles'
100
+ const position = PRELOAD_OPTIONS.statusPosition.replace('-', ': 20px; ') + ': 20px;'
101
+ style.textContent = \`
102
+ .preloader-status {
103
+ position: fixed; \${position}
104
+ background: rgba(0,0,0,0.8); color: white; padding: 8px 16px;
105
+ border-radius: 6px; font-size: 12px; z-index: 9999;
106
+ pointer-events: none; font-family: system-ui;
107
+ }
108
+ \`
109
+ document.head.appendChild(style)
110
+ }
111
+ })
112
+
113
+ return () => {
114
+ if (!self.isPreloading.value || !import.meta.env.DEV || !PRELOAD_OPTIONS.showStatus) {
115
+ return null
116
+ }
117
+ return h('div', { class: 'preloader-status' }, [
118
+ '\u{1F504} \u6B63\u5728\u4F18\u5316\u9875\u9762... ',
119
+ \`\${self.stats.value.completed}/\${self.stats.value.total}\`
120
+ ])
121
+ }
122
+ }
123
+ })
124
+ }
125
+ }
126
+
127
+ // \u{1F680} \u5168\u5C40\u5B9E\u4F8B
128
+ const preloader = new PreloaderManager()
129
+
130
+ // \u{1F6E0}\uFE0F \u5F00\u53D1\u73AF\u5883\u8C03\u8BD5\u5DE5\u5177
131
+ if (import.meta.env.DEV && PRELOAD_OPTIONS.debug) {
132
+ window.preloaderDebug = {
133
+ stats: () => preloader.getStats(),
134
+ restart: () => preloader.start(),
135
+ check: (path) => preloader.isPreloaded(path),
136
+ help: () => console.log('\u{1F6E0}\uFE0F \u9884\u52A0\u8F7D\u8C03\u8BD5: stats() | restart() | check(path)')
137
+ }
138
+ console.log('\u{1F6E0}\uFE0F \u9884\u52A0\u8F7D\u8C03\u8BD5\u5DE5\u5177: window.preloaderDebug')
139
+ }
140
+
141
+ // \u{1F3AF} \u5BFC\u51FA
142
+ export const usePreloader = () => ({
143
+ start: () => preloader.start(),
144
+ isPreloaded: (path) => preloader.isPreloaded(path),
145
+ StatusComponent: preloader.createStatusComponent()
146
+ })
147
+
148
+ // \u{1F680} \u81EA\u52A8\u542F\u52A8
149
+ if (typeof window !== 'undefined') {
150
+ setTimeout(() => preloader.start(), PRELOAD_OPTIONS.delay || 2000)
151
+ }`;
152
+
153
+ // src/generator.ts
154
+ var CodeGenerator = class {
155
+ constructor(options) {
156
+ this.options = options;
157
+ }
158
+ /**
159
+ * 生成运行时代码
160
+ */
161
+ generateRuntime() {
162
+ const routes = this.processRoutes();
163
+ const options = this.processOptions();
164
+ return runtimeTemplate.replace("__PRELOAD_ROUTES__", JSON.stringify(routes, null, 2)).replace("__PRELOAD_OPTIONS__", JSON.stringify(options, null, 2));
165
+ }
166
+ /**
167
+ * 处理路由配置
168
+ */
169
+ processRoutes() {
170
+ return this.options.routes.map((route) => {
171
+ const componentPath = route.component || this.inferComponentPath(route.path);
172
+ return {
173
+ path: route.path,
174
+ component: `() => import('${componentPath}')`,
175
+ reason: route.reason || "\u7528\u6237\u914D\u7F6E\u7684\u9884\u52A0\u8F7D\u9875\u9762",
176
+ priority: route.priority || 2
177
+ };
178
+ });
179
+ }
180
+ /**
181
+ * 处理选项配置
182
+ */
183
+ processOptions() {
184
+ return {
185
+ delay: this.options.delay || 2e3,
186
+ showStatus: this.options.showStatus !== false,
187
+ statusPosition: this.options.statusPosition || "bottom-right",
188
+ debug: this.options.debug || false
189
+ };
190
+ }
191
+ /**
192
+ * 推断组件路径
193
+ */
194
+ inferComponentPath(routePath) {
195
+ const cleanPath = routePath.replace(/^\//, "").replace(/\//g, "-");
196
+ return `@/views/${cleanPath}/index.vue`;
197
+ }
198
+ /**
199
+ * 生成HTML注入代码
200
+ */
201
+ generateHtmlInject() {
202
+ if (this.options.showStatus === false) return "";
203
+ return "<preloader-status></preloader-status>";
204
+ }
205
+ };
206
+
207
+ // src/index.ts
208
+ var VIRTUAL_MODULE_ID = "virtual:preloader";
209
+ var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
210
+ function preloaderPlugin(options) {
211
+ let generator;
212
+ return {
213
+ name: "vite-plugin-preloader",
214
+ // 🎯 设置插件执行顺序
215
+ enforce: "post",
216
+ configResolved() {
217
+ generator = new CodeGenerator(options);
218
+ console.log(`\u{1F680} \u9884\u52A0\u8F7D\u63D2\u4EF6\u5DF2\u542F\u7528\uFF0C\u914D\u7F6E\u4E86 ${options.routes.length} \u4E2A\u8DEF\u7531`);
219
+ },
220
+ resolveId(id) {
221
+ if (id === VIRTUAL_MODULE_ID) {
222
+ return RESOLVED_VIRTUAL_MODULE_ID;
223
+ }
224
+ return null;
225
+ },
226
+ load(id) {
227
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
228
+ return generator.generateRuntime();
229
+ }
230
+ return null;
231
+ },
232
+ // 🎨 HTML 转换(修复类型错误)
233
+ transformIndexHtml(html) {
234
+ const inject = generator.generateHtmlInject();
235
+ if (inject) {
236
+ return html.replace(
237
+ '<div id="app">',
238
+ `<div id="app">
239
+ ${inject}`
240
+ );
241
+ }
242
+ return html;
243
+ },
244
+ // 🔥 HMR 支持
245
+ handleHotUpdate(ctx) {
246
+ if (ctx.file.includes("vite.config")) {
247
+ console.log("\u{1F504} \u9884\u52A0\u8F7D\u914D\u7F6E\u5DF2\u66F4\u65B0");
248
+ ctx.server.ws.send({
249
+ type: "full-reload"
250
+ });
251
+ return [];
252
+ }
253
+ return void 0;
254
+ }
255
+ };
256
+ }
257
+ export {
258
+ preloaderPlugin as default
259
+ };
260
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime.ts","../src/generator.ts","../src/index.ts"],"sourcesContent":["// ============================================================================\r\n// ⚡ src/runtime.ts - 运行时代码模板(字符串)\r\n// ============================================================================\r\n\r\nexport const runtimeTemplate = `// 🚀 Auto-generated by vite-plugin-preloader\r\nimport { ref, defineComponent, h, onMounted } from 'vue'\r\n\r\ninterface PreloadRoute {\r\n path: string\r\n component: () => Promise<any>\r\n reason: string\r\n priority: number\r\n}\r\n\r\ninterface PreloadStats {\r\n total: number\r\n completed: number\r\n failed: number\r\n startTime: number\r\n endTime: number\r\n}\r\n\r\n// 🎯 预加载配置(构建时注入)\r\nconst PRELOAD_ROUTES = __PRELOAD_ROUTES__\r\nconst PRELOAD_OPTIONS = __PRELOAD_OPTIONS__\r\n\r\nclass PreloaderManager {\r\n private preloadedRoutes = new Set()\r\n private isPreloading = ref(false)\r\n private stats = ref({\r\n total: 0, completed: 0, failed: 0, startTime: 0, endTime: 0\r\n })\r\n\r\n async start() {\r\n if (this.isPreloading.value) return\r\n\r\n this.isPreloading.value = true\r\n this.stats.value = {\r\n total: PRELOAD_ROUTES.length,\r\n completed: 0, failed: 0,\r\n startTime: Date.now(), endTime: 0\r\n }\r\n\r\n console.log(\\`🚀 [预加载] 开始预加载 \\${PRELOAD_ROUTES.length} 个页面\\`)\r\n\r\n const sortedRoutes = [...PRELOAD_ROUTES].sort((a, b) => a.priority - b.priority)\r\n \r\n for (const route of sortedRoutes) {\r\n await this.preloadSingle(route)\r\n await this.sleep(100)\r\n }\r\n\r\n this.stats.value.endTime = Date.now()\r\n this.isPreloading.value = false\r\n \r\n console.log(\\`🎉 [预加载] 完成! 耗时 \\${this.stats.value.endTime - this.stats.value.startTime}ms\\`)\r\n }\r\n\r\n async preloadSingle(route) {\r\n if (this.preloadedRoutes.has(route.path)) return\r\n\r\n try {\r\n const startTime = Date.now()\r\n await route.component()\r\n const loadTime = Date.now() - startTime\r\n \r\n this.preloadedRoutes.add(route.path)\r\n this.stats.value.completed++\r\n console.log(\\`✅ [预加载] \\${route.path} (\\${loadTime}ms) - \\${route.reason}\\`)\r\n } catch (error) {\r\n this.stats.value.failed++\r\n console.error(\\`❌ [预加载] \\${route.path} 失败:\\`, error)\r\n }\r\n }\r\n\r\n sleep(ms) {\r\n return new Promise(resolve => setTimeout(resolve, ms))\r\n }\r\n\r\n isPreloaded(path) {\r\n return this.preloadedRoutes.has(path)\r\n }\r\n\r\n getStats() {\r\n return {\r\n ...this.stats.value,\r\n preloadedPaths: Array.from(this.preloadedRoutes),\r\n isPreloading: this.isPreloading.value\r\n }\r\n }\r\n\r\n createStatusComponent() {\r\n const self = this\r\n return defineComponent({\r\n name: 'PreloadStatus',\r\n setup() {\r\n onMounted(() => {\r\n if (!document.getElementById('preloader-styles')) {\r\n const style = document.createElement('style')\r\n style.id = 'preloader-styles'\r\n const position = PRELOAD_OPTIONS.statusPosition.replace('-', ': 20px; ') + ': 20px;'\r\n style.textContent = \\`\r\n .preloader-status {\r\n position: fixed; \\${position}\r\n background: rgba(0,0,0,0.8); color: white; padding: 8px 16px;\r\n border-radius: 6px; font-size: 12px; z-index: 9999;\r\n pointer-events: none; font-family: system-ui;\r\n }\r\n \\`\r\n document.head.appendChild(style)\r\n }\r\n })\r\n\r\n return () => {\r\n if (!self.isPreloading.value || !import.meta.env.DEV || !PRELOAD_OPTIONS.showStatus) {\r\n return null\r\n }\r\n return h('div', { class: 'preloader-status' }, [\r\n '🔄 正在优化页面... ',\r\n \\`\\${self.stats.value.completed}/\\${self.stats.value.total}\\`\r\n ])\r\n }\r\n }\r\n })\r\n }\r\n}\r\n\r\n// 🚀 全局实例\r\nconst preloader = new PreloaderManager()\r\n\r\n// 🛠️ 开发环境调试工具\r\nif (import.meta.env.DEV && PRELOAD_OPTIONS.debug) {\r\n window.preloaderDebug = {\r\n stats: () => preloader.getStats(),\r\n restart: () => preloader.start(),\r\n check: (path) => preloader.isPreloaded(path),\r\n help: () => console.log('🛠️ 预加载调试: stats() | restart() | check(path)')\r\n }\r\n console.log('🛠️ 预加载调试工具: window.preloaderDebug')\r\n}\r\n\r\n// 🎯 导出\r\nexport const usePreloader = () => ({\r\n start: () => preloader.start(),\r\n isPreloaded: (path) => preloader.isPreloaded(path),\r\n StatusComponent: preloader.createStatusComponent()\r\n})\r\n\r\n// 🚀 自动启动\r\nif (typeof window !== 'undefined') {\r\n setTimeout(() => preloader.start(), PRELOAD_OPTIONS.delay || 2000)\r\n}`","// ============================================================================\r\n// 🛠️ src/generator.ts - 代码生成器\r\n// ============================================================================\r\n\r\nimport type { PreloaderOptions, PreloadRoute } from './types'\r\nimport { runtimeTemplate } from './runtime'\r\n\r\nexport class CodeGenerator {\r\n constructor(private options: PreloaderOptions) {}\r\n\r\n /**\r\n * 生成运行时代码\r\n */\r\n generateRuntime(): string {\r\n const routes = this.processRoutes()\r\n const options = this.processOptions()\r\n\r\n return runtimeTemplate\r\n .replace('__PRELOAD_ROUTES__', JSON.stringify(routes, null, 2))\r\n .replace('__PRELOAD_OPTIONS__', JSON.stringify(options, null, 2))\r\n }\r\n\r\n /**\r\n * 处理路由配置\r\n */\r\n private processRoutes(): any[] {\r\n return this.options.routes.map(route => {\r\n const componentPath = route.component || this.inferComponentPath(route.path)\r\n return {\r\n path: route.path,\r\n component: `() => import('${componentPath}')`,\r\n reason: route.reason || '用户配置的预加载页面',\r\n priority: route.priority || 2\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * 处理选项配置\r\n */\r\n private processOptions() {\r\n return {\r\n delay: this.options.delay || 2000,\r\n showStatus: this.options.showStatus !== false,\r\n statusPosition: this.options.statusPosition || 'bottom-right',\r\n debug: this.options.debug || false\r\n }\r\n }\r\n\r\n /**\r\n * 推断组件路径\r\n */\r\n private inferComponentPath(routePath: string): string {\r\n const cleanPath = routePath.replace(/^\\//, '').replace(/\\//g, '-')\r\n return `@/views/${cleanPath}/index.vue`\r\n }\r\n\r\n /**\r\n * 生成HTML注入代码\r\n */\r\n generateHtmlInject(): string {\r\n if (this.options.showStatus === false) return ''\r\n \r\n return '<preloader-status></preloader-status>'\r\n }\r\n}","// ============================================================================\r\n// 🚀 src/index.ts - 主插件文件\r\n// ============================================================================\r\n\r\nimport type { Plugin } from 'vite'\r\nimport type { PreloaderOptions } from './types'\r\nimport { CodeGenerator } from './generator'\r\n\r\nconst VIRTUAL_MODULE_ID = 'virtual:preloader'\r\nconst RESOLVED_VIRTUAL_MODULE_ID = '\\0' + VIRTUAL_MODULE_ID\r\n\r\nexport default function preloaderPlugin(options: PreloaderOptions): Plugin {\r\n let generator: CodeGenerator\r\n\r\n return {\r\n name: 'vite-plugin-preloader',\r\n \r\n // 🎯 设置插件执行顺序\r\n enforce: 'post',\r\n \r\n configResolved() {\r\n generator = new CodeGenerator(options)\r\n console.log(`🚀 预加载插件已启用,配置了 ${options.routes.length} 个路由`)\r\n },\r\n\r\n resolveId(id) {\r\n if (id === VIRTUAL_MODULE_ID) {\r\n return RESOLVED_VIRTUAL_MODULE_ID\r\n }\r\n return null // 🔧 修复:明确返回 null\r\n },\r\n\r\n load(id) {\r\n if (id === RESOLVED_VIRTUAL_MODULE_ID) {\r\n return generator.generateRuntime()\r\n }\r\n return null // 🔧 修复:明确返回 null\r\n },\r\n\r\n // 🎨 HTML 转换(修复类型错误)\r\n transformIndexHtml(html) {\r\n const inject = generator.generateHtmlInject()\r\n if (inject) {\r\n return html.replace(\r\n '<div id=\"app\">',\r\n `<div id=\"app\">\\n ${inject}`\r\n )\r\n }\r\n return html // 🔧 修复:确保总是返回 html\r\n },\r\n\r\n // 🔥 HMR 支持\r\n handleHotUpdate(ctx) {\r\n if (ctx.file.includes('vite.config')) {\r\n console.log('🔄 预加载配置已更新')\r\n ctx.server.ws.send({\r\n type: 'full-reload'\r\n })\r\n return []\r\n }\r\n return undefined // 🔧 修复:明确返回 undefined\r\n }\r\n }\r\n}\r\n\r\n// 命名导出,支持多种导入方式\r\nexport { type PreloaderOptions, type PreloadRoute } from './types'"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "vite-plugin-preloader",
3
+ "version": "1.0.0",
4
+ "description": "🚀 Vite plugin for intelligent route preloading - 智能路由预加载插件",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "CHANGELOG.md"
19
+ ],
20
+ "keywords": [
21
+ "vite",
22
+ "vite-plugin",
23
+ "preload",
24
+ "vue",
25
+ "vue3",
26
+ "performance",
27
+ "route",
28
+ "lazy-loading",
29
+ "optimization",
30
+ "build-tool",
31
+ "intelligent",
32
+ "preloading"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "dev": "tsup --watch",
37
+ "prepublishOnly": "npm run build",
38
+ "test": "vitest",
39
+ "test:watch": "vitest --watch",
40
+ "lint": "eslint src --fix",
41
+ "format": "prettier --write src/",
42
+ "type-check": "tsc --noEmit"
43
+ },
44
+ "peerDependencies": {
45
+ "vite": "^7.0.0 || ^6.0.0 || ^5.0.0",
46
+ "vue": "^3.5.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.13.9",
50
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
51
+ "@typescript-eslint/parser": "^8.0.0",
52
+ "eslint": "^9.21.0",
53
+ "prettier": "^3.5.3",
54
+ "tsup": "^8.3.5",
55
+ "typescript": "~5.8.0",
56
+ "vite": "^7.0.0",
57
+ "vitest": "^3.0.8",
58
+ "vue": "^3.5.13"
59
+ },
60
+ "engines": {
61
+ "node": ">=18.0.0"
62
+ },
63
+ "author": {
64
+ "name": "ChenYu",
65
+ "email": "ycyplus@gmail.com",
66
+ "url": "https://github.com/ChenyCHENYU"
67
+ },
68
+ "license": "MIT",
69
+ "homepage": "https://github.com/ChenyCHENYU/vite-plugin-preloader#readme",
70
+ "repository": {
71
+ "type": "git",
72
+ "url": "git+https://github.com/ChenyCHENYU/vite-plugin-preloader.git"
73
+ },
74
+ "bugs": {
75
+ "url": "https://github.com/ChenyCHENYU/vite-plugin-preloader/issues"
76
+ },
77
+ "publishConfig": {
78
+ "access": "public",
79
+ "registry": "https://registry.npmjs.org/"
80
+ }
81
+ }