@@ -80,6 +80,9 @@ let wasmModule = null;
8080// Compiled standalone modules, keyed by command name (e.g. "grep", "find").
8181// A key is present only once its module has loaded successfully.
8282const standaloneModules = { } ;
83+ // In-flight loads, keyed by command name, so a button click and a command that
84+ // both trigger a load (or two rapid clicks) share one fetch instead of racing.
85+ const standaloneLoading = { } ;
8386let wasiShim = null ;
8487let terminal = null ;
8588let inputBuffer = "" ;
@@ -167,32 +170,54 @@ async function loadWasm() {
167170}
168171
169172/**
170- * Load the optional standalone WASM modules (grep, find, …). Each is optional:
171- * if a binary isn't present (e.g. local dev without a CI build), its error is
172- * swallowed and the corresponding command reports that it's unavailable rather
173- * than breaking the terminal or blocking the coreutils module.
173+ * Load one optional standalone module (grep, find, …) on demand. These are not
174+ * part of the eager startup download — they're fetched the first time the user
175+ * runs the command or clicks its "Load" button, keeping the initial page load
176+ * to just the coreutils multicall binary.
177+ *
178+ * Concurrent callers share a single in-flight fetch. Returns the compiled
179+ * module, or null if it's unknown or its binary isn't present (e.g. local dev
180+ * without a CI build) — in which case the command reports it's unavailable
181+ * rather than breaking the terminal.
182+ *
183+ * When `announce` is set and a terminal exists, a "loading <cmd>… done" notice
184+ * is printed live; the button UI instead reacts to the `uutils:program-loaded`
185+ * event dispatched on success.
174186 */
175- async function loadStandaloneWasm ( ) {
176- await Promise . all (
177- Object . entries ( STANDALONE_WASM_URLS ) . map ( async ( [ cmd , url ] ) => {
178- if ( standaloneModules [ cmd ] ) return ;
179- try {
180- const { module, size } = await compileWasmModule ( url ) ;
181- standaloneModules [ cmd ] = module ;
182- wasmSize += size ;
183- } catch ( e ) {
184- console . warn ( `${ cmd } WASM unavailable:` , e . message ) ;
187+ function loadStandalone ( cmd , { announce = false } = { } ) {
188+ if ( standaloneModules [ cmd ] ) return Promise . resolve ( standaloneModules [ cmd ] ) ;
189+ if ( standaloneLoading [ cmd ] ) return standaloneLoading [ cmd ] ;
190+ const url = STANDALONE_WASM_URLS [ cmd ] ;
191+ if ( ! url ) return Promise . resolve ( null ) ;
192+ const notify = announce && terminal ;
193+ if ( notify ) terminal . write ( `loading ${ cmd } … ` ) ;
194+ standaloneLoading [ cmd ] = ( async ( ) => {
195+ try {
196+ const { module, size } = await compileWasmModule ( url ) ;
197+ standaloneModules [ cmd ] = module ;
198+ wasmSize += size ;
199+ if ( notify ) terminal . write ( `done (${ ( size / 1024 / 1024 ) . toFixed ( 1 ) } MB)\r\n` ) ;
200+ if ( typeof document !== "undefined" ) {
201+ document . dispatchEvent ( new CustomEvent ( "uutils:program-loaded" , { detail : { cmd, size } } ) ) ;
185202 }
186- } )
187- ) ;
203+ return module ;
204+ } catch ( e ) {
205+ console . warn ( `${ cmd } WASM unavailable:` , e . message ) ;
206+ if ( notify ) terminal . write ( "unavailable\r\n" ) ;
207+ return null ;
208+ } finally {
209+ delete standaloneLoading [ cmd ] ;
210+ }
211+ } ) ( ) ;
212+ return standaloneLoading [ cmd ] ;
188213}
189214
190215async function initWasm ( ) {
191216 if ( wasmReady ) return ;
192217 try {
193- // The standalone modules are optional and loadStandaloneWasm swallows its
194- // own errors, so they never block the coreutils module from becoming ready .
195- await Promise . all ( [ loadWasiShim ( ) , loadWasm ( ) , loadStandaloneWasm ( ) ] ) ;
218+ // Only the coreutils multicall binary loads eagerly; the standalone
219+ // modules (grep, find) are fetched on demand — see loadStandalone .
220+ await Promise . all ( [ loadWasiShim ( ) , loadWasm ( ) ] ) ;
196221 wasmReady = true ;
197222 } catch ( e ) {
198223 // Will fall back to JS implementations
@@ -583,10 +608,12 @@ async function executeCommandLine(line) {
583608 }
584609
585610 // Some utilities (grep, find) are separate WASM modules rather than part
586- // of the coreutils multicall binary.
611+ // of the coreutils multicall binary, and are loaded on demand. Fetch the
612+ // module the first time the command is used (no-op once cached).
587613 const isStandalone = cmd in STANDALONE_WASM_URLS ;
588614 if ( isStandalone && ! standaloneModules [ cmd ] ) {
589- return `${ cmd } is not available in this build.\n` ;
615+ const mod = await loadStandalone ( cmd , { announce : true } ) ;
616+ if ( ! mod ) return `${ cmd } is not available in this build.\n` ;
590617 }
591618
592619 try {
@@ -887,6 +914,7 @@ async function initPlayground(containerId) {
887914 terminal . writeln ( "" ) ;
888915 terminal . writeln ( "Type \x1b[1;32mhelp\x1b[0m for available commands." ) ;
889916 terminal . writeln ( "Sample data files: names.txt, numbers.txt, fruits.txt, csv.txt, words.txt" ) ;
917+ terminal . writeln ( "\x1b[2mgrep and find load on demand — just run them, or use the buttons below.\x1b[0m" ) ;
890918 } catch ( e ) {
891919 terminal . writeln ( " \x1b[1;31mfailed\x1b[0m" ) ;
892920 terminal . writeln ( "Failed to load WASM binary. Commands are not available." ) ;
@@ -934,6 +962,25 @@ window.uutilsExecute = executeCommandLine;
934962window . runInTerminal = runInTerminal ;
935963window . setLocale = setLocale ;
936964
965+ // On-demand loading of the optional standalone programs (grep, find), used by
966+ // the "Load" buttons on the playground page.
967+ window . uutilsPrograms = Object . keys ( STANDALONE_WASM_URLS ) ;
968+ window . loadProgram = ( cmd ) => loadStandalone ( cmd ) ;
969+ window . isProgramLoaded = ( cmd ) => ! ! standaloneModules [ cmd ] ;
970+ // Best-effort byte size of a program's module, for the button label (0 if the
971+ // server doesn't report Content-Length or the binary is missing).
972+ window . programSize = async ( cmd ) => {
973+ const url = STANDALONE_WASM_URLS [ cmd ] ;
974+ if ( ! url ) return 0 ;
975+ try {
976+ const r = await fetch ( url , { method : "HEAD" } ) ;
977+ const cl = r . ok ? r . headers . get ( "content-length" ) : null ;
978+ return cl ? parseInt ( cl , 10 ) : 0 ;
979+ } catch {
980+ return 0 ;
981+ }
982+ } ;
983+
937984// Expose internals for testing
938985window . _uutilsTestInternals = {
939986 parseCommandLine,
@@ -951,6 +998,7 @@ window._uutilsTestInternals = {
951998 get grepReady ( ) { return ! ! standaloneModules . grep ; } ,
952999 get findReady ( ) { return ! ! standaloneModules . find ; } ,
9531000 initWasm,
1001+ loadStandalone,
9541002 LOCALE_SHORTCUTS ,
9551003 SAMPLE_FILES ,
9561004 AVAILABLE_COMMANDS ,
0 commit comments