Skip to content

Commit 27fd9ff

Browse files
Merge branch 'master' of github.com:ampproject/filesize
2 parents 6b7901e + 5338d11 commit 27fd9ff

File tree

13 files changed

+191
-74
lines changed

13 files changed

+191
-74
lines changed

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"printWidth": 150,
2+
"printWidth": 120,
33
"trailingComma": "all",
44
"parser": "typescript",
55
"singleQuote": true

package.json

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"name": "@ampproject/filesize",
3-
"version": "3.0.2",
3+
"version": "3.1.0",
44
"description": "Audit the filesize of items specified in package.json.",
55
"author": "Kristofer Baxter",
66
"license": "Apache-2.0",
7-
"main": "dist/filesize",
7+
"main": "dist/api.js",
88
"module": "dist/api.mjs",
99
"files": [
1010
"dist"
@@ -32,28 +32,27 @@
3232
"mri": "1.1.4"
3333
},
3434
"devDependencies": {
35-
"@ampproject/rollup-plugin-closure-compiler": "0.21.0",
35+
"@ampproject/rollup-plugin-closure-compiler": "0.22.2",
3636
"@rollup/plugin-commonjs": "11.0.2",
3737
"@rollup/plugin-node-resolve": "7.1.1",
3838
"@rollup/plugin-typescript": "3.0.0",
3939
"@types/bytes": "3.1.0",
4040
"@types/mri": "1.1.0",
41-
"@types/node": "12.12.26",
42-
"ava": "3.2.0",
43-
"husky": "4.2.1",
41+
"@types/node": "12.12.27",
42+
"ava": "3.3.0",
43+
"husky": "4.2.3",
4444
"lint-staged": "10.0.7",
45-
"magic-string": "0.25.6",
46-
"np": "6.0.0",
45+
"np": "6.1.0",
4746
"npm-run-all": "4.1.5",
4847
"prettier": "1.19.1",
49-
"rimraf": "3.0.1",
50-
"rollup": "1.31.0",
48+
"rimraf": "3.0.2",
49+
"rollup": "1.31.1",
5150
"shx": "0.3.2",
5251
"tslib": "1.10.0",
5352
"typescript": "3.7.5"
5453
},
5554
"volta": {
56-
"node": "12.15.0",
55+
"node": "12.16.0",
5756
"yarn": "1.22.0"
5857
},
5958
"filesize": {
@@ -89,5 +88,8 @@
8988
"pre-push": "yarn npm-run-all clean build test",
9089
"pre-commit": "lint-staged"
9190
}
91+
},
92+
"publishConfig": {
93+
"access": "public"
9294
}
9395
}

rollup.config.js

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,13 @@ import resolve from '@rollup/plugin-node-resolve';
1818
import typescript from '@rollup/plugin-typescript';
1919
import commonjs from '@rollup/plugin-commonjs';
2020
import compiler from '@ampproject/rollup-plugin-closure-compiler';
21-
import MagicString from 'magic-string';
22-
23-
function makeExecutable() {
24-
return {
25-
name: 'make-executable',
26-
renderChunk(code, chunkInfo) {
27-
if (chunkInfo.fileName === 'filesize') {
28-
const magicString = new MagicString(code);
29-
magicString.prepend('#!/usr/bin/env node\n\n');
30-
return { code: magicString.toString(), map: magicString.generateMap({ hires: true }) };
31-
}
32-
},
33-
};
34-
}
3521

3622
const external = ['os', 'zlib', 'path', 'fs', 'stream', 'util', 'events', 'fast-glob', 'process'];
3723
const plugins = executable => [
3824
resolve({ preferBuiltins: true }),
3925
commonjs({ include: 'node_modules/**' }),
4026
typescript({ tsconfig: 'src/tsconfig.json', include: '**/*.ts', exclude: 'dist/**/*.ts' }),
4127
executable ? compiler() : null,
42-
executable ? makeExecutable() : null,
4328
];
4429

4530
export default [
@@ -49,15 +34,16 @@ export default [
4934
file: 'dist/filesize',
5035
format: 'cjs',
5136
sourcemap: true,
37+
banner: '#!/usr/bin/env node',
5238
},
5339
external,
5440
plugins: plugins(true),
5541
},
5642
{
57-
input: 'src/index.ts',
43+
input: 'src/api.ts',
5844
output: {
59-
file: 'dist/index.js',
60-
format: 'cjs',
45+
file: 'dist/api.mjs',
46+
format: 'esm',
6147
sourcemap: true,
6248
},
6349
external,
@@ -66,8 +52,8 @@ export default [
6652
{
6753
input: 'src/api.ts',
6854
output: {
69-
file: 'dist/api.mjs',
70-
format: 'esm',
55+
file: 'dist/api.js',
56+
format: 'cjs',
7157
sourcemap: true,
7258
},
7359
external,

src/api.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import Config from './validation/Config';
1919
import { Context, SizeMap } from './validation/Condition';
2020
import compress from './compress';
2121

22-
export async function report(projectPath: string): Promise<[SizeMap, SizeMap]> {
22+
export async function* report(
23+
projectPath: string,
24+
fileModifier: ((contents: string) => string) | null,
25+
): AsyncGenerator<[SizeMap, SizeMap]> {
2326
const conditions = [Project, Config];
2427
let context: Context = {
2528
projectPath,
@@ -31,6 +34,8 @@ export async function report(projectPath: string): Promise<[SizeMap, SizeMap]> {
3134
compressed: new Map(),
3235
// Stores the basis of comparison.
3336
comparison: new Map(),
37+
fileModifier,
38+
fileContents: new Map(),
3439
};
3540

3641
for (const condition of conditions) {
@@ -40,6 +45,11 @@ export async function report(projectPath: string): Promise<[SizeMap, SizeMap]> {
4045
}
4146
}
4247

43-
await compress(context);
48+
const compressResults = compress(context, false);
49+
let nextResult = await compressResults.next();
50+
while (!nextResult.done) {
51+
yield [context.compressed, context.comparison];
52+
nextResult = await compressResults.next();
53+
}
4454
return [context.compressed, context.comparison];
4555
}

src/compress.ts

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,13 @@ interface CompressionItem {
4848
* @param error Error from compressing an Item
4949
* @param size actual size for this comparison
5050
*/
51-
function store(report: Report | null, context: Context, item: CompressionItem, error: Error | null, size: number): boolean {
51+
function store(
52+
report: Report | null,
53+
context: Context,
54+
item: CompressionItem,
55+
error: Error | null,
56+
size: number,
57+
): boolean {
5258
if (error !== null) {
5359
LogError(`Could not compress '${item.path}' with '${item.compression}'.`);
5460
return false;
@@ -70,46 +76,64 @@ function store(report: Report | null, context: Context, item: CompressionItem, e
7076
}
7177

7278
/**
73-
* Given a context, compress all Items within splitting work eagly per cpu core to achieve some concurrency.
74-
* @param context Finalized Valid Context from Configuration
79+
* Compress an Item and report status to the console.
80+
* @param item Configuration for an Item.
7581
*/
76-
export default async function compress(context: Context): Promise<boolean> {
77-
/**
78-
* Compress an Item and report status to the console.
79-
* @param item Configuration for an Item.
80-
*/
81-
async function compressor(item: CompressionItem): Promise<boolean> {
82-
const contents = await readFile(item.path);
83-
if (contents) {
84-
const buffer = Buffer.from(contents, 'utf8');
82+
async function compressor(report: Report | null, context: Context, item: CompressionItem): Promise<boolean> {
83+
const contents = context.fileContents.get(item.path);
84+
if (contents) {
85+
const buffer = Buffer.from(contents, 'utf8');
8586

86-
switch (item.compression) {
87-
case 'brotli':
88-
return new Promise(resolve =>
89-
brotliCompress(buffer, BROTLI_OPTIONS, (error: Error | null, result: Buffer) =>
90-
resolve(store(report, context, item, error, result.byteLength)),
91-
),
92-
);
93-
case 'gzip':
94-
return new Promise(resolve =>
95-
gzip(buffer, GZIP_OPTIONS, (error: Error | null, result: Buffer) => resolve(store(report, context, item, error, result.byteLength))),
96-
);
97-
default:
98-
return store(report, context, item, null, buffer.byteLength);
99-
}
87+
switch (item.compression) {
88+
case 'brotli':
89+
return new Promise(resolve =>
90+
brotliCompress(buffer, BROTLI_OPTIONS, (error: Error | null, result: Buffer) =>
91+
resolve(store(report, context, item, error, result.byteLength)),
92+
),
93+
);
94+
case 'gzip':
95+
return new Promise(resolve =>
96+
gzip(buffer, GZIP_OPTIONS, (error: Error | null, result: Buffer) =>
97+
resolve(store(report, context, item, error, result.byteLength)),
98+
),
99+
);
100+
default:
101+
return store(report, context, item, null, buffer.byteLength);
100102
}
103+
}
101104

102-
return false;
105+
return false;
106+
}
107+
108+
/**
109+
* Store the original content so it isn't retrieved from FileSystem for each compression.
110+
* @param context
111+
* @param path
112+
*/
113+
async function storeOriginalFileContents(context: Context, path: string): Promise<void> {
114+
if (!context.fileContents.has(path)) {
115+
let content = await readFile(path);
116+
if (context.fileModifier !== null && content !== null) {
117+
content = context.fileModifier(content);
118+
}
119+
context.fileContents.set(path, content || '');
103120
}
121+
}
104122

105-
let report: Report | null = null;
123+
/**
124+
* Find all items to compress, and store them for future compression.
125+
* @param context
126+
* @param findDefaultSize
127+
*/
128+
async function findItemsToCompress(context: Context, findDefaultSize: boolean): Promise<Array<CompressionItem>> {
106129
const toCompress: Array<CompressionItem> = [];
107130
for (const [path, sizeMapValue] of context.compressed) {
108131
for (let iterator: number = 0; iterator < OrderedCompressionValues.length; iterator++) {
109132
const compression: Compression = OrderedCompressionValues[iterator] as Compression;
110133
const [size, maxSize] = sizeMapValue[iterator];
111-
if (compression === 'none') {
112-
await compressor({ path, compression, maxSize });
134+
await storeOriginalFileContents(context, path);
135+
if (findDefaultSize && compression === 'none') {
136+
await compressor(null, context, { path, compression, maxSize });
113137
}
114138
if (size !== undefined) {
115139
toCompress.push({
@@ -121,18 +145,35 @@ export default async function compress(context: Context): Promise<boolean> {
121145
}
122146
}
123147

124-
report = stdout.isTTY && toCompress.length < 30 ? new TTYReport(context) : new Report(context);
148+
return toCompress;
149+
}
150+
151+
/**
152+
* Given a context, compress all Items within splitting work eagly per cpu core to achieve some concurrency.
153+
* @param context Finalized Valid Context from Configuration
154+
*/
155+
export default async function* compress(context: Context, outputReport: boolean): AsyncGenerator<boolean, boolean> {
156+
const toCompress: Array<CompressionItem> = await findItemsToCompress(context, true);
157+
const report: Report | null = outputReport
158+
? null
159+
: stdout.isTTY && toCompress.length < 30
160+
? new TTYReport(context)
161+
: new Report(context);
125162
let success: boolean = true;
163+
126164
for (let iterator: number = 0; iterator < toCompress.length; iterator += COMPRESSION_CONCURRENCY) {
127165
if (iterator === 0) {
128-
report.update(context);
166+
report?.update(context);
129167
}
130-
let itemsSuccessful = await Promise.all(toCompress.slice(iterator, iterator + COMPRESSION_CONCURRENCY).map(compressor));
168+
let itemsSuccessful = await Promise.all(
169+
toCompress.slice(iterator, iterator + COMPRESSION_CONCURRENCY).map(item => compressor(report, context, item)),
170+
);
131171
if (itemsSuccessful.includes(false)) {
132172
success = false;
133173
}
174+
yield success;
134175
}
135176

136-
report.end();
177+
report?.end();
137178
return success;
138179
}

src/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const args = mri(process.argv.slice(2), {
4343
compressed: new Map(),
4444
// Stores the basis of comparison.
4545
comparison: new Map(),
46+
fileModifier: null,
47+
fileContents: new Map(),
4648
};
4749

4850
let errorOccured: boolean = false;
@@ -56,7 +58,17 @@ const args = mri(process.argv.slice(2), {
5658
}
5759

5860
if (!errorOccured) {
59-
if (!(await compress(context))) {
61+
const compressResults = compress(context, false);
62+
let successful: boolean = true;
63+
let nextResult: IteratorResult<boolean, boolean> = await compressResults.next();
64+
while (!nextResult.done) {
65+
if (!nextResult.value) {
66+
successful = false;
67+
}
68+
nextResult = await compressResults.next();
69+
}
70+
71+
if (!successful) {
6072
shutdown(6);
6173
}
6274
}

src/validation/Condition.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export const SizeMapValueIndex = {
4747
};
4848
export type SizeMap = Map<path, SizeMapValue>;
4949

50+
export type FileContentsMap = Map<path, string>;
51+
5052
export interface Context {
5153
projectPath: string;
5254
packagePath: string;
@@ -57,4 +59,8 @@ export interface Context {
5759
compressed: SizeMap;
5860
// Stores the basis of comparison.
5961
comparison: SizeMap;
62+
// Allows the API to specify a method that alters content before analysis.
63+
fileModifier: ((contents: string) => string) | null;
64+
// Stores the contents of files, to avoid reading from disk per compression type.
65+
fileContents: FileContentsMap;
6066
}

0 commit comments

Comments
 (0)