Skip to content

Commit e5f1533

Browse files
committed
optimise zip
1 parent a153608 commit e5f1533

File tree

7 files changed

+920
-54
lines changed

7 files changed

+920
-54
lines changed

src/bundle-pack.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'path';
22
import * as fs from 'fs-extra';
33
import { ZipFile as YazlZipFile } from 'yazl';
44
import { t } from './utils/i18n';
5+
import { zipOptionsForPayloadFile } from './utils/zip-options';
56

67
const ignorePackingFileNames = [
78
'.',
@@ -32,7 +33,11 @@ export async function packBundle(dir: string, output: string): Promise<void> {
3233
const fullPath = path.join(root, name);
3334
const stat = fs.statSync(fullPath);
3435
if (stat.isFile()) {
35-
zipfile.addFile(fullPath, rel + name);
36+
zipfile.addFile(
37+
fullPath,
38+
rel + name,
39+
zipOptionsForPayloadFile(fullPath, rel + name),
40+
);
3641
} else if (stat.isDirectory()) {
3742
addDirectory(fullPath, `${rel}${name}/`);
3843
}

src/diff.ts

Lines changed: 209 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,54 @@ import type { CommandContext } from './types';
66
import { translateOptions } from './utils';
77
import { isPPKBundleFileName, scriptName, tempDir } from './utils/constants';
88
import { t } from './utils/i18n';
9-
import { enumZipEntries, readEntry } from './utils/zip-entries';
9+
import {
10+
enumZipEntries,
11+
readEntry,
12+
readEntryPrefix,
13+
} from './utils/zip-entries';
14+
import {
15+
ZIP_ENTRY_SNIFF_BYTES,
16+
zipOptionsForManifestEntry,
17+
zipOptionsForPatchEntry,
18+
zipOptionsForPayloadEntry,
19+
} from './utils/zip-options';
1020

1121
type Diff = (oldSource?: Buffer, newSource?: Buffer) => Buffer;
22+
type HpatchCover = {
23+
oldPos: number | string | bigint;
24+
newPos: number | string | bigint;
25+
len: number | string | bigint;
26+
};
27+
type HpatchCompatiblePlan = {
28+
covers?: HpatchCover[];
29+
};
30+
type HdiffWithCoversOptions = {
31+
mode?: 'replace' | 'merge' | 'native-coalesce';
32+
};
33+
type HdiffModule = {
34+
diff?: Diff;
35+
diffWithCovers?: (
36+
oldSource: Buffer,
37+
newSource: Buffer,
38+
covers: HpatchCover[],
39+
options?: HdiffWithCoversOptions,
40+
) => { diff?: Buffer };
41+
};
42+
type BsdiffModule = {
43+
diff?: Diff;
44+
};
45+
type ChiffModule = {
46+
hpatchCompatiblePlanResult?: (
47+
oldSource: Buffer,
48+
newSource: Buffer,
49+
) => HpatchCompatiblePlan;
50+
hpatchApproximatePlanResult?: (
51+
oldSource: Buffer,
52+
newSource: Buffer,
53+
) => HpatchCompatiblePlan;
54+
};
55+
type ChiffHpatchPolicy = 'off' | 'costed';
56+
type ChiffHpatchExactPolicy = 'off' | 'on';
1257
type EntryMap = Record<string, { crc32: number; fileName: string }>;
1358
type CrcMap = Record<number, string>;
1459
type CopyMap = Record<string, string>;
@@ -28,22 +73,134 @@ type DiffCommandConfig = {
2873

2974
export { enumZipEntries, readEntry };
3075

31-
const loadDiffModule = (pkgName: string): Diff | undefined => {
76+
const loadModule = <T>(pkgName: string): T | undefined => {
3277
const resolvePaths = ['.', npm.packages, yarn.packages];
3378

3479
try {
3580
const resolved = require.resolve(pkgName, { paths: resolvePaths });
36-
const mod = require(resolved);
37-
if (mod?.diff) {
38-
return mod.diff as Diff;
39-
}
81+
return require(resolved) as T;
4082
} catch {}
4183

4284
return undefined;
4385
};
4486

45-
const hdiff = loadDiffModule('node-hdiffpatch');
46-
const bsdiff = loadDiffModule('node-bsdiff');
87+
const hdiff = loadModule<HdiffModule>('node-hdiffpatch');
88+
const bsdiff = loadModule<BsdiffModule>('node-bsdiff');
89+
const chiff = loadModule<ChiffModule>('@chiff/node');
90+
91+
// Structured covers are experimental and can be expensive on real Hermes input.
92+
// Keep native hdiff as the default unless the server explicitly opts in.
93+
function resolveChiffHpatchPolicy(policy?: unknown): ChiffHpatchPolicy {
94+
const value = String(
95+
policy ?? process.env.RNU_CHIFF_HPATCH_POLICY ?? 'off',
96+
).toLowerCase();
97+
if (
98+
value === 'costed' ||
99+
value === 'on' ||
100+
value === 'true' ||
101+
value === '1'
102+
) {
103+
return 'costed';
104+
}
105+
return 'off';
106+
}
107+
108+
function resolveChiffHpatchMinNativeBytes(value?: unknown): number {
109+
const raw = value ?? process.env.RNU_CHIFF_HPATCH_MIN_NATIVE_BYTES ?? 4096;
110+
const parsed = Number(raw);
111+
if (!Number.isFinite(parsed) || parsed < 0) {
112+
return 4096;
113+
}
114+
return Math.floor(parsed);
115+
}
116+
117+
function resolveChiffHpatchExactPolicy(policy?: unknown): ChiffHpatchExactPolicy {
118+
const value = String(
119+
policy ?? process.env.RNU_CHIFF_HPATCH_EXACT_COVERS ?? 'off',
120+
).toLowerCase();
121+
if (value === 'on' || value === 'true' || value === '1') {
122+
return 'on';
123+
}
124+
return 'off';
125+
}
126+
127+
function createChiffAwareHdiff(
128+
hdiffModule: HdiffModule,
129+
chiffModule: ChiffModule | undefined,
130+
policy: ChiffHpatchPolicy,
131+
minNativeBytes: number,
132+
exactPolicy: ChiffHpatchExactPolicy,
133+
): Diff {
134+
const baseDiff = hdiffModule.diff;
135+
if (!baseDiff) {
136+
throw new Error(t('nodeHdiffpatchRequired', { scriptName }));
137+
}
138+
139+
if (policy === 'off') {
140+
return baseDiff;
141+
}
142+
143+
return (oldSource?: Buffer, newSource?: Buffer) => {
144+
const nativeDiff = baseDiff(oldSource, newSource);
145+
if (!oldSource || !newSource || !hdiffModule.diffWithCovers) {
146+
return nativeDiff;
147+
}
148+
149+
let bestDiff = nativeDiff;
150+
const tryDiffWithCovers = (
151+
covers: HpatchCover[],
152+
mode: 'replace' | 'merge' | 'native-coalesce',
153+
) => {
154+
try {
155+
const result = hdiffModule.diffWithCovers?.(
156+
oldSource,
157+
newSource,
158+
covers,
159+
{ mode },
160+
);
161+
if (
162+
Buffer.isBuffer(result?.diff) &&
163+
result.diff.length < bestDiff.length
164+
) {
165+
bestDiff = result.diff;
166+
}
167+
} catch {}
168+
};
169+
170+
tryDiffWithCovers([], 'native-coalesce');
171+
172+
if (nativeDiff.length < minNativeBytes) {
173+
return bestDiff;
174+
}
175+
176+
try {
177+
const approximatePlan = chiffModule?.hpatchApproximatePlanResult?.(
178+
oldSource,
179+
newSource,
180+
);
181+
if (Array.isArray(approximatePlan?.covers)) {
182+
tryDiffWithCovers(approximatePlan.covers, 'merge');
183+
}
184+
} catch {}
185+
186+
if (
187+
exactPolicy === 'off' ||
188+
!chiffModule?.hpatchCompatiblePlanResult
189+
) {
190+
return bestDiff;
191+
}
192+
193+
try {
194+
const plan = chiffModule.hpatchCompatiblePlanResult(oldSource, newSource);
195+
if (Array.isArray(plan.covers)) {
196+
tryDiffWithCovers(plan.covers, 'replace');
197+
tryDiffWithCovers(plan.covers, 'merge');
198+
}
199+
} catch {}
200+
201+
return bestDiff;
202+
};
203+
}
47204

48205
function basename(fn: string): string | undefined {
49206
const m = /^(.+\/)[^\/]+\/?$/.exec(fn);
@@ -123,6 +280,7 @@ async function diffFromPPK(
123280
zipfile.addBuffer(
124281
diffFn(originSource, newSource),
125282
`${entry.fileName}.patch`,
283+
zipOptionsForPatchEntry(),
126284
);
127285
//console.log('End diff');
128286
} else {
@@ -150,6 +308,11 @@ async function diffFromPPK(
150308
addEntry(basePath);
151309
}
152310

311+
const entryPrefix = await readEntryPrefix(
312+
entry,
313+
nextZipfile,
314+
ZIP_ENTRY_SNIFF_BYTES,
315+
);
153316
await new Promise<void>((resolve, reject) => {
154317
nextZipfile.openReadStream(entry, (err, readStream) => {
155318
if (err) {
@@ -160,7 +323,11 @@ async function diffFromPPK(
160323
new Error(`Unable to read zip entry: ${entry.fileName}`),
161324
);
162325
}
163-
zipfile.addReadStream(readStream, entry.fileName);
326+
zipfile.addReadStream(
327+
readStream,
328+
entry.fileName,
329+
zipOptionsForPayloadEntry(entry.fileName, entryPrefix),
330+
);
164331
readStream.on('end', () => {
165332
//console.log('add finished');
166333
resolve(void 0);
@@ -183,6 +350,7 @@ async function diffFromPPK(
183350
zipfile.addBuffer(
184351
Buffer.from(JSON.stringify({ copies, deletes })),
185352
'__diff.json',
353+
zipOptionsForManifestEntry(),
186354
);
187355
zipfile.end();
188356
await writePromise;
@@ -248,6 +416,7 @@ async function diffFromPackage(
248416
zipfile.addBuffer(
249417
diffFn(originSource, newSource),
250418
`${entry.fileName}.patch`,
419+
zipOptionsForPatchEntry(),
251420
);
252421
//console.log('End diff');
253422
} else {
@@ -263,6 +432,11 @@ async function diffFromPackage(
263432
return;
264433
}
265434

435+
const entryPrefix = await readEntryPrefix(
436+
entry,
437+
nextZipfile,
438+
ZIP_ENTRY_SNIFF_BYTES,
439+
);
266440
await new Promise<void>((resolve, reject) => {
267441
nextZipfile.openReadStream(entry, (err, readStream) => {
268442
if (err) {
@@ -273,7 +447,11 @@ async function diffFromPackage(
273447
new Error(`Unable to read zip entry: ${entry.fileName}`),
274448
);
275449
}
276-
zipfile.addReadStream(readStream, entry.fileName);
450+
zipfile.addReadStream(
451+
readStream,
452+
entry.fileName,
453+
zipOptionsForPayloadEntry(entry.fileName, entryPrefix),
454+
);
277455
readStream.on('end', () => {
278456
//console.log('add finished');
279457
resolve(void 0);
@@ -283,13 +461,22 @@ async function diffFromPackage(
283461
}
284462
});
285463

286-
zipfile.addBuffer(Buffer.from(JSON.stringify({ copies })), '__diff.json');
464+
zipfile.addBuffer(
465+
Buffer.from(JSON.stringify({ copies })),
466+
'__diff.json',
467+
zipOptionsForManifestEntry(),
468+
);
287469
zipfile.end();
288470
await writePromise;
289471
}
290472

291473
type DiffCommandOptions = {
292474
customDiff?: Diff;
475+
customHdiffModule?: HdiffModule;
476+
customChiffModule?: ChiffModule;
477+
chiffHpatchPolicy?: ChiffHpatchPolicy;
478+
chiffHpatchMinNativeBytes?: number | string;
479+
chiffHpatchExactCovers?: ChiffHpatchExactPolicy | boolean | string | number;
293480
[key: string]: any;
294481
};
295482

@@ -302,16 +489,23 @@ function resolveDiffImplementation(
302489
}
303490

304491
if (useHdiff) {
305-
if (!hdiff) {
492+
const hdiffModule = options.customHdiffModule ?? hdiff;
493+
if (!hdiffModule?.diff) {
306494
throw new Error(t('nodeHdiffpatchRequired', { scriptName }));
307495
}
308-
return hdiff;
496+
return createChiffAwareHdiff(
497+
hdiffModule,
498+
options.customChiffModule ?? chiff,
499+
resolveChiffHpatchPolicy(options.chiffHpatchPolicy),
500+
resolveChiffHpatchMinNativeBytes(options.chiffHpatchMinNativeBytes),
501+
resolveChiffHpatchExactPolicy(options.chiffHpatchExactCovers),
502+
);
309503
}
310504

311-
if (!bsdiff) {
505+
if (!bsdiff?.diff) {
312506
throw new Error(t('nodeBsdiffRequired', { scriptName }));
313507
}
314-
return bsdiff;
508+
return bsdiff.diff;
315509
}
316510

317511
function diffArgsCheck(

0 commit comments

Comments
 (0)