chore: reduce production bundle below 5 MB (#1130)
* chore: reduce production bundle below 5 MB * test: support npm paths in bundle assertion * test: stabilize OpenCode history retry coverage
This commit is contained in:
@@ -46,6 +46,7 @@
|
||||
"jsdom": "^26.1.0",
|
||||
"obsidian": "1.13.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"terser": "^5.50.0",
|
||||
"ts-jest": "^29.4.9",
|
||||
"tsx": "^4.23.1",
|
||||
"typescript": "^6.0.2",
|
||||
@@ -358,6 +359,8 @@
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
@@ -678,6 +681,8 @@
|
||||
|
||||
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
|
||||
|
||||
"commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
|
||||
@@ -1480,7 +1485,7 @@
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="],
|
||||
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||
|
||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||
|
||||
@@ -1542,6 +1547,8 @@
|
||||
|
||||
"tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
|
||||
|
||||
"terser": ["terser@5.50.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w=="],
|
||||
|
||||
"test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
|
||||
|
||||
"thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="],
|
||||
@@ -1816,6 +1823,8 @@
|
||||
|
||||
"jest-message-util/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
|
||||
|
||||
"jest-runner/source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="],
|
||||
|
||||
"jest-runtime/strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="],
|
||||
|
||||
"jest-snapshot/synckit": ["synckit@0.11.13", "", { "dependencies": { "@pkgr/core": "^0.3.6" } }, "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg=="],
|
||||
|
||||
+9
-1
@@ -12,6 +12,8 @@ import {
|
||||
} from 'fs';
|
||||
import rendererSafeUnrefHelpers from './scripts/rendererSafeUnref.js';
|
||||
import desktopRuntimeAliasHelpers from './scripts/desktopRuntimeAliases.js';
|
||||
import sourcePackageAliasHelpers from './scripts/sourcePackageAliases.js';
|
||||
import terserProductionBundleHelpers from './scripts/terserProductionBundle.js';
|
||||
import pierreShikiBundleHelpers from './scripts/pierreShikiBundle.js';
|
||||
import compressedStaticAssetsHelpers from './scripts/compressedStaticAssets.js';
|
||||
|
||||
@@ -20,6 +22,8 @@ const {
|
||||
patchRendererUnsafeUnrefSites,
|
||||
} = rendererSafeUnrefHelpers;
|
||||
const { createDesktopRuntimeAliases } = desktopRuntimeAliasHelpers;
|
||||
const { createSourcePackageAliases } = sourcePackageAliasHelpers;
|
||||
const { createTerserProductionBundlePlugin } = terserProductionBundleHelpers;
|
||||
const { createPierreShikiBundlePlugin } = pierreShikiBundleHelpers;
|
||||
const { createCompressedStaticAssetsPlugin } = compressedStaticAssetsHelpers;
|
||||
|
||||
@@ -189,12 +193,16 @@ const external = [
|
||||
|
||||
const mainContext = await esbuild.context({
|
||||
entryPoints: ['src/main.ts'],
|
||||
alias: createDesktopRuntimeAliases(),
|
||||
alias: {
|
||||
...createDesktopRuntimeAliases(),
|
||||
...createSourcePackageAliases(),
|
||||
},
|
||||
bundle: true,
|
||||
plugins: [
|
||||
patchSdkImportMeta,
|
||||
createCompressedStaticAssetsPlugin(),
|
||||
createPierreShikiBundlePlugin(),
|
||||
...(prod ? [createTerserProductionBundlePlugin(['main.js'])] : []),
|
||||
createPatchRendererUnsafeUnref(['main.js']),
|
||||
copyToObsidian,
|
||||
],
|
||||
|
||||
Generated
+49
@@ -54,6 +54,7 @@
|
||||
"jsdom": "^26.1.0",
|
||||
"obsidian": "1.13.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"terser": "^5.50.0",
|
||||
"ts-jest": "^29.4.9",
|
||||
"tsx": "^4.23.1",
|
||||
"typescript": "^6.0.2"
|
||||
@@ -2474,6 +2475,17 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
@@ -4635,6 +4647,13 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -11845,6 +11864,36 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.50.0.tgz",
|
||||
"integrity": "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
"jsdom": "^26.1.0",
|
||||
"obsidian": "1.13.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"terser": "^5.50.0",
|
||||
"ts-jest": "^29.4.9",
|
||||
"tsx": "^4.23.1",
|
||||
"typescript": "^6.0.2"
|
||||
|
||||
@@ -7,11 +7,10 @@ import ts from 'typescript';
|
||||
import {
|
||||
evaluationIndicatorMs,
|
||||
evaluationReviewThresholdMs,
|
||||
historicalMainWarningBytes,
|
||||
inspectArtifactSize,
|
||||
inspectEvaluationDuration,
|
||||
inspectPluginArtifactReferences,
|
||||
mainReviewThresholdBytes,
|
||||
mainBudgetBytes,
|
||||
preCollabReferenceMainBytes,
|
||||
} from './check-startup-performance.mjs';
|
||||
|
||||
@@ -417,16 +416,19 @@ test('ordinary main evaluation cannot reach Collab runtime foundations', () => {
|
||||
sourceImport.dynamic && sourceImport.specifier === './app/collab'
|
||||
)),
|
||||
);
|
||||
const collabReviewRoot = path.join(featuresRoot, 'collab', 'detail', 'review');
|
||||
assert.ok(
|
||||
listSourceImports(path.join(
|
||||
featuresRoot,
|
||||
'collab',
|
||||
'detail',
|
||||
'review',
|
||||
'CollabDiffRenderer.ts',
|
||||
))
|
||||
listSourceImports(path.join(collabReviewRoot, 'CollabDiffRenderer.ts'))
|
||||
.some(sourceImport => (
|
||||
sourceImport.dynamic && sourceImport.specifier === '@pierre/diffs'
|
||||
sourceImport.dynamic
|
||||
&& sourceImport.specifier === './CollabPierreDiffModule'
|
||||
)),
|
||||
);
|
||||
assert.ok(
|
||||
listSourceImports(path.join(collabReviewRoot, 'CollabPierreDiffModule.ts'))
|
||||
.some(sourceImport => (
|
||||
!sourceImport.dynamic
|
||||
&& sourceImport.specifier === '@pierre/diffs'
|
||||
)),
|
||||
);
|
||||
});
|
||||
@@ -824,16 +826,12 @@ test('source-only typecheck resolves the canonical collab protocol source', () =
|
||||
);
|
||||
});
|
||||
|
||||
test('performance policy reports the pre-Collab delta and review thresholds', () => {
|
||||
assert.deepEqual(inspectArtifactSize(historicalMainWarningBytes + 1), {
|
||||
historicalNotice: true,
|
||||
referenceDeltaBytes: historicalMainWarningBytes + 1 - preCollabReferenceMainBytes,
|
||||
reviewRequired: false,
|
||||
test('performance policy enforces the main bundle budget and reports the pre-Collab delta', () => {
|
||||
assert.deepEqual(inspectArtifactSize(mainBudgetBytes), {
|
||||
budgetExceeded: false,
|
||||
referenceDeltaBytes: mainBudgetBytes - preCollabReferenceMainBytes,
|
||||
});
|
||||
assert.equal(
|
||||
inspectArtifactSize(mainReviewThresholdBytes + 1).reviewRequired,
|
||||
true,
|
||||
);
|
||||
assert.equal(inspectArtifactSize(mainBudgetBytes + 1).budgetExceeded, true);
|
||||
assert.equal(inspectEvaluationDuration(evaluationIndicatorMs), 'within-indicator');
|
||||
assert.equal(inspectEvaluationDuration(evaluationIndicatorMs + 1), 'warning');
|
||||
assert.equal(
|
||||
|
||||
@@ -10,17 +10,15 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const mainPath = path.join(root, 'main.js');
|
||||
const requiredArtifacts = ['main.js', 'manifest.json', 'styles.css'];
|
||||
export const preCollabReferenceMainBytes = 3_739_584;
|
||||
export const historicalMainWarningBytes = 5_000_000;
|
||||
export const mainReviewThresholdBytes = 20_000_000;
|
||||
export const mainBudgetBytes = 5_000_000;
|
||||
export const evaluationIndicatorMs = 50;
|
||||
export const evaluationReviewThresholdMs = 150;
|
||||
const pluginArtifactNames = ['main.js', 'manifest.json'];
|
||||
|
||||
export function inspectArtifactSize(mainBytes) {
|
||||
return {
|
||||
historicalNotice: mainBytes > historicalMainWarningBytes,
|
||||
budgetExceeded: mainBytes > mainBudgetBytes,
|
||||
referenceDeltaBytes: mainBytes - preCollabReferenceMainBytes,
|
||||
reviewRequired: mainBytes > mainReviewThresholdBytes,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -80,14 +78,9 @@ function run() {
|
||||
|
||||
const mainBytes = statSync(mainPath).size;
|
||||
const artifact = inspectArtifactSize(mainBytes);
|
||||
if (artifact.reviewRequired) {
|
||||
if (artifact.budgetExceeded) {
|
||||
throw new Error(
|
||||
`main.js is ${mainBytes} bytes; the ${mainReviewThresholdBytes}-byte review threshold requires an explicit dependency decision.`,
|
||||
);
|
||||
}
|
||||
if (artifact.historicalNotice) {
|
||||
console.warn(
|
||||
`Bundle size notice: main.js is ${mainBytes} bytes; ${historicalMainWarningBytes} bytes is a historical warning, not a hard limit.`,
|
||||
`main.js is ${mainBytes} bytes; the production bundle budget is ${mainBudgetBytes} bytes.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -144,7 +137,13 @@ Module._load = function (request, parent, isMain) {
|
||||
return originalLoad.call(this, request, parent, isMain);
|
||||
};
|
||||
const startedAt = performance.now();
|
||||
require(mainPath);
|
||||
try {
|
||||
require(mainPath);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
|
||||
process.stderr.write('Module evaluation failed: ' + message);
|
||||
process.exit(1);
|
||||
}
|
||||
process.stdout.write(JSON.stringify({
|
||||
childProcessStarts,
|
||||
durationMs: performance.now() - startedAt,
|
||||
@@ -153,11 +152,18 @@ process.stdout.write(JSON.stringify({
|
||||
}));
|
||||
`;
|
||||
|
||||
const childNodePath = [
|
||||
path.join(root, 'node_modules', '.bun', 'node_modules'),
|
||||
process.env.NODE_PATH,
|
||||
].filter(candidate => candidate && existsSync(candidate)).join(path.delimiter);
|
||||
const samples = [];
|
||||
for (let index = 0; index < 7; index += 1) {
|
||||
const result = spawnSync(process.execPath, ['-e', childScript, mainPath], {
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
env: childNodePath
|
||||
? { ...process.env, NODE_PATH: childNodePath }
|
||||
: process.env,
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Module evaluation harness failed: ${result.stderr || result.stdout}`);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const { readFileSync } = require('node:fs');
|
||||
const { readFileSync, readdirSync } = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const {
|
||||
brotliCompressSync,
|
||||
constants: zlibConstants,
|
||||
@@ -6,6 +7,8 @@ const {
|
||||
|
||||
const localeFilter = /[\\/]src[\\/]i18n[\\/]locales[\\/][^\\/]+\.json$/;
|
||||
const sqlWasmFilter = /[\\/]node_modules[\\/]sql\.js[\\/]dist[\\/]sql-wasm\.wasm$/;
|
||||
const localeCatalogSpecifier = 'claudian:compressed-locale-catalog';
|
||||
const localeCatalogNamespace = 'compressed-locale-catalog';
|
||||
|
||||
function compress(contents, mode) {
|
||||
return brotliCompressSync(contents, {
|
||||
@@ -20,10 +23,47 @@ function decodeExpression(base64) {
|
||||
return `brotliDecompressSync(Buffer.from(${JSON.stringify(base64)}, "base64"))`;
|
||||
}
|
||||
|
||||
function createCompressedStaticAssetsPlugin() {
|
||||
function createCompressedStaticAssetsPlugin({ root = process.cwd() } = {}) {
|
||||
const localeDirectory = path.join(root, 'src', 'i18n', 'locales');
|
||||
|
||||
return {
|
||||
name: 'compressed-static-assets',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^claudian:compressed-locale-catalog$/ }, () => ({
|
||||
namespace: localeCatalogNamespace,
|
||||
path: 'non-english',
|
||||
}));
|
||||
|
||||
build.onLoad({ filter: /.*/, namespace: localeCatalogNamespace }, () => {
|
||||
const catalog = Object.fromEntries(
|
||||
readdirSync(localeDirectory)
|
||||
.filter(fileName => fileName.endsWith('.json') && fileName !== 'en.json')
|
||||
.sort()
|
||||
.map(fileName => [
|
||||
path.basename(fileName, '.json'),
|
||||
JSON.parse(readFileSync(path.join(localeDirectory, fileName), 'utf8')),
|
||||
]),
|
||||
);
|
||||
const base64 = compress(
|
||||
Buffer.from(JSON.stringify(catalog)),
|
||||
zlibConstants.BROTLI_MODE_TEXT,
|
||||
);
|
||||
return {
|
||||
contents: [
|
||||
'import { brotliDecompressSync } from "node:zlib";',
|
||||
`const compressedCatalog = ${JSON.stringify(base64)};`,
|
||||
'export function loadCompressedLocale(locale) {',
|
||||
' const bytes = brotliDecompressSync(Buffer.from(compressedCatalog, "base64"));',
|
||||
' const catalog = JSON.parse(bytes.toString("utf8"));',
|
||||
' const dictionary = catalog[locale];',
|
||||
' if (!dictionary) throw new Error(`Unsupported compressed locale: ${locale}`);',
|
||||
' return dictionary;',
|
||||
'}',
|
||||
].join('\n'),
|
||||
loader: 'js',
|
||||
};
|
||||
});
|
||||
|
||||
build.onLoad({ filter: sqlWasmFilter }, (args) => {
|
||||
const base64 = compress(
|
||||
readFileSync(args.path),
|
||||
@@ -46,11 +86,18 @@ function createCompressedStaticAssetsPlugin() {
|
||||
if (!exportNames.every(name => /^[$A-Z_a-z][$\w]*$/.test(name))) {
|
||||
throw new Error(`Locale ${args.path} has a top-level key that cannot be exported`);
|
||||
}
|
||||
const base64 = compress(raw, zlibConstants.BROTLI_MODE_TEXT);
|
||||
const locale = path.basename(args.path, '.json');
|
||||
const dictionaryExpression = locale === 'en'
|
||||
? `JSON.parse(${decodeExpression(
|
||||
compress(raw, zlibConstants.BROTLI_MODE_TEXT),
|
||||
)}.toString("utf8"))`
|
||||
: `loadCompressedLocale(${JSON.stringify(locale)})`;
|
||||
return {
|
||||
contents: [
|
||||
'import { brotliDecompressSync } from "node:zlib";',
|
||||
`const dictionary = JSON.parse(${decodeExpression(base64)}.toString("utf8"));`,
|
||||
locale === 'en'
|
||||
? 'import { brotliDecompressSync } from "node:zlib";'
|
||||
: `import { loadCompressedLocale } from ${JSON.stringify(localeCatalogSpecifier)};`,
|
||||
`const dictionary = ${dictionaryExpression};`,
|
||||
...exportNames.map(name => `const ${name} = dictionary.${name};`),
|
||||
`export { ${exportNames.join(', ')} };`,
|
||||
'export default dictionary;',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
const path = require('node:path');
|
||||
|
||||
function createSourcePackageAliases({ root = process.cwd() } = {}) {
|
||||
return Object.freeze({
|
||||
'@claudian/collab-protocol': path.join(
|
||||
root,
|
||||
'packages',
|
||||
'collab-protocol',
|
||||
'src',
|
||||
'index.ts',
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { createSourcePackageAliases };
|
||||
@@ -0,0 +1,49 @@
|
||||
const { promises: fs } = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { minify } = require('terser');
|
||||
|
||||
const preservedComment = /^!|@preserve|@license|@cc_on/i;
|
||||
|
||||
async function minifyProductionBundle(source) {
|
||||
const result = await minify(source, {
|
||||
compress: {
|
||||
passes: 3,
|
||||
toplevel: true,
|
||||
},
|
||||
ecma: 2022,
|
||||
format: {
|
||||
comments: preservedComment,
|
||||
},
|
||||
mangle: {
|
||||
toplevel: true,
|
||||
},
|
||||
module: false,
|
||||
});
|
||||
if (typeof result.code !== 'string') {
|
||||
throw new Error('Terser did not produce a production bundle');
|
||||
}
|
||||
return result.code;
|
||||
}
|
||||
|
||||
function createTerserProductionBundlePlugin(outputPaths) {
|
||||
return {
|
||||
name: 'terser-production-bundle',
|
||||
setup(build) {
|
||||
build.onEnd(async (result) => {
|
||||
if (result.errors.length > 0) return;
|
||||
const workingDirectory = build.initialOptions.absWorkingDir ?? process.cwd();
|
||||
for (const outputPath of outputPaths) {
|
||||
const absolutePath = path.resolve(workingDirectory, outputPath);
|
||||
const source = await fs.readFile(absolutePath, 'utf8');
|
||||
const minified = await minifyProductionBundle(source);
|
||||
await fs.writeFile(absolutePath, `${minified}\n`, 'utf8');
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createTerserProductionBundlePlugin,
|
||||
minifyProductionBundle,
|
||||
};
|
||||
@@ -9,8 +9,10 @@ import {
|
||||
import { lstat, open, readFile, unlink } from 'node:fs/promises';
|
||||
import { isIP } from 'node:net';
|
||||
|
||||
import forgePki from 'node-forge/lib/pki';
|
||||
import forgeAsn1 from 'node-forge/lib/asn1';
|
||||
import forgePem from 'node-forge/lib/pem';
|
||||
import forgeSha256 from 'node-forge/lib/sha256';
|
||||
import forgePki from 'node-forge/lib/x509';
|
||||
|
||||
import {
|
||||
ensureCollabContainerGuard,
|
||||
@@ -90,6 +92,19 @@ function normalizePem(pem: string): string {
|
||||
return `${pem.replace(/\r\n?/g, '\n').trim()}\n`;
|
||||
}
|
||||
|
||||
function privateKeyFromPem(pem: string): ReturnType<typeof forgePki.privateKeyFromAsn1> {
|
||||
const message = forgePem.decode(pem)[0];
|
||||
const procType = message?.procType as { readonly type?: unknown } | null | undefined;
|
||||
if (
|
||||
!message
|
||||
|| (message.type !== 'PRIVATE KEY' && message.type !== 'RSA PRIVATE KEY')
|
||||
|| procType?.type === 'ENCRYPTED'
|
||||
) {
|
||||
throw new Error('Unsupported RSA private key PEM');
|
||||
}
|
||||
return forgePki.privateKeyFromAsn1(forgeAsn1.fromDer(message.body));
|
||||
}
|
||||
|
||||
function certificateSerial(): string {
|
||||
const bytes = randomBytes(16);
|
||||
bytes[0] &= 0x7f;
|
||||
@@ -193,7 +208,7 @@ export class LanTlsIdentity {
|
||||
}
|
||||
const hostCa = await this.loadOrCreate();
|
||||
const caCertificate = forgePki.certificateFromPem(hostCa.caCertificatePem);
|
||||
const caPrivateKey = forgePki.privateKeyFromPem(hostCa.caPrivateKeyPem);
|
||||
const caPrivateKey = privateKeyFromPem(hostCa.caPrivateKeyPem);
|
||||
const keyPairPem = await generateRsaKeyPair();
|
||||
const certificate = forgePki.createCertificate();
|
||||
const issuedAt = options.now ?? this.now();
|
||||
@@ -305,7 +320,7 @@ export class LanTlsIdentity {
|
||||
|
||||
private async createHostCa(): Promise<LanTlsHostCa> {
|
||||
const keyPairPem = await generateRsaKeyPair();
|
||||
const privateKey = forgePki.privateKeyFromPem(keyPairPem.privateKeyPem);
|
||||
const privateKey = privateKeyFromPem(keyPairPem.privateKeyPem);
|
||||
const certificate = forgePki.createCertificate();
|
||||
const now = this.now();
|
||||
certificate.publicKey = forgePki.publicKeyFromPem(keyPairPem.publicKeyPem);
|
||||
@@ -351,7 +366,7 @@ export class LanTlsIdentity {
|
||||
) {
|
||||
throw new Error('Host CA validation failed');
|
||||
}
|
||||
forgePki.privateKeyFromPem(normalizedPrivateKey);
|
||||
privateKeyFromPem(normalizedPrivateKey);
|
||||
return Object.freeze({
|
||||
caCertificatePem: normalizedCertificate,
|
||||
caFingerprint: fingerprintCertificatePem(normalizedCertificate),
|
||||
|
||||
@@ -130,7 +130,7 @@ let sharedPierreModulePromise: Promise<PierreDiffModule> | null = null;
|
||||
|
||||
export function preloadCollabDiffRenderer(): Promise<PierreDiffModule> {
|
||||
sharedPierreModulePromise ??= (
|
||||
import('@pierre/diffs') as unknown as Promise<PierreDiffModule>
|
||||
import('./CollabPierreDiffModule') as unknown as Promise<PierreDiffModule>
|
||||
).catch((error: unknown) => {
|
||||
sharedPierreModulePromise = null;
|
||||
throw error;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { FileDiff as PierreFileDiff } from '@pierre/diffs';
|
||||
|
||||
export const FileDiff = PierreFileDiff;
|
||||
Vendored
+21
-7
@@ -1,13 +1,27 @@
|
||||
declare module 'node-forge/lib/pki' {
|
||||
import type forge from 'node-forge';
|
||||
|
||||
const pki: typeof forge.pki;
|
||||
export default pki;
|
||||
}
|
||||
|
||||
declare module 'node-forge/lib/sha256' {
|
||||
import type forge from 'node-forge';
|
||||
|
||||
const sha256: typeof forge.md.sha256;
|
||||
export default sha256;
|
||||
}
|
||||
|
||||
declare module 'node-forge/lib/asn1' {
|
||||
import type forge from 'node-forge';
|
||||
|
||||
const asn1: typeof forge.asn1;
|
||||
export default asn1;
|
||||
}
|
||||
|
||||
declare module 'node-forge/lib/pem' {
|
||||
import type forge from 'node-forge';
|
||||
|
||||
const pem: typeof forge.pem;
|
||||
export default pem;
|
||||
}
|
||||
|
||||
declare module 'node-forge/lib/x509' {
|
||||
import type forge from 'node-forge';
|
||||
|
||||
const pki: typeof forge.pki;
|
||||
export default pki;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ import { build, stop } from 'esbuild';
|
||||
import * as compressedStaticAssetsHelpers from '../../../scripts/compressedStaticAssets.js';
|
||||
import * as desktopRuntimeAliasHelpers from '../../../scripts/desktopRuntimeAliases.js';
|
||||
import * as pierreShikiBundleHelpers from '../../../scripts/pierreShikiBundle.js';
|
||||
import * as sourcePackageAliasHelpers from '../../../scripts/sourcePackageAliases.js';
|
||||
|
||||
const { createDesktopRuntimeAliases } = desktopRuntimeAliasHelpers;
|
||||
const { createSourcePackageAliases } = sourcePackageAliasHelpers;
|
||||
const { createCompressedStaticAssetsPlugin } = compressedStaticAssetsHelpers;
|
||||
const {
|
||||
createPierreShikiBundlePlugin,
|
||||
@@ -30,12 +32,16 @@ const performanceScriptPath = path.join(root, 'scripts/check-startup-performance
|
||||
describe('Collab dependency envelope', () => {
|
||||
const tempDirectory = mkdtempSync(path.join(tmpdir(), 'claudian-collab-build-'));
|
||||
const bundlePath = path.join(tempDirectory, 'dependency-envelope.cjs');
|
||||
let bundleContributors: string[] = [];
|
||||
let bundleInputs: string[] = [];
|
||||
|
||||
beforeAll(async () => {
|
||||
const result = await build({
|
||||
absWorkingDir: root,
|
||||
alias: createDesktopRuntimeAliases(),
|
||||
alias: {
|
||||
...createDesktopRuntimeAliases(),
|
||||
...createSourcePackageAliases({ root }),
|
||||
},
|
||||
bundle: true,
|
||||
external: [
|
||||
...builtinModules,
|
||||
@@ -54,11 +60,18 @@ describe('Collab dependency envelope', () => {
|
||||
stdin: {
|
||||
contents: `
|
||||
import { WebSocket, WebSocketServer } from 'ws';
|
||||
import { parser as markdownParser } from '@lezer/markdown';
|
||||
import { scanCollabTicketReferences } from '@claudian/collab-protocol';
|
||||
import initSqlJs from 'sql.js';
|
||||
import sqlWasmBinary from 'sql.js/dist/sql-wasm.wasm';
|
||||
import * as english from './src/i18n/locales/en.json';
|
||||
import * as german from './src/i18n/locales/de.json';
|
||||
import { pierreThemes, shikiThemes } from '@pierre/theming/themes';
|
||||
import { CollabDiffRenderer } from './src/features/collab/detail/review/CollabDiffRenderer';
|
||||
import { LanTlsIdentity } from './src/app/collab/lan/LanTlsIdentity';
|
||||
import {
|
||||
CollabDiffRenderer,
|
||||
preloadCollabDiffRenderer,
|
||||
} from './src/features/collab/detail/review/CollabDiffRenderer';
|
||||
|
||||
export function probeWebSocket() {
|
||||
return [typeof WebSocket, typeof WebSocketServer];
|
||||
@@ -73,17 +86,31 @@ describe('Collab dependency envelope', () => {
|
||||
}
|
||||
|
||||
export async function probeDiffs() {
|
||||
const diffs = await import('@pierre/diffs');
|
||||
const diffs = await preloadCollabDiffRenderer();
|
||||
return typeof diffs.FileDiff;
|
||||
}
|
||||
|
||||
export function probeLocale() {
|
||||
return english.collab.commands.createProject;
|
||||
return [
|
||||
english.collab.commands.createProject,
|
||||
german.common.save,
|
||||
];
|
||||
}
|
||||
|
||||
export function probeMarkdownDependencies() {
|
||||
return [
|
||||
markdownParser.parse('# heading').length,
|
||||
scanCollabTicketReferences('References #12').length,
|
||||
];
|
||||
}
|
||||
|
||||
export function probeThemes() {
|
||||
return [pierreThemes.getThemeNames(), shikiThemes.getThemeNames()];
|
||||
}
|
||||
|
||||
export function probeTlsIdentity() {
|
||||
return typeof LanTlsIdentity;
|
||||
}
|
||||
export async function renderCollabTextDiff(container) {
|
||||
const renderer = new CollabDiffRenderer({
|
||||
themeSource: {
|
||||
@@ -105,8 +132,12 @@ describe('Collab dependency envelope', () => {
|
||||
sourcefile: 'collab-dependency-envelope.ts',
|
||||
},
|
||||
target: 'node24',
|
||||
treeShaking: true,
|
||||
});
|
||||
bundleInputs = Object.keys(result.metafile.inputs);
|
||||
bundleContributors = Object.entries(Object.values(result.metafile.outputs)[0].inputs)
|
||||
.filter(([, contribution]) => contribution.bytesInOutput > 0)
|
||||
.map(([input]) => input);
|
||||
}, 60_000);
|
||||
|
||||
afterAll(() => {
|
||||
@@ -166,12 +197,60 @@ describe('Collab dependency envelope', () => {
|
||||
expect(inlinedOnigurumaInputs).toEqual([]);
|
||||
});
|
||||
|
||||
it('retains only the Pierre component surface used by Collab', () => {
|
||||
const normalizedInputs = bundleContributors.map(input => input.replaceAll('\\\\', '/'));
|
||||
const unusedComponentInputs = normalizedInputs.filter(input => (
|
||||
input.endsWith('/@pierre/diffs/dist/components/CodeView.js')
|
||||
|| input.endsWith('/@pierre/diffs/dist/components/FileStream.js')
|
||||
|| input.endsWith('/@pierre/diffs/dist/components/UnresolvedFile.js')
|
||||
));
|
||||
|
||||
expect(unusedComponentInputs).toEqual([]);
|
||||
});
|
||||
|
||||
it('bundles one shared Markdown parser implementation', () => {
|
||||
const markdownParserContributors = bundleContributors
|
||||
.map(input => input.replaceAll('\\\\', '/'))
|
||||
.filter(input => input.includes('/@lezer/markdown/dist/'));
|
||||
|
||||
expect(markdownParserContributors).toHaveLength(1);
|
||||
expect(markdownParserContributors[0]).toMatch(
|
||||
/(?:^|\/)node_modules\/@lezer\/markdown\/dist\/index\.js$/,
|
||||
);
|
||||
});
|
||||
|
||||
it('excludes unused Forge PKCS and password-encryption modules', () => {
|
||||
const normalizedContributors = bundleContributors.map(input => (
|
||||
input.replaceAll('\\\\', '/')
|
||||
));
|
||||
const unusedForgeInputs = normalizedContributors.filter(input => (
|
||||
input.endsWith('/node-forge/lib/pbe.js')
|
||||
|| input.endsWith('/node-forge/lib/pbkdf2.js')
|
||||
|| input.endsWith('/node-forge/lib/pkcs12.js')
|
||||
|| input.endsWith('/node-forge/lib/pkcs7asn1.js')
|
||||
|| input.endsWith('/node-forge/lib/rc2.js')
|
||||
));
|
||||
|
||||
expect(unusedForgeInputs).toEqual([]);
|
||||
});
|
||||
|
||||
it('forces the Node WebSocket implementation inside the browser-oriented bundle', () => {
|
||||
const config = readFileSync(esbuildConfigPath, 'utf8');
|
||||
const aliases = createDesktopRuntimeAliases();
|
||||
const aliases = {
|
||||
...createDesktopRuntimeAliases(),
|
||||
...createSourcePackageAliases({ root }),
|
||||
};
|
||||
|
||||
expect(path.basename(aliases.ws)).toBe('index.js');
|
||||
expect(config).toContain('alias: createDesktopRuntimeAliases()');
|
||||
expect(aliases['@claudian/collab-protocol']).toBe(path.join(
|
||||
root,
|
||||
'packages',
|
||||
'collab-protocol',
|
||||
'src',
|
||||
'index.ts',
|
||||
));
|
||||
expect(config).toContain('...createDesktopRuntimeAliases()');
|
||||
expect(config).toContain('...createSourcePackageAliases()');
|
||||
expect(readFileSync(bundlePath, 'utf8')).not.toContain('ws does not work in the browser');
|
||||
expect(runBundle(`
|
||||
const dependencyEnvelope = require(process.argv[1]);
|
||||
@@ -179,20 +258,24 @@ describe('Collab dependency envelope', () => {
|
||||
`)).toBe('["function","function"]');
|
||||
});
|
||||
|
||||
it('treats 5 MB as historical guidance and 20 MB as the review gate', () => {
|
||||
it('enforces a 5 MB main bundle budget', () => {
|
||||
const script = readFileSync(performanceScriptPath, 'utf8');
|
||||
|
||||
expect(script).toContain('preCollabReferenceMainBytes = 3_739_584');
|
||||
expect(script).toContain('historicalMainWarningBytes = 5_000_000');
|
||||
expect(script).toContain('mainReviewThresholdBytes = 20_000_000');
|
||||
expect(script).toContain('mainBudgetBytes = 5_000_000');
|
||||
expect(script).toContain('evaluationReviewThresholdMs = 150');
|
||||
expect(script).toContain('pre-Collab reference delta');
|
||||
expect(script).not.toContain('mainBudgetBytes');
|
||||
expect(script).toContain('artifact.budgetExceeded');
|
||||
expect(script).not.toContain('historicalMainWarningBytes');
|
||||
expect(script).not.toContain('mainReviewThresholdBytes');
|
||||
});
|
||||
|
||||
it('guards ordinary evaluation from deferred runtime initialization', () => {
|
||||
const script = readFileSync(performanceScriptPath, 'utf8');
|
||||
|
||||
expect(script).toContain('Module evaluation failed:');
|
||||
expect(script).toContain("path.join(root, 'node_modules', '.bun', 'node_modules')");
|
||||
expect(script).toContain('NODE_PATH: childNodePath');
|
||||
expect(script).toContain('childProcessStarts !== 0');
|
||||
expect(script).toContain('networkListens !== 0');
|
||||
expect(script).toContain('wasmInitializations !== 0');
|
||||
@@ -219,8 +302,10 @@ describe('Collab dependency envelope', () => {
|
||||
exports: [
|
||||
'probeDiffs',
|
||||
'probeLocale',
|
||||
'probeMarkdownDependencies',
|
||||
'probeSql',
|
||||
'probeThemes',
|
||||
'probeTlsIdentity',
|
||||
'probeWebSocket',
|
||||
'renderCollabTextDiff',
|
||||
],
|
||||
@@ -244,12 +329,25 @@ describe('Collab dependency envelope', () => {
|
||||
const bundle = readFileSync(bundlePath, 'utf8');
|
||||
const localeResult = runBundle(`
|
||||
const dependencyEnvelope = require(process.argv[1]);
|
||||
process.stdout.write(dependencyEnvelope.probeLocale());
|
||||
process.stdout.write(JSON.stringify(dependencyEnvelope.probeLocale()));
|
||||
`);
|
||||
|
||||
expect(bundle).toContain('brotliDecompressSync');
|
||||
expect(bundle).not.toContain('Create Collab project');
|
||||
expect(localeResult).toBe('Create Collab project');
|
||||
expect(JSON.parse(localeResult)).toEqual([
|
||||
'Create Collab project',
|
||||
'Speichern',
|
||||
]);
|
||||
});
|
||||
|
||||
it('shares one compressed catalog across non-English locales', () => {
|
||||
const compressedCatalogContributors = bundleContributors.filter(input => (
|
||||
input.includes('compressed-locale-catalog')
|
||||
));
|
||||
|
||||
expect(compressedCatalogContributors).toEqual([
|
||||
'compressed-locale-catalog:non-english',
|
||||
]);
|
||||
});
|
||||
|
||||
it('mounts Collab review through the styled Pierre custom element', () => {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as terserProductionBundleHelpers from '../../../scripts/terserProductionBundle.js';
|
||||
|
||||
const { minifyProductionBundle } = terserProductionBundleHelpers;
|
||||
|
||||
describe('production bundle minification', () => {
|
||||
it('preserves CommonJS behavior and license comments while reducing output', async () => {
|
||||
const source = `
|
||||
/*! Example dependency license */
|
||||
function computeVisibleResult(firstValue, secondValue) {
|
||||
const unusedIntermediateName = firstValue * 100;
|
||||
if (unusedIntermediateName < 0) throw new Error('unreachable');
|
||||
return firstValue + secondValue;
|
||||
}
|
||||
module.exports = { computeVisibleResult };
|
||||
`;
|
||||
|
||||
const output = await minifyProductionBundle(source);
|
||||
const module = { exports: {} as { computeVisibleResult?: (left: number, right: number) => number } };
|
||||
Function('module', 'exports', output)(module, module.exports);
|
||||
|
||||
expect(output.length).toBeLessThan(source.length);
|
||||
expect(output).toContain('Example dependency license');
|
||||
expect(module.exports.computeVisibleResult?.(20, 22)).toBe(42);
|
||||
});
|
||||
});
|
||||
@@ -88,7 +88,7 @@ describe('OpencodeConversationHistoryService', () => {
|
||||
userMessageId: 'msg-user',
|
||||
},
|
||||
]);
|
||||
});
|
||||
}, 15_000);
|
||||
|
||||
it('recovers the last OpenCode model from native message metadata', async () => {
|
||||
const dbPath = path.join(tmpRoot, 'model-history.db');
|
||||
|
||||
Reference in New Issue
Block a user