From 0fd7b27d08235450ccc9d53e52db41273cef617f Mon Sep 17 00:00:00 2001 From: masi Date: Sat, 26 Sep 2026 14:25:48 -0300 Subject: [PATCH] perf(scripts): share npm dependencies across plugin bundles Bundled standalone, every plugin carried its own cheerio/htmlparser2/etc.: loading 165 plugins took ~13 s and 1.2 GB (2.2 GB while building). With npm packages external they share node_modules: 0.5 s / 104 MB (build 1.5 s). Co-Authored-By: Claude Opus 5.5 --- .gitignore | 1 + scripts/load-plugins.js | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 7538883..b84d1cc 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ broken-sites-report.json .agents/ .claude/ .cursor/ +.plugin-cache/ diff --git a/scripts/load-plugins.js b/scripts/load-plugins.js index 1267474..18a640e 100644 --- a/scripts/load-plugins.js +++ b/scripts/load-plugins.js @@ -1,7 +1,9 @@ // Load compiled plugin instances outside the app, for the novel CLI scripts. -// Bundling ~165 plugins takes ~15 s and each bundle carries its own libraries, so the -// bundle is built once per source revision and cached, with an id -> file index: -// fetching a novel then loads a single plugin instead of all of them. +// npm packages stay external, so all plugins share one copy of cheerio & co. from +// node_modules (bundled standalone, each plugin carried its own: ~1.2 GB for 165). +// The bundle is built once per source revision and cached with an id -> file index, +// so fetching a novel loads a single plugin. The cache lives inside the repo so the +// bundles resolve node_modules from it. import * as esbuild from 'esbuild'; import { execFileSync } from 'child_process'; import { createHash } from 'crypto'; @@ -9,13 +11,11 @@ import { createRequire } from 'module'; import { fileURLToPath } from 'url'; import path, { dirname } from 'path'; import fs from 'fs'; -import os from 'os'; const require = createRequire(import.meta.url); export const ROOT = path.resolve(dirname(fileURLToPath(import.meta.url)), '..'); const CACHE = - process.env.NOVEL_PLUGIN_CACHE || - path.join(os.homedir(), '.cache', 'novel-plugins'); + process.env.NOVEL_PLUGIN_CACHE || path.join(ROOT, '.plugin-cache'); export const LANGS = ['english', 'multi']; // The shared fetch helper logs every request with its headers (credentials @@ -72,6 +72,7 @@ async function bundle(langs) { format: 'cjs', target: 'node22', outExtension: { '.js': '.cjs' }, + packages: 'external', logLevel: 'silent', alias: { '@libs': path.join(ROOT, 'src/libs'),