@playcraft/build 0.0.8 → 0.0.11

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.
Files changed (53) hide show
  1. package/README.md +122 -6
  2. package/dist/analyzers/build-analyzer.d.ts +98 -0
  3. package/dist/analyzers/build-analyzer.js +1160 -0
  4. package/dist/analyzers/enhanced-report-template.d.ts +13 -0
  5. package/dist/analyzers/enhanced-report-template.js +957 -0
  6. package/dist/analyzers/index.d.ts +6 -0
  7. package/dist/analyzers/index.js +9 -0
  8. package/dist/analyzers/optimization-analyzer.d.ts +88 -0
  9. package/dist/analyzers/optimization-analyzer.js +278 -0
  10. package/dist/analyzers/playable-analyzer.d.ts +91 -0
  11. package/dist/analyzers/playable-analyzer.js +976 -0
  12. package/dist/analyzers/report-template.d.ts +50 -0
  13. package/dist/analyzers/report-template.js +591 -0
  14. package/dist/analyzers/scene-asset-collector.js +8 -0
  15. package/dist/base-builder.d.ts +9 -0
  16. package/dist/base-builder.js +149 -1
  17. package/dist/build-state-manager.d.ts +110 -0
  18. package/dist/build-state-manager.js +169 -0
  19. package/dist/generators/config-generator.d.ts +2 -0
  20. package/dist/generators/config-generator.js +179 -10
  21. package/dist/index.d.ts +8 -0
  22. package/dist/index.js +6 -0
  23. package/dist/loaders/playcanvas-loader.d.ts +7 -0
  24. package/dist/loaders/playcanvas-loader.js +17 -0
  25. package/dist/state/build-state-manager.d.ts +174 -0
  26. package/dist/state/build-state-manager.js +235 -0
  27. package/dist/state/index.d.ts +4 -0
  28. package/dist/state/index.js +2 -0
  29. package/dist/state/state-to-report-converter.d.ts +141 -0
  30. package/dist/state/state-to-report-converter.js +177 -0
  31. package/dist/utils.d.ts +4 -0
  32. package/dist/utils.js +11 -0
  33. package/dist/vite/config-builder.js +8 -1
  34. package/dist/vite/plugin-build-state.d.ts +11 -0
  35. package/dist/vite/plugin-build-state.js +145 -0
  36. package/dist/vite/plugin-source-builder.js +1 -0
  37. package/package.json +12 -12
  38. package/dist/templates/__loading__.js +0 -100
  39. package/dist/templates/__modules__.js +0 -47
  40. package/dist/templates/__settings__.template.js +0 -20
  41. package/dist/templates/__start__.js +0 -332
  42. package/dist/templates/index.html +0 -18
  43. package/dist/templates/logo.png +0 -0
  44. package/dist/templates/manifest.json +0 -1
  45. package/dist/templates/patches/cannon.min.js +0 -28
  46. package/dist/templates/patches/lz4.js +0 -10
  47. package/dist/templates/patches/one-page-http-get.js +0 -20
  48. package/dist/templates/patches/one-page-inline-game-scripts.js +0 -52
  49. package/dist/templates/patches/one-page-mraid-resize-canvas.js +0 -46
  50. package/dist/templates/patches/p2.min.js +0 -27
  51. package/dist/templates/patches/playcraft-no-xhr.js +0 -76
  52. package/dist/templates/playcanvas-stable.min.js +0 -16363
  53. package/dist/templates/styles.css +0 -43
@@ -0,0 +1,6 @@
1
+ export * from './build-analyzer.js';
2
+ export * from './playable-analyzer.js';
3
+ export * from './scene-asset-collector.js';
4
+ export * from './optimization-analyzer.js';
5
+ export * from './report-template.js';
6
+ export * from './enhanced-report-template.js';
@@ -0,0 +1,9 @@
1
+ // 分析器
2
+ export * from './build-analyzer.js';
3
+ export * from './playable-analyzer.js';
4
+ export * from './scene-asset-collector.js';
5
+ // 优化分析器
6
+ export * from './optimization-analyzer.js';
7
+ // 报告模板
8
+ export * from './report-template.js';
9
+ export * from './enhanced-report-template.js';
@@ -0,0 +1,88 @@
1
+ import type { BuildState } from '../state/index.js';
2
+ /**
3
+ * 优化建议类型
4
+ */
5
+ export type OptimizationType = 'large-uncompressed' | 'format-conversion' | 'merge-small-files' | 'unused-asset' | 'enable-minify' | 'enable-image-compression' | 'adjust-inline-limit';
6
+ /**
7
+ * 优化建议
8
+ */
9
+ export interface OptimizationSuggestion {
10
+ /** 建议类型 */
11
+ type: OptimizationType;
12
+ /** 严重程度 */
13
+ severity: 'high' | 'medium' | 'low';
14
+ /** 问题描述 */
15
+ description: string;
16
+ /** 受影响的资产 */
17
+ affectedAssets: string[];
18
+ /** 预估优化效果(字节) */
19
+ estimatedSavings: number;
20
+ /** 预估优化效果(格式化) */
21
+ estimatedSavingsFormatted: string;
22
+ /** 具体建议 */
23
+ recommendation: string;
24
+ /** 配置示例 */
25
+ configExample?: string;
26
+ }
27
+ /**
28
+ * 优化分析结果
29
+ */
30
+ export interface OptimizationAnalysisResult {
31
+ /** 总建议数 */
32
+ totalSuggestions: number;
33
+ /** 预估总节省(字节) */
34
+ totalEstimatedSavings: number;
35
+ /** 预估总节省(格式化) */
36
+ totalEstimatedSavingsFormatted: string;
37
+ /** 预估优化率 */
38
+ estimatedOptimizationRate: number;
39
+ /** 建议列表 */
40
+ suggestions: OptimizationSuggestion[];
41
+ /** 按严重程度分组 */
42
+ bySeverity: {
43
+ high: OptimizationSuggestion[];
44
+ medium: OptimizationSuggestion[];
45
+ low: OptimizationSuggestion[];
46
+ };
47
+ }
48
+ /**
49
+ * 优化分析器
50
+ * 分析构建状态并提供优化建议
51
+ */
52
+ export declare class OptimizationAnalyzer {
53
+ private state;
54
+ private suggestions;
55
+ constructor(state: BuildState);
56
+ /**
57
+ * 执行优化分析
58
+ */
59
+ analyze(): OptimizationAnalysisResult;
60
+ /**
61
+ * 检测未压缩的大文件
62
+ */
63
+ private detectLargeUncompressedFiles;
64
+ /**
65
+ * 检测可以转换格式的资源
66
+ */
67
+ private detectFormatConversionOpportunities;
68
+ /**
69
+ * 检测可以合并的小文件
70
+ */
71
+ private detectMergeableSmallFiles;
72
+ /**
73
+ * 检测未使用的资源
74
+ */
75
+ private detectUnusedAssets;
76
+ /**
77
+ * 检测缺少代码压缩
78
+ */
79
+ private detectMissingMinification;
80
+ /**
81
+ * 检测缺少图片压缩
82
+ */
83
+ private detectMissingImageCompression;
84
+ /**
85
+ * 检测内联限制问题
86
+ */
87
+ private detectInlineLimitIssues;
88
+ }
@@ -0,0 +1,278 @@
1
+ import { StateToReportConverter } from '../state/state-to-report-converter.js';
2
+ /**
3
+ * 优化分析器
4
+ * 分析构建状态并提供优化建议
5
+ */
6
+ export class OptimizationAnalyzer {
7
+ constructor(state) {
8
+ this.suggestions = [];
9
+ this.state = state;
10
+ }
11
+ /**
12
+ * 执行优化分析
13
+ */
14
+ analyze() {
15
+ this.suggestions = [];
16
+ // 检测各种优化机会
17
+ this.detectLargeUncompressedFiles();
18
+ this.detectFormatConversionOpportunities();
19
+ this.detectMergeableSmallFiles();
20
+ this.detectUnusedAssets();
21
+ this.detectMissingMinification();
22
+ this.detectMissingImageCompression();
23
+ this.detectInlineLimitIssues();
24
+ // 计算总节省
25
+ const totalEstimatedSavings = this.suggestions.reduce((sum, s) => sum + s.estimatedSavings, 0);
26
+ const totalSize = Object.values(this.state.assets).reduce((sum, asset) => sum + asset.finalSize, 0);
27
+ const estimatedOptimizationRate = totalSize > 0
28
+ ? totalEstimatedSavings / totalSize
29
+ : 0;
30
+ // 按严重程度分组
31
+ const bySeverity = {
32
+ high: this.suggestions.filter(s => s.severity === 'high'),
33
+ medium: this.suggestions.filter(s => s.severity === 'medium'),
34
+ low: this.suggestions.filter(s => s.severity === 'low'),
35
+ };
36
+ return {
37
+ totalSuggestions: this.suggestions.length,
38
+ totalEstimatedSavings,
39
+ totalEstimatedSavingsFormatted: StateToReportConverter.formatBytes(totalEstimatedSavings),
40
+ estimatedOptimizationRate,
41
+ suggestions: this.suggestions,
42
+ bySeverity,
43
+ };
44
+ }
45
+ /**
46
+ * 检测未压缩的大文件
47
+ */
48
+ detectLargeUncompressedFiles() {
49
+ const threshold = 100 * 1024; // 100KB
50
+ const minCompressionRatio = 0.1; // 10%
51
+ const largeUncompressed = Object.values(this.state.assets).filter(asset => asset.finalSize > threshold &&
52
+ asset.totalCompressionRatio < minCompressionRatio);
53
+ if (largeUncompressed.length > 0) {
54
+ const totalSize = largeUncompressed.reduce((sum, a) => sum + a.finalSize, 0);
55
+ const estimatedSavings = totalSize * 0.5; // 假设可以压缩 50%
56
+ this.suggestions.push({
57
+ type: 'large-uncompressed',
58
+ severity: 'high',
59
+ description: `发现 ${largeUncompressed.length} 个大文件(> 100KB)未进行有效压缩`,
60
+ affectedAssets: largeUncompressed.map(a => a.originalName),
61
+ estimatedSavings,
62
+ estimatedSavingsFormatted: StateToReportConverter.formatBytes(estimatedSavings),
63
+ recommendation: '建议对这些文件启用压缩或使用更高效的压缩算法',
64
+ configExample: `// 在 Vite 配置中启用压缩
65
+ build: {
66
+ minify: 'terser',
67
+ terserOptions: {
68
+ compress: {
69
+ drop_console: true,
70
+ drop_debugger: true
71
+ }
72
+ }
73
+ }`,
74
+ });
75
+ }
76
+ }
77
+ /**
78
+ * 检测可以转换格式的资源
79
+ */
80
+ detectFormatConversionOpportunities() {
81
+ const pngAssets = Object.values(this.state.assets).filter(asset => asset.type === 'texture' &&
82
+ asset.originalName.toLowerCase().endsWith('.png') &&
83
+ asset.finalSize > 50 * 1024 // > 50KB
84
+ );
85
+ if (pngAssets.length > 0) {
86
+ const totalSize = pngAssets.reduce((sum, a) => sum + a.finalSize, 0);
87
+ const estimatedSavings = totalSize * 0.3; // WebP 通常可以节省 30%
88
+ this.suggestions.push({
89
+ type: 'format-conversion',
90
+ severity: 'medium',
91
+ description: `发现 ${pngAssets.length} 个 PNG 图片可以转换为 WebP 格式`,
92
+ affectedAssets: pngAssets.map(a => a.originalName),
93
+ estimatedSavings,
94
+ estimatedSavingsFormatted: StateToReportConverter.formatBytes(estimatedSavings),
95
+ recommendation: '建议将 PNG 图片转换为 WebP 格式以减小文件大小',
96
+ configExample: `// 使用 vite-plugin-imagemin
97
+ import viteImagemin from 'vite-plugin-imagemin';
98
+
99
+ plugins: [
100
+ viteImagemin({
101
+ webp: {
102
+ quality: 75
103
+ }
104
+ })
105
+ ]`,
106
+ });
107
+ }
108
+ }
109
+ /**
110
+ * 检测可以合并的小文件
111
+ */
112
+ detectMergeableSmallFiles() {
113
+ const smallThreshold = 10 * 1024; // 10KB
114
+ const smallScripts = Object.values(this.state.assets).filter(asset => asset.type === 'script' && asset.finalSize < smallThreshold);
115
+ if (smallScripts.length >= 5) {
116
+ const totalSize = smallScripts.reduce((sum, a) => sum + a.finalSize, 0);
117
+ const estimatedSavings = smallScripts.length * 500; // 每个文件节省约 500 字节的 HTTP 开销
118
+ this.suggestions.push({
119
+ type: 'merge-small-files',
120
+ severity: 'low',
121
+ description: `发现 ${smallScripts.length} 个小脚本文件(< 10KB)可以合并`,
122
+ affectedAssets: smallScripts.map(a => a.originalName),
123
+ estimatedSavings,
124
+ estimatedSavingsFormatted: StateToReportConverter.formatBytes(estimatedSavings),
125
+ recommendation: '建议合并这些小文件以减少 HTTP 请求数量',
126
+ configExample: `// Vite 会自动进行代码分割和合并
127
+ // 如果需要手动控制,可以使用 manualChunks
128
+ build: {
129
+ rollupOptions: {
130
+ output: {
131
+ manualChunks: {
132
+ 'vendor': ['module1', 'module2']
133
+ }
134
+ }
135
+ }
136
+ }`,
137
+ });
138
+ }
139
+ }
140
+ /**
141
+ * 检测未使用的资源
142
+ */
143
+ detectUnusedAssets() {
144
+ // 检测在处理历史中标记为未使用的资源
145
+ const unusedAssets = Object.values(this.state.assets).filter(asset => {
146
+ // 检查是否有任何处理步骤标记为 'unused'
147
+ return asset.processingHistory.some(step => step.optimizations.includes('unused'));
148
+ });
149
+ if (unusedAssets.length > 0) {
150
+ const totalSize = unusedAssets.reduce((sum, a) => sum + a.finalSize, 0);
151
+ this.suggestions.push({
152
+ type: 'unused-asset',
153
+ severity: 'high',
154
+ description: `发现 ${unusedAssets.length} 个未使用的资源`,
155
+ affectedAssets: unusedAssets.map(a => a.originalName),
156
+ estimatedSavings: totalSize,
157
+ estimatedSavingsFormatted: StateToReportConverter.formatBytes(totalSize),
158
+ recommendation: '建议从项目中移除这些未使用的资源',
159
+ });
160
+ }
161
+ }
162
+ /**
163
+ * 检测缺少代码压缩
164
+ */
165
+ detectMissingMinification() {
166
+ const unminifiedScripts = Object.values(this.state.assets).filter(asset => {
167
+ if (asset.type !== 'script')
168
+ return false;
169
+ // 检查是否应用了 minify 优化
170
+ const hasMinify = asset.processingHistory.some(step => step.optimizations.includes('minify'));
171
+ return !hasMinify && asset.finalSize > 10 * 1024; // > 10KB
172
+ });
173
+ if (unminifiedScripts.length > 0) {
174
+ const totalSize = unminifiedScripts.reduce((sum, a) => sum + a.finalSize, 0);
175
+ const estimatedSavings = totalSize * 0.4; // 压缩通常可以节省 40%
176
+ this.suggestions.push({
177
+ type: 'enable-minify',
178
+ severity: 'high',
179
+ description: `发现 ${unminifiedScripts.length} 个脚本文件未进行代码压缩`,
180
+ affectedAssets: unminifiedScripts.map(a => a.originalName),
181
+ estimatedSavings,
182
+ estimatedSavingsFormatted: StateToReportConverter.formatBytes(estimatedSavings),
183
+ recommendation: '建议启用代码压缩以减小脚本文件大小',
184
+ configExample: `// 在 Vite 配置中启用 minify
185
+ build: {
186
+ minify: 'terser', // 或 'esbuild'
187
+ }`,
188
+ });
189
+ }
190
+ }
191
+ /**
192
+ * 检测缺少图片压缩
193
+ */
194
+ detectMissingImageCompression() {
195
+ const uncompressedImages = Object.values(this.state.assets).filter(asset => {
196
+ if (asset.type !== 'texture')
197
+ return false;
198
+ // 检查是否应用了图片压缩
199
+ const hasCompression = asset.processingHistory.some(step => step.optimizations.includes('image-compress'));
200
+ return !hasCompression && asset.finalSize > 50 * 1024; // > 50KB
201
+ });
202
+ if (uncompressedImages.length > 0) {
203
+ const totalSize = uncompressedImages.reduce((sum, a) => sum + a.finalSize, 0);
204
+ const estimatedSavings = totalSize * 0.3; // 图片压缩通常可以节省 30%
205
+ this.suggestions.push({
206
+ type: 'enable-image-compression',
207
+ severity: 'medium',
208
+ description: `发现 ${uncompressedImages.length} 个图片未进行压缩`,
209
+ affectedAssets: uncompressedImages.map(a => a.originalName),
210
+ estimatedSavings,
211
+ estimatedSavingsFormatted: StateToReportConverter.formatBytes(estimatedSavings),
212
+ recommendation: '建议启用图片压缩插件以减小图片大小',
213
+ configExample: `// 使用 vite-plugin-imagemin
214
+ import viteImagemin from 'vite-plugin-imagemin';
215
+
216
+ plugins: [
217
+ viteImagemin({
218
+ gifsicle: { optimizationLevel: 7 },
219
+ optipng: { optimizationLevel: 7 },
220
+ mozjpeg: { quality: 80 },
221
+ pngquant: { quality: [0.8, 0.9] },
222
+ svgo: { plugins: [{ name: 'removeViewBox' }] }
223
+ })
224
+ ]`,
225
+ });
226
+ }
227
+ }
228
+ /**
229
+ * 检测内联限制问题
230
+ */
231
+ detectInlineLimitIssues() {
232
+ const inlinedAssets = Object.values(this.state.assets).filter(asset => {
233
+ const lastStep = asset.processingHistory[asset.processingHistory.length - 1];
234
+ return lastStep?.inlined;
235
+ });
236
+ // 检测过大的内联资源
237
+ const largeInlined = inlinedAssets.filter(a => a.finalSize > 10 * 1024); // > 10KB
238
+ if (largeInlined.length > 0) {
239
+ const totalSize = largeInlined.reduce((sum, a) => sum + a.finalSize, 0);
240
+ const estimatedSavings = totalSize * 0.33; // Base64 编码增加约 33%
241
+ this.suggestions.push({
242
+ type: 'adjust-inline-limit',
243
+ severity: 'medium',
244
+ description: `发现 ${largeInlined.length} 个较大的资源被内联(> 10KB)`,
245
+ affectedAssets: largeInlined.map(a => a.originalName),
246
+ estimatedSavings,
247
+ estimatedSavingsFormatted: StateToReportConverter.formatBytes(estimatedSavings),
248
+ recommendation: '建议降低内联限制,将大文件作为外部资源加载',
249
+ configExample: `// 调整 Vite 的内联限制
250
+ build: {
251
+ assetsInlineLimit: 4096, // 4KB
252
+ }`,
253
+ });
254
+ }
255
+ // 检测可以内联的小资源
256
+ const notInlined = Object.values(this.state.assets).filter(asset => {
257
+ const lastStep = asset.processingHistory[asset.processingHistory.length - 1];
258
+ return !lastStep?.inlined && asset.finalSize < 4 * 1024; // < 4KB
259
+ });
260
+ if (notInlined.length >= 5) {
261
+ const totalSize = notInlined.reduce((sum, a) => sum + a.finalSize, 0);
262
+ const estimatedSavings = notInlined.length * 500; // 每个文件节省约 500 字节的 HTTP 开销
263
+ this.suggestions.push({
264
+ type: 'adjust-inline-limit',
265
+ severity: 'low',
266
+ description: `发现 ${notInlined.length} 个小资源(< 4KB)未被内联`,
267
+ affectedAssets: notInlined.map(a => a.originalName),
268
+ estimatedSavings,
269
+ estimatedSavingsFormatted: StateToReportConverter.formatBytes(estimatedSavings),
270
+ recommendation: '建议提高内联限制,将小文件内联以减少 HTTP 请求',
271
+ configExample: `// 调整 Vite 的内联限制
272
+ build: {
273
+ assetsInlineLimit: 8192, // 8KB
274
+ }`,
275
+ });
276
+ }
277
+ }
278
+ }
@@ -0,0 +1,91 @@
1
+ export interface PlayableAssetAnalysis {
2
+ name: string;
3
+ type: string;
4
+ category: string;
5
+ size: number;
6
+ sizeFormatted: string;
7
+ percentage: number;
8
+ }
9
+ export interface PlayableAnalysisReport {
10
+ htmlPath: string;
11
+ totalSize: number;
12
+ totalSizeFormatted: string;
13
+ assets: PlayableAssetAnalysis[];
14
+ byCategory: Record<string, {
15
+ count: number;
16
+ size: number;
17
+ sizeFormatted: string;
18
+ percentage: number;
19
+ }>;
20
+ byType: Record<string, {
21
+ count: number;
22
+ size: number;
23
+ sizeFormatted: string;
24
+ percentage: number;
25
+ }>;
26
+ optimizationInfo?: {
27
+ baseBuildSize?: number;
28
+ baseBuildSizeFormatted?: string;
29
+ optimizationRate?: number;
30
+ };
31
+ }
32
+ /**
33
+ * Playable 分析器 - 分析最终打包后的单 HTML 文件
34
+ *
35
+ * 职责:
36
+ * 1. 解析单 HTML 文件中的所有内联资源
37
+ * 2. 提取 data URL 并计算实际大小
38
+ * 3. 生成准确的资源分布报告
39
+ */
40
+ export declare class PlayableAnalyzer {
41
+ private htmlPath;
42
+ private outputDir;
43
+ private baseBuildReportPath?;
44
+ constructor(htmlPath: string, outputDir?: string, baseBuildReportPath?: string);
45
+ /**
46
+ * 分析 Playable HTML 文件
47
+ *
48
+ * 策略:
49
+ * 1. 优先尝试从 build-state.json 读取资源信息(更准确)
50
+ * 2. 如果没有 state 文件,则从 HTML 中提取资源(fallback)
51
+ */
52
+ analyze(): Promise<PlayableAnalysisReport>;
53
+ /**
54
+ * 提取 HTML 中的所有资源
55
+ *
56
+ * 注意:不要重复计算!
57
+ * - script/style 标签的大小已经包含了其中的 data URL
58
+ * - 所以我们只统计标签本身,不单独提取 data URL
59
+ */
60
+ private extractAssets;
61
+ /**
62
+ * 从 build-state.json 提取资源信息
63
+ *
64
+ * 这个方法使用打包过程中记录的真实资源信息,比从 HTML 中提取更准确
65
+ */
66
+ private extractAssetsFromState;
67
+ /**
68
+ * 根据 MIME 类型获取文件信息
69
+ */
70
+ private getMimeTypeInfo;
71
+ /**
72
+ * 生成 HTML 报告
73
+ *
74
+ * 策略:
75
+ * 1. 如果提供了 baseBuildReportPath,则复用 Base Build 的 HTML 模板,只替换数据
76
+ * 2. 否则,使用统一的模板生成新的 HTML
77
+ */
78
+ generateHTMLReport(report: PlayableAnalysisReport): Promise<string>;
79
+ /**
80
+ * 格式化文件大小
81
+ */
82
+ private formatSize;
83
+ /**
84
+ * 生成树状图数据结构
85
+ */
86
+ private generateTreemapData;
87
+ /**
88
+ * 生成 HTML 报告内容
89
+ */
90
+ private generateReportHTML;
91
+ }