toiljs 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/prettier.xml +1 -0
- package/as-pect.config.js +1 -1
- package/build/backend/.tsbuildinfo +1 -1
- package/build/backend/index.js +1 -2
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/configure.d.ts +15 -0
- package/build/cli/configure.js +201 -0
- package/build/cli/create.d.ts +4 -0
- package/build/cli/create.js +193 -68
- package/build/cli/features.d.ts +23 -0
- package/build/cli/features.js +85 -0
- package/build/cli/index.js +42 -2
- package/build/cli/proc.d.ts +1 -0
- package/build/cli/proc.js +11 -0
- package/build/cli/ui.js +1 -2
- package/build/cli/validate.d.ts +4 -0
- package/build/cli/validate.js +19 -0
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/Link.d.ts +8 -0
- package/build/client/Link.js +44 -0
- package/build/client/NavLink.d.ts +14 -0
- package/build/client/NavLink.js +37 -0
- package/build/client/Router.d.ts +7 -0
- package/build/client/Router.js +55 -0
- package/build/client/channel/channel.d.ts +23 -0
- package/build/client/channel/channel.js +94 -0
- package/build/client/error-boundary.d.ts +16 -0
- package/build/client/error-boundary.js +19 -0
- package/build/client/head/head.d.ts +26 -0
- package/build/client/head/head.js +87 -0
- package/build/client/head.d.ts +26 -0
- package/build/client/head.js +87 -0
- package/build/client/hooks.d.ts +17 -0
- package/build/client/hooks.js +48 -0
- package/build/client/index.d.ts +18 -6
- package/build/client/index.js +10 -3
- package/build/client/lazy.d.ts +16 -0
- package/build/client/lazy.js +53 -0
- package/build/client/match.js +7 -0
- package/build/client/mount.d.ts +2 -0
- package/build/client/mount.js +13 -0
- package/build/client/navigation/Link.d.ts +8 -0
- package/build/client/navigation/Link.js +44 -0
- package/build/client/navigation/NavLink.d.ts +14 -0
- package/build/client/navigation/NavLink.js +37 -0
- package/build/client/navigation/navigation.d.ts +13 -0
- package/build/client/navigation/navigation.js +97 -0
- package/build/client/navigation/prefetch.d.ts +11 -0
- package/build/client/navigation/prefetch.js +100 -0
- package/build/client/navigation/scroll.d.ts +8 -0
- package/build/client/navigation/scroll.js +36 -0
- package/build/client/navigation.d.ts +13 -0
- package/build/client/navigation.js +97 -0
- package/build/client/params-context.d.ts +2 -0
- package/build/client/params-context.js +2 -0
- package/build/client/prefetch.d.ts +11 -0
- package/build/client/prefetch.js +100 -0
- package/build/client/routing/Router.d.ts +7 -0
- package/build/client/routing/Router.js +55 -0
- package/build/client/routing/error-boundary.d.ts +16 -0
- package/build/client/routing/error-boundary.js +19 -0
- package/build/client/routing/hooks.d.ts +17 -0
- package/build/client/routing/hooks.js +48 -0
- package/build/client/routing/lazy.d.ts +16 -0
- package/build/client/routing/lazy.js +53 -0
- package/build/client/routing/match.d.ts +2 -0
- package/build/client/routing/match.js +32 -0
- package/build/client/routing/mount.d.ts +2 -0
- package/build/client/routing/mount.js +13 -0
- package/build/client/routing/params-context.d.ts +2 -0
- package/build/client/routing/params-context.js +2 -0
- package/build/client/scroll.d.ts +8 -0
- package/build/client/scroll.js +36 -0
- package/build/client/types.d.ts +27 -0
- package/build/client/types.js +1 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +2 -0
- package/build/compiler/config.js +14 -2
- package/build/compiler/docs.d.ts +10 -0
- package/build/compiler/docs.js +59 -0
- package/build/compiler/generate.js +144 -16
- package/build/compiler/index.d.ts +4 -2
- package/build/compiler/index.js +4 -3
- package/build/compiler/plugin.js +16 -1
- package/build/compiler/routes.js +5 -1
- package/build/compiler/vite.d.ts +1 -1
- package/build/compiler/vite.js +17 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/io/BinaryWriter.js +2 -2
- package/eslint.config.js +1 -1
- package/examples/basic/.toil/docs/cli.md +3 -0
- package/examples/basic/.toil/docs/client.md +3 -0
- package/examples/basic/.toil/docs/index.md +3 -0
- package/examples/basic/.toil/docs/routing.md +3 -0
- package/examples/basic/.toil/docs/server.md +3 -0
- package/examples/basic/.toil/docs/styling.md +3 -0
- package/examples/basic/.toil/entry.tsx +3 -8
- package/examples/basic/.toil/globals.ts +6 -0
- package/examples/basic/.toil/index.html +16 -12
- package/examples/basic/.toil/public/images/.gitkeep +1 -0
- package/examples/basic/.toil/public/images/logo.svg +37 -0
- package/examples/basic/.toil/public/robots.txt +2 -0
- package/examples/basic/.toil/routes.ts +9 -7
- package/examples/basic/build/client/assets/404-Bq0jNTUo.js +1 -0
- package/examples/basic/build/client/assets/_...slug_-CXKf6qnB.js +1 -0
- package/examples/basic/build/client/assets/_id_-BadAyQnb.js +1 -0
- package/examples/basic/build/client/assets/about-BOhoEcEO.js +1 -0
- package/examples/basic/build/client/assets/get-started-BIXpcjkT.js +9 -0
- package/examples/basic/build/client/assets/index-BmqcTaBB.js +1 -0
- package/examples/basic/build/client/assets/io-DEVjjaJj.js +1 -0
- package/examples/basic/build/client/assets/layout-DJegirdz.js +1 -0
- package/examples/basic/build/client/assets/react-DEQrz1q7.js +9 -0
- package/examples/basic/build/client/assets/rolldown-runtime-KL5VtC6j.js +1 -0
- package/examples/basic/build/client/assets/routes-BYWn6TxK.js +1 -0
- package/examples/basic/build/client/css/style.css +2 -0
- package/examples/basic/build/client/images/.gitkeep +1 -0
- package/examples/basic/build/client/images/logo.svg +37 -0
- package/examples/basic/build/client/index.html +17 -0
- package/examples/basic/build/client/robots.txt +2 -0
- package/examples/basic/client/404.tsx +2 -5
- package/examples/basic/client/components/.gitkeep +1 -0
- package/examples/basic/client/components/Footer.tsx +8 -0
- package/examples/basic/client/components/HoneycombBackground.tsx +162 -0
- package/examples/basic/client/layout.tsx +43 -26
- package/examples/basic/client/public/favicon.ico +0 -0
- package/examples/basic/client/public/images/.gitkeep +1 -0
- package/examples/basic/client/public/images/logo.svg +37 -0
- package/examples/basic/client/public/index.html +16 -0
- package/examples/basic/client/public/robots.txt +2 -0
- package/examples/basic/client/routes/about.tsx +1 -3
- package/examples/basic/client/routes/blog/[id].tsx +2 -4
- package/examples/basic/client/routes/docs/[...slug].tsx +3 -6
- package/examples/basic/client/routes/get-started.tsx +84 -0
- package/examples/basic/client/routes/index.tsx +74 -7
- package/examples/basic/client/routes/io.tsx +3 -7
- package/examples/basic/client/styles/main.css +461 -0
- package/examples/basic/client/toil.tsx +7 -0
- package/examples/basic/node_modules/.bin/toilinit +16 -0
- package/examples/basic/node_modules/.bin/toilinit.cmd +17 -0
- package/examples/basic/node_modules/.bin/toilinit.ps1 +28 -0
- package/examples/basic/node_modules/.bin/toilscript +16 -0
- package/examples/basic/node_modules/.bin/toilscript.cmd +17 -0
- package/examples/basic/node_modules/.bin/toilscript.ps1 +28 -0
- package/examples/basic/node_modules/.bin/wasm-as +16 -0
- package/examples/basic/node_modules/.bin/wasm-as.cmd +17 -0
- package/examples/basic/node_modules/.bin/wasm-as.ps1 +28 -0
- package/examples/basic/node_modules/.bin/wasm-ctor-eval +16 -0
- package/examples/basic/node_modules/.bin/wasm-ctor-eval.cmd +17 -0
- package/examples/basic/node_modules/.bin/wasm-ctor-eval.ps1 +28 -0
- package/examples/basic/node_modules/.bin/wasm-dis +16 -0
- package/examples/basic/node_modules/.bin/wasm-dis.cmd +17 -0
- package/examples/basic/node_modules/.bin/wasm-dis.ps1 +28 -0
- package/examples/basic/node_modules/.bin/wasm-merge +16 -0
- package/examples/basic/node_modules/.bin/wasm-merge.cmd +17 -0
- package/examples/basic/node_modules/.bin/wasm-merge.ps1 +28 -0
- package/examples/basic/node_modules/.bin/wasm-metadce +16 -0
- package/examples/basic/node_modules/.bin/wasm-metadce.cmd +17 -0
- package/examples/basic/node_modules/.bin/wasm-metadce.ps1 +28 -0
- package/examples/basic/node_modules/.bin/wasm-opt +16 -0
- package/examples/basic/node_modules/.bin/wasm-opt.cmd +17 -0
- package/examples/basic/node_modules/.bin/wasm-opt.ps1 +28 -0
- package/examples/basic/node_modules/.bin/wasm-reduce +16 -0
- package/examples/basic/node_modules/.bin/wasm-reduce.cmd +17 -0
- package/examples/basic/node_modules/.bin/wasm-reduce.ps1 +28 -0
- package/examples/basic/node_modules/.bin/wasm-shell +16 -0
- package/examples/basic/node_modules/.bin/wasm-shell.cmd +17 -0
- package/examples/basic/node_modules/.bin/wasm-shell.ps1 +28 -0
- package/examples/basic/node_modules/.bin/wasm2js +16 -0
- package/examples/basic/node_modules/.bin/wasm2js.cmd +17 -0
- package/examples/basic/node_modules/.bin/wasm2js.ps1 +28 -0
- package/examples/basic/node_modules/.package-lock.json +49 -1
- package/examples/basic/node_modules/.vite/deps/_metadata.json +9 -9
- package/examples/basic/node_modules/binaryen/LICENSE +201 -0
- package/examples/basic/node_modules/binaryen/README.md +1362 -0
- package/examples/basic/node_modules/binaryen/bin/package.json +3 -0
- package/examples/basic/node_modules/binaryen/bin/wasm-as +0 -0
- package/examples/basic/node_modules/binaryen/bin/wasm-ctor-eval +0 -0
- package/examples/basic/node_modules/binaryen/bin/wasm-dis +0 -0
- package/examples/basic/node_modules/binaryen/bin/wasm-merge +0 -0
- package/examples/basic/node_modules/binaryen/bin/wasm-metadce +0 -0
- package/examples/basic/node_modules/binaryen/bin/wasm-opt +0 -0
- package/examples/basic/node_modules/binaryen/bin/wasm-reduce +0 -0
- package/examples/basic/node_modules/binaryen/bin/wasm-shell +0 -0
- package/examples/basic/node_modules/binaryen/bin/wasm2js +0 -0
- package/examples/basic/node_modules/binaryen/index.d.ts +2371 -0
- package/examples/basic/node_modules/binaryen/index.js +30552 -0
- package/examples/basic/node_modules/binaryen/package.json +50 -0
- package/examples/basic/node_modules/long/LICENSE +202 -0
- package/examples/basic/node_modules/long/README.md +286 -0
- package/examples/basic/node_modules/long/index.d.ts +2 -0
- package/examples/basic/node_modules/long/index.js +1581 -0
- package/examples/basic/node_modules/long/package.json +58 -0
- package/examples/basic/node_modules/long/types.d.ts +474 -0
- package/examples/basic/node_modules/long/umd/index.d.ts +3 -0
- package/examples/basic/node_modules/long/umd/index.js +1622 -0
- package/examples/basic/node_modules/long/umd/package.json +3 -0
- package/examples/basic/node_modules/long/umd/types.d.ts +474 -0
- package/examples/basic/node_modules/toilscript/LICENSE +201 -0
- package/examples/basic/node_modules/toilscript/NOTICE +94 -0
- package/examples/basic/node_modules/toilscript/README.md +66 -0
- package/examples/basic/node_modules/toilscript/bin/toilinit.js +468 -0
- package/examples/basic/node_modules/toilscript/bin/toilscript.js +35 -0
- package/examples/basic/node_modules/toilscript/dist/cli.d.ts +4 -0
- package/examples/basic/node_modules/toilscript/dist/cli.generated.d.ts +10027 -0
- package/examples/basic/node_modules/toilscript/dist/cli.js +24474 -0
- package/examples/basic/node_modules/toilscript/dist/cli.js.map +7 -0
- package/examples/basic/node_modules/toilscript/dist/importmap.json +9 -0
- package/examples/basic/node_modules/toilscript/dist/toilscript.d.ts +4 -0
- package/examples/basic/node_modules/toilscript/dist/toilscript.generated.d.ts +11242 -0
- package/examples/basic/node_modules/toilscript/dist/toilscript.js +337 -0
- package/examples/basic/node_modules/toilscript/dist/toilscript.js.map +7 -0
- package/examples/basic/node_modules/toilscript/dist/transform.cjs +1 -0
- package/examples/basic/node_modules/toilscript/dist/transform.d.ts +1 -0
- package/examples/basic/node_modules/toilscript/dist/transform.js +1 -0
- package/examples/basic/node_modules/toilscript/dist/web.js +22 -0
- package/examples/basic/node_modules/toilscript/lib/binaryen.d.ts +2 -0
- package/examples/basic/node_modules/toilscript/lib/binaryen.js +2 -0
- package/examples/basic/node_modules/toilscript/package.json +115 -0
- package/examples/basic/node_modules/toilscript/std/README.md +6 -0
- package/examples/basic/node_modules/toilscript/std/assembly/array.ts +550 -0
- package/examples/basic/node_modules/toilscript/std/assembly/arraybuffer.ts +77 -0
- package/examples/basic/node_modules/toilscript/std/assembly/atomics.ts +127 -0
- package/examples/basic/node_modules/toilscript/std/assembly/bindings/asyncify.ts +16 -0
- package/examples/basic/node_modules/toilscript/std/assembly/bindings/dom.ts +291 -0
- package/examples/basic/node_modules/toilscript/std/assembly/bindings/node.ts +6 -0
- package/examples/basic/node_modules/toilscript/std/assembly/bitflags.ts +53 -0
- package/examples/basic/node_modules/toilscript/std/assembly/builtins.ts +2650 -0
- package/examples/basic/node_modules/toilscript/std/assembly/byteslice.ts +177 -0
- package/examples/basic/node_modules/toilscript/std/assembly/compat.ts +2 -0
- package/examples/basic/node_modules/toilscript/std/assembly/console.ts +42 -0
- package/examples/basic/node_modules/toilscript/std/assembly/crypto.ts +9 -0
- package/examples/basic/node_modules/toilscript/std/assembly/dataview.ts +181 -0
- package/examples/basic/node_modules/toilscript/std/assembly/date.ts +375 -0
- package/examples/basic/node_modules/toilscript/std/assembly/diagnostics.ts +11 -0
- package/examples/basic/node_modules/toilscript/std/assembly/encoding.ts +151 -0
- package/examples/basic/node_modules/toilscript/std/assembly/endian.ts +45 -0
- package/examples/basic/node_modules/toilscript/std/assembly/error.ts +44 -0
- package/examples/basic/node_modules/toilscript/std/assembly/fixedarray.ts +173 -0
- package/examples/basic/node_modules/toilscript/std/assembly/fixedmap.ts +326 -0
- package/examples/basic/node_modules/toilscript/std/assembly/fixedset.ts +275 -0
- package/examples/basic/node_modules/toilscript/std/assembly/function.ts +42 -0
- package/examples/basic/node_modules/toilscript/std/assembly/index.d.ts +2892 -0
- package/examples/basic/node_modules/toilscript/std/assembly/iterator.ts +35 -0
- package/examples/basic/node_modules/toilscript/std/assembly/map.ts +269 -0
- package/examples/basic/node_modules/toilscript/std/assembly/math.ts +3289 -0
- package/examples/basic/node_modules/toilscript/std/assembly/memory.ts +123 -0
- package/examples/basic/node_modules/toilscript/std/assembly/number.ts +388 -0
- package/examples/basic/node_modules/toilscript/std/assembly/object.ts +36 -0
- package/examples/basic/node_modules/toilscript/std/assembly/performance.ts +9 -0
- package/examples/basic/node_modules/toilscript/std/assembly/pointer.ts +80 -0
- package/examples/basic/node_modules/toilscript/std/assembly/polyfills.ts +27 -0
- package/examples/basic/node_modules/toilscript/std/assembly/process.ts +50 -0
- package/examples/basic/node_modules/toilscript/std/assembly/reference.ts +48 -0
- package/examples/basic/node_modules/toilscript/std/assembly/regexp.ts +12 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/README.md +83 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/common.ts +81 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/index-incremental.ts +2 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/index-memory.ts +1 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/index-minimal.ts +2 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/index-stub.ts +1 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/index.d.ts +37 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/itcms.ts +419 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/memory-runtime.ts +94 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/rtrace.ts +15 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/stub.ts +133 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/tcms.ts +254 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt/tlsf.ts +592 -0
- package/examples/basic/node_modules/toilscript/std/assembly/rt.ts +90 -0
- package/examples/basic/node_modules/toilscript/std/assembly/set.ts +225 -0
- package/examples/basic/node_modules/toilscript/std/assembly/shared/feature.ts +68 -0
- package/examples/basic/node_modules/toilscript/std/assembly/shared/runtime.ts +13 -0
- package/examples/basic/node_modules/toilscript/std/assembly/shared/target.ts +11 -0
- package/examples/basic/node_modules/toilscript/std/assembly/shared/tsconfig.json +11 -0
- package/examples/basic/node_modules/toilscript/std/assembly/shared/typeinfo.ts +72 -0
- package/examples/basic/node_modules/toilscript/std/assembly/staticarray.ts +423 -0
- package/examples/basic/node_modules/toilscript/std/assembly/string.ts +850 -0
- package/examples/basic/node_modules/toilscript/std/assembly/symbol.ts +114 -0
- package/examples/basic/node_modules/toilscript/std/assembly/table.ts +16 -0
- package/examples/basic/node_modules/toilscript/std/assembly/toilscript.ts +16 -0
- package/examples/basic/node_modules/toilscript/std/assembly/tsconfig.json +6 -0
- package/examples/basic/node_modules/toilscript/std/assembly/typedarray.ts +1954 -0
- package/examples/basic/node_modules/toilscript/std/assembly/uri.ts +17 -0
- package/examples/basic/node_modules/toilscript/std/assembly/util/bytes.ts +107 -0
- package/examples/basic/node_modules/toilscript/std/assembly/util/casemap.ts +497 -0
- package/examples/basic/node_modules/toilscript/std/assembly/util/error.ts +58 -0
- package/examples/basic/node_modules/toilscript/std/assembly/util/hash.ts +117 -0
- package/examples/basic/node_modules/toilscript/std/assembly/util/math.ts +1922 -0
- package/examples/basic/node_modules/toilscript/std/assembly/util/memory.ts +290 -0
- package/examples/basic/node_modules/toilscript/std/assembly/util/number.ts +873 -0
- package/examples/basic/node_modules/toilscript/std/assembly/util/sort.ts +313 -0
- package/examples/basic/node_modules/toilscript/std/assembly/util/string.ts +1202 -0
- package/examples/basic/node_modules/toilscript/std/assembly/util/uri.ts +275 -0
- package/examples/basic/node_modules/toilscript/std/assembly/vector.ts +4 -0
- package/examples/basic/node_modules/toilscript/std/assembly.json +16 -0
- package/examples/basic/node_modules/toilscript/std/portable/index.d.ts +461 -0
- package/examples/basic/node_modules/toilscript/std/portable/index.js +416 -0
- package/examples/basic/node_modules/toilscript/std/portable.json +11 -0
- package/examples/basic/node_modules/toilscript/std/types/assembly/index.d.ts +1 -0
- package/examples/basic/node_modules/toilscript/std/types/assembly/package.json +3 -0
- package/examples/basic/node_modules/toilscript/std/types/portable/index.d.ts +1 -0
- package/examples/basic/node_modules/toilscript/std/types/portable/package.json +3 -0
- package/examples/basic/node_modules/toilscript/tsconfig-base.json +13 -0
- package/examples/basic/node_modules/toilscript/util/README.md +23 -0
- package/examples/basic/node_modules/toilscript/util/browser/fs.js +1 -0
- package/examples/basic/node_modules/toilscript/util/browser/module.js +5 -0
- package/examples/basic/node_modules/toilscript/util/browser/path.js +520 -0
- package/examples/basic/node_modules/toilscript/util/browser/process.js +59 -0
- package/examples/basic/node_modules/toilscript/util/browser/url.js +23 -0
- package/examples/basic/node_modules/toilscript/util/cpu.d.ts +9 -0
- package/examples/basic/node_modules/toilscript/util/cpu.js +42 -0
- package/examples/basic/node_modules/toilscript/util/find.d.ts +6 -0
- package/examples/basic/node_modules/toilscript/util/find.js +20 -0
- package/examples/basic/node_modules/toilscript/util/node.d.ts +21 -0
- package/examples/basic/node_modules/toilscript/util/node.js +34 -0
- package/examples/basic/node_modules/toilscript/util/options.d.ts +70 -0
- package/examples/basic/node_modules/toilscript/util/options.js +262 -0
- package/examples/basic/node_modules/toilscript/util/terminal.d.ts +52 -0
- package/examples/basic/node_modules/toilscript/util/terminal.js +35 -0
- package/examples/basic/node_modules/toilscript/util/text.d.ts +26 -0
- package/examples/basic/node_modules/toilscript/util/text.js +114 -0
- package/examples/basic/node_modules/toilscript/util/tsconfig.json +9 -0
- package/examples/basic/node_modules/toilscript/util/web.d.ts +11 -0
- package/examples/basic/node_modules/toilscript/util/web.js +33 -0
- package/examples/basic/package-lock.json +50 -1
- package/examples/basic/package.json +5 -2
- package/examples/basic/server/index.ts +3 -0
- package/examples/basic/server/main.ts +6 -0
- package/examples/basic/server/tsconfig.json +7 -0
- package/examples/basic/toil-env.d.ts +20 -1
- package/examples/basic/toil.config.ts +2 -5
- package/examples/basic/toilconfig.json +30 -0
- package/package.json +2 -2
- package/presets/eslint.js +2 -7
- package/presets/no-uint8array-tostring.js +4 -5
- package/presets/prettier.json +8 -1
- package/src/backend/index.ts +11 -18
- package/src/cli/configure.ts +272 -0
- package/src/cli/create.ts +267 -82
- package/src/cli/features.ts +128 -0
- package/src/cli/index.ts +44 -3
- package/src/cli/proc.ts +20 -0
- package/src/cli/ui.ts +4 -6
- package/src/cli/validate.ts +31 -0
- package/src/client/head/head.ts +140 -0
- package/src/client/index.ts +39 -9
- package/src/client/navigation/Link.tsx +99 -0
- package/src/client/navigation/NavLink.tsx +86 -0
- package/src/client/navigation/navigation.ts +142 -0
- package/src/client/navigation/prefetch.ts +130 -0
- package/src/client/navigation/scroll.ts +53 -0
- package/src/client/routing/Router.tsx +95 -0
- package/src/client/routing/error-boundary.tsx +43 -0
- package/src/client/routing/hooks.ts +115 -0
- package/src/client/routing/lazy.ts +93 -0
- package/src/client/{match.ts → routing/match.ts} +11 -3
- package/src/client/routing/mount.tsx +28 -0
- package/src/client/routing/params-context.ts +10 -0
- package/src/client/types.ts +36 -0
- package/src/compiler/config.ts +26 -10
- package/src/compiler/docs.ts +87 -0
- package/src/compiler/generate.ts +180 -23
- package/src/compiler/index.ts +6 -4
- package/src/compiler/plugin.ts +22 -1
- package/src/compiler/routes.ts +13 -7
- package/src/compiler/vite.ts +28 -5
- package/src/io/BinaryReader.ts +1 -5
- package/src/io/BinaryWriter.ts +3 -3
- package/src/server/index.ts +3 -4
- package/src/server/tsconfig.json +4 -0
- package/templates/app/client/404.tsx +11 -0
- package/templates/app/client/components/.gitkeep +1 -0
- package/templates/app/client/components/Footer.tsx +8 -0
- package/templates/app/client/components/HoneycombBackground.tsx +162 -0
- package/templates/app/client/layout.tsx +53 -0
- package/templates/app/client/public/favicon.ico +0 -0
- package/templates/app/client/public/images/.gitkeep +1 -0
- package/templates/app/client/public/images/logo.svg +37 -0
- package/templates/app/client/public/index.html +16 -0
- package/templates/app/client/public/robots.txt +2 -0
- package/templates/app/client/routes/about.tsx +11 -0
- package/templates/app/client/routes/blog/[id].tsx +12 -0
- package/templates/app/client/routes/docs/[...slug].tsx +12 -0
- package/templates/app/client/routes/get-started.tsx +84 -0
- package/templates/app/client/routes/index.tsx +80 -0
- package/templates/app/client/routes/io.tsx +24 -0
- package/templates/app/client/styles/main.css +461 -0
- package/templates/app/client/toil.tsx +7 -0
- package/test/channel.test.ts +1 -1
- package/test/configure.test.ts +90 -0
- package/test/features.test.ts +111 -0
- package/test/head.test.ts +35 -0
- package/test/io.test.ts +8 -0
- package/test/navlink.test.ts +28 -0
- package/test/routes.test.ts +16 -1
- package/test/validate.test.ts +42 -0
- package/vitest.config.ts +1 -1
- package/examples/basic/dist/assets/404-D1bS2aH_.js +0 -1
- package/examples/basic/dist/assets/_...slug_-wR3shlWn.js +0 -1
- package/examples/basic/dist/assets/_id_-EWYvHfi2.js +0 -1
- package/examples/basic/dist/assets/about-Ddvj1tjF.js +0 -1
- package/examples/basic/dist/assets/index-CdG0me90.js +0 -1
- package/examples/basic/dist/assets/io-CODNJU57.js +0 -1
- package/examples/basic/dist/assets/layout-C15ZTPYI.js +0 -1
- package/examples/basic/dist/assets/react-JbAfoxYe.js +0 -9
- package/examples/basic/dist/assets/rolldown-runtime-1VNLd2iN.js +0 -1
- package/examples/basic/dist/assets/routes-GoydenoY.js +0 -1
- package/examples/basic/dist/index.html +0 -12
- package/src/client/runtime.tsx +0 -190
- /package/src/client/{channel.ts → channel/channel.ts} +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History-based navigation core. Owns the location subscribers, the single `popstate` handler, and
|
|
3
|
+
* the per-entry history keys used for scroll restoration. Consumed by `useLocation` (to re-render),
|
|
4
|
+
* `Link` / `navigate` (to change location), and `Router` (which calls `applyScroll` after commit).
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
enableManualScrollRestoration,
|
|
8
|
+
planScroll,
|
|
9
|
+
rememberScroll,
|
|
10
|
+
} from './scroll.js';
|
|
11
|
+
|
|
12
|
+
const listeners = new Set<() => void>();
|
|
13
|
+
let popstateBound = false;
|
|
14
|
+
|
|
15
|
+
interface ToilHistoryState {
|
|
16
|
+
__toilKey?: string;
|
|
17
|
+
}
|
|
18
|
+
let keyCounter = 0;
|
|
19
|
+
let currentKey = 'initial';
|
|
20
|
+
function nextKey(): string {
|
|
21
|
+
keyCounter += 1;
|
|
22
|
+
return `t${String(keyCounter)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Notifies every subscriber that the location may have changed. */
|
|
26
|
+
function notify(): void {
|
|
27
|
+
for (const listener of listeners) listener();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Navigation-pending tracking: a navigation is "pending" from when it starts until the new route
|
|
31
|
+
// commits. Drives useNavigationPending() (e.g. a top loading bar).
|
|
32
|
+
let startedTick = 0;
|
|
33
|
+
let committedTick = 0;
|
|
34
|
+
const pendingListeners = new Set<() => void>();
|
|
35
|
+
function emitPending(): void {
|
|
36
|
+
for (const listener of pendingListeners) listener();
|
|
37
|
+
}
|
|
38
|
+
function beginNavigation(): void {
|
|
39
|
+
startedTick += 1;
|
|
40
|
+
emitPending();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Marks the in-flight navigation as committed. Called by `Router` after each commit. */
|
|
44
|
+
export function settleNavigation(): void {
|
|
45
|
+
if (committedTick !== startedTick) {
|
|
46
|
+
committedTick = startedTick;
|
|
47
|
+
emitPending();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Whether a navigation is in flight (started but not yet committed). */
|
|
52
|
+
export function isNavigationPending(): boolean {
|
|
53
|
+
return startedTick !== committedTick;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Subscribes to navigation-pending changes; returns an unsubscribe function. */
|
|
57
|
+
export function subscribePending(listener: () => void): () => void {
|
|
58
|
+
pendingListeners.add(listener);
|
|
59
|
+
return () => {
|
|
60
|
+
pendingListeners.delete(listener);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Options for {@link navigate}. */
|
|
65
|
+
export interface NavigateOptions {
|
|
66
|
+
/** Replace the current history entry instead of pushing a new one. Default `false`. */
|
|
67
|
+
readonly replace?: boolean;
|
|
68
|
+
/** Scroll to the top of the page after navigating. Default `true`. */
|
|
69
|
+
readonly scroll?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Initializes manual scroll restoration and the initial history key. Called once by `mount`. */
|
|
73
|
+
export function initNavigation(): void {
|
|
74
|
+
enableManualScrollRestoration();
|
|
75
|
+
const state = window.history.state as ToilHistoryState | null;
|
|
76
|
+
if (state?.__toilKey) {
|
|
77
|
+
currentKey = state.__toilKey;
|
|
78
|
+
} else {
|
|
79
|
+
currentKey = nextKey();
|
|
80
|
+
window.history.replaceState({ ...state, __toilKey: currentKey }, '');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Navigates to `href` without a full page reload (history push/replace + subscriber re-render). */
|
|
85
|
+
export function navigate(href: string, options?: NavigateOptions): void {
|
|
86
|
+
beginNavigation();
|
|
87
|
+
rememberScroll(currentKey);
|
|
88
|
+
let hash = '';
|
|
89
|
+
try {
|
|
90
|
+
hash = new URL(href, window.location.href).hash;
|
|
91
|
+
} catch {
|
|
92
|
+
hash = '';
|
|
93
|
+
}
|
|
94
|
+
if (options?.replace) {
|
|
95
|
+
window.history.replaceState({ __toilKey: currentKey }, '', href);
|
|
96
|
+
} else {
|
|
97
|
+
currentKey = nextKey();
|
|
98
|
+
window.history.pushState({ __toilKey: currentKey }, '', href);
|
|
99
|
+
}
|
|
100
|
+
planScroll({ hash, toTop: options?.scroll !== false });
|
|
101
|
+
notify();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Goes back one entry in history (fires `popstate`, which notifies subscribers). */
|
|
105
|
+
export function back(): void {
|
|
106
|
+
window.history.back();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Goes forward one entry in history. */
|
|
110
|
+
export function forward(): void {
|
|
111
|
+
window.history.forward();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Re-renders the current route without changing the URL (there is no server data to refetch). */
|
|
115
|
+
export function refresh(): void {
|
|
116
|
+
notify();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Handles browser back/forward: restores the saved scroll for the target entry, then re-renders. */
|
|
120
|
+
function handlePopState(event: PopStateEvent): void {
|
|
121
|
+
beginNavigation();
|
|
122
|
+
rememberScroll(currentKey);
|
|
123
|
+
const state = event.state as ToilHistoryState | null;
|
|
124
|
+
currentKey = state?.__toilKey ?? 'initial';
|
|
125
|
+
planScroll({ restoreKey: currentKey, hash: window.location.hash, toTop: false });
|
|
126
|
+
notify();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Subscribes `listener` to location changes and returns an unsubscribe function. Browser
|
|
131
|
+
* back/forward is wired once, on the first subscription, via a shared `popstate` handler.
|
|
132
|
+
*/
|
|
133
|
+
export function subscribeLocation(listener: () => void): () => void {
|
|
134
|
+
if (!popstateBound) {
|
|
135
|
+
window.addEventListener('popstate', handlePopState);
|
|
136
|
+
popstateBound = true;
|
|
137
|
+
}
|
|
138
|
+
listeners.add(listener);
|
|
139
|
+
return () => {
|
|
140
|
+
listeners.delete(listener);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { matchRoute } from '../routing/match.js';
|
|
2
|
+
import type { RouteDef } from '../types.js';
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
interface Navigator {
|
|
6
|
+
/** Non-standard but widely shipped; used to skip prefetch on data-saver / slow links. */
|
|
7
|
+
readonly connection?: {
|
|
8
|
+
readonly saveData?: boolean;
|
|
9
|
+
readonly effectiveType?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let routeTable: RouteDef[] = [];
|
|
15
|
+
const warmed = new WeakSet<RouteDef>();
|
|
16
|
+
let io: IntersectionObserver | null = null;
|
|
17
|
+
let mo: MutationObserver | null = null;
|
|
18
|
+
|
|
19
|
+
/** Resolves a same-origin `href` to a registered route, or `null` for external/unknown targets. */
|
|
20
|
+
function routeForHref(href: string): RouteDef | null {
|
|
21
|
+
let url: URL;
|
|
22
|
+
try {
|
|
23
|
+
url = new URL(href, window.location.href);
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (url.origin !== window.location.origin) return null;
|
|
28
|
+
for (const route of routeTable) {
|
|
29
|
+
if (matchRoute(route.pattern, url.pathname)) return route;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Warms a route's lazy chunk by triggering its loader once. Best-effort: each route loads at most
|
|
36
|
+
* once, and a failed load is forgotten (so the real navigation can retry and surface the error).
|
|
37
|
+
*/
|
|
38
|
+
function warm(route: RouteDef): void {
|
|
39
|
+
if (warmed.has(route)) return;
|
|
40
|
+
warmed.add(route);
|
|
41
|
+
void route.load().catch(() => {
|
|
42
|
+
warmed.delete(route);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Prefetches the route chunk for an internal `href` so a later navigation resolves instantly.
|
|
48
|
+
* No-op for external, unknown, or already-prefetched targets — safe to call from anywhere,
|
|
49
|
+
* including before an imperative {@link navigate} (e.g. `prefetch('/dashboard')` on hover/intent).
|
|
50
|
+
*/
|
|
51
|
+
export function prefetch(href: string): void {
|
|
52
|
+
const route = routeForHref(href);
|
|
53
|
+
if (route) warm(route);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Anchors to skip even when internal: new-tab, downloads, or an explicit `data-no-prefetch` opt-out. */
|
|
57
|
+
function isPrefetchable(a: HTMLAnchorElement): boolean {
|
|
58
|
+
if (a.target && a.target !== '_self') return false;
|
|
59
|
+
if (a.hasAttribute('download')) return false;
|
|
60
|
+
if (a.dataset.noPrefetch !== undefined) return false;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Observes an anchor for viewport entry if it points at a known internal route. */
|
|
65
|
+
function observeAnchor(a: HTMLAnchorElement): void {
|
|
66
|
+
if (!io || !isPrefetchable(a) || !routeForHref(a.href)) return;
|
|
67
|
+
io.observe(a);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Finds and observes every `<a href>` under `root`. */
|
|
71
|
+
function scan(root: ParentNode): void {
|
|
72
|
+
root.querySelectorAll('a[href]').forEach((el) => {
|
|
73
|
+
if (el instanceof HTMLAnchorElement) observeAnchor(el);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Skip prefetching on data-saver mode or 2g-class connections, where bandwidth is precious. */
|
|
78
|
+
function shouldSkipForConnection(): boolean {
|
|
79
|
+
const c = navigator.connection;
|
|
80
|
+
if (!c) return false;
|
|
81
|
+
return c.saveData === true || c.effectiveType === 'slow-2g' || c.effectiveType === '2g';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Starts idle-time prefetching of internal links. As each `<a>` pointing at a known route scrolls
|
|
86
|
+
* into view (or near it — 200px margin) its chunk is warmed once; links added later by client
|
|
87
|
+
* navigation are picked up via a MutationObserver. Called by {@link mount}; runs once per app.
|
|
88
|
+
*/
|
|
89
|
+
export function startPrefetcher(routes: RouteDef[]): void {
|
|
90
|
+
routeTable = routes;
|
|
91
|
+
if (
|
|
92
|
+
typeof window === 'undefined' ||
|
|
93
|
+
typeof IntersectionObserver === 'undefined' ||
|
|
94
|
+
typeof MutationObserver === 'undefined' ||
|
|
95
|
+
io
|
|
96
|
+
) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (shouldSkipForConnection()) return;
|
|
100
|
+
|
|
101
|
+
io = new IntersectionObserver(
|
|
102
|
+
(entries) => {
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
if (!entry.isIntersecting) continue;
|
|
105
|
+
const a = entry.target;
|
|
106
|
+
if (a instanceof HTMLAnchorElement) {
|
|
107
|
+
io?.unobserve(a);
|
|
108
|
+
prefetch(a.href);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{ rootMargin: '200px' },
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
mo = new MutationObserver((mutations) => {
|
|
116
|
+
for (const m of mutations) {
|
|
117
|
+
for (const node of m.addedNodes) {
|
|
118
|
+
if (node instanceof HTMLAnchorElement) observeAnchor(node);
|
|
119
|
+
else if (node instanceof Element) scan(node);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const begin = (): void => {
|
|
125
|
+
scan(document);
|
|
126
|
+
mo?.observe(document.body, { childList: true, subtree: true });
|
|
127
|
+
};
|
|
128
|
+
if (typeof requestIdleCallback === 'function') requestIdleCallback(begin);
|
|
129
|
+
else setTimeout(begin, 200);
|
|
130
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manual scroll management for client navigation: scroll to top on push navigations, restore the
|
|
3
|
+
* saved position on back/forward, and honor in-page `#hash` targets. Positions are keyed by
|
|
4
|
+
* history entry; {@link applyScroll} runs once after the navigation commits.
|
|
5
|
+
*/
|
|
6
|
+
const positions = new Map<string, number>();
|
|
7
|
+
|
|
8
|
+
interface ScrollPlan {
|
|
9
|
+
readonly restore: number | null;
|
|
10
|
+
readonly hash: string;
|
|
11
|
+
readonly toTop: boolean;
|
|
12
|
+
}
|
|
13
|
+
let plan: ScrollPlan | null = null;
|
|
14
|
+
|
|
15
|
+
/** Switches off the browser's automatic scroll restoration so the router can manage it. */
|
|
16
|
+
export function enableManualScrollRestoration(): void {
|
|
17
|
+
if (typeof window !== 'undefined' && 'scrollRestoration' in window.history) {
|
|
18
|
+
window.history.scrollRestoration = 'manual';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Saves the current scroll position for a history key (called before leaving an entry). */
|
|
23
|
+
export function rememberScroll(key: string): void {
|
|
24
|
+
positions.set(key, window.scrollY);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Plans what {@link applyScroll} should do after the next navigation commits. */
|
|
28
|
+
export function planScroll(opts: { restoreKey?: string; hash: string; toTop: boolean }): void {
|
|
29
|
+
plan = {
|
|
30
|
+
restore: opts.restoreKey !== undefined ? (positions.get(opts.restoreKey) ?? 0) : null,
|
|
31
|
+
hash: opts.hash,
|
|
32
|
+
toTop: opts.toTop,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Applies the pending scroll plan once: hash target, else restored position, else top. */
|
|
37
|
+
export function applyScroll(): void {
|
|
38
|
+
const current = plan;
|
|
39
|
+
plan = null;
|
|
40
|
+
if (!current) return;
|
|
41
|
+
if (current.hash) {
|
|
42
|
+
const el = document.getElementById(decodeURIComponent(current.hash.slice(1)));
|
|
43
|
+
if (el) {
|
|
44
|
+
el.scrollIntoView();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (current.restore !== null) {
|
|
49
|
+
window.scrollTo(0, current.restore);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (current.toTop) window.scrollTo(0, 0);
|
|
53
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createElement, Suspense, useLayoutEffect, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { ErrorBoundary } from './error-boundary.js';
|
|
4
|
+
import { useLocation } from './hooks.js';
|
|
5
|
+
import {
|
|
6
|
+
errorComponent,
|
|
7
|
+
loadingComponent,
|
|
8
|
+
nestedLayout,
|
|
9
|
+
pageComponent,
|
|
10
|
+
resolveLayout,
|
|
11
|
+
resolveNotFound,
|
|
12
|
+
} from './lazy.js';
|
|
13
|
+
import { matchRoute, type RouteParams } from './match.js';
|
|
14
|
+
import { ParamsContext } from './params-context.js';
|
|
15
|
+
import { settleNavigation } from '../navigation/navigation.js';
|
|
16
|
+
import { applyScroll } from '../navigation/scroll.js';
|
|
17
|
+
import type { LayoutLoader, NotFoundLoader, RouteDef } from '../types.js';
|
|
18
|
+
|
|
19
|
+
/** Matches the current location to a route and renders it, optionally wrapped in the root layout. */
|
|
20
|
+
export function Router(props: {
|
|
21
|
+
routes: RouteDef[];
|
|
22
|
+
layout?: LayoutLoader;
|
|
23
|
+
notFound?: NotFoundLoader;
|
|
24
|
+
}): ReactNode {
|
|
25
|
+
const { routes, layout = null, notFound = null } = props;
|
|
26
|
+
const pathname = useLocation();
|
|
27
|
+
|
|
28
|
+
// After each navigation commits, apply the planned scroll (top / restore / #hash) and mark the
|
|
29
|
+
// navigation settled. A layout effect runs before paint, so the scroll lands without a flash.
|
|
30
|
+
useLayoutEffect(() => {
|
|
31
|
+
applyScroll();
|
|
32
|
+
settleNavigation();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
let matched: RouteDef | undefined;
|
|
36
|
+
let params: RouteParams = {};
|
|
37
|
+
for (const route of routes) {
|
|
38
|
+
const result = matchRoute(route.pattern, pathname);
|
|
39
|
+
if (result) {
|
|
40
|
+
matched = route;
|
|
41
|
+
params = result;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let content: ReactNode;
|
|
47
|
+
if (matched) {
|
|
48
|
+
const Page = pageComponent(matched);
|
|
49
|
+
const fallback: ReactNode = matched.loading
|
|
50
|
+
? createElement(Suspense, { fallback: null }, createElement(loadingComponent(matched.loading)))
|
|
51
|
+
: null;
|
|
52
|
+
content = (
|
|
53
|
+
<Suspense fallback={fallback}>
|
|
54
|
+
<Page />
|
|
55
|
+
</Suspense>
|
|
56
|
+
);
|
|
57
|
+
// Wrap in nested layouts, deepest first so the shallowest ends up outermost.
|
|
58
|
+
const chain = matched.layouts ?? [];
|
|
59
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
60
|
+
const NestedLayout = nestedLayout(chain[i]);
|
|
61
|
+
content = (
|
|
62
|
+
<Suspense fallback={null}>
|
|
63
|
+
<NestedLayout>{content}</NestedLayout>
|
|
64
|
+
</Suspense>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (matched.errorComponent) {
|
|
68
|
+
content = (
|
|
69
|
+
<ErrorBoundary fallback={errorComponent(matched.errorComponent)}>
|
|
70
|
+
{content}
|
|
71
|
+
</ErrorBoundary>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
} else if (notFound) {
|
|
75
|
+
const NotFound = resolveNotFound(notFound);
|
|
76
|
+
content = (
|
|
77
|
+
<Suspense fallback={null}>
|
|
78
|
+
<NotFound />
|
|
79
|
+
</Suspense>
|
|
80
|
+
);
|
|
81
|
+
} else {
|
|
82
|
+
content = <div style={{ padding: 24, fontFamily: 'system-ui' }}>404 — Not found</div>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (layout) {
|
|
86
|
+
const Layout = resolveLayout(layout);
|
|
87
|
+
content = (
|
|
88
|
+
<Suspense fallback={null}>
|
|
89
|
+
<Layout>{content}</Layout>
|
|
90
|
+
</Suspense>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return <ParamsContext.Provider value={params}>{content}</ParamsContext.Provider>;
|
|
95
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Component, Suspense, type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { RouteErrorProps } from '../types.js';
|
|
4
|
+
|
|
5
|
+
interface ErrorBoundaryProps {
|
|
6
|
+
readonly fallback: ComponentType<RouteErrorProps>;
|
|
7
|
+
readonly children: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
interface ErrorBoundaryState {
|
|
10
|
+
readonly error: Error | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Catches render errors in its subtree and shows the route's `error.tsx` (with a `reset` to retry).
|
|
15
|
+
* Error boundaries must be class components — React has no hook equivalent.
|
|
16
|
+
*/
|
|
17
|
+
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
18
|
+
state: ErrorBoundaryState = { error: null };
|
|
19
|
+
|
|
20
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
21
|
+
return { error };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
reset = (): void => {
|
|
25
|
+
this.setState({ error: null });
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
render(): ReactNode {
|
|
29
|
+
const { error } = this.state;
|
|
30
|
+
if (error) {
|
|
31
|
+
const Fallback = this.props.fallback;
|
|
32
|
+
return (
|
|
33
|
+
<Suspense fallback={null}>
|
|
34
|
+
<Fallback
|
|
35
|
+
error={error}
|
|
36
|
+
reset={this.reset}
|
|
37
|
+
/>
|
|
38
|
+
</Suspense>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return this.props.children;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router hooks for user route components: read the params / pathname / search params, navigate
|
|
3
|
+
* imperatively, and grab a router handle.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
startTransition,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useReducer,
|
|
11
|
+
useSyncExternalStore,
|
|
12
|
+
} from 'react';
|
|
13
|
+
|
|
14
|
+
import type { RouteParams } from './match.js';
|
|
15
|
+
import {
|
|
16
|
+
back,
|
|
17
|
+
forward,
|
|
18
|
+
isNavigationPending,
|
|
19
|
+
navigate,
|
|
20
|
+
refresh,
|
|
21
|
+
subscribeLocation,
|
|
22
|
+
subscribePending,
|
|
23
|
+
type NavigateOptions,
|
|
24
|
+
} from '../navigation/navigation.js';
|
|
25
|
+
import { ParamsContext } from './params-context.js';
|
|
26
|
+
import { prefetch } from '../navigation/prefetch.js';
|
|
27
|
+
|
|
28
|
+
/** Imperative router handle returned by {@link useRouter}. */
|
|
29
|
+
export interface RouterInstance {
|
|
30
|
+
/** Navigate to `href`, pushing a new history entry (or replacing with `{ replace: true }`). */
|
|
31
|
+
push(href: string, options?: NavigateOptions): void;
|
|
32
|
+
/** Navigate to `href`, replacing the current history entry. */
|
|
33
|
+
replace(href: string): void;
|
|
34
|
+
/** Go back one history entry. */
|
|
35
|
+
back(): void;
|
|
36
|
+
/** Go forward one history entry. */
|
|
37
|
+
forward(): void;
|
|
38
|
+
/** Re-render the current route. */
|
|
39
|
+
refresh(): void;
|
|
40
|
+
/** Prefetch a route's chunk ahead of navigation. */
|
|
41
|
+
prefetch(href: string): void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const ROUTER: RouterInstance = {
|
|
45
|
+
push: (href, options) => {
|
|
46
|
+
navigate(href, options);
|
|
47
|
+
},
|
|
48
|
+
replace: (href) => {
|
|
49
|
+
navigate(href, { replace: true });
|
|
50
|
+
},
|
|
51
|
+
back,
|
|
52
|
+
forward,
|
|
53
|
+
refresh,
|
|
54
|
+
prefetch,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Current dynamic route params, e.g. `{ id }` inside `/blog/:id`. */
|
|
58
|
+
export function useParams(): RouteParams {
|
|
59
|
+
return useContext(ParamsContext);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Returns the imperative `navigate(href, { replace })` function. */
|
|
63
|
+
export function useNavigate(): typeof navigate {
|
|
64
|
+
return navigate;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Returns the router handle (`push` / `replace` / `back` / `forward` / `refresh` / `prefetch`). */
|
|
68
|
+
export function useRouter(): RouterInstance {
|
|
69
|
+
return ROUTER;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Subscribes to location changes (in a transition, so the current page stays on screen while the
|
|
74
|
+
* next chunk loads) and reads the live `window.location` on render. Re-renders on any pathname,
|
|
75
|
+
* search, or hash change.
|
|
76
|
+
*/
|
|
77
|
+
function useLocationSubscription(): void {
|
|
78
|
+
const [, forceUpdate] = useReducer((n: number): number => n + 1, 0);
|
|
79
|
+
useEffect(
|
|
80
|
+
() =>
|
|
81
|
+
subscribeLocation(() => {
|
|
82
|
+
startTransition(() => {
|
|
83
|
+
forceUpdate();
|
|
84
|
+
});
|
|
85
|
+
}),
|
|
86
|
+
[],
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Subscribes to and returns the current `location.pathname`. */
|
|
91
|
+
export function useLocation(): string {
|
|
92
|
+
useLocationSubscription();
|
|
93
|
+
return window.location.pathname;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Alias of {@link useLocation}: the current `location.pathname`. */
|
|
97
|
+
export function usePathname(): string {
|
|
98
|
+
return useLocation();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The current query string as a `URLSearchParams`, re-read on every navigation. */
|
|
102
|
+
export function useSearchParams(): URLSearchParams {
|
|
103
|
+
useLocationSubscription();
|
|
104
|
+
const search = window.location.search;
|
|
105
|
+
return useMemo(() => new URLSearchParams(search), [search]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** True while a navigation is in flight (started but not yet committed) — e.g. for a loading bar. */
|
|
109
|
+
export function useNavigationPending(): boolean {
|
|
110
|
+
return useSyncExternalStore(
|
|
111
|
+
subscribePending,
|
|
112
|
+
isNavigationPending,
|
|
113
|
+
() => false,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy-component resolution and caching. Each page/layout/not-found loader is wrapped in
|
|
3
|
+
* `React.lazy` exactly once and memoized, so re-renders reuse the same component (and React's
|
|
4
|
+
* Suspense cache) instead of re-creating it. Keyed by loader identity.
|
|
5
|
+
*/
|
|
6
|
+
import { lazy, type ComponentType, type ReactNode } from 'react';
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
LayoutComponentLoader,
|
|
10
|
+
LayoutLoader,
|
|
11
|
+
NotFoundLoader,
|
|
12
|
+
RouteDef,
|
|
13
|
+
RouteErrorProps,
|
|
14
|
+
} from '../types.js';
|
|
15
|
+
|
|
16
|
+
type Loader<P> = () => Promise<{ default: ComponentType<P> }>;
|
|
17
|
+
|
|
18
|
+
/** Memoizes `lazy()` per loader identity in `cache`. */
|
|
19
|
+
function memoLazy<P>(
|
|
20
|
+
cache: Map<Loader<P>, ComponentType<P>>,
|
|
21
|
+
loader: Loader<P>,
|
|
22
|
+
): ComponentType<P> {
|
|
23
|
+
let component = cache.get(loader);
|
|
24
|
+
if (!component) {
|
|
25
|
+
component = lazy(loader);
|
|
26
|
+
cache.set(loader, component);
|
|
27
|
+
}
|
|
28
|
+
return component;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const loadingCache = new Map<Loader<object>, ComponentType<object>>();
|
|
32
|
+
/** Memoized lazy component for a route's `loading.tsx`. */
|
|
33
|
+
export function loadingComponent(loader: Loader<object>): ComponentType<object> {
|
|
34
|
+
return memoLazy(loadingCache, loader);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const errorCache = new Map<Loader<RouteErrorProps>, ComponentType<RouteErrorProps>>();
|
|
38
|
+
/** Memoized lazy component for a route's `error.tsx`. */
|
|
39
|
+
export function errorComponent(loader: Loader<RouteErrorProps>): ComponentType<RouteErrorProps> {
|
|
40
|
+
return memoLazy(errorCache, loader);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const pageCache = new Map<RouteDef, ComponentType>();
|
|
44
|
+
|
|
45
|
+
/** Returns the memoized lazy component for `route`, creating it on first use. */
|
|
46
|
+
export function pageComponent(route: RouteDef): ComponentType {
|
|
47
|
+
let component = pageCache.get(route);
|
|
48
|
+
if (!component) {
|
|
49
|
+
component = lazy(route.load);
|
|
50
|
+
pageCache.set(route, component);
|
|
51
|
+
}
|
|
52
|
+
return component;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let layoutComponent: ComponentType<{ children?: ReactNode }> | null = null;
|
|
56
|
+
let layoutLoader: LayoutLoader = null;
|
|
57
|
+
|
|
58
|
+
/** Returns the memoized lazy root-layout component, rebuilding only if the loader identity changes. */
|
|
59
|
+
export function resolveLayout(
|
|
60
|
+
loader: NonNullable<LayoutLoader>,
|
|
61
|
+
): ComponentType<{ children?: ReactNode }> {
|
|
62
|
+
if (layoutLoader !== loader || !layoutComponent) {
|
|
63
|
+
layoutComponent = lazy(loader);
|
|
64
|
+
layoutLoader = loader;
|
|
65
|
+
}
|
|
66
|
+
return layoutComponent;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const nestedLayoutCache = new Map<LayoutComponentLoader, ComponentType<{ children?: ReactNode }>>();
|
|
70
|
+
|
|
71
|
+
/** Returns the memoized lazy component for a nested layout loader, keyed by loader identity. */
|
|
72
|
+
export function nestedLayout(
|
|
73
|
+
loader: LayoutComponentLoader,
|
|
74
|
+
): ComponentType<{ children?: ReactNode }> {
|
|
75
|
+
let component = nestedLayoutCache.get(loader);
|
|
76
|
+
if (!component) {
|
|
77
|
+
component = lazy(loader);
|
|
78
|
+
nestedLayoutCache.set(loader, component);
|
|
79
|
+
}
|
|
80
|
+
return component;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let notFoundComponent: ComponentType | null = null;
|
|
84
|
+
let notFoundLoader: NotFoundLoader = null;
|
|
85
|
+
|
|
86
|
+
/** Returns the memoized lazy not-found component, rebuilding only if the loader identity changes. */
|
|
87
|
+
export function resolveNotFound(loader: NonNullable<NotFoundLoader>): ComponentType {
|
|
88
|
+
if (notFoundLoader !== loader || !notFoundComponent) {
|
|
89
|
+
notFoundComponent = lazy(loader);
|
|
90
|
+
notFoundLoader = loader;
|
|
91
|
+
}
|
|
92
|
+
return notFoundComponent;
|
|
93
|
+
}
|