acrylblends 0.6.2 → 0.6.4
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/README.md +5 -1
- package/docs/framework/README.md +3 -2
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/cordis/acryl-in-a-cordis-app.md +6 -0
- package/docs/framework/getting-started/basic.md +1 -0
- package/docs/framework/getting-started/desktop.md +41 -0
- package/docs/framework/guides/jsx-dom.md +0 -3
- package/docs/framework/guides/jsx.md +24 -3
- package/docs/framework/helpers/css.md +0 -3
- package/docs/framework/helpers/ssg.md +3 -6
- package/docs/framework/middleware/builtin/jsx-renderer.md +0 -3
- package/docs/framework/upstream.json +3 -3
- package/lib/accepts.d.ts +1 -1
- package/lib/acryl-base.d.ts +1 -1
- package/lib/adapter.d.ts +1 -1
- package/lib/{app-BdrVHBlJ.d.ts → app-D8riatvv.d.ts} +1 -1
- package/lib/base-BpQy2Rk7.d.ts +783 -0
- package/lib/base-ddiVmiE7.js +721 -0
- package/lib/basic-auth.d.ts +1 -1
- package/lib/bearer-auth.d.ts +1 -1
- package/lib/body-limit.d.ts +1 -1
- package/lib/cache.d.ts +1 -1
- package/lib/cache.js +4 -4
- package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
- package/lib/client.d.ts +2 -2
- package/lib/client.js +2 -1
- package/lib/combine.d.ts +1 -1
- package/lib/common-D8Z4JRCw.d.ts +46 -0
- package/lib/components-0o0CBYNv.js +268 -0
- package/lib/components-DdI7-ns7.js +24 -0
- package/lib/compress.d.ts +1 -1
- package/lib/conninfo.d.ts +2 -2
- package/lib/constants-BR9jM9BC.js +9 -0
- package/lib/{context-B5Y1lLha.d.ts → context-DS3fYyG-.d.ts} +2 -2
- package/lib/context-DxFdY_un.d.ts +250 -0
- package/lib/context-storage.d.ts +1 -1
- package/lib/{cookie-D9_nQbsM.d.ts → cookie-CCelEqky.d.ts} +1 -1
- package/lib/cookie.d.ts +3 -3
- package/lib/cors.d.ts +1 -1
- package/lib/csrf.d.ts +1 -1
- package/lib/css-D2jb7X8b.js +320 -0
- package/lib/css.d.ts +82 -0
- package/lib/css.js +134 -0
- package/lib/dev.d.ts +2 -2
- package/lib/etag.d.ts +1 -1
- package/lib/factory.d.ts +2 -2
- package/lib/hooks-BMw6BAyP.js +762 -0
- package/lib/html-SllAe0YT.d.ts +46 -0
- package/lib/html.d.ts +1 -1
- package/lib/{index-m12zcmph.d.ts → index-Bb7YJKCq.d.ts} +3 -3
- package/lib/{index-Kqou8Fjh.d.ts → index-BvFh1Gfx.d.ts} +2 -2
- package/lib/index-C850MeBA.d.ts +94 -0
- package/lib/{index-DtEoak1i.d.ts → index-CTy4N7ng.d.ts} +2 -2
- package/lib/index.d.ts +4 -4
- package/lib/ip-restriction.d.ts +2 -2
- package/lib/jsx/dom/client.d.ts +30 -0
- package/lib/jsx/dom/client.js +49 -0
- package/lib/jsx/dom/css.d.ts +84 -0
- package/lib/jsx/dom/css.js +2 -0
- package/lib/jsx/dom/jsx-dev-runtime.d.ts +3 -0
- package/lib/jsx/dom/jsx-dev-runtime.js +2 -0
- package/lib/jsx/dom/jsx-runtime.d.ts +3 -0
- package/lib/jsx/dom/jsx-runtime.js +2 -0
- package/lib/jsx/dom/server.d.ts +105 -0
- package/lib/jsx/dom/server.js +37 -0
- package/lib/jsx/dom.d.ts +5 -0
- package/lib/jsx/dom.js +74 -0
- package/lib/jsx/jsx-dev-runtime.d.ts +6 -0
- package/lib/jsx/jsx-dev-runtime.js +14 -0
- package/lib/jsx/jsx-runtime.d.ts +10 -0
- package/lib/jsx/jsx-runtime.js +30 -0
- package/lib/jsx/streaming.d.ts +2 -0
- package/lib/jsx/streaming.js +2 -0
- package/lib/jsx-dev-runtime-B22O-Npk.d.ts +7 -0
- package/lib/jsx-dev-runtime-D18Y35TI.js +354 -0
- package/lib/jsx-renderer.d.ts +75 -0
- package/lib/jsx-renderer.js +99 -0
- package/lib/jsx-runtime-BVVgDSdq.d.ts +1 -0
- package/lib/jsx.d.ts +3 -0
- package/lib/jsx.js +47 -0
- package/lib/jwk.d.ts +4 -4
- package/lib/{jwt-BA65QhnJ.d.ts → jwt-BARhotDz.d.ts} +1 -1
- package/lib/jwt.d.ts +4 -4
- package/lib/language.d.ts +1 -1
- package/lib/logger.d.ts +1 -1
- package/lib/loopback-only.d.ts +1 -1
- package/lib/method-not-allowed.d.ts +2 -2
- package/lib/method-override.d.ts +2 -2
- package/lib/mount.d.ts +1 -1
- package/lib/node-server/conninfo.d.ts +1 -1
- package/lib/node-server/serve-static.d.ts +1 -1
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.d.ts +1 -1
- package/lib/node-server.js +1 -1
- package/lib/powered-by.d.ts +1 -1
- package/lib/pretty-json.d.ts +1 -1
- package/lib/proxy.d.ts +1 -1
- package/lib/quick.d.ts +1 -1
- package/lib/request-id.d.ts +1 -1
- package/lib/request.d.ts +1 -1
- package/lib/route.d.ts +1 -1
- package/lib/schema-validator.d.ts +1 -1
- package/lib/secure-headers.d.ts +1 -1
- package/lib/{serve-static-Dw_m6KxR.js → serve-static-B14MF1Va.js} +2 -0
- package/lib/serve-static.d.ts +1 -1
- package/lib/ssg-BwjcA-Ge.js +389 -0
- package/lib/ssg.d.ts +156 -0
- package/lib/ssg.js +2 -0
- package/lib/streaming.d.ts +1 -1
- package/lib/testing.d.ts +4 -4
- package/lib/testing.js +1 -1
- package/lib/timeout.d.ts +1 -1
- package/lib/timing.d.ts +1 -1
- package/lib/tiny.d.ts +1 -1
- package/lib/trailing-slash.d.ts +1 -1
- package/lib/{types-ZM9gIiKh.d.ts → types-Dv_nV2cv.d.ts} +1 -1
- package/lib/{types-D9DEXMjE.d.ts → types-Lqx5BxQF.d.ts} +8 -1
- package/lib/{types-Bd29gh2o.d.ts → types-zNxyEpG2.d.ts} +3 -3
- package/lib/types.d.ts +1 -1
- package/lib/utils/body.d.ts +1 -1
- package/lib/utils/cookie.d.ts +1 -1
- package/lib/utils/crypto.d.ts +1 -1
- package/lib/utils/html.d.ts +1 -45
- package/lib/utils/ipaddr.d.ts +1 -1
- package/lib/utils/jwt.d.ts +1 -1
- package/lib/utils/types.d.ts +2 -2
- package/lib/utils-Cx61GFMC.js +125 -0
- package/lib/validator.d.ts +2 -2
- package/lib/ws.d.ts +1 -1
- package/package.json +53 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/upstream.json +1 -1
- package/plugins/acryl-router/src/core/helper/css/common.ts +337 -0
- package/plugins/acryl-router/src/core/helper/css/index.ts +262 -0
- package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
- package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
- package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
- package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
- package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
- package/plugins/acryl-router/src/core/jsx/base.ts +490 -0
- package/plugins/acryl-router/src/core/jsx/children.ts +20 -0
- package/plugins/acryl-router/src/core/jsx/components.ts +257 -0
- package/plugins/acryl-router/src/core/jsx/constants.ts +6 -0
- package/plugins/acryl-router/src/core/jsx/context.ts +266 -0
- package/plugins/acryl-router/src/core/jsx/dom/client.ts +89 -0
- package/plugins/acryl-router/src/core/jsx/dom/components.ts +39 -0
- package/plugins/acryl-router/src/core/jsx/dom/context.ts +52 -0
- package/plugins/acryl-router/src/core/jsx/dom/css.ts +263 -0
- package/plugins/acryl-router/src/core/jsx/dom/hooks/index.ts +91 -0
- package/plugins/acryl-router/src/core/jsx/dom/index.ts +170 -0
- package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.ts +438 -0
- package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.ts +23 -0
- package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.ts +8 -0
- package/plugins/acryl-router/src/core/jsx/dom/render.ts +879 -0
- package/plugins/acryl-router/src/core/jsx/dom/server.ts +76 -0
- package/plugins/acryl-router/src/core/jsx/dom/utils.ts +7 -0
- package/plugins/acryl-router/src/core/jsx/hooks/index.ts +437 -0
- package/plugins/acryl-router/src/core/jsx/index.ts +115 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.ts +23 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.ts +210 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-elements.ts +938 -0
- package/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts +26 -0
- package/plugins/acryl-router/src/core/jsx/jsx-runtime.ts +51 -0
- package/plugins/acryl-router/src/core/jsx/streaming.ts +231 -0
- package/plugins/acryl-router/src/core/jsx/types.ts +41 -0
- package/plugins/acryl-router/src/core/jsx/utils.ts +202 -0
- package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
- package/plugins/acryl-router/src/core/utils/types.ts +6 -0
- package/plugins/acryl-router/src/helpers/index.ts +2 -0
- package/plugins/acryl-router/src/middleware/index.ts +1 -0
- package/plugins/acryl-router/upstream.json +3 -3
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/upstream.json +1 -1
- package/plugins/extensions/resolve.ts +4 -0
- package/plugins/save/plugin.json +1 -1
- package/shared/AGENTS.core.md +4 -0
- package/templates/agent/tsconfig.json +7 -1
- package/templates/todo/upstream.json +1 -1
- package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
- package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
- package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
- package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
- package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
- package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
- package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
- package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
- package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
- package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
- package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
- package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
- package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
- package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
- package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
- package/plugins/acryl-router/src/core/app.test.ts +0 -3833
- package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
- package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
- package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
- package/plugins/acryl-router/src/core/compose.test.ts +0 -751
- package/plugins/acryl-router/src/core/context.test.ts +0 -629
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
- package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
- package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
- package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
- package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
- package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
- package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
- package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
- package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
- package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
- package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
- package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
- package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
- package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
- package/plugins/acryl-router/src/core/request.test.ts +0 -751
- package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
- package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
- package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
- package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
- package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
- package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
- package/plugins/acryl-router/src/core/types.test.ts +0 -4134
- package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
- package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
- package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
- package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
- package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
- package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
- package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
- package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
- package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
- package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
- package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
- package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
- package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
- package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
- package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
- package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
- package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
- package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
- package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
- package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
- package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
- package/plugins/acryl-router/tests/loader.spec.ts +0 -72
- package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
- package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
- package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
- package/plugins/acryl-router/tests/service.spec.ts +0 -426
- package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
- package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
- package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
- package/plugins/acryl-state/src/react.ts +0 -11
- package/plugins/acryl-state/tests/cell.spec.ts +0 -71
- package/plugins/acryl-state/tests/react.spec.ts +0 -39
- package/plugins/acryl-state/tests/service.spec.ts +0 -132
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { raw } from '../helper/html/index.ts'
|
|
2
|
+
import type { HtmlEscapedCallback, HtmlEscapedString } from '../utils/html.ts'
|
|
3
|
+
import { HtmlEscapedCallbackPhase, resolveCallback } from '../utils/html.ts'
|
|
4
|
+
import type { Child, FC, PropsWithChildren } from './index.ts'
|
|
5
|
+
import { jsx, Fragment, isUntrustedObject, renderChildren, renderUntrustedObject } from './base.ts'
|
|
6
|
+
import { DOM_RENDERER } from './constants.ts'
|
|
7
|
+
import { captureRenderContext, useContext } from './context.ts'
|
|
8
|
+
import { ErrorBoundary as ErrorBoundaryDomRenderer } from './dom/components.ts'
|
|
9
|
+
import type { HasRenderToDom } from './dom/render.ts'
|
|
10
|
+
import { StreamingContext } from './streaming.ts'
|
|
11
|
+
|
|
12
|
+
let errorBoundaryCounter = 0
|
|
13
|
+
|
|
14
|
+
export const childrenToString = async (children: Child[]): Promise<HtmlEscapedString[]> => {
|
|
15
|
+
try {
|
|
16
|
+
return children.flat().map(resolveChildEarly) as HtmlEscapedString[]
|
|
17
|
+
} catch (e) {
|
|
18
|
+
if (e instanceof Promise) {
|
|
19
|
+
// Capture before `await`: on the fallback path the render context is
|
|
20
|
+
// only observable during this synchronous window.
|
|
21
|
+
const resume = captureRenderContext()
|
|
22
|
+
await e
|
|
23
|
+
return resume(() => childrenToString(children))
|
|
24
|
+
} else {
|
|
25
|
+
throw e
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const resolveChildEarly = (child: Child): HtmlEscapedString | Promise<HtmlEscapedString> => {
|
|
31
|
+
if (child == null || typeof child === 'boolean') {
|
|
32
|
+
return '' as HtmlEscapedString
|
|
33
|
+
} else if (typeof child === 'string' || Array.isArray(child)) {
|
|
34
|
+
return renderChildren([child])
|
|
35
|
+
} else if (isUntrustedObject(child)) {
|
|
36
|
+
return renderUntrustedObject(child)
|
|
37
|
+
} else {
|
|
38
|
+
const str = child.toString()
|
|
39
|
+
return str instanceof Promise ? (str as Promise<HtmlEscapedString>) : raw(str)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ErrorHandler = (error: Error) => void
|
|
44
|
+
export type FallbackRender = (error: Error) => Child
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @experimental
|
|
48
|
+
* `ErrorBoundary` is an experimental feature.
|
|
49
|
+
* The API might be changed.
|
|
50
|
+
*/
|
|
51
|
+
export const ErrorBoundary: FC<
|
|
52
|
+
PropsWithChildren<{
|
|
53
|
+
fallback?: Child
|
|
54
|
+
fallbackRender?: FallbackRender
|
|
55
|
+
onError?: ErrorHandler
|
|
56
|
+
}>
|
|
57
|
+
> = async ({ children, fallback, fallbackRender, onError }) => {
|
|
58
|
+
if (!children) {
|
|
59
|
+
return raw('')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!Array.isArray(children)) {
|
|
63
|
+
children = [children]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const nonce = useContext(StreamingContext)?.scriptNonce
|
|
67
|
+
|
|
68
|
+
let resume: ReturnType<typeof captureRenderContext> | undefined
|
|
69
|
+
const getResume = () => (resume ||= captureRenderContext())
|
|
70
|
+
|
|
71
|
+
let fallbackStrPromise: Promise<HtmlEscapedString | string | undefined> | undefined
|
|
72
|
+
const resolveFallbackStr = (): Promise<HtmlEscapedString | string | undefined> =>
|
|
73
|
+
(fallbackStrPromise ||= (async () => {
|
|
74
|
+
const awaitedFallback = await fallback
|
|
75
|
+
if (awaitedFallback === null || awaitedFallback === undefined) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
if (typeof awaitedFallback === 'string' || Array.isArray(awaitedFallback)) {
|
|
79
|
+
return getResume()(() => renderChildren([awaitedFallback]))
|
|
80
|
+
}
|
|
81
|
+
if (isUntrustedObject(awaitedFallback)) {
|
|
82
|
+
return getResume()(() => renderUntrustedObject(awaitedFallback))
|
|
83
|
+
}
|
|
84
|
+
const fallbackResult = await getResume()(() => awaitedFallback.toString())
|
|
85
|
+
return raw(
|
|
86
|
+
fallbackResult,
|
|
87
|
+
(fallbackResult as HtmlEscapedString).callbacks ||
|
|
88
|
+
(awaitedFallback as unknown as HtmlEscapedString).callbacks
|
|
89
|
+
)
|
|
90
|
+
})())
|
|
91
|
+
const renderFallback = async (error: Error): Promise<HtmlEscapedString> => {
|
|
92
|
+
const fallbackStr = await resolveFallbackStr()
|
|
93
|
+
return getResume()(async () => {
|
|
94
|
+
onError?.(error)
|
|
95
|
+
const fallbackRes = (
|
|
96
|
+
fallbackStr !== undefined
|
|
97
|
+
? fallbackStr
|
|
98
|
+
: (fallbackRender && jsx(Fragment, {}, fallbackRender(error) as HtmlEscapedString)) || ''
|
|
99
|
+
) as HtmlEscapedString
|
|
100
|
+
const fallbackResString = await Fragment({ children: fallbackRes }).toString()
|
|
101
|
+
return raw(
|
|
102
|
+
fallbackResString,
|
|
103
|
+
(fallbackResString as HtmlEscapedString).callbacks || fallbackRes.callbacks
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
let resArray: HtmlEscapedString[] | Promise<HtmlEscapedString[]>[] = []
|
|
108
|
+
try {
|
|
109
|
+
resArray = children.map(resolveChildEarly) as unknown as HtmlEscapedString[]
|
|
110
|
+
} catch (e) {
|
|
111
|
+
const resume = getResume()
|
|
112
|
+
if (e instanceof Promise) {
|
|
113
|
+
resArray = [
|
|
114
|
+
e
|
|
115
|
+
.then(() => resume(() => childrenToString(children as Child[])))
|
|
116
|
+
.catch((e) => renderFallback(e)),
|
|
117
|
+
] as Promise<HtmlEscapedString[]>[]
|
|
118
|
+
} else {
|
|
119
|
+
resArray = [await renderFallback(e as Error)]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (resArray.some((res) => (res as {}) instanceof Promise)) {
|
|
124
|
+
// Prime the context capture while still synchronous: a child that returned
|
|
125
|
+
// a Promise from `resolveChildEarly` skipped the `catch`, so the deferred
|
|
126
|
+
// `catchCallback` would otherwise capture too late.
|
|
127
|
+
getResume()
|
|
128
|
+
const index = errorBoundaryCounter++
|
|
129
|
+
const replaceRe = RegExp(`(<template id="E:${index}"></template>)(.*?)(<!--E:${index}-->)`, 's')
|
|
130
|
+
let caught = false
|
|
131
|
+
const catchCallback = async ({ error, buffer }: { error: Error; buffer?: [string] }) => {
|
|
132
|
+
if (caught) {
|
|
133
|
+
return ''
|
|
134
|
+
}
|
|
135
|
+
caught = true
|
|
136
|
+
|
|
137
|
+
const fallbackResString = await renderFallback(error)
|
|
138
|
+
const fallbackCallbacks = fallbackResString.callbacks
|
|
139
|
+
if (buffer) {
|
|
140
|
+
buffer[0] = buffer[0].replace(replaceRe, () => fallbackResString)
|
|
141
|
+
return fallbackCallbacks?.length ? raw('', fallbackCallbacks) : ''
|
|
142
|
+
}
|
|
143
|
+
return raw(
|
|
144
|
+
`<template data-acryl-target="E:${index}">${fallbackResString}</template><script>
|
|
145
|
+
((d,c,n) => {
|
|
146
|
+
c=d.currentScript.previousSibling
|
|
147
|
+
d=d.getElementById('E:${index}')
|
|
148
|
+
if(!d)return
|
|
149
|
+
do{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='E:${index}')
|
|
150
|
+
d.replaceWith(c.content)
|
|
151
|
+
})(document)
|
|
152
|
+
</script>`,
|
|
153
|
+
fallbackCallbacks
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let error: unknown
|
|
158
|
+
const promiseAll = Promise.all(resArray).catch((e) => (error = e))
|
|
159
|
+
return raw(`<template id="E:${index}"></template><!--E:${index}-->`, [
|
|
160
|
+
({ phase, buffer, context }) => {
|
|
161
|
+
if (phase === HtmlEscapedCallbackPhase.BeforeStream) {
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
return promiseAll
|
|
165
|
+
.then(async (htmlArray: HtmlEscapedString[]) => {
|
|
166
|
+
if (error) {
|
|
167
|
+
throw error
|
|
168
|
+
}
|
|
169
|
+
htmlArray = htmlArray.flat()
|
|
170
|
+
const content = htmlArray.join('')
|
|
171
|
+
let html = buffer
|
|
172
|
+
? ''
|
|
173
|
+
: `<template data-acryl-target="E:${index}">${content}</template><script${
|
|
174
|
+
nonce ? ` nonce="${nonce}"` : ''
|
|
175
|
+
}>
|
|
176
|
+
((d,c) => {
|
|
177
|
+
c=d.currentScript.previousSibling
|
|
178
|
+
d=d.getElementById('E:${index}')
|
|
179
|
+
if(!d)return
|
|
180
|
+
d.parentElement.insertBefore(c.content,d.nextSibling)
|
|
181
|
+
})(document)
|
|
182
|
+
</script>`
|
|
183
|
+
|
|
184
|
+
if (htmlArray.every((html) => !(html as HtmlEscapedString).callbacks?.length)) {
|
|
185
|
+
if (buffer) {
|
|
186
|
+
buffer[0] = buffer[0].replace(replaceRe, () => content)
|
|
187
|
+
}
|
|
188
|
+
return html
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (buffer) {
|
|
192
|
+
buffer[0] = buffer[0].replace(
|
|
193
|
+
replaceRe,
|
|
194
|
+
(_all, pre, _, post) => `${pre}${content}${post}`
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const callbacks = htmlArray
|
|
199
|
+
.map((html) => (html as HtmlEscapedString).callbacks || [])
|
|
200
|
+
.flat()
|
|
201
|
+
|
|
202
|
+
if (phase === HtmlEscapedCallbackPhase.Stream) {
|
|
203
|
+
html = await resolveCallback(
|
|
204
|
+
html,
|
|
205
|
+
HtmlEscapedCallbackPhase.BeforeStream,
|
|
206
|
+
true,
|
|
207
|
+
context
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let resolvedCount = 0
|
|
212
|
+
const promises = callbacks.map<HtmlEscapedCallback>(
|
|
213
|
+
(c) =>
|
|
214
|
+
(...args) =>
|
|
215
|
+
c(...args)
|
|
216
|
+
?.then((content) => {
|
|
217
|
+
resolvedCount++
|
|
218
|
+
|
|
219
|
+
if (buffer) {
|
|
220
|
+
if (resolvedCount === callbacks.length) {
|
|
221
|
+
buffer[0] = buffer[0].replace(replaceRe, (_all, _pre, content) => content)
|
|
222
|
+
}
|
|
223
|
+
buffer[0] += content
|
|
224
|
+
return raw('', (content as HtmlEscapedString).callbacks)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return raw(
|
|
228
|
+
content +
|
|
229
|
+
(resolvedCount !== callbacks.length
|
|
230
|
+
? ''
|
|
231
|
+
: `<script>
|
|
232
|
+
((d,c,n) => {
|
|
233
|
+
d=d.getElementById('E:${index}')
|
|
234
|
+
if(!d)return
|
|
235
|
+
n=d.nextSibling
|
|
236
|
+
while(n.nodeType!=8||n.nodeValue!='E:${index}'){n=n.nextSibling}
|
|
237
|
+
n.remove()
|
|
238
|
+
d.remove()
|
|
239
|
+
})(document)
|
|
240
|
+
</script>`),
|
|
241
|
+
(content as HtmlEscapedString).callbacks
|
|
242
|
+
)
|
|
243
|
+
})
|
|
244
|
+
.catch((error) => catchCallback({ error, buffer }))
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
248
|
+
return raw(html, promises as any)
|
|
249
|
+
})
|
|
250
|
+
.catch((error) => catchCallback({ error, buffer }))
|
|
251
|
+
},
|
|
252
|
+
])
|
|
253
|
+
} else {
|
|
254
|
+
return Fragment({ children: resArray as Child[] })
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
;(ErrorBoundary as HasRenderToDom)[DOM_RENDERER] = ErrorBoundaryDomRenderer
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const DOM_RENDERER = Symbol('RENDERER')
|
|
2
|
+
export const DOM_ERROR_HANDLER = Symbol('ERROR_HANDLER')
|
|
3
|
+
export const DOM_STASH = Symbol('STASH')
|
|
4
|
+
export const DOM_INTERNAL_TAG = Symbol('INTERNAL')
|
|
5
|
+
export const DOM_MEMO = Symbol('MEMO')
|
|
6
|
+
export const PERMALINK = Symbol('PERMALINK')
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { raw } from '../helper/html/index.ts'
|
|
2
|
+
import type { HtmlEscapedString } from '../utils/html.ts'
|
|
3
|
+
import type { FC, PropsWithChildren } from './index.ts'
|
|
4
|
+
import { isUntrustedObject, JSXFragmentNode, renderChildren, renderUntrustedObject } from './base.ts'
|
|
5
|
+
import { DOM_RENDERER } from './constants.ts'
|
|
6
|
+
import { createContextProviderFunction } from './dom/context.ts'
|
|
7
|
+
|
|
8
|
+
export interface Context<T> extends FC<PropsWithChildren<{ value: T }>> {
|
|
9
|
+
values: T[]
|
|
10
|
+
Provider: FC<PropsWithChildren<{ value: T }>>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const globalContexts: Context<unknown>[] = []
|
|
14
|
+
|
|
15
|
+
/** Per-render context store, isolated per request so values never leak across renders. */
|
|
16
|
+
type RenderStore = WeakMap<Context<unknown>, unknown[]>
|
|
17
|
+
|
|
18
|
+
type AsyncLocalStorageLike<T> = {
|
|
19
|
+
getStore(): T | undefined
|
|
20
|
+
run<R>(store: T, callback: () => R): R
|
|
21
|
+
}
|
|
22
|
+
type AsyncLocalStorageClass = new <T>() => AsyncLocalStorageLike<T>
|
|
23
|
+
type AsyncHooksModule = { AsyncLocalStorage?: AsyncLocalStorageClass }
|
|
24
|
+
|
|
25
|
+
let alsProbed = false
|
|
26
|
+
let asyncLocalStorage: AsyncLocalStorageLike<RenderStore> | undefined
|
|
27
|
+
let fallbackStore: RenderStore | undefined
|
|
28
|
+
// Unsettled async renders on the fallback path. While > 0, a store-less
|
|
29
|
+
// context access is a post-`await` read that degraded to the default value
|
|
30
|
+
// — worth a one-time warning. Stays 0 in the browser DOM renderer.
|
|
31
|
+
let fallbackRendersInFlight = 0
|
|
32
|
+
let warnedFallbackDefault = false
|
|
33
|
+
|
|
34
|
+
const loadAsyncLocalStorage = (): AsyncLocalStorageLike<RenderStore> | undefined => {
|
|
35
|
+
if (alsProbed) {
|
|
36
|
+
return asyncLocalStorage
|
|
37
|
+
}
|
|
38
|
+
alsProbed = true
|
|
39
|
+
|
|
40
|
+
const global = globalThis as unknown as {
|
|
41
|
+
process?: {
|
|
42
|
+
getBuiltinModule?: (specifier: string) => AsyncHooksModule
|
|
43
|
+
mainModule?: { require?: (specifier: string) => AsyncHooksModule }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
let AsyncLocalStorage: AsyncLocalStorageClass | undefined
|
|
47
|
+
for (const probe of [
|
|
48
|
+
// Node.js >= 20.16, Deno, Bun, Cloudflare Workers (nodejs_compat). Property
|
|
49
|
+
// access only, so bundlers don't statically resolve `node:async_hooks`.
|
|
50
|
+
() => global.process?.getBuiltinModule?.('node:async_hooks')?.AsyncLocalStorage,
|
|
51
|
+
// Node.js < 20.16 has no `process.getBuiltinModule`, but a CJS entrypoint
|
|
52
|
+
// exposes the main module's `require` here.
|
|
53
|
+
() => global.process?.mainModule?.require?.('node:async_hooks')?.AsyncLocalStorage,
|
|
54
|
+
]) {
|
|
55
|
+
try {
|
|
56
|
+
AsyncLocalStorage = probe()
|
|
57
|
+
} catch {}
|
|
58
|
+
if (AsyncLocalStorage) {
|
|
59
|
+
break
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (AsyncLocalStorage) {
|
|
64
|
+
asyncLocalStorage = new AsyncLocalStorage<RenderStore>()
|
|
65
|
+
}
|
|
66
|
+
return asyncLocalStorage
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const getCurrentStore = (): RenderStore | undefined => {
|
|
70
|
+
return loadAsyncLocalStorage()?.getStore() || fallbackStore
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const warnIfStorelessAccess = (): void => {
|
|
74
|
+
if (fallbackRendersInFlight > 0 && !warnedFallbackDefault) {
|
|
75
|
+
warnedFallbackDefault = true
|
|
76
|
+
console.warn(
|
|
77
|
+
'acryl-router/jsx: AsyncLocalStorage is unavailable in this runtime, so useContext() after an await in an async component falls back to the context default value during server-side rendering. To get provided values across await boundaries, use a runtime with AsyncLocalStorage (Node.js >= 20.16, Deno, Bun, or Cloudflare Workers with the nodejs_compat flag).'
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Write path: materializes the store entry so a Provider's push/pop pair
|
|
83
|
+
// operates on a per-render array.
|
|
84
|
+
const getContextValuesIn = <T>(store: RenderStore | undefined, context: Context<T>): T[] => {
|
|
85
|
+
if (!store) {
|
|
86
|
+
warnIfStorelessAccess()
|
|
87
|
+
return context.values
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let values = store.get(context as Context<unknown>) as T[] | undefined
|
|
91
|
+
if (!values) {
|
|
92
|
+
values = [context.values[0]]
|
|
93
|
+
store.set(context as Context<unknown>, values)
|
|
94
|
+
}
|
|
95
|
+
return values
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Read path: never materializes a store entry — a missing entry always
|
|
99
|
+
// resolves to the default value, so reads stay allocation-free.
|
|
100
|
+
const readContextValueIn = <T>(store: RenderStore | undefined, context: Context<T>): T => {
|
|
101
|
+
if (!store) {
|
|
102
|
+
warnIfStorelessAccess()
|
|
103
|
+
return context.values.at(-1) as T
|
|
104
|
+
}
|
|
105
|
+
const values = store.get(context as Context<unknown>) as T[] | undefined
|
|
106
|
+
return values?.length ? (values.at(-1) as T) : context.values[0]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type LocalContexts = [Context<unknown>, unknown][]
|
|
110
|
+
|
|
111
|
+
const captureContextValues = (store: RenderStore | undefined): LocalContexts =>
|
|
112
|
+
(store ? globalContexts.filter((c) => store.has(c)) : globalContexts).map((c) => [
|
|
113
|
+
c,
|
|
114
|
+
readContextValueIn(store, c),
|
|
115
|
+
])
|
|
116
|
+
|
|
117
|
+
const resumeWithContextValues = <T>(
|
|
118
|
+
callback: () => T,
|
|
119
|
+
store: RenderStore | undefined,
|
|
120
|
+
contexts: LocalContexts
|
|
121
|
+
): T =>
|
|
122
|
+
runWithRenderContext(() => {
|
|
123
|
+
// Resolve each target array once so every pop operates on the same array
|
|
124
|
+
// as its push, as the Provider in `createContext` does.
|
|
125
|
+
const currentStore = getCurrentStore()
|
|
126
|
+
const valuesPerContext = contexts.map(([context, value]) => {
|
|
127
|
+
const values = getContextValuesIn(currentStore, context)
|
|
128
|
+
values.push(value)
|
|
129
|
+
return values
|
|
130
|
+
})
|
|
131
|
+
const popContextValues = () => {
|
|
132
|
+
valuesPerContext.forEach((values) => {
|
|
133
|
+
values.pop()
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const result = callback()
|
|
138
|
+
if (result instanceof Promise) {
|
|
139
|
+
return result.finally(popContextValues) as T
|
|
140
|
+
}
|
|
141
|
+
popContextValues()
|
|
142
|
+
return result
|
|
143
|
+
} catch (e) {
|
|
144
|
+
popContextValues()
|
|
145
|
+
throw e
|
|
146
|
+
}
|
|
147
|
+
}, store)
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Establish the request-scoped context store for a render.
|
|
151
|
+
*
|
|
152
|
+
* `resumeStore` continues a suspended subtree in the same store on the fallback
|
|
153
|
+
* path (ignored when `AsyncLocalStorage` is available, where isolation is
|
|
154
|
+
* automatic).
|
|
155
|
+
*
|
|
156
|
+
* Without `AsyncLocalStorage` a render can't be followed across `await`, so the
|
|
157
|
+
* store lives in `fallbackStore` only during synchronous work (mirroring
|
|
158
|
+
* React's request storage). Reading context after `await` then finds no store
|
|
159
|
+
* and falls back to the default value — never another request's value.
|
|
160
|
+
*/
|
|
161
|
+
export const runWithRenderContext = <T>(callback: () => T, resumeStore?: RenderStore): T => {
|
|
162
|
+
if (getCurrentStore()) {
|
|
163
|
+
// Already inside a render (synchronously nested or resumed subtree).
|
|
164
|
+
return callback()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const store = resumeStore ?? new WeakMap()
|
|
168
|
+
const storage = loadAsyncLocalStorage()
|
|
169
|
+
if (storage) {
|
|
170
|
+
// AsyncLocalStorage path: request isolation propagates across `await`.
|
|
171
|
+
return storage.run(store, callback)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Fallback path (no AsyncLocalStorage).
|
|
175
|
+
fallbackStore = store
|
|
176
|
+
let result: T
|
|
177
|
+
try {
|
|
178
|
+
result = callback()
|
|
179
|
+
} finally {
|
|
180
|
+
fallbackStore = undefined
|
|
181
|
+
}
|
|
182
|
+
if (!warnedFallbackDefault && result instanceof Promise) {
|
|
183
|
+
fallbackRendersInFlight++
|
|
184
|
+
result = result.finally(() => {
|
|
185
|
+
fallbackRendersInFlight--
|
|
186
|
+
}) as T
|
|
187
|
+
}
|
|
188
|
+
return result
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Capture the current render store and return a resumer that re-establishes it
|
|
193
|
+
* around a deferred continuation (e.g. a re-render after a suspended promise
|
|
194
|
+
* settles). Shared by every suspension point so none reimplements it.
|
|
195
|
+
*/
|
|
196
|
+
export const captureRenderContext = (): (<T>(callback: () => T) => T) => {
|
|
197
|
+
const store = getCurrentStore()
|
|
198
|
+
const contexts = captureContextValues(store)
|
|
199
|
+
return (callback) => resumeWithContextValues(callback, store, contexts)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create a context whose value can be provided with `<Context.Provider>` and
|
|
204
|
+
* read with {@link useContext}.
|
|
205
|
+
*
|
|
206
|
+
* Server-side renders are isolated per request, so a provided value never leaks
|
|
207
|
+
* into a concurrent request — even across `await` in an async component, when
|
|
208
|
+
* `AsyncLocalStorage` is available (Node.js >= 20.16, Deno, Bun, Cloudflare
|
|
209
|
+
* Workers with `nodejs_compat`). Without it, reading context after `await`
|
|
210
|
+
* returns the default value; synchronous components and `use()`-based
|
|
211
|
+
* suspension are unaffected.
|
|
212
|
+
*/
|
|
213
|
+
export const createContext = <T>(defaultValue: T): Context<T> => {
|
|
214
|
+
const values = [defaultValue]
|
|
215
|
+
const context: Context<T> = ((props): HtmlEscapedString | Promise<HtmlEscapedString> => {
|
|
216
|
+
// Resolve the target array once, synchronously: the pop in `.finally()`
|
|
217
|
+
// below may run later and must operate on the same array as the push.
|
|
218
|
+
const contextValues = getContextValuesIn(getCurrentStore(), context)
|
|
219
|
+
contextValues.push(props.value)
|
|
220
|
+
let rendered
|
|
221
|
+
try {
|
|
222
|
+
rendered =
|
|
223
|
+
typeof props.children === 'string'
|
|
224
|
+
? renderChildren([props.children])
|
|
225
|
+
: isUntrustedObject(props.children)
|
|
226
|
+
? renderUntrustedObject(props.children)
|
|
227
|
+
: props.children
|
|
228
|
+
? (Array.isArray(props.children)
|
|
229
|
+
? new JSXFragmentNode('', {}, props.children)
|
|
230
|
+
: props.children
|
|
231
|
+
).toString()
|
|
232
|
+
: raw('')
|
|
233
|
+
} catch (e) {
|
|
234
|
+
contextValues.pop()
|
|
235
|
+
throw e
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (rendered instanceof Promise) {
|
|
239
|
+
return rendered
|
|
240
|
+
.finally(() => contextValues.pop())
|
|
241
|
+
.then((resString) => raw(resString, (resString as HtmlEscapedString).callbacks))
|
|
242
|
+
} else {
|
|
243
|
+
contextValues.pop()
|
|
244
|
+
return raw(rendered)
|
|
245
|
+
}
|
|
246
|
+
}) as Context<T>
|
|
247
|
+
context.values = values
|
|
248
|
+
context.Provider = context
|
|
249
|
+
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
251
|
+
;(context as any)[DOM_RENDERER] = createContextProviderFunction(values)
|
|
252
|
+
|
|
253
|
+
globalContexts.push(context as Context<unknown>)
|
|
254
|
+
|
|
255
|
+
return context
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Read the current value of a context created with {@link createContext}.
|
|
260
|
+
*
|
|
261
|
+
* Safe to call from async components after `await`. See {@link createContext}
|
|
262
|
+
* for the per-runtime isolation guarantees.
|
|
263
|
+
*/
|
|
264
|
+
export const useContext = <T>(context: Context<T>): T => {
|
|
265
|
+
return readContextValueIn(getCurrentStore(), context)
|
|
266
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* This module provides APIs for `acryl-router/jsx/dom/client`, which is compatible with `react-dom/client`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Child } from '../base.ts'
|
|
7
|
+
import { useState } from '../hooks/index.ts'
|
|
8
|
+
import { buildNode, renderNode } from './render.ts'
|
|
9
|
+
import type { NodeObject } from './render.ts'
|
|
10
|
+
|
|
11
|
+
export interface Root {
|
|
12
|
+
render(children: Child): void
|
|
13
|
+
unmount(): void
|
|
14
|
+
}
|
|
15
|
+
export type RootOptions = Record<string, unknown>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a root object for rendering
|
|
19
|
+
* @param element Render target
|
|
20
|
+
* @param options Options for createRoot (not supported yet)
|
|
21
|
+
* @returns Root object has `render` and `unmount` methods
|
|
22
|
+
*/
|
|
23
|
+
export const createRoot = (
|
|
24
|
+
element: HTMLElement | DocumentFragment,
|
|
25
|
+
options: RootOptions = {}
|
|
26
|
+
): Root => {
|
|
27
|
+
let setJsxNode:
|
|
28
|
+
| undefined // initial state
|
|
29
|
+
| ((jsxNode: unknown) => void) // rendered
|
|
30
|
+
| null = // unmounted
|
|
31
|
+
undefined
|
|
32
|
+
|
|
33
|
+
if (Object.keys(options).length > 0) {
|
|
34
|
+
console.warn('createRoot options are not supported yet')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
render(jsxNode: unknown) {
|
|
39
|
+
if (setJsxNode === null) {
|
|
40
|
+
// unmounted
|
|
41
|
+
throw new Error('Cannot update an unmounted root')
|
|
42
|
+
}
|
|
43
|
+
if (setJsxNode) {
|
|
44
|
+
// rendered
|
|
45
|
+
setJsxNode(jsxNode)
|
|
46
|
+
} else {
|
|
47
|
+
renderNode(
|
|
48
|
+
buildNode({
|
|
49
|
+
tag: () => {
|
|
50
|
+
const [_jsxNode, _setJsxNode] = useState(jsxNode)
|
|
51
|
+
setJsxNode = _setJsxNode
|
|
52
|
+
return _jsxNode
|
|
53
|
+
},
|
|
54
|
+
props: {},
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
+
} as any) as NodeObject,
|
|
57
|
+
element
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
unmount() {
|
|
62
|
+
setJsxNode?.(null)
|
|
63
|
+
setJsxNode = null
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create a root object and hydrate app to the target element.
|
|
70
|
+
* In acryl-router/jsx/dom, hydrate is equivalent to render.
|
|
71
|
+
* @param element Render target
|
|
72
|
+
* @param reactNode A JSXNode to render
|
|
73
|
+
* @param options Options for createRoot (not supported yet)
|
|
74
|
+
* @returns Root object has `render` and `unmount` methods
|
|
75
|
+
*/
|
|
76
|
+
export const hydrateRoot = (
|
|
77
|
+
element: HTMLElement | DocumentFragment,
|
|
78
|
+
reactNode: Child,
|
|
79
|
+
options: RootOptions = {}
|
|
80
|
+
): Root => {
|
|
81
|
+
const root = createRoot(element, options)
|
|
82
|
+
root.render(reactNode)
|
|
83
|
+
return root
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default {
|
|
87
|
+
createRoot,
|
|
88
|
+
hydrateRoot,
|
|
89
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Child, FC, PropsWithChildren } from '../index.ts'
|
|
2
|
+
import type { ErrorHandler, FallbackRender } from '../components.ts'
|
|
3
|
+
import { DOM_ERROR_HANDLER } from '../constants.ts'
|
|
4
|
+
import { Fragment } from './jsx-runtime.ts'
|
|
5
|
+
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
+
export const ErrorBoundary: FC<
|
|
8
|
+
PropsWithChildren<{
|
|
9
|
+
fallback?: Child
|
|
10
|
+
fallbackRender?: FallbackRender
|
|
11
|
+
onError?: ErrorHandler
|
|
12
|
+
}>
|
|
13
|
+
> = (({ children, fallback, fallbackRender, onError }: any) => {
|
|
14
|
+
const res = Fragment({ children })
|
|
15
|
+
;(res as any)[DOM_ERROR_HANDLER] = (err: any) => {
|
|
16
|
+
if (err instanceof Promise) {
|
|
17
|
+
throw err
|
|
18
|
+
}
|
|
19
|
+
onError?.(err)
|
|
20
|
+
return fallbackRender?.(err) || fallback
|
|
21
|
+
}
|
|
22
|
+
return res
|
|
23
|
+
}) as any
|
|
24
|
+
|
|
25
|
+
export const Suspense: FC<PropsWithChildren<{ fallback: any }>> = (({
|
|
26
|
+
children,
|
|
27
|
+
fallback,
|
|
28
|
+
}: any) => {
|
|
29
|
+
const res = Fragment({ children })
|
|
30
|
+
;(res as any)[DOM_ERROR_HANDLER] = (err: any, retry: () => void) => {
|
|
31
|
+
if (!(err instanceof Promise)) {
|
|
32
|
+
throw err
|
|
33
|
+
}
|
|
34
|
+
err.finally(retry)
|
|
35
|
+
return fallback
|
|
36
|
+
}
|
|
37
|
+
return res
|
|
38
|
+
}) as any
|
|
39
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|