portapack 0.3.1 → 0.3.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.
Files changed (74) hide show
  1. package/.eslintrc.json +67 -8
  2. package/.releaserc.js +25 -27
  3. package/CHANGELOG.md +14 -22
  4. package/LICENSE.md +21 -0
  5. package/README.md +22 -53
  6. package/commitlint.config.js +30 -34
  7. package/dist/cli/cli-entry.cjs +183 -98
  8. package/dist/cli/cli-entry.cjs.map +1 -1
  9. package/dist/index.d.ts +0 -3
  10. package/dist/index.js +178 -97
  11. package/dist/index.js.map +1 -1
  12. package/docs/.vitepress/config.ts +38 -33
  13. package/docs/.vitepress/sidebar-generator.ts +89 -38
  14. package/docs/architecture.md +186 -0
  15. package/docs/cli.md +23 -23
  16. package/docs/code-of-conduct.md +7 -1
  17. package/docs/configuration.md +12 -11
  18. package/docs/contributing.md +6 -2
  19. package/docs/deployment.md +10 -5
  20. package/docs/development.md +8 -5
  21. package/docs/getting-started.md +13 -13
  22. package/docs/index.md +1 -1
  23. package/docs/public/android-chrome-192x192.png +0 -0
  24. package/docs/public/android-chrome-512x512.png +0 -0
  25. package/docs/public/apple-touch-icon.png +0 -0
  26. package/docs/public/favicon-16x16.png +0 -0
  27. package/docs/public/favicon-32x32.png +0 -0
  28. package/docs/public/favicon.ico +0 -0
  29. package/docs/roadmap.md +233 -0
  30. package/docs/site.webmanifest +1 -0
  31. package/docs/troubleshooting.md +12 -1
  32. package/examples/main.ts +5 -30
  33. package/examples/sample-project/script.js +1 -1
  34. package/jest.config.ts +8 -13
  35. package/nodemon.json +5 -10
  36. package/package.json +2 -5
  37. package/src/cli/cli-entry.ts +2 -2
  38. package/src/cli/cli.ts +21 -16
  39. package/src/cli/options.ts +127 -113
  40. package/src/core/bundler.ts +253 -222
  41. package/src/core/extractor.ts +632 -565
  42. package/src/core/minifier.ts +173 -162
  43. package/src/core/packer.ts +141 -137
  44. package/src/core/parser.ts +74 -73
  45. package/src/core/web-fetcher.ts +270 -258
  46. package/src/index.ts +18 -17
  47. package/src/types.ts +9 -11
  48. package/src/utils/font.ts +12 -6
  49. package/src/utils/logger.ts +110 -105
  50. package/src/utils/meta.ts +75 -76
  51. package/src/utils/mime.ts +50 -50
  52. package/src/utils/slugify.ts +33 -34
  53. package/tests/unit/cli/cli-entry.test.ts +72 -70
  54. package/tests/unit/cli/cli.test.ts +314 -278
  55. package/tests/unit/cli/options.test.ts +294 -301
  56. package/tests/unit/core/bundler.test.ts +426 -329
  57. package/tests/unit/core/extractor.test.ts +793 -549
  58. package/tests/unit/core/minifier.test.ts +374 -274
  59. package/tests/unit/core/packer.test.ts +298 -264
  60. package/tests/unit/core/parser.test.ts +538 -150
  61. package/tests/unit/core/web-fetcher.test.ts +389 -359
  62. package/tests/unit/index.test.ts +238 -197
  63. package/tests/unit/utils/font.test.ts +26 -21
  64. package/tests/unit/utils/logger.test.ts +267 -260
  65. package/tests/unit/utils/meta.test.ts +29 -28
  66. package/tests/unit/utils/mime.test.ts +73 -74
  67. package/tests/unit/utils/slugify.test.ts +14 -12
  68. package/tsconfig.build.json +9 -10
  69. package/tsconfig.jest.json +1 -1
  70. package/tsconfig.json +2 -2
  71. package/tsup.config.ts +8 -9
  72. package/typedoc.json +5 -9
  73. /package/docs/{portapack-transparent.png → public/portapack-transparent.png} +0 -0
  74. /package/docs/{portapack.jpg → public/portapack.jpg} +0 -0
@@ -26,14 +26,15 @@ import { Logger } from '../utils/logger.js';
26
26
  * Represents the expected structure of the synchronous output from clean-css.
27
27
  * Used with type assertion as a workaround for problematic official type definitions.
28
28
  */
29
- export interface CleanCSSSyncResult { // <<< MUST HAVE 'export'
30
- styles?: string;
31
- errors?: string[];
32
- warnings?: string[];
33
- stats?: {
34
- originalSize: number;
35
- minifiedSize: number;
36
- };
29
+ export interface CleanCSSSyncResult {
30
+ // <<< MUST HAVE 'export'
31
+ styles?: string;
32
+ errors?: string[];
33
+ warnings?: string[];
34
+ stats?: {
35
+ originalSize: number;
36
+ minifiedSize: number;
37
+ };
37
38
  }
38
39
 
39
40
  // --- Default Minification Options Constants ---
@@ -42,16 +43,16 @@ export interface CleanCSSSyncResult { // <<< MUST HAVE 'export'
42
43
  * Default options for html-minifier-terser.
43
44
  */
44
45
  const HTML_MINIFY_OPTIONS: HtmlMinifyOptions = {
45
- collapseWhitespace: true,
46
- removeComments: true,
47
- conservativeCollapse: true,
48
- minifyCSS: false, // Handled separately
49
- minifyJS: false, // Handled separately
50
- removeAttributeQuotes: false,
51
- removeRedundantAttributes: true,
52
- removeScriptTypeAttributes: true,
53
- removeStyleLinkTypeAttributes: true,
54
- useShortDoctype: true,
46
+ collapseWhitespace: true,
47
+ removeComments: true,
48
+ conservativeCollapse: true,
49
+ minifyCSS: false, // Handled separately
50
+ minifyJS: false, // Handled separately
51
+ removeAttributeQuotes: false,
52
+ removeRedundantAttributes: true,
53
+ removeScriptTypeAttributes: true,
54
+ removeStyleLinkTypeAttributes: true,
55
+ useShortDoctype: true,
55
56
  };
56
57
 
57
58
  /**
@@ -59,44 +60,46 @@ const HTML_MINIFY_OPTIONS: HtmlMinifyOptions = {
59
60
  * Explicitly set returnPromise to false to ensure synchronous operation.
60
61
  */
61
62
  const CSS_MINIFY_OPTIONS: CleanCSSOptions = {
62
- returnPromise: false, // <<< *** Ensures sync operation at runtime ***
63
- level: {
64
- 1: { // Level 1 optimizations (safe transformations)
65
- optimizeBackground: true,
66
- optimizeBorderRadius: true,
67
- optimizeFilter: true,
68
- optimizeFontWeight: true,
69
- optimizeOutline: true,
70
- },
71
- 2: { // Level 2 optimizations (structural changes, generally safe)
72
- mergeMedia: true,
73
- mergeNonAdjacentRules: true,
74
- removeDuplicateFontRules: true,
75
- removeDuplicateMediaBlocks: true,
76
- removeDuplicateRules: true,
77
- restructureRules: true,
78
- }
79
- }
80
- // Note: Type checking based on these options seems problematic with current @types/clean-css
63
+ returnPromise: false, // <<< *** Ensures sync operation at runtime ***
64
+ level: {
65
+ 1: {
66
+ // Level 1 optimizations (safe transformations)
67
+ optimizeBackground: true,
68
+ optimizeBorderRadius: true,
69
+ optimizeFilter: true,
70
+ optimizeFontWeight: true,
71
+ optimizeOutline: true,
72
+ },
73
+ 2: {
74
+ // Level 2 optimizations (structural changes, generally safe)
75
+ mergeMedia: true,
76
+ mergeNonAdjacentRules: true,
77
+ removeDuplicateFontRules: true,
78
+ removeDuplicateMediaBlocks: true,
79
+ removeDuplicateRules: true,
80
+ restructureRules: true,
81
+ },
82
+ },
83
+ // Note: Type checking based on these options seems problematic with current @types/clean-css
81
84
  };
82
85
 
83
86
  /**
84
87
  * Default options for terser (JavaScript minifier).
85
88
  */
86
89
  const JS_MINIFY_OPTIONS: MinifyOptions = {
87
- compress: {
88
- dead_code: true,
89
- drop_console: false,
90
- drop_debugger: true,
91
- ecma: 2020,
92
- keep_classnames: true,
93
- keep_fnames: true
94
- },
95
- mangle: {
96
- keep_classnames: true,
97
- keep_fnames: true
98
- },
99
- format: { comments: false }
90
+ compress: {
91
+ dead_code: true,
92
+ drop_console: false,
93
+ drop_debugger: true,
94
+ ecma: 2020,
95
+ keep_classnames: true,
96
+ keep_fnames: true,
97
+ },
98
+ mangle: {
99
+ keep_classnames: true,
100
+ keep_fnames: true,
101
+ },
102
+ format: { comments: false },
100
103
  };
101
104
 
102
105
  // --- Main Minification Function ---
@@ -113,121 +116,129 @@ const JS_MINIFY_OPTIONS: MinifyOptions = {
113
116
  * @returns {Promise<ParsedHTML>} A Promise resolving to a new ParsedHTML object.
114
117
  */
115
118
  export async function minifyAssets(
116
- parsed: ParsedHTML,
117
- options: BundleOptions = {},
118
- logger?: Logger
119
+ parsed: ParsedHTML,
120
+ options: BundleOptions = {},
121
+ logger?: Logger
119
122
  ): Promise<ParsedHTML> {
120
- const { htmlContent, assets } = parsed;
121
-
122
- // Use optional chaining and nullish coalescing for safer access
123
- const currentHtmlContent = htmlContent ?? '';
124
- const currentAssets = assets ?? [];
125
-
126
-
127
- if (!currentHtmlContent && currentAssets.length === 0) {
128
- logger?.debug('Minification skipped: No content.');
129
- return { htmlContent: currentHtmlContent, assets: currentAssets };
130
- }
131
-
132
- const minifyFlags = {
133
- minifyHtml: options.minifyHtml !== false,
134
- minifyCss: options.minifyCss !== false,
135
- minifyJs: options.minifyJs !== false
136
- };
137
-
138
- logger?.debug(`Minification flags: ${JSON.stringify(minifyFlags)}`);
139
-
140
- const minifiedAssets: Asset[] = await Promise.all(
141
- currentAssets.map(async (asset): Promise<Asset> => {
142
- // Make a shallow copy to avoid modifying the original asset object
143
- let processedAsset = { ...asset };
144
-
145
- if (typeof processedAsset.content !== 'string' || processedAsset.content.length === 0) {
146
- return processedAsset; // Return the copy
123
+ const { htmlContent, assets } = parsed;
124
+
125
+ // Use optional chaining and nullish coalescing for safer access
126
+ const currentHtmlContent = htmlContent ?? '';
127
+ const currentAssets = assets ?? [];
128
+
129
+ if (!currentHtmlContent && currentAssets.length === 0) {
130
+ logger?.debug('Minification skipped: No content.');
131
+ return { htmlContent: currentHtmlContent, assets: currentAssets };
132
+ }
133
+
134
+ const minifyFlags = {
135
+ minifyHtml: options.minifyHtml !== false,
136
+ minifyCss: options.minifyCss !== false,
137
+ minifyJs: options.minifyJs !== false,
138
+ };
139
+
140
+ logger?.debug(`Minification flags: ${JSON.stringify(minifyFlags)}`);
141
+
142
+ const minifiedAssets: Asset[] = await Promise.all(
143
+ currentAssets.map(async (asset): Promise<Asset> => {
144
+ // Make a shallow copy to avoid modifying the original asset object
145
+ const processedAsset = { ...asset };
146
+
147
+ if (typeof processedAsset.content !== 'string' || processedAsset.content.length === 0) {
148
+ return processedAsset; // Return the copy
149
+ }
150
+
151
+ let newContent = processedAsset.content; // Work with the content of the copy
152
+ const assetIdentifier = processedAsset.url || `inline ${processedAsset.type}`;
153
+
154
+ try {
155
+ // --- Minify CSS (Synchronous Call with Type Assertion Workaround) ---
156
+ if (minifyFlags.minifyCss && processedAsset.type === 'css') {
157
+ logger?.debug(`Minifying CSS: ${assetIdentifier}`);
158
+
159
+ // @ts-ignore - Suppress error TS2769 due to likely faulty @types/clean-css constructor overload definitions for sync mode.
160
+ const cssMinifier = new CleanCSS(CSS_MINIFY_OPTIONS); // <<< @ts-ignore HERE
161
+
162
+ // WORKAROUND using Type Assertion
163
+ const result = cssMinifier.minify(processedAsset.content) as CleanCSSSyncResult;
164
+
165
+ // Access properties based on the asserted type
166
+ if (result.errors && result.errors.length > 0) {
167
+ logger?.warn(`⚠️ CleanCSS failed for ${assetIdentifier}: ${result.errors.join(', ')}`);
168
+ } else {
169
+ if (result.warnings && result.warnings.length > 0) {
170
+ logger?.debug(
171
+ `CleanCSS warnings for ${assetIdentifier}: ${result.warnings.join(', ')}`
172
+ );
147
173
  }
148
-
149
- let newContent = processedAsset.content; // Work with the content of the copy
150
- const assetIdentifier = processedAsset.url || `inline ${processedAsset.type}`;
151
-
152
- try {
153
- // --- Minify CSS (Synchronous Call with Type Assertion Workaround) ---
154
- if (minifyFlags.minifyCss && processedAsset.type === 'css') {
155
- logger?.debug(`Minifying CSS: ${assetIdentifier}`);
156
-
157
- // @ts-ignore - Suppress error TS2769 due to likely faulty @types/clean-css constructor overload definitions for sync mode.
158
- const cssMinifier = new CleanCSS(CSS_MINIFY_OPTIONS); // <<< @ts-ignore HERE
159
-
160
- // WORKAROUND using Type Assertion
161
- const result = cssMinifier.minify(processedAsset.content) as CleanCSSSyncResult;
162
-
163
- // Access properties based on the asserted type
164
- if (result.errors && result.errors.length > 0) {
165
- logger?.warn(`⚠️ CleanCSS failed for ${assetIdentifier}: ${result.errors.join(', ')}`);
166
- } else {
167
- if (result.warnings && result.warnings.length > 0) {
168
- logger?.debug(`CleanCSS warnings for ${assetIdentifier}: ${result.warnings.join(', ')}`);
169
- }
170
- if (result.styles) {
171
- newContent = result.styles; // Update newContent
172
- logger?.debug(`CSS minified successfully: ${assetIdentifier}`);
173
- } else {
174
- logger?.warn(`⚠️ CleanCSS produced no styles but reported no errors for ${assetIdentifier}. Keeping original.`);
175
- }
176
- }
177
- }
178
-
179
- // --- Minify JS (Asynchronous Call) ---
180
- if (minifyFlags.minifyJs && processedAsset.type === 'js') {
181
- logger?.debug(`Minifying JS: ${assetIdentifier}`);
182
- const result: MinifyOutput = await jsMinify(processedAsset.content, JS_MINIFY_OPTIONS);
183
- if (result.code) {
184
- newContent = result.code; // Update newContent
185
- logger?.debug(`JS minified successfully: ${assetIdentifier}`);
186
- } else {
187
- const terserError = (result as any).error;
188
- if (terserError) {
189
- logger?.warn(`⚠️ Terser failed for ${assetIdentifier}: ${terserError.message || terserError}`);
190
- } else {
191
- logger?.warn(`⚠️ Terser produced no code but reported no errors for ${assetIdentifier}. Keeping original.`);
192
- }
193
- }
194
- }
195
- } catch (err: unknown) {
196
- const errorMessage = err instanceof Error ? err.message : String(err);
197
- logger?.warn(`⚠️ Failed to minify asset ${assetIdentifier} (${processedAsset.type}): ${errorMessage}`);
198
- // Keep original content if error occurs (newContent remains unchanged)
174
+ if (result.styles) {
175
+ newContent = result.styles; // Update newContent
176
+ logger?.debug(`CSS minified successfully: ${assetIdentifier}`);
177
+ } else {
178
+ logger?.warn(
179
+ `⚠️ CleanCSS produced no styles but reported no errors for ${assetIdentifier}. Keeping original.`
180
+ );
199
181
  }
182
+ }
183
+ }
200
184
 
201
- // Update the content property of the copied asset
202
- processedAsset.content = newContent;
203
- return processedAsset; // Return the modified copy
204
- })
205
- );
206
-
207
- // --- Minify the main HTML content itself ---
208
- let finalHtml = currentHtmlContent; // Start with potentially empty original HTML
209
- if (minifyFlags.minifyHtml && finalHtml.length > 0) {
210
- logger?.debug('Minifying HTML content...');
211
- try {
212
- finalHtml = await htmlMinify(finalHtml, {
213
- ...HTML_MINIFY_OPTIONS,
214
- minifyCSS: minifyFlags.minifyCss,
215
- minifyJS: minifyFlags.minifyJs
216
- });
217
- logger?.debug('HTML minified successfully.');
218
- } catch (err: unknown) {
219
- const errorMessage = err instanceof Error ? err.message : String(err);
220
- logger?.warn(`⚠️ HTML minification failed: ${errorMessage}`);
221
- // Keep original HTML (finalHtml already holds it)
185
+ // --- Minify JS (Asynchronous Call) ---
186
+ if (minifyFlags.minifyJs && processedAsset.type === 'js') {
187
+ logger?.debug(`Minifying JS: ${assetIdentifier}`);
188
+ const result: MinifyOutput = await jsMinify(processedAsset.content, JS_MINIFY_OPTIONS);
189
+ if (result.code) {
190
+ newContent = result.code; // Update newContent
191
+ logger?.debug(`JS minified successfully: ${assetIdentifier}`);
192
+ } else {
193
+ const terserError = (result as any).error;
194
+ if (terserError) {
195
+ logger?.warn(
196
+ `⚠️ Terser failed for ${assetIdentifier}: ${terserError.message || terserError}`
197
+ );
198
+ } else {
199
+ logger?.warn(
200
+ `⚠️ Terser produced no code but reported no errors for ${assetIdentifier}. Keeping original.`
201
+ );
202
+ }
203
+ }
222
204
  }
223
- } else if (finalHtml.length > 0) {
224
- logger?.debug('HTML minification skipped (disabled).');
205
+ } catch (err: unknown) {
206
+ const errorMessage = err instanceof Error ? err.message : String(err);
207
+ logger?.warn(
208
+ `⚠️ Failed to minify asset ${assetIdentifier} (${processedAsset.type}): ${errorMessage}`
209
+ );
210
+ // Keep original content if error occurs (newContent remains unchanged)
211
+ }
212
+
213
+ // Update the content property of the copied asset
214
+ processedAsset.content = newContent;
215
+ return processedAsset; // Return the modified copy
216
+ })
217
+ );
218
+
219
+ // --- Minify the main HTML content itself ---
220
+ let finalHtml = currentHtmlContent; // Start with potentially empty original HTML
221
+ if (minifyFlags.minifyHtml && finalHtml.length > 0) {
222
+ logger?.debug('Minifying HTML content...');
223
+ try {
224
+ finalHtml = await htmlMinify(finalHtml, {
225
+ ...HTML_MINIFY_OPTIONS,
226
+ minifyCSS: minifyFlags.minifyCss,
227
+ minifyJS: minifyFlags.minifyJs,
228
+ });
229
+ logger?.debug('HTML minified successfully.');
230
+ } catch (err: unknown) {
231
+ const errorMessage = err instanceof Error ? err.message : String(err);
232
+ logger?.warn(`⚠️ HTML minification failed: ${errorMessage}`);
233
+ // Keep original HTML (finalHtml already holds it)
225
234
  }
226
-
227
-
228
- // --- Return the final result object ---
229
- return {
230
- htmlContent: finalHtml,
231
- assets: minifiedAssets // The array of processed asset copies
232
- };
233
- }
235
+ } else if (finalHtml.length > 0) {
236
+ logger?.debug('HTML minification skipped (disabled).');
237
+ }
238
+
239
+ // --- Return the final result object ---
240
+ return {
241
+ htmlContent: finalHtml,
242
+ assets: minifiedAssets, // The array of processed asset copies
243
+ };
244
+ }