duto 0.0.0 → 0.260906.0
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/LICENSE +21 -0
- package/README.md +21 -12
- package/package.json +91 -2
- package/src/cli.ts +42 -0
- package/src/enum.ts +2 -0
- package/src/index.d.ts +61 -0
- package/src/index.ts +19 -0
- package/src/middleware.ts +2 -0
- package/src/pages/client/app.tsx +123 -0
- package/src/pages/client/hmr.ts +13 -0
- package/src/pages/client/island.ts +136 -0
- package/src/pages/client/link.ts +70 -0
- package/src/pages/client/router.ts +329 -0
- package/src/pages/client/storage.ts +95 -0
- package/src/pages/commands/build.ts +74 -0
- package/src/pages/commands/dev-old.ts +213 -0
- package/src/pages/commands/dev.ts +87 -0
- package/src/pages/commands/preview.ts +33 -0
- package/src/pages/compiler/aliases.ts +36 -0
- package/src/pages/compiler/assets.ts +43 -0
- package/src/pages/compiler/ast.ts +764 -0
- package/src/pages/compiler/constants.ts +24 -0
- package/src/pages/compiler/file.ts +60 -0
- package/src/pages/compiler/index.ts +307 -0
- package/src/pages/compiler/island.ts +108 -0
- package/src/pages/compiler/meta.ts +601 -0
- package/src/pages/compiler/routes.ts +29 -0
- package/src/pages/compiler/types.ts +209 -0
- package/src/pages/compiler/utils.ts +5 -0
- package/src/pages/compiler/vite-plugin.ts +324 -0
- package/src/pages/components/link.tsx +38 -0
- package/src/pages/i18n/client.ts +403 -0
- package/src/pages/i18n/element.ts +44 -0
- package/src/pages/i18n/server.ts +56 -0
- package/src/pages/index.ts +28 -0
- package/src/pages/preact/client-dev.ts +5 -0
- package/src/pages/preact/client.ts +87 -0
- package/src/pages/preact/context.ts +41 -0
- package/src/pages/preact/server.ts +186 -0
- package/src/pages/preact/signals.ts +89 -0
- package/src/pages/preact/static-html.ts +45 -0
- package/src/pages/preact/types.ts +26 -0
- package/src/server/adapter/aws.ts +4 -0
- package/src/server/adapter/workerd.ts +4 -0
- package/src/server/app.ts +96 -0
- package/src/server/auth/ability.ts +72 -0
- package/src/server/auth/authnz.ts +86 -0
- package/src/server/auth/index.ts +5 -0
- package/src/server/auth/token.ts +147 -0
- package/src/server/auth/types.ts +2 -0
- package/src/server/cli/commands/build.ts +41 -0
- package/src/server/cli/commands/deploy.ts +82 -0
- package/src/server/cli/commands/dev.ts +277 -0
- package/src/server/cli/commands/make.ts +98 -0
- package/src/server/cli/commands/migrate.ts +93 -0
- package/src/server/cli/commands/routes.ts +79 -0
- package/src/server/cli/index.ts +27 -0
- package/src/server/cli/stubs.ts +39 -0
- package/src/server/cli/types.ts +3 -0
- package/src/server/cli/utils.ts +443 -0
- package/src/server/config.ts +4 -0
- package/src/server/db/d1.d.ts +1 -0
- package/src/server/db/d1.ts +1 -0
- package/src/server/db/dynamodb.d.ts +1 -0
- package/src/server/db/dynamodb.ts +1 -0
- package/src/server/db/index.ts +1 -0
- package/src/server/entry.ts +19 -0
- package/src/server/handle.ts +30 -0
- package/src/server/http.ts +23 -0
- package/src/server/middlewares/auth.ts +40 -0
- package/src/server/middlewares/logger.ts +30 -0
- package/src/server/open-api/register.ts +29 -0
- package/src/server/open-api/spec.ts +107 -0
- package/src/server/request.ts +233 -0
- package/src/server/response.ts +142 -0
- package/src/server/routes.ts +474 -0
- package/src/server/types.ts +142 -0
- package/src/server/utils/environment.ts +17 -0
- package/src/server/utils.ts +46 -0
- package/src/server/validator.ts +70 -0
- package/src/utils.ts +5 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present, HUB.BI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="left">Duto<br/><a href="https://pr.new/attla/duto"><img align="right" src="https://developer.stackblitz.com/img/start_pr_dark_small.svg" alt="Start new PR in StackBlitz Codeflow"></a><a href="https://npmjs.com/package/duto"><img align="right" src="https://img.shields.io/npm/v/duto.svg" alt="npm package"></a></h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
```
|
|
3
|
+
> ⚠️ Duto is under ALFA development — expect updates, rough edges, and occasional breack changes.
|
|
4
|
+
<br/>
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
This framework is fully geared towards the serverless world, specifically for static pages.
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
- 💡 SSG
|
|
9
|
+
- ⚡️ Dynamic static routes
|
|
10
|
+
- 📦 Optimized Build
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
[Read the Docs to Learn More](https://github.com/attla/duto/blob/main/DOCS.md)
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
## Ecosystem Packages
|
|
15
|
+
|
|
16
|
+
| Package | Version | Description |
|
|
17
|
+
| -: | :- | :- |
|
|
18
|
+
| [rajt](https://github.com/attla/rajt) | [](https://npm.im/rajt) | 🧱 The core of Rajt serverless framework. |
|
|
19
|
+
| [create-rajt](https://github.com/attla/create-rajt) | [](https://npm.im/create-rajt) | 📁 CLI tool for create new Rajt projects, from starter templates. |
|
|
20
|
+
| [forj](https://github.com/attla/forj) | [](https://npm.im/forj) | 🗂️ Just another query builder in typescript |
|
|
21
|
+
| [t0n](https://github.com/attla/t0n) | [](https://npm.im/t0n) | 🧰 Collection of elegant typescript resources for web artisans. |
|
|
22
|
+
| [cripta](https://github.com/attla/cripta) | [](https://npm.im/cripta) | 🧛 A layer of encryption a little too judicious. |
|
|
23
|
+
|
|
24
|
+
## License
|
|
25
|
+
|
|
26
|
+
This package is licensed under the [MIT license](https://github.com/attla/duto/blob/main/LICENSE) © [HUB](https://hub.bi)
|
package/package.json
CHANGED
|
@@ -1,4 +1,93 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "duto",
|
|
3
|
-
"
|
|
2
|
+
"name": "duto",
|
|
3
|
+
"description": "All-in-one serverless framework for web artisans",
|
|
4
|
+
"version": "0.260906.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"bin": "./src/cli.ts",
|
|
9
|
+
"files": ["src"],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts",
|
|
12
|
+
"./auth": "./src/server/auth/index.ts",
|
|
13
|
+
"./db": "./src/db/index.ts",
|
|
14
|
+
"./database": "./src/db/index.ts",
|
|
15
|
+
"./d1": "./src/server/db/d1.ts",
|
|
16
|
+
"./d1/types": "./src/server/db/d1.d.ts",
|
|
17
|
+
"./dynamodb": "./src/server/db/dynamodb.ts",
|
|
18
|
+
"./dynamodb/types": "./src/server/db/dynamodb.d.ts",
|
|
19
|
+
"./enum": "./src/enum.ts",
|
|
20
|
+
"./http": "./src/server/http.ts",
|
|
21
|
+
"./middleware": "./src/middleware.ts",
|
|
22
|
+
"./types": "./src/server/types.ts",
|
|
23
|
+
"./types/global": "./global.d.ts",
|
|
24
|
+
"./env": "./src/server/utils/environment.ts",
|
|
25
|
+
"./length": "./src/server/utils/length.ts"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@hono/node-server": "^1.19.11",
|
|
29
|
+
"@hono/standard-validator": "^0.2.2",
|
|
30
|
+
"@hono/zod-validator": "^0.7.6",
|
|
31
|
+
"@scalar/hono-api-reference": "^0.12.0",
|
|
32
|
+
"@minify-html/node": "^0.18.1",
|
|
33
|
+
"@preact/signals": "^2.9.2",
|
|
34
|
+
"@swc/core": "^1.15.21",
|
|
35
|
+
"chokidar": "^3.5.2",
|
|
36
|
+
"citty": "^0.2.2",
|
|
37
|
+
"consola": "^3.4.2",
|
|
38
|
+
"cripta": "0.x",
|
|
39
|
+
"dotenv": "^16.5.0",
|
|
40
|
+
"esbuild": "^0.25.2",
|
|
41
|
+
"filesize": "^11.0",
|
|
42
|
+
"forj": "0.x",
|
|
43
|
+
"hono": "^4.13.7",
|
|
44
|
+
"hono-openapi": "^1.3.0",
|
|
45
|
+
"localflare-api": "^0.5.0",
|
|
46
|
+
"localflare-core": "^0.5.0",
|
|
47
|
+
"miniflare": "^4.20260730.0",
|
|
48
|
+
"pathe": "^2.0",
|
|
49
|
+
"preact": "^10.29.7",
|
|
50
|
+
"preact-render-to-string": "^6.6.6",
|
|
51
|
+
"quansync": "^0.2.11",
|
|
52
|
+
"t0n": "0.x",
|
|
53
|
+
"wouter-preact": "^3.10.0",
|
|
54
|
+
"wrangler": "^4.128.0",
|
|
55
|
+
"zod": "^4.3.6",
|
|
56
|
+
"zod-openapi": "^5.4.6"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@cloudflare/workers-types": "^4.20260113.0",
|
|
60
|
+
"@types/node": "^26.4.1",
|
|
61
|
+
"bun-types": "^1.4.0",
|
|
62
|
+
"typescript": "^5.9.3"
|
|
63
|
+
},
|
|
64
|
+
"resolutions": {
|
|
65
|
+
"@smithy/types": "^4.3.0",
|
|
66
|
+
"zod": "^4.3.6"
|
|
67
|
+
},
|
|
68
|
+
"sideEffects": false,
|
|
69
|
+
"packageManager": "bun@1.4.0",
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"registry": "https://registry.npmjs.org"
|
|
72
|
+
},
|
|
73
|
+
"author": "HUB <foss@hub.bi>",
|
|
74
|
+
"license": "MIT",
|
|
75
|
+
"homepage": "https://hub.bi/docs/duto",
|
|
76
|
+
"repository": "git://github.com/attla/duto",
|
|
77
|
+
"bugs": "https://github.com/attla/duto/issues",
|
|
78
|
+
"keywords": [
|
|
79
|
+
"SSG",
|
|
80
|
+
"app",
|
|
81
|
+
"http",
|
|
82
|
+
"application",
|
|
83
|
+
"framework",
|
|
84
|
+
"router",
|
|
85
|
+
"serverless",
|
|
86
|
+
"typescript",
|
|
87
|
+
"cloudflare",
|
|
88
|
+
"workers",
|
|
89
|
+
"workerd",
|
|
90
|
+
"vercel",
|
|
91
|
+
"bun"
|
|
92
|
+
]
|
|
4
93
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
function exit(msg?: string, desc?: string) {
|
|
4
|
+
msg && console.error(msg)
|
|
5
|
+
desc && console.log(desc)
|
|
6
|
+
process.exit(0)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (!import.meta.main)
|
|
10
|
+
exit('Error: Duto must be used directly as CLI command.')
|
|
11
|
+
|
|
12
|
+
if (!process?.isBun || typeof Bun === 'undefined')
|
|
13
|
+
exit('Error: "Bun" is not available.', ' Try install: https://bun.sh/get')
|
|
14
|
+
|
|
15
|
+
global.__st = performance.now()
|
|
16
|
+
|
|
17
|
+
import { join } from 'pathe'
|
|
18
|
+
import { cli } from 't0n/cli'
|
|
19
|
+
import { Envir } from 't0n'
|
|
20
|
+
import { version } from '!/package.json'
|
|
21
|
+
import { _root, _duto } from './utils'
|
|
22
|
+
|
|
23
|
+
let commands = {
|
|
24
|
+
// new:
|
|
25
|
+
// upgrade:
|
|
26
|
+
}
|
|
27
|
+
let pkg
|
|
28
|
+
try {
|
|
29
|
+
pkg = await import(join(_root, 'package.json'))
|
|
30
|
+
if (!pkg || !pkg.duto || !pkg?.duto?.type || !['pages', 'server'].includes(pkg.duto.type))
|
|
31
|
+
exit('Error: invalid app type.')
|
|
32
|
+
|
|
33
|
+
Envir.set('duto', pkg.duto)
|
|
34
|
+
commands = (await import(join(_duto, pkg.duto.type, 'cli')))?.default
|
|
35
|
+
} catch {}
|
|
36
|
+
|
|
37
|
+
cli({
|
|
38
|
+
name: 'duto',
|
|
39
|
+
description: 'Duto',
|
|
40
|
+
version,
|
|
41
|
+
commands,
|
|
42
|
+
})
|
package/src/enum.ts
ADDED
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/// <reference types='bun-types' />
|
|
2
|
+
/// <reference types='node' />
|
|
3
|
+
|
|
4
|
+
// export { }
|
|
5
|
+
|
|
6
|
+
import * as Duto from './'
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
// Duto
|
|
10
|
+
var __st: number
|
|
11
|
+
var __hmr: boolean
|
|
12
|
+
|
|
13
|
+
type Req = Duto.Req
|
|
14
|
+
type Res = Duto.Res
|
|
15
|
+
type $v = Duto.$v
|
|
16
|
+
type IRequest = Duto.IRequest
|
|
17
|
+
type IResponse = Duto.IResponse
|
|
18
|
+
type IValidator = Duto.IValidator
|
|
19
|
+
|
|
20
|
+
type Context = Duto.Context
|
|
21
|
+
type Next = Duto.Next
|
|
22
|
+
|
|
23
|
+
type Abilities = Duto.Abilities
|
|
24
|
+
type Roles = Duto.Roles
|
|
25
|
+
|
|
26
|
+
type EnumStatic = Duto.EnumStatic
|
|
27
|
+
type EnumValue = Duto.EnumValue
|
|
28
|
+
type EnumType = Duto.EnumType
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// Bun:test
|
|
32
|
+
var test: typeof import('bun:test').test
|
|
33
|
+
var describe: typeof import('bun:test').describe
|
|
34
|
+
var it: typeof import('bun:test').it
|
|
35
|
+
var expect: typeof import('bun:test').expect
|
|
36
|
+
// Hooks
|
|
37
|
+
var beforeAll: typeof import('bun:test').beforeAll
|
|
38
|
+
var afterAll: typeof import('bun:test').afterAll
|
|
39
|
+
var beforeEach: typeof import('bun:test').beforeEach
|
|
40
|
+
var afterEach: typeof import('bun:test').afterEach
|
|
41
|
+
// Mocking
|
|
42
|
+
var mock: typeof import('bun:test').mock
|
|
43
|
+
var vi: typeof import('bun:test').vi
|
|
44
|
+
var jest: typeof import('bun:test').jest
|
|
45
|
+
var spyOn: typeof import('bun:test').spyOn
|
|
46
|
+
// Utils
|
|
47
|
+
var setSystemTime: typeof import('bun:test').setSystemTime
|
|
48
|
+
var setDefaultTimeout: typeof import('bun:test').setDefaultTimeout
|
|
49
|
+
// Types
|
|
50
|
+
var Test: typeof import('bun:test').Test
|
|
51
|
+
var Describe: typeof import('bun:test').Describe
|
|
52
|
+
var Expect: typeof import('bun:test').Expect
|
|
53
|
+
var Mock: typeof import('bun:test').Mock
|
|
54
|
+
var TestOptions: typeof import('bun:test').TestOptions
|
|
55
|
+
var Matchers: typeof import('bun:test').Matchers
|
|
56
|
+
// Namespace
|
|
57
|
+
namespace jest {
|
|
58
|
+
var fn: typeof import('bun:test').vi.fn
|
|
59
|
+
var mock: typeof import('bun:test').vi.mock
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { default as Response } from './server/response'
|
|
2
|
+
export { default as Config } from './server/config'
|
|
3
|
+
|
|
4
|
+
export { Enum, Envir } from 't0n'
|
|
5
|
+
export type { EnumStatic, EnumValue, EnumType } from 't0n'
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
IRequest, IRequest as Req, IRequest as $req,
|
|
9
|
+
IResponse, IResponse as Res, IResponse as $res,
|
|
10
|
+
IValidator, IValidator as $v,
|
|
11
|
+
Context, Next,
|
|
12
|
+
} from './server/types'
|
|
13
|
+
|
|
14
|
+
// Auth
|
|
15
|
+
export { Ability } from './server/auth/ability'
|
|
16
|
+
export { Authnz } from './server/auth/authnz'
|
|
17
|
+
export { Token } from './server/auth/token'
|
|
18
|
+
|
|
19
|
+
export type { Abilities, Roles } from './server/auth/types'
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import ':duto/client/hmr'
|
|
3
|
+
|
|
4
|
+
import { render, hydrate, h } from 'preact'
|
|
5
|
+
import { Switch, Route, Router } from 'wouter-preact'
|
|
6
|
+
import { useLocation, useParams, useSearchParams, } from 'wouter-preact'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
fallbackLocale, locales, messages, routes, routeMap,
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
} from ':duto/i18n'
|
|
12
|
+
// import i18n from '../i18n/client'
|
|
13
|
+
import i18n, { updatePickers } from '../i18n/client'
|
|
14
|
+
|
|
15
|
+
i18n(fallbackLocale, locales, messages, routes, routeMap)
|
|
16
|
+
|
|
17
|
+
import EI18n from '../i18n/element'
|
|
18
|
+
import Island from './island'
|
|
19
|
+
import Link from './link'
|
|
20
|
+
|
|
21
|
+
// import Router from './router'
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
views, SRoutes,
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
} from ':duto'
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
import ':root/styles'
|
|
29
|
+
|
|
30
|
+
window.$duto = {
|
|
31
|
+
sroutes: SRoutes,
|
|
32
|
+
location: '/',
|
|
33
|
+
navigate: {},
|
|
34
|
+
params: {},
|
|
35
|
+
search: {},
|
|
36
|
+
setSearch: {},
|
|
37
|
+
}
|
|
38
|
+
import { renderRoute } from './router'
|
|
39
|
+
// import { navigate } from 'wouter-preact/use-hash-location'
|
|
40
|
+
|
|
41
|
+
for (let [tag, el, opts] of [
|
|
42
|
+
['i-i', EI18n],
|
|
43
|
+
['i-l', Link, { extends: 'a' }],
|
|
44
|
+
['i-s', Island],
|
|
45
|
+
] as [string, new () => HTMLElement, ElementDefinitionOptions?][]) {
|
|
46
|
+
if (!customElements.get(tag))
|
|
47
|
+
customElements.define(tag, el, opts)
|
|
48
|
+
}
|
|
49
|
+
// Router.load(SRoutes).mount(views)
|
|
50
|
+
|
|
51
|
+
// render(h(Router, null,
|
|
52
|
+
// h(() => {
|
|
53
|
+
// // const __ = useRoute(location.pathname)
|
|
54
|
+
// document.body.removeAttribute('hidden')
|
|
55
|
+
// const [, navigate] = useLocation()
|
|
56
|
+
// // console.log(navigate)
|
|
57
|
+
// // this.navigate = navigate
|
|
58
|
+
|
|
59
|
+
// // TODO: talvez registrar todos os valores pelo id do elemento em uma fila e atualizar nesse tick...
|
|
60
|
+
// // input, select etc registra um update val e aqui popula..
|
|
61
|
+
// updatePickers()
|
|
62
|
+
// return null
|
|
63
|
+
// }, null),
|
|
64
|
+
// h(Switch, null,
|
|
65
|
+
// h(Route, { path: SRoutes.has(location.pathname) ? location.pathname : '/' }),
|
|
66
|
+
// ...views.map(([path, Component, defs, getProps]) =>
|
|
67
|
+
// h(Route, {
|
|
68
|
+
// path,
|
|
69
|
+
// component: ({ params }: { params: Record<string, string> }) => {
|
|
70
|
+
// // Trigger async render (updates container imperatively to avoid
|
|
71
|
+
// // React/Preact hook rules issues with async components)
|
|
72
|
+
|
|
73
|
+
// renderRoute(document.body, Component, params, defs, getProps)
|
|
74
|
+
// // Return null — renderRoute will call render() directly on the container
|
|
75
|
+
// return null
|
|
76
|
+
// },
|
|
77
|
+
// })
|
|
78
|
+
// ),
|
|
79
|
+
|
|
80
|
+
// ) ), document.body)
|
|
81
|
+
render(<Router>
|
|
82
|
+
{h(() => {
|
|
83
|
+
document.body.removeAttribute('hidden')
|
|
84
|
+
const [location, navigate] = useLocation()
|
|
85
|
+
window.$duto.location = location
|
|
86
|
+
window.$duto.navigate = navigate
|
|
87
|
+
window.$duto.params = useParams()
|
|
88
|
+
const [search, setSearch] = useSearchParams()
|
|
89
|
+
window.$duto.search = search
|
|
90
|
+
window.$duto.setSearch = setSearch
|
|
91
|
+
|
|
92
|
+
// TODO: talvez registrar todos os valores pelo id do elemento em uma fila e atualizar nesse tick...
|
|
93
|
+
// input, select etc registra um update val e aqui popula..
|
|
94
|
+
updatePickers()
|
|
95
|
+
|
|
96
|
+
// React.useEffect(() => {
|
|
97
|
+
// document.body.removeAttribute('hidden');
|
|
98
|
+
// navigateRef.current = navigate;
|
|
99
|
+
// updatePickers();
|
|
100
|
+
// }, [navigate]);
|
|
101
|
+
// return null;
|
|
102
|
+
}, null)}
|
|
103
|
+
<Switch>
|
|
104
|
+
<Route path={SRoutes.has(location.pathname) ? location.pathname : '/'} component={({ params }) => {
|
|
105
|
+
delete params[0]
|
|
106
|
+
window.$duto.route = { pattern: location.pathname, params, path: location.pathname }
|
|
107
|
+
}} />
|
|
108
|
+
{/* @ts-ignore */}
|
|
109
|
+
{...views.map(([path, Component, defs, getProps, layouts]) => (
|
|
110
|
+
<Route path={path} component={({ params }: { params: Record<string, string> }) => {
|
|
111
|
+
// Trigger async render (updates container imperatively to avoid
|
|
112
|
+
// React/Preact hook rules issues with async components)
|
|
113
|
+
|
|
114
|
+
delete params[0]
|
|
115
|
+
window.$duto.route = { pattern: path, params, path: location.pathname }
|
|
116
|
+
|
|
117
|
+
renderRoute(document.body, Component, params, defs, getProps, layouts)
|
|
118
|
+
// Return null — renderRoute will call render() directly on the container
|
|
119
|
+
return null
|
|
120
|
+
}} />
|
|
121
|
+
))}
|
|
122
|
+
</Switch>
|
|
123
|
+
</Router>, document.body)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
let hmr: WebSocket | null = null
|
|
2
|
+
const url = `ws${location.protocol === 'https:' ? 'ws' : ''}://${location.host}`
|
|
3
|
+
|
|
4
|
+
const connect = () => {
|
|
5
|
+
hmr = new WebSocket(url, 'duto')
|
|
6
|
+
hmr.onmessage = ({ data }) => data === 'reload' && location.reload()
|
|
7
|
+
hmr.onerror = () => hmr?.close()
|
|
8
|
+
}
|
|
9
|
+
const reconect = () => (!hmr || hmr?.readyState === WebSocket.CLOSED) && location.reload()
|
|
10
|
+
connect()
|
|
11
|
+
document.addEventListener('visibilitychange', () => document.visibilityState === 'visible' && reconect())
|
|
12
|
+
window.addEventListener('focus', reconect)
|
|
13
|
+
window.addEventListener('pageshow', reconect)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import hydrator from '../preact/client'
|
|
2
|
+
|
|
3
|
+
const propTypes: { [k: string]: (value: any) => any } = {
|
|
4
|
+
0: (value) => reviveObject(value),
|
|
5
|
+
1: (value) => reviveArray(value),
|
|
6
|
+
2: (value) => new RegExp(value),
|
|
7
|
+
3: (value) => new Date(value),
|
|
8
|
+
4: (value) => new Map(reviveArray(value)),
|
|
9
|
+
5: (value) => new Set(reviveArray(value)),
|
|
10
|
+
6: (value) => BigInt(value),
|
|
11
|
+
7: (value) => new URL(value),
|
|
12
|
+
8: (value) => new Uint8Array(value),
|
|
13
|
+
9: (value) => new Uint16Array(value),
|
|
14
|
+
10: (value) => new Uint32Array(value),
|
|
15
|
+
11: (value) => Number.POSITIVE_INFINITY * value,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Not using JSON.parse reviver because it's bottom-up but we want top-down
|
|
19
|
+
const reviveTuple = (raw: any): any => {
|
|
20
|
+
const [type, value] = raw
|
|
21
|
+
return type in propTypes ? propTypes[type](value) : undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const reviveArray = (raw: any): any => (raw as Array<any>).map(reviveTuple)
|
|
25
|
+
const reviveObject = (raw: any): any => {
|
|
26
|
+
if (typeof raw !== 'object' || raw === null) return raw
|
|
27
|
+
return Object.fromEntries(Object.entries(raw).map(([key, value]) => [key, reviveTuple(value)]))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 🌊🏝🌴
|
|
31
|
+
export default class Island extends HTMLElement {
|
|
32
|
+
public Component: any
|
|
33
|
+
private _hydrated = false
|
|
34
|
+
static observedAttributes = ['props']
|
|
35
|
+
|
|
36
|
+
connectedCallback() {
|
|
37
|
+
if (!this.hasAttribute('await-children') || document.readyState === 'interactive' || document.readyState === 'complete') {
|
|
38
|
+
this.build()
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
this.build()
|
|
44
|
+
}, 0)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
disconnectedCallback() {
|
|
48
|
+
// document.removeEventListener('d:after-swap', this.unmount);
|
|
49
|
+
// document.addEventListener('d:after-swap', this.unmount, { once: true });
|
|
50
|
+
|
|
51
|
+
this.unmount()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
|
55
|
+
if (this.Component) {
|
|
56
|
+
// console.log(`[AttributeChanged] O atributo "${name}" mudou de "${oldValue}" para "${newValue}".`, this)
|
|
57
|
+
this._hydrated = false
|
|
58
|
+
this.hydrate()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async build() {
|
|
63
|
+
const componentModule = await import(this.getAttribute('url')!)
|
|
64
|
+
const componentExport = this.getAttribute('export') || 'default'
|
|
65
|
+
if (!componentExport.includes('.')) {
|
|
66
|
+
this.Component = componentModule[componentExport]
|
|
67
|
+
} else {
|
|
68
|
+
this.Component = componentModule
|
|
69
|
+
for (const part of componentExport.split('.'))
|
|
70
|
+
this.Component = this.Component[part]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
await this.hydrate()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// hydrate = async () => {
|
|
77
|
+
async hydrate() {
|
|
78
|
+
// Make sure the island is mounted on the DOM before hydrating. It could be unmounted
|
|
79
|
+
// when the parent island hydrates and re-creates this island.
|
|
80
|
+
if (!this.isConnected || this._hydrated) return
|
|
81
|
+
|
|
82
|
+
// Wait for parent island to hydrate first so we hydrate top-down. The `ssr` attribute
|
|
83
|
+
// represents that it has not completed hydration yet.
|
|
84
|
+
const parent = this.parentElement?.closest('i-s')
|
|
85
|
+
if (parent) {
|
|
86
|
+
parent.addEventListener('d:hydrate', this.hydrate, { once: true })
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const slotted = this.querySelectorAll('d-b')
|
|
91
|
+
const slots: Record<string, string> = {};
|
|
92
|
+
// Always check to see if there are templates.
|
|
93
|
+
// This happens if slots were passed but the client component did not render them
|
|
94
|
+
const templates = this.querySelectorAll('template[d-template]')
|
|
95
|
+
for (const template of templates) {
|
|
96
|
+
const closest = template.closest(this.tagName)
|
|
97
|
+
if (!closest?.isSameNode(this)) continue
|
|
98
|
+
slots[template.getAttribute('d-template') || 'default'] = template.innerHTML
|
|
99
|
+
template.remove()
|
|
100
|
+
}
|
|
101
|
+
for (const slot of slotted) {
|
|
102
|
+
const closest = slot.closest(this.tagName)
|
|
103
|
+
if (!closest?.isSameNode(this)) continue
|
|
104
|
+
slots[slot.getAttribute('name') || 'default'] = slot.innerHTML
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let props: Record<string, unknown>
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
props = this.hasAttribute('props') ? reviveObject(JSON.parse(this.getAttribute('props')!)) : {}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
let componentName: string = this.getAttribute('url') || '<unknown>'
|
|
113
|
+
const componentExport = this.getAttribute('export')
|
|
114
|
+
|
|
115
|
+
if (componentExport)
|
|
116
|
+
componentName += ` (export ${componentExport})`
|
|
117
|
+
|
|
118
|
+
console.error(
|
|
119
|
+
`[hydrate] Error parsing props for component ${componentName}`,
|
|
120
|
+
this.getAttribute('props'),
|
|
121
|
+
e,
|
|
122
|
+
)
|
|
123
|
+
throw e
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// @ts-ignore
|
|
127
|
+
await hydrator(this, this.Component, props, slots)
|
|
128
|
+
|
|
129
|
+
this._hydrated = true
|
|
130
|
+
this.dispatchEvent(new CustomEvent('d:hydrate'))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
unmount() {
|
|
134
|
+
if (!this.isConnected) this.dispatchEvent(new CustomEvent('d:unmount'))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export default class Link extends HTMLAnchorElement {
|
|
2
|
+
static observedAttributes = [
|
|
3
|
+
'href',
|
|
4
|
+
'target',
|
|
5
|
+
'download',
|
|
6
|
+
'rel',
|
|
7
|
+
'hreflang',
|
|
8
|
+
'referrerpolicy',
|
|
9
|
+
'ping',
|
|
10
|
+
'type',
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
super()
|
|
15
|
+
|
|
16
|
+
this.addEventListener('click', this.#onClick)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
disconnectedCallback() {
|
|
20
|
+
this.removeEventListener('click', this.#onClick)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#onClick = (e: MouseEvent) => {
|
|
24
|
+
// Respeita comportamento padrão
|
|
25
|
+
if (
|
|
26
|
+
e.defaultPrevented
|
|
27
|
+
|| this.target === '_blank'
|
|
28
|
+
|| e.metaKey
|
|
29
|
+
|| e.ctrlKey
|
|
30
|
+
|| e.shiftKey
|
|
31
|
+
|| e.altKey
|
|
32
|
+
|| e.button !== 0
|
|
33
|
+
) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
// Allow ctrl/cmd+click to open in new tab normally
|
|
37
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return
|
|
38
|
+
// e.preventDefault()
|
|
39
|
+
if ((e.target as HTMLAnchorElement).hasAttribute('href'))
|
|
40
|
+
window.dispatchEvent(new CustomEvent('d:navigate', { detail: e.target }))
|
|
41
|
+
|
|
42
|
+
// Exemplo: interceptar navegação SPA
|
|
43
|
+
// event.preventDefault()
|
|
44
|
+
// router.push(this.href)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Proxy opcional para facilitar typings
|
|
48
|
+
get url() {
|
|
49
|
+
return new URL(this.href)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
window.addEventListener('d:navigate', e => {
|
|
55
|
+
const el = (e as CustomEvent<HTMLAnchorElement>).detail
|
|
56
|
+
|
|
57
|
+
console.warn('d:navigate', el)
|
|
58
|
+
// if (href) _navigate?.(href)
|
|
59
|
+
// _navigate?.('/haha-2')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// const links = document.querySelectorAll('a[d]')
|
|
63
|
+
// for (const link of links) { // @ts-ignore
|
|
64
|
+
// link.addEventListener('click', (e: MouseEvent) => {
|
|
65
|
+
// // Allow ctrl/cmd+click to open in new tab normally
|
|
66
|
+
// if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return
|
|
67
|
+
// e.preventDefault()
|
|
68
|
+
// window.dispatchEvent(new CustomEvent('d:navigate', { detail: e.target }))
|
|
69
|
+
// })
|
|
70
|
+
// }
|