portapack 0.3.0 → 0.3.2
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/.eslintrc.json +67 -8
- package/.github/workflows/ci.yml +5 -4
- package/.releaserc.js +25 -27
- package/CHANGELOG.md +12 -19
- package/LICENSE.md +21 -0
- package/README.md +34 -36
- package/commitlint.config.js +30 -34
- package/dist/cli/cli-entry.cjs +199 -135
- package/dist/cli/cli-entry.cjs.map +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +194 -134
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.ts +36 -34
- package/docs/.vitepress/sidebar-generator.ts +89 -38
- package/docs/cli.md +29 -82
- package/docs/code-of-conduct.md +7 -1
- package/docs/configuration.md +103 -117
- package/docs/contributing.md +6 -2
- package/docs/deployment.md +10 -5
- package/docs/development.md +8 -5
- package/docs/getting-started.md +76 -45
- package/docs/index.md +1 -1
- package/docs/public/android-chrome-192x192.png +0 -0
- package/docs/public/android-chrome-512x512.png +0 -0
- package/docs/public/apple-touch-icon.png +0 -0
- package/docs/public/favicon-16x16.png +0 -0
- package/docs/public/favicon-32x32.png +0 -0
- package/docs/public/favicon.ico +0 -0
- package/docs/site.webmanifest +1 -0
- package/docs/troubleshooting.md +12 -1
- package/examples/main.ts +7 -10
- package/examples/sample-project/script.js +1 -1
- package/jest.config.ts +8 -13
- package/nodemon.json +5 -10
- package/package.json +2 -5
- package/src/cli/cli-entry.ts +2 -2
- package/src/cli/cli.ts +21 -16
- package/src/cli/options.ts +127 -113
- package/src/core/bundler.ts +254 -221
- package/src/core/extractor.ts +639 -520
- package/src/core/minifier.ts +173 -162
- package/src/core/packer.ts +141 -137
- package/src/core/parser.ts +74 -73
- package/src/core/web-fetcher.ts +270 -258
- package/src/index.ts +18 -17
- package/src/types.ts +9 -11
- package/src/utils/font.ts +12 -6
- package/src/utils/logger.ts +110 -105
- package/src/utils/meta.ts +75 -76
- package/src/utils/mime.ts +50 -50
- package/src/utils/slugify.ts +33 -34
- package/tests/unit/cli/cli-entry.test.ts +72 -70
- package/tests/unit/cli/cli.test.ts +314 -278
- package/tests/unit/cli/options.test.ts +294 -301
- package/tests/unit/core/bundler.test.ts +426 -329
- package/tests/unit/core/extractor.test.ts +828 -380
- package/tests/unit/core/minifier.test.ts +374 -274
- package/tests/unit/core/packer.test.ts +298 -264
- package/tests/unit/core/parser.test.ts +538 -150
- package/tests/unit/core/web-fetcher.test.ts +389 -359
- package/tests/unit/index.test.ts +238 -197
- package/tests/unit/utils/font.test.ts +26 -21
- package/tests/unit/utils/logger.test.ts +267 -260
- package/tests/unit/utils/meta.test.ts +29 -28
- package/tests/unit/utils/mime.test.ts +73 -74
- package/tests/unit/utils/slugify.test.ts +14 -12
- package/tsconfig.build.json +9 -10
- package/tsconfig.jest.json +2 -1
- package/tsconfig.json +2 -2
- package/tsup.config.ts +8 -8
- package/typedoc.json +5 -9
- package/docs/demo.md +0 -46
- /package/docs/{portapack-transparent.png → public/portapack-transparent.png} +0 -0
- /package/docs/{portapack.jpg → public/portapack.jpg} +0 -0
package/src/core/minifier.ts
CHANGED
@@ -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 {
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
+
parsed: ParsedHTML,
|
120
|
+
options: BundleOptions = {},
|
121
|
+
logger?: Logger
|
119
122
|
): Promise<ParsedHTML> {
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
224
|
-
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
+
}
|