@@ -16,12 +16,21 @@ if (typeof SharedArrayBuffer === "undefined") {
1616const WASM_URL = "/wasm/uutils.wasm" ;
1717// Some utilities ship as their own standalone WASM modules rather than as part
1818// of the coreutils multicall binary (grep lives in uutils/grep, find in
19- // uutils/findutils). They are loaded lazily alongside the multicall module and
20- // each is optional — see loadStandaloneWasm.
21- const STANDALONE_WASM_URLS = {
22- grep : "/wasm/grep.wasm" ,
23- find : "/wasm/find.wasm" ,
19+ // uutils/findutils, diff and cmp in uutils/diffutils). Each module is loaded on
20+ // demand and is optional — see loadStandalone. A single module can provide
21+ // several commands (diffutils → diff, cmp), which the diffutils binary
22+ // dispatches on argv[0], so each command is invoked directly by its own name.
23+ const STANDALONE_MODULES = {
24+ grep : { url : "/wasm/grep.wasm" , commands : [ "grep" ] } ,
25+ find : { url : "/wasm/find.wasm" , commands : [ "find" ] } ,
26+ diffutils : { url : "/wasm/diffutils.wasm" , commands : [ "diff" , "cmp" ] } ,
2427} ;
28+ // Map each command to the module that provides it (e.g. diff -> "diffutils").
29+ const STANDALONE_COMMAND_MODULE = Object . fromEntries (
30+ Object . entries ( STANDALONE_MODULES ) . flatMap (
31+ ( [ mod , def ] ) => def . commands . map ( cmd => [ cmd , mod ] )
32+ )
33+ ) ;
2534const XTERM_CSS = "https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css" ;
2635const XTERM_CSS_INTEGRITY = "sha384-tStR1zLfWgsiXCF3IgfB3lBa8KmBe/lG287CL9WCeKgQYcp1bjb4/+mwN6oti4Co" ;
2736const XTERM_JS = "https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js" ;
@@ -44,6 +53,9 @@ const SAMPLE_FILES = {
4453 "🍌.md" : "# Banana\n" ,
4554 "🍒.md" : "# Cherry\n" ,
4655 "🥝.md" : "# Kiwi\n" ,
56+ // Two near-identical lists so `diff`/`cmp` have a small, readable change to show.
57+ "shopping-old.txt" : "milk\neggs\nbread\nbutter\napples\n" ,
58+ "shopping-new.txt" : "milk\neggs\nyogurt\nbread\nhoney\napples\n" ,
4759} ;
4860
4961// Commands available in the feat_wasm build.
@@ -61,7 +73,7 @@ const FALLBACK_COMMANDS = [
6173 "sha1sum" , "sha224sum" , "sha256sum" , "sha384sum" , "sha512sum" ,
6274 "shred" , "shuf" , "sleep" , "sum" , "tee" , "true" , "truncate" ,
6375 "uname" , "unexpand" , "uniq" , "unlink" , "vdir" , "wc" ,
64- "grep" , "find" ,
76+ "grep" , "find" , "diff" , "cmp" ,
6577] ;
6678const AVAILABLE_COMMANDS =
6779 ( typeof WASM_COMMANDS !== "undefined" && Array . isArray ( WASM_COMMANDS ) && WASM_COMMANDS . length > 0 )
@@ -77,10 +89,10 @@ const LOCALE_SHORTCUTS = {
7789} ;
7890
7991let wasmModule = null ;
80- // Compiled standalone modules, keyed by command name (e.g. "grep", "find ").
92+ // Compiled standalone modules, keyed by module name (e.g. "grep", "diffutils ").
8193// A key is present only once its module has loaded successfully.
8294const standaloneModules = { } ;
83- // In-flight loads, keyed by command name, so a button click and a command that
95+ // In-flight loads, keyed by module name, so a button click and a command that
8496// both trigger a load (or two rapid clicks) share one fetch instead of racing.
8597const standaloneLoading = { } ;
8698let wasiShim = null ;
@@ -180,43 +192,46 @@ async function loadWasm() {
180192 * without a CI build) — in which case the command reports it's unavailable
181193 * rather than breaking the terminal.
182194 *
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.
195+ * When `announce` is set and a terminal exists, a "loading <module>… done"
196+ * notice is printed live; the button UI instead reacts to the
197+ * `uutils:program-loaded` event dispatched on success.
198+ *
199+ * Keyed by module name (e.g. "diffutils"), so commands that share a module
200+ * (diff, cmp) trigger a single fetch.
186201 */
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 ] ;
202+ function loadStandalone ( mod , { announce = false } = { } ) {
203+ if ( standaloneModules [ mod ] ) return Promise . resolve ( standaloneModules [ mod ] ) ;
204+ if ( standaloneLoading [ mod ] ) return standaloneLoading [ mod ] ;
205+ const url = STANDALONE_MODULES [ mod ] && STANDALONE_MODULES [ mod ] . url ;
191206 if ( ! url ) return Promise . resolve ( null ) ;
192207 const notify = announce && terminal ;
193- if ( notify ) terminal . write ( `loading ${ cmd } … ` ) ;
194- standaloneLoading [ cmd ] = ( async ( ) => {
208+ if ( notify ) terminal . write ( `loading ${ mod } … ` ) ;
209+ standaloneLoading [ mod ] = ( async ( ) => {
195210 try {
196211 const { module, size } = await compileWasmModule ( url ) ;
197- standaloneModules [ cmd ] = module ;
212+ standaloneModules [ mod ] = module ;
198213 wasmSize += size ;
199214 if ( notify ) terminal . write ( `done (${ ( size / 1024 / 1024 ) . toFixed ( 1 ) } MB)\r\n` ) ;
200215 if ( typeof document !== "undefined" ) {
201- document . dispatchEvent ( new CustomEvent ( "uutils:program-loaded" , { detail : { cmd , size } } ) ) ;
216+ document . dispatchEvent ( new CustomEvent ( "uutils:program-loaded" , { detail : { module : mod , size } } ) ) ;
202217 }
203218 return module ;
204219 } catch ( e ) {
205- console . warn ( `${ cmd } WASM unavailable:` , e . message ) ;
220+ console . warn ( `${ mod } WASM unavailable:` , e . message ) ;
206221 if ( notify ) terminal . write ( "unavailable\r\n" ) ;
207222 return null ;
208223 } finally {
209- delete standaloneLoading [ cmd ] ;
224+ delete standaloneLoading [ mod ] ;
210225 }
211226 } ) ( ) ;
212- return standaloneLoading [ cmd ] ;
227+ return standaloneLoading [ mod ] ;
213228}
214229
215230async function initWasm ( ) {
216231 if ( wasmReady ) return ;
217232 try {
218233 // Only the coreutils multicall binary loads eagerly; the standalone
219- // modules (grep, find) are fetched on demand — see loadStandalone.
234+ // modules (grep, find, diffutils ) are fetched on demand — see loadStandalone.
220235 await Promise . all ( [ loadWasiShim ( ) , loadWasm ( ) ] ) ;
221236 wasmReady = true ;
222237 } catch ( e ) {
@@ -534,6 +549,7 @@ async function executeCommandLine(line) {
534549 " seq 1 10 | factor\n" +
535550 " grep -i alice names.txt\n" +
536551 " find . -name '*.md'\n" +
552+ " diff -u shopping-old.txt shopping-new.txt\n" +
537553 " basename /usr/local/bin/rustc\n" +
538554 " date\n" +
539555 " uname -a\n"
@@ -607,12 +623,14 @@ async function executeCommandLine(line) {
607623 return `uutils: command not found: ${ cmd } \nType 'help' for available commands.\n` ;
608624 }
609625
610- // Some utilities (grep, find) are separate WASM modules rather than part
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).
613- const isStandalone = cmd in STANDALONE_WASM_URLS ;
614- if ( isStandalone && ! standaloneModules [ cmd ] ) {
615- const mod = await loadStandalone ( cmd , { announce : true } ) ;
626+ // Some utilities (grep, find, diff, cmp) are separate WASM modules rather
627+ // than part of the coreutils multicall binary, and are loaded on demand.
628+ // Fetch the module the first time one of its commands is used (no-op once
629+ // cached; diff and cmp share the single diffutils module).
630+ const moduleName = STANDALONE_COMMAND_MODULE [ cmd ] ;
631+ const isStandalone = ! ! moduleName ;
632+ if ( isStandalone && ! standaloneModules [ moduleName ] ) {
633+ const mod = await loadStandalone ( moduleName , { announce : true } ) ;
616634 if ( ! mod ) return `${ cmd } is not available in this build.\n` ;
617635 }
618636
@@ -651,7 +669,7 @@ async function executeCommandLine(line) {
651669 : [ resolvedArgs [ 0 ] , "--color=never" , ...resolvedArgs . slice ( 1 ) ] ;
652670 }
653671 const wasmArgs = isStandalone ? dispatchArgs : [ "coreutils" , ...resolvedArgs ] ;
654- const result = await runCommand ( wasmArgs , stdinData , isStandalone ? standaloneModules [ cmd ] : wasmModule ) ;
672+ const result = await runCommand ( wasmArgs , stdinData , isStandalone ? standaloneModules [ moduleName ] : wasmModule ) ;
655673 if ( result . stderr ) {
656674 return result . stderr + result . stdout ;
657675 }
@@ -914,7 +932,7 @@ async function initPlayground(containerId) {
914932 terminal . writeln ( "" ) ;
915933 terminal . writeln ( "Type \x1b[1;32mhelp\x1b[0m for available commands." ) ;
916934 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" ) ;
935+ terminal . writeln ( "\x1b[2mgrep, find and diff/cmp load on demand — just run them, or use the buttons below.\x1b[0m" ) ;
918936 } catch ( e ) {
919937 terminal . writeln ( " \x1b[1;31mfailed\x1b[0m" ) ;
920938 terminal . writeln ( "Failed to load WASM binary. Commands are not available." ) ;
@@ -962,15 +980,16 @@ window.uutilsExecute = executeCommandLine;
962980window . runInTerminal = runInTerminal ;
963981window . setLocale = setLocale ;
964982
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 ] ;
983+ // On-demand loading of the optional standalone modules (grep, find, diffutils),
984+ // used by the "Load" buttons on the playground page. Keyed by module name; one
985+ // module may back several commands (diffutils → diff, cmp).
986+ window . uutilsPrograms = Object . keys ( STANDALONE_MODULES ) ;
987+ window . loadProgram = ( mod ) => loadStandalone ( mod ) ;
988+ window . isProgramLoaded = ( mod ) => ! ! standaloneModules [ mod ] ;
989+ // Best-effort byte size of a module, for the button label (0 if the server
990+ // doesn't report Content-Length or the binary is missing).
991+ window . programSize = async ( mod ) => {
992+ const url = STANDALONE_MODULES [ mod ] && STANDALONE_MODULES [ mod ] . url ;
974993 if ( ! url ) return 0 ;
975994 try {
976995 const r = await fetch ( url , { method : "HEAD" } ) ;
@@ -997,6 +1016,7 @@ window._uutilsTestInternals = {
9971016 get wasmReady ( ) { return wasmReady ; } ,
9981017 get grepReady ( ) { return ! ! standaloneModules . grep ; } ,
9991018 get findReady ( ) { return ! ! standaloneModules . find ; } ,
1019+ get diffutilsReady ( ) { return ! ! standaloneModules . diffutils ; } ,
10001020 initWasm,
10011021 loadStandalone,
10021022 LOCALE_SHORTCUTS ,
0 commit comments