Compare commits

..
2 Commits
Author SHA1 Message Date
amitwh 111d36bf3d refactor(flowchart): build script generates bundle from pure modules (D1)
Before this commit, src/renderer/flowchart-bundle.js was a 1700+
line hand-maintained file with ~1100 lines of inlined copies of the
pure modules (shapes, mermaid, mermaid-parse, store, canvas,
clipboard, align). Every change to a pure module required a manual
re-sync, and the C17 multi-select commit had to be applied in two
places — drift between source and bundle was a real risk.

This refactor introduces scripts/build-flowchart-bundle.js that
reads each pure module from src/flowchart/ and concatenates them
into the final src/renderer/flowchart-bundle.js that the standalone
window loads as a single <script> tag.

Architecture:

  - src/renderer/flowchart-bundle.js — now auto-generated, gitignored
    only conceptually (still tracked so the standalone window works
    without running a build step in dev).
  - src/renderer/flowchart-bundle-tail.js — the unique code that
    was previously buried in the middle of the hand-maintained bundle:
    IIFE open, defensive checks, pure-module destructure, controller
    bootstrap, modal helpers, node-list panel, closing IIFE, and
    DOMContentLoaded bootstrap trigger.
  - scripts/build-flowchart-bundle.js — the generator. Reads 8 pure
    modules in dependency order (vsdx-export excluded — main-process
    only), wraps each in its own IIFE for scope isolation, rewrites
    canvas's CommonJS require() lines to use window globals, and
    rewrites bare 'module.exports = X' to 'window.FlowchartX = X'
    for modules without UMD guards.

Tests: 1386/1386 passing (no regressions). The pre-existing per-node
fill color test that was passing before now passes for the right
reason — the canvas source was updated to pass node.color through
to shapeSvg, which it always should have. The hand-maintained bundle
had been silently carrying a patched version that passed color but
was never backported to src/.

Amit Haridas
2026-10-01 07:31:37 +05:30
amitwh a13a87c499 chore(deps): npm update + Node 22 LTS engines
Brings the dependency tree up to the latest in-range versions of every
package: codemirror ^6.43.13, dompurify ^3.4.16, mermaid stays at
^11.17.2 (v12 needs a rendering QA pass before bumping), highlight.js
^11.12.0, jszip ^3.10.2, katex ^0.18.10, prettier ^3.9.9, etc.

Also pins the dev Node version to >=22 LTS via the engines field.
Currently running on Node 22.22.1.

Deliberately deferred (would each need a QA pass):

  - Electron 41 -> 44 (3 majors; would need to re-validate every
    IPC + BrowserWindow API used in main.js / preload.js / the
    standalone windows).
  - Mermaid 11 -> 12 (major rendering pipeline change).
  - Marked 17 -> 18 (markdown renderer rewrite).
  - ESLint 9 -> 10 (flat-config breaking changes).
  - pdfjs-dist 5 -> 6 (worker pipeline rework).
  - electron-store 10 -> 11 (storage backend swap).

In-range updates verified by the full test suite (1386/1386).

Amit Haridas
2026-10-01 07:11:18 +05:30
7 changed files with 5098 additions and 3702 deletions
+1845 -2535
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -4,6 +4,9 @@
"description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting", "description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting",
"main": "src/main.js", "main": "src/main.js",
"desktopName": "MarkdownConverter", "desktopName": "MarkdownConverter",
"engines": {
"node": ">=22.0.0"
},
"scripts": { "scripts": {
"start": "electron .", "start": "electron .",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest", "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
@@ -26,6 +29,7 @@
"dist:all": "electron-builder --config electron-builder.config.js -mwl", "dist:all": "electron-builder --config electron-builder.config.js -mwl",
"download-tools": "node scripts/download-tools.js", "download-tools": "node scripts/download-tools.js",
"bundle:markitdown": "node scripts/bundle-markitdown.js", "bundle:markitdown": "node scripts/bundle-markitdown.js",
"build:bundle": "node scripts/build-flowchart-bundle.js",
"generate-icons": "node scripts/generate-icons.js" "generate-icons": "node scripts/generate-icons.js"
}, },
"keywords": [ "keywords": [
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env node
// v4.13.1 — Flowchart bundle generator.
//
// Reads the pure modules from src/flowchart/ and the unique "tail"
// (controller bootstrap + modal helpers + node-list panel) from
// src/renderer/flowchart-bundle-tail.js, then concatenates them into
// the final src/renderer/flowchart-bundle.js that the standalone
// BrowserWindow loads as a single <script> tag.
//
// Why this exists: before this script, the bundle manually inlined
// ~1100 lines of pure modules at the top. Every change to a pure
// module required a manual re-sync, which led to drift (the C17
// multi-select change had to be applied in two places, and a missed
// sync would silently break the standalone window).
//
// Usage: npm run build:bundle
// Output: src/renderer/flowchart-bundle.js
//
// @module build-flowchart-bundle
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..');
const SRC = path.join(ROOT, 'src');
const FLOWCHART_DIR = path.join(SRC, 'flowchart');
const TAIL_PATH = path.join(SRC, 'renderer', 'flowchart-bundle-tail.js');
const OUTPUT_PATH = path.join(SRC, 'renderer', 'flowchart-bundle.js');
// Pure modules in load order. flowchart-vsdx-export is intentionally
// excluded — it runs in the main process only (consumed by the IPC
// handler), never by the renderer. The remaining modules are
// collision-free at the top level (each declares its own unique
// constants) so they can be concatenated without IIFE wrapping.
const PURE_MODULES = [
'flowchart-shapes.js',
'flowchart-viewport.js',
'flowchart-mermaid.js',
'flowchart-mermaid-parse.js',
'flowchart-clipboard.js',
'flowchart-store.js',
'flowchart-canvas.js',
'flowchart-align.js',
];
function readModule(filename) {
const filePath = path.join(FLOWCHART_DIR, filename);
if (!fs.existsSync(filePath)) {
throw new Error('build-flowchart-bundle: missing pure module ' + filePath);
}
let source = fs.readFileSync(filePath, 'utf-8');
const basename = filename.replace(/\.js$/, '');
// Camel-case the basename for the window global: flowchart-mermaid-parse
// -> FlowchartMermaidParse, flowchart-clipboard -> FlowchartClipboard.
const windowName = 'window.Flowchart' + basename
.replace(/^flowchart-/, '')
.split('-')
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
.join('');
// v4.13.1 — strip CommonJS-isms before concatenating into the bundle.
// The bundle is a browser-only script (no Node `module` / `require`),
// so we have to:
// 1. Replace `const shapesModule = (typeof window !== 'undefined'
// && window.FlowchartShapes) || require('./flowchart-shapes')`
// with the pure browser form so canvas can still pick up
// SHAPE_KINDS / DEFAULT_WIDTH / etc. from the shapes module
// that was inlined just above.
// 2. Replace any top-level `module.exports = ...` lines that some
// modules (mermaid-parse, vsdx-export) have without a UMD
// guard with an equivalent `window.X = ...` assignment so the
// bundle's tail can still find the module's exports.
// The CommonJS paths stay intact for tests + the sidebar panel.
if (filename === 'flowchart-canvas.js') {
source = source.replace(
/const shapesModule =\s*\n?\s*\(typeof window[\s\S]*?require\('\.\/flowchart-shapes'\);/,
'const shapesModule = window.FlowchartShapes;'
);
source = source.replace(
/const viewportModule =\s*\n?\s*\(typeof window[\s\S]*?require\('\.\/flowchart-viewport'\);/,
'const viewportModule = window.FlowchartViewport;'
);
}
// Replace bare `module.exports = X;` with `window.X = X;` so the
// tail can pick up the module's exports from window globals.
source = source.replace(
/^module\.exports\s*=\s*([^\n;]+);?\s*$/gm,
windowName + ' = $1;'
);
source = source.replace(/\n{3,}/g, '\n\n');
return source;
}
function buildHeader() {
const parts = [
'// v4.13.1 — AUTO-GENERATED FILE. DO NOT EDIT.',
'//',
'// This file is the concatenation of every pure module in',
'// src/flowchart/ plus the unique tail from',
'// src/renderer/flowchart-bundle-tail.js. Regenerate it by running',
"// `npm run build:bundle` whenever a pure module changes.",
'//',
'// Each pure module is wrapped in its own IIFE so top-level',
'// `const` declarations (DEFAULT_WIDTH, DEFAULT_HEIGHT, SHAPE_KINDS,',
'// etc.) stay local to that module and do not collide with',
'// identically-named declarations in other modules. The UMD',
'// wrappers inside each module continue to expose the public API',
'// as window.FlowchartXxx, which the tail consumes.',
];
for (const filename of PURE_MODULES) {
const basename = filename.replace(/\.js$/, '');
parts.push('');
parts.push(' // ========== ' + basename + ' (inlined by build script) ==========');
// Wrap each module in its own IIFE for scope isolation. The UMD
// wrapper inside still runs and assigns to window.FlowchartXxx
// on the outer window object.
parts.push('(function () {');
parts.push(readModule(filename));
parts.push('})();');
}
return parts.join('\n');
}
function buildTail() {
if (!fs.existsSync(TAIL_PATH)) {
throw new Error('build-flowchart-bundle: missing tail ' + TAIL_PATH);
}
return fs.readFileSync(TAIL_PATH, 'utf-8');
}
function main() {
const header = buildHeader();
const tail = buildTail();
const output = header + '\n' + tail;
fs.writeFileSync(OUTPUT_PATH, output);
const stat = fs.statSync(OUTPUT_PATH);
console.log(
'build-flowchart-bundle: wrote ' +
path.relative(ROOT, OUTPUT_PATH) +
' (' +
stat.size +
' bytes, ' +
PURE_MODULES.length +
' pure modules + tail)'
);
}
if (require.main === module) {
try {
main();
} catch (err) {
console.error(err.message);
process.exit(1);
}
}
module.exports = { main, PURE_MODULES };
+4 -3
View File
@@ -19,14 +19,15 @@
'use strict'; 'use strict';
const DEFAULT_HEIGHT = 60; // v4.13.1 — DEFAULT_HEIGHT constant removed and inlined as 60 to avoid
// colliding with shapes.js's identically-named const when the build
// script concatenates both into the bundle.
function nodeWidth(node) { function nodeWidth(node) {
return Number(node.width) > 0 ? Number(node.width) : 120; return Number(node.width) > 0 ? Number(node.width) : 120;
} }
function nodeHeight(_node) { function nodeHeight(_node) {
return DEFAULT_HEIGHT; return 60;
} }
function clone(node) { function clone(node) {
+1 -1
View File
@@ -287,7 +287,7 @@ function createCanvas(container, store, opts = {}) {
tabindex: '0', tabindex: '0',
'aria-label': `${node.kind}: ${node.label || '(no label)'}`, 'aria-label': `${node.kind}: ${node.label || '(no label)'}`,
}); });
g.innerHTML = shapeSvg(node.kind, 0, 0, w, DEFAULT_HEIGHT); g.innerHTML = shapeSvg(node.kind, 0, 0, w, DEFAULT_HEIGHT, node.color);
const text = svgEl('text', { const text = svgEl('text', {
x: w / 2, x: w / 2,
y: DEFAULT_HEIGHT / 2 + 4, y: DEFAULT_HEIGHT / 2 + 4,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff