npm libraries on the web target: what was run

Each library below was installed by zinc install (npm 11), used the way its README starts, and run in headless Chrome on macOS arm64 twice: through zinc dev --target web (pre-bundled by esbuild) and zinc export --target web (linked into the app), and the two gave the same answer. The test is tests/t1/web_libs.sh (fixture tests/fixtures/web-libs, versions pinned in its package.json); the second group was run by hand in a scratch project with the same tool (tools/web-libs-test <zinc> <dir>). How packages are resolved, and the package manager commands: web.md. Only Chrome was used: Firefox and Safari were not tried.

Reading the columns: web = both zinc dev --target web and zinc export --target web; native = zinc bundle (the pinned esbuild, --platform=neutral) and zinc run x.js --engine quickjs, the QuickJS-ng path (limits of that engine: the bundler report, section 1; the guide: 11-quickjs-bundle.md). “not run” means it was not tried.

Run by the test (web_libs.sh)

Library (version)Used aswebnativeNote
zustand 5.0.15createStore from zustand/vanillaworksworksThe React hook (zustand, create) imports React: not applicable, zinc:ui is the UI layer. Use the vanilla store with zinc:ui/solid signals or a subscription.
jotai 3.0.1atom, createStore from jotai/vanillaworksworksjotai/react needs React.
immer 11.1.21produceworksworks
date-fns 4.4.0format, addDaysworksworks with noteNative: Date getters return UTC (see below), so local-time formatting is off by the zone offset.
dayjs 1.11.23dayjs().add().format()worksworks with noteSame Date note on native. CommonJS/UMD package: bundled fine.
lodash-es 4.18.1named imports and import * as _worksworksTree-shaken in the export.
zod 4.6.7z.object().parse, safeParseworksworks
@tanstack/query-core 5.104.3QueryClient.fetchQueryworksworksThe framework-agnostic core. @tanstack/react-query needs React; the Solid, Vue and Svelte adapters were not run.
ky 2.2.0ky.get(url).json()worksnot runUses the browser’s fetch.
axios 1.20.0axios.get(url)worksnot runThe browser build is chosen by the browser conditions.
marked 18.1.1marked.parseworksworks
Chart.js 4.5.1new Chart(canvas, ...) then draw() on a document.createElement('canvas')worksnot run38% of the pixels were drawn. The app may also give Chart.js the page’s own canvas.
three 0.186.1import * as THREE from 'three'does not, yetnot runA bare three resolves to Zinc’s own three plugin (plugins/three) when the plugins folder is present, and its Render3D has no browser backend: plugin Render3D.target is not available in this browser. Loading the npm package as a plain module on a canvas needs the WebGL side of the other task (the zinc:three backend); it is recorded, not integrated here.

The export of these thirteen libraries together is 879 KB of JavaScript before compression (tree-shaken, minified).

Run by hand (same tool, scratch project)

LibrarywebnativeNote
eventemitter3worksworksCommonJS, default import.
rxjs 7worksworks
d3-arrayworksworks
big.js, msworksworks
@preact/signals-coreworksworks
preact (h, render)worksworksImports and calls h; render needs a DOM and is the browser’s, not Zinc’s.
react (createElement)worksworksThe element factory only; react-dom was not tried and is not needed with zinc:ui.
uuidworksdoes notNative: crypto is not defined. Zinc adds the Web globals (crypto, fetch, URL…) only to the files of the project; a package that reads them gets undefined on QuickJS.
nanoidworksdoes notNative: the Node build is chosen (Buffer is not defined); the browser build would work.
papaparseworksdoes notNative: esbuild stops on the Node built-ins (stream, util) a package requires. The browser build has them mapped away.
qsworksdoes notNative: util (Node built-in) as above. On the web, import * as qs from 'qs' of a CommonJS package works in dev too (the pre-bundle exports esbuild’s namespace).

What it says

  • Web target. Everything that is plain JavaScript or runs on the browser’s own APIs worked, in both modes, with the same result; the differences between dev and export are only process.env.NODE_ENV and size. The cases that do not work are libraries that need React or the DOM tree as their UI, Node built-ins, and the three plugin conflict above.
  • Native QuickJS path. zinc bundle links packages (ES, CommonJS and UMD), and the program runs with zinc run bundle.js --engine quickjs; pure-JS libraries such as the state, schema, format and data ones above work the same way as in the browser. What QuickJS-ng lacks is the line between the two paths: Intl and Temporal are missing, and WebAssembly comes from the wasm pack (zinc engine install wasm) (a package that calls them gets the Q0001 warning when it is bundled: bundle.js: warning Q0001: Intl.NumberFormat is not in QuickJS-ng ... (in the bundled packages; a package may guard it), the lint scanning the linked text), the Web globals are not given to packages, Node built-ins stop the link, and the resolution is module, then main (no browser field). zinc run x.ts --engine quickjs without bundling loads ES-module packages only (a CommonJS package such as dayjs says Could not find export 'default'): bundle first.
  • Dates on the native path. Date.prototype.getHours, getDate and the other local getters return the UTC fields on QuickJS in Zinc (src/qjs/prelude.cpp: “no time zones, like the typed engine”), while the constructor new Date(y, m, d) and getTimezoneOffset() still use the machine’s zone. Libraries that format local time (date-fns, dayjs, moment) show the date off by the offset in any zone but UTC.

Edit this page on GitHub ↗