vike-ripple 0.2.1 → 0.4.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/README.md +42 -31
- package/package.json +24 -23
- package/src/components/ClientOnly.js +4 -2
- package/src/components/Config/Config-client.js +6 -1
- package/src/components/Config/Config-server.js +5 -3
- package/src/components/Head/Head-client.js +1 -3
- package/src/components/Head/Head-server.js +5 -4
- package/src/{+config.js → config.js} +5 -5
- package/src/helpers/clientOnly.js +4 -0
- package/src/hooks/useConfig/configsCumulative.js +7 -0
- package/src/hooks/useConfig/useConfig-client.js +25 -0
- package/src/hooks/useConfig/useConfig-server.js +24 -0
- package/src/hooks/useData.js +4 -3
- package/src/hooks/useHydrated.js +7 -0
- package/src/hooks/usePageContext.js +11 -5
- package/src/index.js +4 -27
- package/src/integration/getHeadSetting.js +2 -2
- package/src/integration/onRenderClient.js +10 -39
- package/src/integration/onRenderHtml.js +64 -41
- package/src/integration/ssrEffect.js +3 -6
- package/src/setup.js +28 -62
- package/src/utils/callCumulativeHooks.js +1 -3
- package/src/utils/getTagAttributesString.js +7 -7
- package/src/hooks/useConfig.js +0 -14
- package/src/integration/applyHeadSettings.js +0 -10
- package/src/integration/getPageElement.js +0 -32
- package/src/types/Config.ts +0 -33
- package/src/types/PageContext.ts +0 -13
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vike-ripple
|
|
2
|
+
|
|
3
|
+
> ⚠️ **HIGHLY EXPERIMENTAL** — This package is in early development. APIs may change without notice, parts may not work, and documentation may be incomplete. Use at your own risk.
|
|
2
4
|
|
|
3
5
|
[Vike](https://vike.dev) integration for [Ripple TS](https://ripple-ts.com) — SSR rendering, client hydration with mount fallback, streaming, `<head>` management, and `.tsrx` page file support.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```sh
|
|
8
|
-
npm install
|
|
10
|
+
npm install vike-ripple
|
|
9
11
|
```
|
|
10
12
|
|
|
11
13
|
## Setup
|
|
@@ -24,35 +26,28 @@ Or add to your project's `package.json` so it runs automatically after `npm inst
|
|
|
24
26
|
}
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
### 2.
|
|
29
|
+
### 2. Configure `vite.config.ts`
|
|
28
30
|
|
|
29
31
|
```ts
|
|
30
32
|
import { defineConfig } from 'vite'
|
|
31
33
|
import vike from 'vike/plugin'
|
|
32
34
|
import { ripple } from '@ripple-ts/vite-plugin'
|
|
33
|
-
import vikeRipple from '
|
|
35
|
+
import vikeRipple from 'vike-ripple'
|
|
34
36
|
|
|
35
37
|
export default defineConfig({
|
|
36
|
-
optimizeDeps: {
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
plugins: [
|
|
40
|
-
vikeRipple(),
|
|
41
|
-
ripple({ excludeRippleExternalModules: true }),
|
|
42
|
-
vike(),
|
|
43
|
-
],
|
|
38
|
+
optimizeDeps: { exclude: ['ripple'] },
|
|
39
|
+
plugins: [vikeRipple(), ripple({ excludeRippleExternalModules: true }), vike()],
|
|
44
40
|
})
|
|
45
|
-
|
|
41
|
+
```
|
|
46
42
|
|
|
47
|
-
### 3. Add renderer
|
|
43
|
+
### 3. Add renderer config
|
|
48
44
|
|
|
49
|
-
|
|
45
|
+
Create `renderer/+config.ts`:
|
|
50
46
|
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
+onRenderClient.tsx
|
|
47
|
+
```ts
|
|
48
|
+
export default {
|
|
49
|
+
extends: ['import:vike-ripple/config:default'],
|
|
50
|
+
}
|
|
56
51
|
```
|
|
57
52
|
|
|
58
53
|
### 4. Create a page
|
|
@@ -71,22 +66,38 @@ export function Page(props: {}) @{
|
|
|
71
66
|
}
|
|
72
67
|
```
|
|
73
68
|
|
|
69
|
+
## Features
|
|
70
|
+
|
|
71
|
+
| Feature | Status |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `.tsrx` page file support | ✅ |
|
|
74
|
+
| SSR rendering | ✅ |
|
|
75
|
+
| Client hydration with mount fallback | ✅ |
|
|
76
|
+
| Streaming SSR (`rippleStream` config) | ✅ |
|
|
77
|
+
| `<head>` tag extraction | ✅ |
|
|
78
|
+
| `+Layout.tsrx` support | ✅ |
|
|
79
|
+
| `+Head.tsrx` support | ✅ |
|
|
80
|
+
| Config: `title`, `description`, `image`, `viewport`, `favicon`, `lang` | ✅ |
|
|
81
|
+
| Config: `ssr` toggle, `stream` toggle | ✅ |
|
|
82
|
+
| Config: `htmlAttributes`, `bodyAttributes` | ✅ |
|
|
83
|
+
| Config: `headHtmlBegin/End`, `bodyHtmlBegin/End` | ✅ |
|
|
84
|
+
| Hooks: `onBefore/AfterRenderHtml`, `onBefore/AfterRenderClient` | ✅ |
|
|
85
|
+
| `@tailwindcss` integration (via `vike-ripple-tailwindcss`) | ✅ |
|
|
86
|
+
| `@apply` in `<style>` blocks (via `vike-ripple-tailwindcss`) | ✅ |
|
|
87
|
+
| HMR stability during development | 🟡 |
|
|
88
|
+
| TypeScript types for `Vike.Config` / `Vike.PageContext` | 🟡 |
|
|
89
|
+
| Production build testing | 🔴 |
|
|
90
|
+
|
|
74
91
|
## What this does
|
|
75
92
|
|
|
76
93
|
| Patch | Why |
|
|
77
94
|
|---|---|
|
|
78
95
|
| **`.tsrx` extension** | Vike doesn't know `.tsrx` is a valid page extension — adds it to `isScriptFile.js` |
|
|
79
96
|
| **`?direct` CSS loading** | Vite's SSR module loader appends `?direct` to module IDs; Ripple's `load` hook checks cache with the wrong key |
|
|
80
|
-
|
|
|
81
|
-
|
|
82
|
-
## API
|
|
83
|
-
|
|
84
|
-
### `vikeRipple()`
|
|
85
|
-
|
|
86
|
-
Vite plugin. Must be placed before `ripple()` in the plugins array, with `enforce: 'pre'` behavior.
|
|
97
|
+
| **`@apply` support** | Prepends `@import "tailwindcss" layer(reference)` to extracted CSS so `@apply` resolves in `<style>` blocks |
|
|
87
98
|
|
|
88
|
-
|
|
99
|
+
## Known Issues
|
|
89
100
|
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
101
|
+
- **Hydration errors**: Ripple's `hydrate()` may throw `TypeError: Illegal invocation` due to Vite dep optimization. Fixed by `optimizeDeps.exclude: ['ripple']` and the mount fallback in the client renderer.
|
|
102
|
+
- **HMR hang**: Editing `.tsrx` files during dev may occasionally cause HMR to hang. Restarting the dev server resolves it.
|
|
103
|
+
- **`</style>` in template literals**: If a `.tsx` file contains `</style>` inside a JavaScript string, the Tailwind Oxide scanner may emit a `CssSyntaxError`. Workaround: break the literal with string concatenation: `"<" + "/style>"`. See [tailwindcss#20000](https://github.com/tailwindlabs/tailwindcss/issues/20000).
|
package/package.json
CHANGED
|
@@ -1,38 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vike-ripple",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Vike extension for Ripple TS —
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Vike extension for Ripple TS — SSR, streaming, Layout, Head, SEO configs, hooks",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"types": "./src/types/Config.ts",
|
|
7
6
|
"exports": {
|
|
8
7
|
".": "./src/index.js",
|
|
9
|
-
"./config": "./src
|
|
8
|
+
"./config": "./src/config.js",
|
|
10
9
|
"./setup": "./src/setup.js",
|
|
11
|
-
"./Head": "./src/components/Head/Head-server.js",
|
|
12
|
-
"./ClientOnly": "./src/components/ClientOnly.js",
|
|
13
10
|
"./usePageContext": "./src/hooks/usePageContext.js",
|
|
14
11
|
"./useData": "./src/hooks/useData.js",
|
|
15
|
-
"./
|
|
16
|
-
"./
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
12
|
+
"./useHydrated": "./src/hooks/useHydrated.js",
|
|
13
|
+
"./useConfig": {
|
|
14
|
+
"browser": "./src/hooks/useConfig/useConfig-client.js",
|
|
15
|
+
"default": "./src/hooks/useConfig/useConfig-server.js"
|
|
16
|
+
},
|
|
17
|
+
"./Config": {
|
|
18
|
+
"browser": "./src/components/Config/Config-client.js",
|
|
19
|
+
"default": "./src/components/Config/Config-server.js"
|
|
20
|
+
},
|
|
21
|
+
"./Head": {
|
|
22
|
+
"browser": "./src/components/Head/Head-client.js",
|
|
23
|
+
"default": "./src/components/Head/Head-server.js"
|
|
24
|
+
},
|
|
25
|
+
"./clientOnly": "./src/helpers/clientOnly.js",
|
|
26
|
+
"./ClientOnly": "./src/components/ClientOnly.js",
|
|
27
|
+
"./__internal/integration/onRenderHtml": "./src/integration/onRenderHtml.js",
|
|
28
|
+
"./__internal/integration/onRenderClient": "./src/integration/onRenderClient.js"
|
|
21
29
|
},
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
],
|
|
25
|
-
"keywords": [
|
|
26
|
-
"vike",
|
|
27
|
-
"ripple",
|
|
28
|
-
"ripplets",
|
|
29
|
-
"ssr",
|
|
30
|
-
"vite"
|
|
31
|
-
],
|
|
30
|
+
"bin": { "vike-ripple": "./src/setup.js" },
|
|
31
|
+
"files": ["src"],
|
|
32
|
+
"keywords": ["vike", "ripple", "ripplets", "ssr", "vite"],
|
|
32
33
|
"license": "MIT",
|
|
33
34
|
"peerDependencies": {
|
|
34
35
|
"vike": ">=0.4.259",
|
|
35
36
|
"@ripple-ts/vite-plugin": ">=0.3.0",
|
|
36
37
|
"ripple": ">=0.1.0"
|
|
37
38
|
}
|
|
38
|
-
}
|
|
39
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
export { Config }
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { useConfig } from '../../hooks/useConfig/useConfig-server.js'
|
|
4
|
+
|
|
5
|
+
function Config(props) {
|
|
6
|
+
useConfig()(props)
|
|
5
7
|
return null
|
|
6
8
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
export { Head }
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { useConfig } from '../../hooks/useConfig/useConfig-server.js'
|
|
4
|
+
|
|
5
|
+
function Head({ children }) {
|
|
6
|
+
useConfig()({ Head: children })
|
|
6
7
|
return null
|
|
7
8
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { ssrEffect } from './integration/ssrEffect.js'
|
|
2
2
|
|
|
3
|
-
/** @type {import('vike/types').Config} */
|
|
4
3
|
const config = {
|
|
5
4
|
name: 'vike-ripple',
|
|
6
5
|
require: { vike: '>=0.4.250' },
|
|
7
6
|
|
|
8
|
-
onRenderHtml: 'import:vike-ripple/
|
|
9
|
-
onRenderClient: 'import:vike-ripple/
|
|
7
|
+
onRenderHtml: 'import:vike-ripple/__internal/integration/onRenderHtml:onRenderHtml',
|
|
8
|
+
onRenderClient: 'import:vike-ripple/__internal/integration/onRenderClient:onRenderClient',
|
|
10
9
|
|
|
11
10
|
clientRouting: true,
|
|
12
11
|
hydrationCanBeAborted: true,
|
|
@@ -16,6 +15,7 @@ const config = {
|
|
|
16
15
|
meta: {
|
|
17
16
|
Head: { env: { server: true }, cumulative: true },
|
|
18
17
|
Layout: { env: { server: true, client: true }, cumulative: true },
|
|
18
|
+
Wrapper: { env: { server: true, client: true }, cumulative: true },
|
|
19
19
|
title: { env: { server: true, client: true } },
|
|
20
20
|
description: { env: { server: true } },
|
|
21
21
|
image: { env: { server: true } },
|
|
@@ -26,8 +26,8 @@ const config = {
|
|
|
26
26
|
stream: { env: { server: true }, cumulative: true },
|
|
27
27
|
onBeforeRenderHtml: { env: { server: true }, cumulative: true },
|
|
28
28
|
onAfterRenderHtml: { env: { server: true }, cumulative: true },
|
|
29
|
-
onBeforeRenderClient: { env: {
|
|
30
|
-
onAfterRenderClient: { env: {
|
|
29
|
+
onBeforeRenderClient: { env: { client: true }, cumulative: true },
|
|
30
|
+
onAfterRenderClient: { env: { client: true }, cumulative: true },
|
|
31
31
|
bodyHtmlBegin: { env: { server: true }, cumulative: true, global: true },
|
|
32
32
|
bodyHtmlEnd: { env: { server: true }, cumulative: true, global: true },
|
|
33
33
|
headHtmlBegin: { env: { server: true }, cumulative: true, global: true },
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { useConfig }
|
|
2
|
+
|
|
3
|
+
import { getPageContext } from 'vike/getPageContext'
|
|
4
|
+
import { usePageContext } from '../usePageContext.js'
|
|
5
|
+
|
|
6
|
+
function useConfig() {
|
|
7
|
+
let pageContext = getPageContext({ asyncHook: false })
|
|
8
|
+
if (pageContext) {
|
|
9
|
+
return (config) => {
|
|
10
|
+
pageContext._configViaHook ??= {}
|
|
11
|
+
Object.assign(pageContext._configViaHook, config)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pageContext = usePageContext()
|
|
16
|
+
return (config) => {
|
|
17
|
+
if (!('_headAlreadySet' in (pageContext || {}))) {
|
|
18
|
+
pageContext._configViaHook ??= {}
|
|
19
|
+
Object.assign(pageContext._configViaHook, config)
|
|
20
|
+
} else {
|
|
21
|
+
if (config.title) document.title = config.title
|
|
22
|
+
if (config.lang) document.documentElement.lang = config.lang
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { useConfig }
|
|
2
|
+
|
|
3
|
+
import { getPageContext } from 'vike/getPageContext'
|
|
4
|
+
import { usePageContext } from '../usePageContext.js'
|
|
5
|
+
|
|
6
|
+
function useConfig() {
|
|
7
|
+
// Vike hook
|
|
8
|
+
let pageContext = getPageContext({ asyncHook: false })
|
|
9
|
+
if (pageContext) {
|
|
10
|
+
return (config) => {
|
|
11
|
+
pageContext._configViaHook ??= {}
|
|
12
|
+
Object.assign(pageContext._configViaHook, config)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Component
|
|
17
|
+
pageContext = usePageContext()
|
|
18
|
+
return (config) => {
|
|
19
|
+
if (!pageContext?._headAlreadySet) {
|
|
20
|
+
pageContext._configViaHook ??= {}
|
|
21
|
+
Object.assign(pageContext._configViaHook, config)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/hooks/useData.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export { usePageContext }
|
|
2
|
+
export { setPageContext }
|
|
3
|
+
|
|
4
|
+
let _pageContext = null
|
|
5
|
+
|
|
6
|
+
function usePageContext() {
|
|
7
|
+
return _pageContext
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function setPageContext(ctx) {
|
|
11
|
+
_pageContext = ctx
|
|
6
12
|
}
|
package/src/index.js
CHANGED
|
@@ -1,27 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* 1. Import config in your renderer/+config.ts:
|
|
6
|
-
* import vikeRipple from 'vike-ripple/config'
|
|
7
|
-
*
|
|
8
|
-
* 2. Run setup (once):
|
|
9
|
-
* npx vike-ripple setup
|
|
10
|
-
*
|
|
11
|
-
* 3. Add optimizeDeps to vite.config.ts:
|
|
12
|
-
* optimizeDeps: { exclude: ['ripple'] }
|
|
13
|
-
*
|
|
14
|
-
* ## Usage
|
|
15
|
-
* - +Head.tsrx — inject <head> content
|
|
16
|
-
* - +Layout.tsrx — layout components
|
|
17
|
-
* - +title.ts — per-page title
|
|
18
|
-
* - +description.ts — per-page description
|
|
19
|
-
* - +ssr.ts — per-page SSR toggle
|
|
20
|
-
* - +stream.ts — per-page streaming toggle
|
|
21
|
-
*/
|
|
22
|
-
export default function vikeRipple() {
|
|
23
|
-
return {
|
|
24
|
-
name: 'vike-ripple',
|
|
25
|
-
enforce: 'pre',
|
|
26
|
-
}
|
|
27
|
-
}
|
|
1
|
+
console.warn(
|
|
2
|
+
"[vike-ripple] Replace `import vikeRipple from 'vike-ripple'` with `import vikeRipple from 'vike-ripple/config'`",
|
|
3
|
+
)
|
|
4
|
+
export { default } from './config.js'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { getHeadSetting }
|
|
2
2
|
|
|
3
3
|
function getHeadSetting(key, pageContext) {
|
|
4
|
-
const
|
|
5
|
-
if (
|
|
4
|
+
const v = pageContext.config[key]
|
|
5
|
+
if (v !== undefined && v !== null) return v
|
|
6
6
|
return pageContext._configViaHook?.[key] ?? null
|
|
7
7
|
}
|
|
@@ -1,62 +1,33 @@
|
|
|
1
|
-
// https://vike.dev/onRenderClient
|
|
2
1
|
export { onRenderClient }
|
|
3
2
|
|
|
4
3
|
import { hydrate } from 'ripple'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { callCumulativeHooks } from '../utils/callCumulativeHooks.js'
|
|
4
|
+
import { setPageContext } from '../hooks/usePageContext.js'
|
|
5
|
+
import { setHydrated } from '../hooks/useHydrated.js'
|
|
8
6
|
|
|
9
7
|
let rendered = false
|
|
10
8
|
|
|
11
9
|
const onRenderClient = async (pageContext) => {
|
|
12
|
-
|
|
10
|
+
const { Page } = pageContext
|
|
11
|
+
if (!Page) return
|
|
13
12
|
|
|
13
|
+
setPageContext(pageContext)
|
|
14
14
|
const container = document.getElementById('root')
|
|
15
15
|
if (!container) return
|
|
16
16
|
|
|
17
|
-
pageContext._headAlreadySet = pageContext.isHydration
|
|
18
|
-
|
|
19
17
|
if (pageContext.isHydration && container.innerHTML !== '') {
|
|
20
18
|
try {
|
|
21
|
-
|
|
19
|
+
hydrate(Page, { target: container, props: {} })
|
|
22
20
|
rendered = true
|
|
21
|
+
setHydrated()
|
|
23
22
|
} catch (err) {
|
|
24
23
|
console.warn('[vike-ripple] hydrate failed, falling back to mount:', err)
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
if (!rendered) {
|
|
29
|
-
|
|
28
|
+
const { mount } = await import('ripple')
|
|
29
|
+
mount(Page, { target: container, props: {} })
|
|
30
30
|
rendered = true
|
|
31
|
+
setHydrated()
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
updateHead(pageContext)
|
|
34
|
-
|
|
35
|
-
await callCumulativeHooks(pageContext.config.onAfterRenderClient, pageContext)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function hydratePage(pageContext, container) {
|
|
39
|
-
hydrate(pageContext.Page, { target: container, props: {} })
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function mountPage(pageContext, container) {
|
|
43
|
-
const { mount } = await import('ripple')
|
|
44
|
-
mount(pageContext.Page, { target: container, props: {} })
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function updateHead(pageContext) {
|
|
48
|
-
if (pageContext._headAlreadySet) return
|
|
49
|
-
|
|
50
|
-
const title = getHeadSetting('title', pageContext)
|
|
51
|
-
if (title && document.title !== title) {
|
|
52
|
-
document.title = title
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const lang = getHeadSetting('lang', pageContext)
|
|
56
|
-
if (lang) {
|
|
57
|
-
document.documentElement.lang = lang
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
applyHeadSettings(pageContext.config.Head, document.head)
|
|
61
|
-
applyHeadSettings(pageContext._configViaHook?.Head, document.head)
|
|
62
33
|
}
|
|
@@ -2,27 +2,48 @@ export { onRenderHtml }
|
|
|
2
2
|
|
|
3
3
|
import { render, create_ssr_stream } from 'ripple/server'
|
|
4
4
|
import { escapeInject, dangerouslySkipEscape } from 'vike/server'
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { setPageContext } from '../hooks/usePageContext.js'
|
|
7
6
|
import { getHeadSetting } from './getHeadSetting.js'
|
|
8
|
-
import { callCumulativeHooks } from '../utils/callCumulativeHooks.js'
|
|
9
7
|
import { getTagAttributesString } from '../utils/getTagAttributesString.js'
|
|
8
|
+
import { callCumulativeHooks } from '../utils/callCumulativeHooks.js'
|
|
10
9
|
|
|
11
10
|
const onRenderHtml = async (pageContext) => {
|
|
12
|
-
const
|
|
13
|
-
const { Page } = pageContext2
|
|
11
|
+
const { Page } = pageContext
|
|
14
12
|
if (!Page) throw new Error('No Page')
|
|
15
13
|
|
|
16
|
-
await callCumulativeHooks(
|
|
14
|
+
await callCumulativeHooks(pageContext.config.onBeforeRenderHtml, pageContext)
|
|
15
|
+
|
|
16
|
+
setPageContext(pageContext)
|
|
17
|
+
|
|
18
|
+
const headHtml = getHeadHtml(pageContext)
|
|
19
|
+
const { headHtmlBegin, headHtmlEnd, bodyHtmlBegin, bodyHtmlEnd } = getHtmlInjections(pageContext)
|
|
20
|
+
const { htmlAttributesString, bodyAttributesString } = getTagAttributes(pageContext)
|
|
21
|
+
|
|
22
|
+
// Wrap in Layout(s) + Wrapper(s)
|
|
23
|
+
let wrappedPage = Page
|
|
24
|
+
const Layout = pageContext.config.Layout
|
|
25
|
+
const Wrapper = pageContext.config.Wrapper
|
|
26
|
+
if (Layout) {
|
|
27
|
+
const layouts = Array.isArray(Layout) ? Layout : [Layout]
|
|
28
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
29
|
+
const L = layouts[i]
|
|
30
|
+
const prev = wrappedPage
|
|
31
|
+
wrappedPage = (props) => L({ ...props, children: prev })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (Wrapper) {
|
|
35
|
+
const wrappers = Array.isArray(Wrapper) ? Wrapper : [Wrapper]
|
|
36
|
+
for (const W of wrappers) {
|
|
37
|
+
const prev = wrappedPage
|
|
38
|
+
wrappedPage = (props) => W({ ...props, children: prev })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
17
41
|
|
|
18
|
-
const
|
|
19
|
-
const { headHtmlBegin, headHtmlEnd, bodyHtmlBegin, bodyHtmlEnd } = await getHtmlInjections(pageContext2)
|
|
20
|
-
const { htmlAttributesString, bodyAttributesString } = getTagAttributes(pageContext2)
|
|
21
|
-
const enableStream = !!(pageContext2.config.stream ?? pageContext2.config.rippleStream)
|
|
42
|
+
const enableStream = !!(pageContext.config.stream ?? pageContext.config.rippleStream)
|
|
22
43
|
|
|
23
44
|
if (enableStream) {
|
|
24
45
|
const rippleStream = create_ssr_stream()
|
|
25
|
-
render(
|
|
46
|
+
render(wrappedPage, { stream: rippleStream.sink }).catch(e => {
|
|
26
47
|
console.error('[ripple] render err:', e?.message)
|
|
27
48
|
})
|
|
28
49
|
return escapeInject`<!DOCTYPE html>
|
|
@@ -41,16 +62,19 @@ const onRenderHtml = async (pageContext) => {
|
|
|
41
62
|
</html>`
|
|
42
63
|
}
|
|
43
64
|
|
|
44
|
-
const { head, body, css } = await render(
|
|
65
|
+
const { head, body, css, topLevelError } = await render(wrappedPage, {})
|
|
66
|
+
if (topLevelError) {
|
|
67
|
+
console.error('[vike-ripple] SSR render error:', topLevelError)
|
|
68
|
+
throw topLevelError
|
|
69
|
+
}
|
|
45
70
|
|
|
71
|
+
// Ripple's render() already extracts <head> content into `head` and CSS into `css`
|
|
46
72
|
const cssHtml = css?.size
|
|
47
73
|
? `<style data-ripple-ssr>${[...css].join('')}<` + `/style>`
|
|
48
74
|
: ''
|
|
49
75
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
pageContext2.pageHtmlString = body
|
|
53
|
-
await callCumulativeHooks(pageContext2.config.onAfterRenderHtml, pageContext2)
|
|
76
|
+
pageContext.pageHtmlString = body
|
|
77
|
+
await callCumulativeHooks(pageContext.config.onAfterRenderHtml, pageContext)
|
|
54
78
|
|
|
55
79
|
return escapeInject`<!DOCTYPE html>
|
|
56
80
|
<html${dangerouslySkipEscape(htmlAttributesString)}>
|
|
@@ -77,44 +101,43 @@ function getHeadHtml(pageContext) {
|
|
|
77
101
|
const description = getHeadSetting('description', pageContext)
|
|
78
102
|
const image = getHeadSetting('image', pageContext)
|
|
79
103
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const imageTags = !image
|
|
86
|
-
? ''
|
|
87
|
-
: `<meta property="og:image" content="${image}"><meta name="twitter:card" content="summary_large_image">`
|
|
104
|
+
const parts = []
|
|
105
|
+
if (favicon) parts.push(`<link rel="icon" href="${favicon}" />`)
|
|
106
|
+
if (title) parts.push(`<title>${title}</title>`)
|
|
107
|
+
if (description) parts.push(`<meta name="description" content="${description}" />`)
|
|
108
|
+
if (image) parts.push(`<meta property="og:image" content="${image}">`)
|
|
88
109
|
const viewportTag = getViewportTag(getHeadSetting('viewport', pageContext))
|
|
89
|
-
|
|
90
|
-
const
|
|
110
|
+
if (viewportTag) parts.push(viewportTag)
|
|
111
|
+
const headElements = [
|
|
91
112
|
...(pageContext.config.Head ?? []),
|
|
92
113
|
...(pageContext._configViaHook?.Head ?? []),
|
|
93
114
|
]
|
|
94
115
|
.filter(Boolean)
|
|
95
|
-
.map(
|
|
116
|
+
.map(h => (typeof h === 'function' ? h(pageContext) : h))
|
|
96
117
|
.join('\n')
|
|
97
|
-
|
|
98
|
-
return
|
|
118
|
+
if (headElements) parts.push(headElements)
|
|
119
|
+
return parts.join('\n')
|
|
99
120
|
}
|
|
100
121
|
|
|
101
|
-
|
|
102
|
-
if (viewport
|
|
122
|
+
function getViewportTag(viewport) {
|
|
123
|
+
if (!viewport && viewport !== 0) return ''
|
|
103
124
|
if (viewport === 'responsive') return '<meta name="viewport" content="width=device-width, initial-scale=1.0" />'
|
|
104
125
|
if (typeof viewport === 'number') return `<meta name="viewport" content="width=${viewport}" />`
|
|
105
|
-
return '
|
|
126
|
+
return ''
|
|
106
127
|
}
|
|
107
128
|
|
|
108
129
|
function getTagAttributes(pageContext) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
return {
|
|
131
|
+
htmlAttributesString: getTagAttributesString(pageContext.config.htmlAttributes),
|
|
132
|
+
bodyAttributesString: getTagAttributesString(pageContext.config.bodyAttributes),
|
|
133
|
+
}
|
|
112
134
|
}
|
|
113
135
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
136
|
+
function getHtmlInjections(pageContext) {
|
|
137
|
+
return {
|
|
138
|
+
headHtmlBegin: (pageContext.config.headHtmlBegin ?? []).join('\n'),
|
|
139
|
+
headHtmlEnd: (pageContext.config.headHtmlEnd ?? []).join('\n'),
|
|
140
|
+
bodyHtmlBegin: (pageContext.config.bodyHtmlBegin ?? []).join('\n'),
|
|
141
|
+
bodyHtmlEnd: (pageContext.config.bodyHtmlEnd ?? []).join('\n'),
|
|
142
|
+
}
|
|
120
143
|
}
|
|
@@ -4,12 +4,9 @@ function ssrEffect({ configDefinedAt, configValue }) {
|
|
|
4
4
|
if (typeof configValue !== 'boolean') throw new Error(`${configDefinedAt} should be a boolean`)
|
|
5
5
|
return {
|
|
6
6
|
meta: {
|
|
7
|
-
Page: {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
server: configValue !== false,
|
|
11
|
-
},
|
|
12
|
-
},
|
|
7
|
+
Page: { env: { client: true, server: configValue !== false } },
|
|
8
|
+
Layout: { env: { client: true, server: configValue !== false } },
|
|
9
|
+
Wrapper: { env: { client: true, server: configValue !== false } },
|
|
13
10
|
},
|
|
14
11
|
}
|
|
15
12
|
}
|
package/src/setup.js
CHANGED
|
@@ -1,116 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import { createRequire } from 'module'
|
|
2
3
|
import { join } from 'path'
|
|
3
4
|
import { readFileSync, writeFileSync, existsSync } from 'fs'
|
|
4
|
-
import { fileURLToPath } from 'url'
|
|
5
5
|
|
|
6
|
-
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
7
6
|
const projectRoot = process.cwd()
|
|
8
7
|
let exitCode = 0
|
|
9
8
|
|
|
10
|
-
function log(
|
|
11
|
-
function warn(
|
|
9
|
+
function log(m) { console.log('[vike-ripple]', m) }
|
|
10
|
+
function warn(m) { console.warn('[vike-ripple]', m) }
|
|
12
11
|
|
|
13
|
-
// ── Patch 1: Register .tsrx with Vike ─────────────────────────
|
|
14
12
|
function patchVikeExtensions() {
|
|
15
13
|
const target = resolveVike('dist/utils/isScriptFile.js')
|
|
16
|
-
if (!target) { warn('vike not found
|
|
17
|
-
|
|
14
|
+
if (!target) { warn('vike not found'); return }
|
|
18
15
|
let src = readFileSync(target, 'utf-8')
|
|
19
|
-
if (src.includes("'tsrx'")) { log('.tsrx already registered
|
|
20
|
-
|
|
16
|
+
if (src.includes("'tsrx'")) { log('.tsrx already registered'); return }
|
|
21
17
|
const patched = src.replace(
|
|
22
18
|
'const scriptFileExtensionList = [...extJsOrTs, ...extJsxOrTsx, ...extTemplates];',
|
|
23
19
|
"const scriptFileExtensionList = [...extJsOrTs, ...extJsxOrTsx, ...extTemplates, 'tsrx'];",
|
|
24
20
|
)
|
|
25
|
-
if (patched === src) { warn('Could not patch Vike
|
|
21
|
+
if (patched === src) { warn('Could not patch Vike'); exitCode = 1; return }
|
|
26
22
|
writeFileSync(target, patched, 'utf-8')
|
|
27
|
-
log('Registered .tsrx extension
|
|
23
|
+
log('Registered .tsrx extension')
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
// ── Patch 2: Fix ?direct in Ripple's load hook ────────────────
|
|
31
26
|
function patchRippleDirect() {
|
|
32
27
|
const target = resolveRipple('src/index.js')
|
|
33
|
-
if (!target) { warn('@ripple-ts/vite-plugin not found
|
|
34
|
-
|
|
28
|
+
if (!target) { warn('@ripple-ts/vite-plugin not found'); return }
|
|
35
29
|
let src = readFileSync(target, 'utf-8')
|
|
36
30
|
if (src.includes('Handle ?direct query param')) { log('?direct fix already applied'); return }
|
|
37
|
-
|
|
38
31
|
const patched = src.replace(
|
|
39
32
|
'if (cssCache.has(id)) {\n\t\t\t\t\treturn cssCache.get(id);\n\t\t\t\t}',
|
|
40
33
|
`if (cssCache.has(id)) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
}`,
|
|
34
|
+
\t\t\t\t\treturn cssCache.get(id);
|
|
35
|
+
\t\t\t\t}
|
|
36
|
+
\t\t\t\tif (id.includes('?direct')) {
|
|
37
|
+
\t\t\t\t\tconst baseId = id.replace('?direct', '');
|
|
38
|
+
\t\t\t\t\tif (cssCache.has(baseId)) {
|
|
39
|
+
\t\t\t\t\t\treturn cssCache.get(baseId);
|
|
40
|
+
\t\t\t\t\t}
|
|
41
|
+
\t\t\t\t}`,
|
|
51
42
|
)
|
|
52
|
-
if (patched === src) { warn('Could not patch Ripple plugin
|
|
43
|
+
if (patched === src) { warn('Could not patch Ripple plugin'); exitCode = 1; return }
|
|
53
44
|
writeFileSync(target, patched, 'utf-8')
|
|
54
|
-
log('Patched Ripple plugin for ?direct CSS
|
|
45
|
+
log('Patched Ripple plugin for ?direct CSS loading')
|
|
55
46
|
}
|
|
56
47
|
|
|
57
|
-
// ── Patch 3: @apply support via @reference (no HMR loops) ─────
|
|
58
48
|
function patchRippleApply() {
|
|
59
49
|
const target = resolveRipple('src/index.js')
|
|
60
50
|
if (!target) return
|
|
61
|
-
|
|
62
51
|
let src = readFileSync(target, 'utf-8')
|
|
63
|
-
|
|
64
52
|
if (src.includes('TW_PATCH_APPLY')) { log('@apply patch already applied'); return }
|
|
65
|
-
|
|
66
|
-
// Upgrade from old TW_PATCH format
|
|
67
53
|
if (src.includes('TW_PATCH:')) {
|
|
68
|
-
src = src.replace(
|
|
69
|
-
|
|
70
|
-
'// TW_PATCH_APPLY: bring tailwindcss into scope for @apply',
|
|
71
|
-
)
|
|
72
|
-
"css = '@import \"tailwindcss\";\\n' + css;",
|
|
73
|
-
"css = '@import \"tailwindcss\" layer(reference);\\n' + css;",
|
|
74
|
-
)
|
|
54
|
+
src = src.replace('// TW_PATCH: prepend tailwindcss','// TW_PATCH_APPLY: apply')
|
|
55
|
+
src = src.replace("css = '@import \"tailwindcss\";\\n' + css;","css = '@import \"tailwindcss\" layer(reference);\\n' + css;")
|
|
75
56
|
writeFileSync(target, src, 'utf-8')
|
|
76
|
-
log('Upgraded @apply patch
|
|
57
|
+
log('Upgraded @apply patch');
|
|
77
58
|
return
|
|
78
59
|
}
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
'\t\t\t\t\t\tconst cssId = createVirtualImportId(filename, root, \'style\');\n' +
|
|
84
|
-
'\t\t\t\t\t\tcssCache.set(cssId, css);'
|
|
85
|
-
)
|
|
86
|
-
const patched = (
|
|
87
|
-
'\t\t\t\t\tif (css) {\n' +
|
|
88
|
-
'\t\t\t\t\t\t// TW_PATCH_APPLY: bring tailwindcss into scope for @apply\n' +
|
|
89
|
-
"\t\t\t\t\t\tcss = '@import \"tailwindcss\" layer(reference);\\n' + css;\n" +
|
|
90
|
-
'\t\t\t\t\t\tconst cssId = createVirtualImportId(filename, root, \'style\');\n' +
|
|
91
|
-
'\t\t\t\t\t\tcssCache.set(cssId, css);'
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
const result = src.replace(orig, patched)
|
|
95
|
-
if (result === src) { warn('Could not patch Ripple plugin for @apply'); return }
|
|
60
|
+
const orig = '\t\t\t\t\tif (css) {\n\t\t\t\t\t\tconst cssId = createVirtualImportId(filename, root, \'style\');\n\t\t\t\t\t\tcssCache.set(cssId, css);'
|
|
61
|
+
const rep = '\t\t\t\t\tif (css) {\n\t\t\t\t\t\t// TW_PATCH_APPLY: @apply support\n\t\t\t\t\t\tcss = \'@import "tailwindcss" layer(reference);\\n\' + css;\n\t\t\t\t\t\tconst cssId = createVirtualImportId(filename, root, \'style\');\n\t\t\t\t\t\tcssCache.set(cssId, css);'
|
|
62
|
+
const result = src.replace(orig, rep)
|
|
63
|
+
if (result === src) { warn('Could not patch @apply'); return }
|
|
96
64
|
writeFileSync(target, result, 'utf-8')
|
|
97
|
-
log('Patched Ripple plugin for @apply
|
|
65
|
+
log('Patched Ripple plugin for @apply')
|
|
98
66
|
}
|
|
99
67
|
|
|
100
|
-
// ── Resolve helpers ────────────────────────────────────────────
|
|
101
68
|
function resolveVike(rel) {
|
|
102
69
|
const p = join(projectRoot, 'node_modules', 'vike', rel)
|
|
103
70
|
if (existsSync(p)) return p
|
|
104
|
-
try { return createRequire(join(projectRoot, '
|
|
71
|
+
try { return createRequire(join(projectRoot, 'package.json')).resolve('vike/' + rel) } catch { return null }
|
|
105
72
|
}
|
|
106
73
|
|
|
107
74
|
function resolveRipple(rel) {
|
|
108
75
|
const p = join(projectRoot, 'node_modules', '@ripple-ts', 'vite-plugin', rel)
|
|
109
76
|
if (existsSync(p)) return p
|
|
110
|
-
try { return createRequire(join(projectRoot, '
|
|
77
|
+
try { return createRequire(join(projectRoot, 'package.json')).resolve('@ripple-ts/vite-plugin/' + rel) } catch { return null }
|
|
111
78
|
}
|
|
112
79
|
|
|
113
|
-
// ── Main ──────────────────────────────────────────────────────
|
|
114
80
|
log('Applying patches...')
|
|
115
81
|
patchVikeExtensions()
|
|
116
82
|
patchRippleDirect()
|
|
@@ -3,9 +3,7 @@ export async function callCumulativeHooks(hooks, ...args) {
|
|
|
3
3
|
for (const hook of hooks) {
|
|
4
4
|
if (typeof hook === 'function') {
|
|
5
5
|
const result = hook(...args)
|
|
6
|
-
if (result && typeof result.then === 'function')
|
|
7
|
-
await result
|
|
8
|
-
}
|
|
6
|
+
if (result && typeof result.then === 'function') await result
|
|
9
7
|
}
|
|
10
8
|
}
|
|
11
9
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export function getTagAttributesString(
|
|
2
|
-
if (!
|
|
3
|
-
return Object.entries(
|
|
4
|
-
.map(([
|
|
5
|
-
if (
|
|
6
|
-
if (
|
|
7
|
-
return ` ${
|
|
1
|
+
export function getTagAttributesString(attrs) {
|
|
2
|
+
if (!attrs) return ''
|
|
3
|
+
return Object.entries(attrs)
|
|
4
|
+
.map(([k, v]) => {
|
|
5
|
+
if (v === true) return ` ${k}`
|
|
6
|
+
if (!v) return ''
|
|
7
|
+
return ` ${k}="${String(v).replace(/"/g, '"')}"`
|
|
8
8
|
})
|
|
9
9
|
.join('')
|
|
10
10
|
}
|
package/src/hooks/useConfig.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { useConfig } from '../hooks/useConfig.js'
|
|
2
|
-
|
|
3
|
-
export function useConfig() {
|
|
4
|
-
const pageContext = typeof window !== 'undefined'
|
|
5
|
-
? window.__vike_pageContext
|
|
6
|
-
: globalThis.__vike_pageContext
|
|
7
|
-
|
|
8
|
-
return (values) => {
|
|
9
|
-
if (!pageContext._configViaHook) {
|
|
10
|
-
pageContext._configViaHook = {}
|
|
11
|
-
}
|
|
12
|
-
Object.assign(pageContext._configViaHook, values)
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export function applyHeadSettings(headList, target) {
|
|
2
|
-
if (!headList || !Array.isArray(headList)) return
|
|
3
|
-
for (const head of headList) {
|
|
4
|
-
if (typeof head === 'string') {
|
|
5
|
-
target.insertAdjacentHTML('beforeend', head)
|
|
6
|
-
} else if (head instanceof Node) {
|
|
7
|
-
target.appendChild(head.cloneNode(true))
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export { getPageElement }
|
|
2
|
-
|
|
3
|
-
import { getHeadSetting } from './getHeadSetting.js'
|
|
4
|
-
|
|
5
|
-
function getPageElement(pageContext) {
|
|
6
|
-
const { Page } = pageContext
|
|
7
|
-
if (!Page) {
|
|
8
|
-
return { page: null, pageElement: null }
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const Layout = pageContext.config.Layout
|
|
12
|
-
const Wrapper = pageContext.config.Wrapper
|
|
13
|
-
|
|
14
|
-
let page = Page
|
|
15
|
-
|
|
16
|
-
if (Layout) {
|
|
17
|
-
const layouts = Array.isArray(Layout) ? Layout : [Layout]
|
|
18
|
-
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
19
|
-
const LayoutComponent = layouts[i]
|
|
20
|
-
page = function NestedPage(props) { return LayoutComponent({ ...props, children: page }) }
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (Wrapper) {
|
|
25
|
-
const wrappers = Array.isArray(Wrapper) ? Wrapper : [Wrapper]
|
|
26
|
-
for (const W of wrappers) {
|
|
27
|
-
page = function WrappedPage(props) { return W({ ...props, children: page }) }
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return { page, pageElement: page }
|
|
32
|
-
}
|
package/src/types/Config.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import 'vike/types'
|
|
2
|
-
|
|
3
|
-
declare global {
|
|
4
|
-
namespace Vike {
|
|
5
|
-
interface Config {
|
|
6
|
-
Head?: unknown[]
|
|
7
|
-
Layout?: unknown[]
|
|
8
|
-
title?: string | null
|
|
9
|
-
description?: string | null
|
|
10
|
-
image?: string | null
|
|
11
|
-
viewport?: 'responsive' | number | null
|
|
12
|
-
favicon?: string | null
|
|
13
|
-
lang?: string | null
|
|
14
|
-
ssr?: boolean
|
|
15
|
-
stream?: boolean
|
|
16
|
-
rippleStream?: boolean
|
|
17
|
-
Wrapper?: unknown[]
|
|
18
|
-
Loading?: unknown
|
|
19
|
-
onBeforeRenderHtml?: ((pageContext: unknown) => void | Promise<void>)[]
|
|
20
|
-
onAfterRenderHtml?: ((pageContext: unknown) => void | Promise<void>)[]
|
|
21
|
-
onBeforeRenderClient?: ((pageContext: unknown) => void | Promise<void>)[]
|
|
22
|
-
onAfterRenderClient?: ((pageContext: unknown) => void | Promise<void>)[]
|
|
23
|
-
bodyHtmlBegin?: string[]
|
|
24
|
-
bodyHtmlEnd?: string[]
|
|
25
|
-
headHtmlBegin?: string[]
|
|
26
|
-
headHtmlEnd?: string[]
|
|
27
|
-
htmlAttributes?: Record<string, string | boolean>
|
|
28
|
-
bodyAttributes?: Record<string, string | boolean>
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type __FakeExport_Config = true
|
package/src/types/PageContext.ts
DELETED