@solidjs/start 0.4.1 → 0.4.3
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/config/index.d.ts +6 -1
- package/config/index.js +11 -9
- package/config/server-fns-runtime.jsx +1 -1
- package/config/server-handler.js +37 -23
- package/config/server-runtime.jsx +9 -1
- package/package.json +13 -8
- package/server/StartServer.tsx +15 -8
- package/server/handler.tsx +6 -1
- package/server/page-event.tsx +3 -1
- package/server/spa/StartServer.tsx +5 -2
- package/shared/ErrorBoundary.tsx +16 -39
- package/shared/dev-overlay/CodeView.tsx +74 -0
- package/shared/dev-overlay/DevOverlayDialog.tsx +277 -0
- package/shared/dev-overlay/createStackFrame.ts +57 -0
- package/shared/dev-overlay/download.ts +6 -0
- package/shared/dev-overlay/env.d.ts +4 -0
- package/shared/dev-overlay/get-source-map.ts +35 -0
- package/shared/dev-overlay/icons.tsx +287 -0
- package/shared/dev-overlay/index.tsx +60 -0
- package/shared/dev-overlay/styles.css +287 -0
- package/shared/routes.ts +3 -2
package/config/index.d.ts
CHANGED
|
@@ -3,12 +3,17 @@ import { InlineConfig } from "vite";
|
|
|
3
3
|
|
|
4
4
|
type SolidStartInlineConfig = Omit<InlineConfig, "router"> & {
|
|
5
5
|
start?: {
|
|
6
|
+
/**
|
|
7
|
+
* true: streaming mode
|
|
8
|
+
* false: csr only
|
|
9
|
+
* async: ssr is in async mode
|
|
10
|
+
*/
|
|
6
11
|
ssr?: boolean | "async",
|
|
7
12
|
extensions?: string[],
|
|
8
13
|
server?: AppOptions['server'],
|
|
9
14
|
appRoot?: string,
|
|
10
15
|
middleware?: string,
|
|
11
|
-
|
|
16
|
+
islands?: boolean
|
|
12
17
|
}
|
|
13
18
|
}
|
|
14
19
|
|
package/config/index.js
CHANGED
|
@@ -50,15 +50,15 @@ export function defineConfig(baseConfig = {}) {
|
|
|
50
50
|
if (!start.ssr) {
|
|
51
51
|
server = { ...server, prerender: { routes: ["/"] } };
|
|
52
52
|
}
|
|
53
|
-
let entryExtension = ".tsx"
|
|
53
|
+
let entryExtension = ".tsx";
|
|
54
54
|
if (existsSync(join(process.cwd(), start.appRoot, "app.jsx"))) {
|
|
55
|
-
entryExtension = ".jsx"
|
|
55
|
+
entryExtension = ".jsx";
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
return createApp({
|
|
59
59
|
server: {
|
|
60
60
|
compressPublicAssets: {
|
|
61
|
-
brotli: true
|
|
61
|
+
brotli: process.versions.bun ? false : true
|
|
62
62
|
},
|
|
63
63
|
...server
|
|
64
64
|
},
|
|
@@ -78,9 +78,10 @@ export function defineConfig(baseConfig = {}) {
|
|
|
78
78
|
routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
|
|
79
79
|
extensions,
|
|
80
80
|
target: "server",
|
|
81
|
-
plugins: () => [
|
|
81
|
+
plugins: async () => [
|
|
82
82
|
config("user", userConfig),
|
|
83
|
-
...plugins,
|
|
83
|
+
...(typeof plugins === "function" ? [...(await plugins())] : plugins),
|
|
84
|
+
|
|
84
85
|
serverTransform({
|
|
85
86
|
runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.jsx", import.meta.url)))
|
|
86
87
|
}),
|
|
@@ -119,9 +120,9 @@ export function defineConfig(baseConfig = {}) {
|
|
|
119
120
|
}),
|
|
120
121
|
extensions,
|
|
121
122
|
target: "browser",
|
|
122
|
-
plugins: () => [
|
|
123
|
+
plugins: async () => [
|
|
123
124
|
config("user", userConfig),
|
|
124
|
-
...plugins,
|
|
125
|
+
...(typeof plugins === "function" ? [...(await plugins())] : plugins),
|
|
125
126
|
serverFunctions.client({
|
|
126
127
|
runtime: normalize(fileURLToPath(new URL("./server-runtime.jsx", import.meta.url)))
|
|
127
128
|
}),
|
|
@@ -159,9 +160,10 @@ export function defineConfig(baseConfig = {}) {
|
|
|
159
160
|
handler: normalize(fileURLToPath(new URL("./server-handler.js", import.meta.url))),
|
|
160
161
|
runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.jsx", import.meta.url))),
|
|
161
162
|
// routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
|
|
162
|
-
plugins: () => [
|
|
163
|
+
plugins: async () => [
|
|
163
164
|
config("user", userConfig),
|
|
164
|
-
...plugins,
|
|
165
|
+
...(typeof plugins === "function" ? [...(await plugins())] : plugins),
|
|
166
|
+
|
|
165
167
|
solid({ ...start.solid, ssr: true, extensions: extensions.map(ext => `.${ext}`) }),
|
|
166
168
|
config("app-server", {
|
|
167
169
|
resolve: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getRequestEvent } from "solid-js/web";
|
|
2
2
|
import { provideRequestEvent } from "solid-js/web/storage";
|
|
3
|
-
import { cloneEvent } from "../server";
|
|
3
|
+
import { cloneEvent } from "../server/middleware";
|
|
4
4
|
|
|
5
5
|
export function createServerReference(fn, id, name) {
|
|
6
6
|
return new Proxy(fn, {
|
package/config/server-handler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="vinxi/types/server" />
|
|
2
|
-
import { crossSerializeStream, fromJSON } from "seroval";
|
|
2
|
+
import { crossSerializeStream, fromJSON, getCrossReferenceHeader } from "seroval";
|
|
3
3
|
import {
|
|
4
4
|
CustomEventPlugin,
|
|
5
5
|
DOMExceptionPlugin,
|
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
RequestPlugin,
|
|
11
11
|
ResponsePlugin,
|
|
12
12
|
URLPlugin,
|
|
13
|
-
URLSearchParamsPlugin
|
|
14
|
-
} from
|
|
13
|
+
URLSearchParamsPlugin
|
|
14
|
+
} from "seroval-plugins/web";
|
|
15
|
+
import { sharedConfig } from "solid-js";
|
|
16
|
+
/* @ts-ignore */
|
|
15
17
|
import { provideRequestEvent } from "solid-js/web/storage";
|
|
16
18
|
import invariant from "vinxi/lib/invariant";
|
|
17
19
|
import {
|
|
@@ -19,7 +21,7 @@ import {
|
|
|
19
21
|
getHeader,
|
|
20
22
|
getRequestURL,
|
|
21
23
|
readBody,
|
|
22
|
-
|
|
24
|
+
readRawBody,
|
|
23
25
|
setHeader
|
|
24
26
|
} from "vinxi/server";
|
|
25
27
|
import { getFetchEvent } from "../server/middleware";
|
|
@@ -39,10 +41,10 @@ function serializeToStream(id, value) {
|
|
|
39
41
|
RequestPlugin,
|
|
40
42
|
ResponsePlugin,
|
|
41
43
|
URLSearchParamsPlugin,
|
|
42
|
-
URLPlugin
|
|
44
|
+
URLPlugin
|
|
43
45
|
],
|
|
44
46
|
onSerialize(data, initial) {
|
|
45
|
-
const result = initial ? `($
|
|
47
|
+
const result = initial ? `(${getCrossReferenceHeader(id)},${data})` : data;
|
|
46
48
|
controller.enqueue(new TextEncoder().encode(`${result};\n`));
|
|
47
49
|
},
|
|
48
50
|
onDone() {
|
|
@@ -89,7 +91,15 @@ async function handleServerFunction(event) {
|
|
|
89
91
|
contentType.startsWith("multipart/form-data") ||
|
|
90
92
|
contentType.startsWith("application/x-www-form-urlencoded")
|
|
91
93
|
) {
|
|
92
|
-
|
|
94
|
+
// Temporary workaround until https://github.com/unjs/nitro/issues/1721 is resolved
|
|
95
|
+
// parsed.push(await readFormData(event));
|
|
96
|
+
|
|
97
|
+
const request = new Request(getRequestURL(event), {
|
|
98
|
+
method: event.method,
|
|
99
|
+
headers: event.headers,
|
|
100
|
+
body: await readRawBody(event)
|
|
101
|
+
});
|
|
102
|
+
parsed.push(await request.formData());
|
|
93
103
|
} else {
|
|
94
104
|
parsed = fromJSON(await readBody(event), {
|
|
95
105
|
plugins: [
|
|
@@ -102,12 +112,17 @@ async function handleServerFunction(event) {
|
|
|
102
112
|
RequestPlugin,
|
|
103
113
|
ResponsePlugin,
|
|
104
114
|
URLSearchParamsPlugin,
|
|
105
|
-
URLPlugin
|
|
106
|
-
]
|
|
115
|
+
URLPlugin
|
|
116
|
+
]
|
|
107
117
|
});
|
|
108
118
|
}
|
|
109
119
|
try {
|
|
110
|
-
const
|
|
120
|
+
const evt = getFetchEvent(event);
|
|
121
|
+
const result = await provideRequestEvent(evt, () => {
|
|
122
|
+
/* @ts-ignore */
|
|
123
|
+
sharedConfig.context = { event: evt };
|
|
124
|
+
return action(...parsed);
|
|
125
|
+
});
|
|
111
126
|
|
|
112
127
|
// handle no JS success case
|
|
113
128
|
if (!instance) {
|
|
@@ -117,16 +132,20 @@ async function handleServerFunction(event) {
|
|
|
117
132
|
status: 302,
|
|
118
133
|
headers: {
|
|
119
134
|
Location: refererUrl.toString(),
|
|
120
|
-
...(result
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
...(result
|
|
136
|
+
? {
|
|
137
|
+
"Set-Cookie": `flash=${JSON.stringify({
|
|
138
|
+
url: url.pathname + encodeURIComponent(url.search),
|
|
139
|
+
result: isError ? result.message : result,
|
|
140
|
+
error: isError,
|
|
141
|
+
input: [...parsed.slice(0, -1), [...parsed[parsed.length - 1].entries()]]
|
|
142
|
+
})}; Secure; HttpOnly;`
|
|
143
|
+
}
|
|
144
|
+
: {})
|
|
126
145
|
}
|
|
127
146
|
});
|
|
128
147
|
}
|
|
129
|
-
|
|
148
|
+
if (typeof result === "string") return new Response(result);
|
|
130
149
|
setHeader(event, "content-type", "text/javascript");
|
|
131
150
|
return serializeToStream(instance, result);
|
|
132
151
|
} catch (x) {
|
|
@@ -138,12 +157,7 @@ async function handleServerFunction(event) {
|
|
|
138
157
|
}
|
|
139
158
|
});
|
|
140
159
|
}
|
|
141
|
-
return
|
|
142
|
-
status: 500,
|
|
143
|
-
headers: {
|
|
144
|
-
"Content-Type": "text/javascript"
|
|
145
|
-
}
|
|
146
|
-
});
|
|
160
|
+
return x;
|
|
147
161
|
}
|
|
148
162
|
}
|
|
149
163
|
|
|
@@ -104,7 +104,15 @@ async function fetchServerFunction(base, id, args) {
|
|
|
104
104
|
}
|
|
105
105
|
throw response;
|
|
106
106
|
}
|
|
107
|
-
const
|
|
107
|
+
const contentType = response.headers.get("Content-Type");
|
|
108
|
+
let result;
|
|
109
|
+
if (contentType && contentType.startsWith("text/plain")) {
|
|
110
|
+
result = await response.text();
|
|
111
|
+
} else if(contentType && contentType.startsWith("application/json")) {
|
|
112
|
+
result = await response.json();
|
|
113
|
+
} else {
|
|
114
|
+
result = deserializeStream(instance, response);
|
|
115
|
+
}
|
|
108
116
|
if (response.ok) {
|
|
109
117
|
return result;
|
|
110
118
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidjs/start",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"publishConfig": {
|
|
@@ -38,16 +38,21 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"solid-js": "^1.8.
|
|
42
|
-
"vinxi": "0.
|
|
41
|
+
"solid-js": "^1.8.8",
|
|
42
|
+
"vinxi": "0.1.1"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@vinxi/plugin-directives": "0.0
|
|
46
|
-
"@vinxi/server-components": "0.0
|
|
47
|
-
"@vinxi/server-functions": "0.0
|
|
45
|
+
"@vinxi/plugin-directives": "^0.1.0",
|
|
46
|
+
"@vinxi/server-components": "^0.1.0",
|
|
47
|
+
"@vinxi/server-functions": "^0.1.0",
|
|
48
48
|
"defu": "^6.1.2",
|
|
49
|
-
"
|
|
50
|
-
"
|
|
49
|
+
"error-stack-parser": "^2.1.4",
|
|
50
|
+
"html-to-image": "^1.11.11",
|
|
51
|
+
"seroval": "^1.0.2",
|
|
52
|
+
"seroval-plugins": "^1.0.2",
|
|
53
|
+
"shikiji": "^0.9.12",
|
|
54
|
+
"source-map-js": "^1.0.2",
|
|
55
|
+
"terracotta": "^1.0.4",
|
|
51
56
|
"vite-plugin-inspect": "^0.7.33",
|
|
52
57
|
"vite-plugin-solid": "^2.8.0"
|
|
53
58
|
},
|
package/server/StartServer.tsx
CHANGED
|
@@ -26,7 +26,8 @@ export function StartServer(props: { document: Component<DocumentComponentProps>
|
|
|
26
26
|
const match = context.routerMatches[0][i];
|
|
27
27
|
if (match.metadata && match.metadata.filesystem) {
|
|
28
28
|
const segment = current.find(r => r.path === match.path);
|
|
29
|
-
const part = import.meta.env.MANIFEST["
|
|
29
|
+
const part = import.meta.env.MANIFEST[import.meta.env.START_ISLANDS ? "ssr" : "client"]
|
|
30
|
+
.inputs[segment["$component"].src];
|
|
30
31
|
const asset = await part.assets();
|
|
31
32
|
assets.push.apply(assets, asset);
|
|
32
33
|
current = segment.children;
|
|
@@ -34,26 +35,32 @@ export function StartServer(props: { document: Component<DocumentComponentProps>
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
// dedupe assets
|
|
37
|
-
assets = [...new Map(assets.map(item => [item.attrs.key, item])).values()].filter(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
assets = [...new Map(assets.map(item => [item.attrs.key, item])).values()].filter(asset =>
|
|
39
|
+
import.meta.env.START_ISLANDS
|
|
40
|
+
? false
|
|
41
|
+
: asset.attrs.rel === "modulepreload" &&
|
|
42
|
+
!context.assets.find(a => a.attrs.key === asset.attrs.key)
|
|
41
43
|
);
|
|
42
44
|
});
|
|
43
45
|
|
|
44
|
-
useAssets(() => assets.length ? assets.map(m => renderAsset(m)) : undefined);
|
|
46
|
+
useAssets(() => (assets.length ? assets.map(m => renderAsset(m)) : undefined));
|
|
45
47
|
|
|
46
48
|
return (
|
|
47
49
|
<NoHydration>
|
|
48
50
|
{docType as unknown as any}
|
|
49
51
|
<props.document
|
|
50
|
-
assets={
|
|
51
|
-
scripts={
|
|
52
|
+
assets={
|
|
52
53
|
<>
|
|
53
54
|
<HydrationScript />
|
|
55
|
+
{context.assets.map(m => renderAsset(m))}
|
|
56
|
+
</>
|
|
57
|
+
}
|
|
58
|
+
scripts={
|
|
59
|
+
<>
|
|
54
60
|
<script innerHTML={`window.manifest = ${JSON.stringify(context.manifest)}`} />
|
|
55
61
|
<script
|
|
56
62
|
type="module"
|
|
63
|
+
async
|
|
57
64
|
src={
|
|
58
65
|
import.meta.env.MANIFEST["client"].inputs[
|
|
59
66
|
import.meta.env.MANIFEST["client"].handler
|
package/server/handler.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sharedConfig } from "solid-js";
|
|
1
2
|
import { renderToStream } from "solid-js/web";
|
|
2
3
|
/* @ts-ignore */
|
|
3
4
|
import { provideRequestEvent } from "solid-js/web/storage";
|
|
@@ -42,6 +43,7 @@ export function createHandler(
|
|
|
42
43
|
const mod = await match.handler.import();
|
|
43
44
|
const fn = mod[event.request.method];
|
|
44
45
|
(event as APIEvent).params = match.params;
|
|
46
|
+
sharedConfig.context = { event } as any;
|
|
45
47
|
return await fn(event);
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -63,7 +65,10 @@ export function createHandler(
|
|
|
63
65
|
og(options);
|
|
64
66
|
};
|
|
65
67
|
} else cloned.onCompleteShell = handleShellCompleteRedirect(context, e);
|
|
66
|
-
const stream = renderToStream(() =>
|
|
68
|
+
const stream = renderToStream(() => {
|
|
69
|
+
(sharedConfig.context as any).event = context;
|
|
70
|
+
return fn(context)
|
|
71
|
+
}, cloned);
|
|
67
72
|
if (context.response && context.response.headers.get("Location")) {
|
|
68
73
|
return sendRedirect(event, context.response.headers.get("Location"));
|
|
69
74
|
}
|
package/server/page-event.tsx
CHANGED
|
@@ -35,7 +35,9 @@ export async function createPageEvent(ctx: FetchEvent) {
|
|
|
35
35
|
assets: [
|
|
36
36
|
...(await clientManifest.inputs[clientManifest.handler].assets()),
|
|
37
37
|
...(import.meta.env.START_ISLANDS
|
|
38
|
-
? await serverManifest.inputs[serverManifest.handler].assets()
|
|
38
|
+
? (await serverManifest.inputs[serverManifest.handler].assets()).filter(
|
|
39
|
+
s => (s as any).attrs.rel !== "modulepreload"
|
|
40
|
+
)
|
|
39
41
|
: [])
|
|
40
42
|
],
|
|
41
43
|
initialSubmission: initFromFlash(ctx),
|
|
@@ -10,11 +10,14 @@ export function StartServer(props) {
|
|
|
10
10
|
<NoHydration>
|
|
11
11
|
{docType as unknown as any}
|
|
12
12
|
<props.document
|
|
13
|
-
assets={
|
|
13
|
+
assets={
|
|
14
|
+
<>
|
|
15
|
+
{context.assets.map(m => renderAsset(m))}
|
|
16
|
+
</>
|
|
17
|
+
}
|
|
14
18
|
scripts={
|
|
15
19
|
<>
|
|
16
20
|
<script innerHTML={`window.manifest = ${JSON.stringify(context.manifest)}`} />
|
|
17
|
-
<script>$R = [];</script>
|
|
18
21
|
<script
|
|
19
22
|
type="module"
|
|
20
23
|
src={
|
package/shared/ErrorBoundary.tsx
CHANGED
|
@@ -1,48 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
ErrorBoundary as ErrorBoundaryBase,
|
|
2
|
+
ErrorBoundary as DefaultErrorBoundary,
|
|
4
3
|
ParentProps,
|
|
5
|
-
resetErrorBoundaries
|
|
6
4
|
} from "solid-js";
|
|
5
|
+
import { HttpStatusCode } from "./HttpStatusCode";
|
|
6
|
+
import { DevOverlay } from "./dev-overlay";
|
|
7
7
|
|
|
8
8
|
export function ErrorBoundary(props: ParentProps) {
|
|
9
|
+
if (import.meta.env.DEV) {
|
|
10
|
+
return (
|
|
11
|
+
<DevOverlay>
|
|
12
|
+
{props.children}
|
|
13
|
+
</DevOverlay>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
9
16
|
return (
|
|
10
|
-
<
|
|
17
|
+
<DefaultErrorBoundary
|
|
18
|
+
fallback={(
|
|
19
|
+
<HttpStatusCode code={500} />
|
|
20
|
+
)}
|
|
21
|
+
>
|
|
11
22
|
{props.children}
|
|
12
|
-
</
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function ErrorMessage(props: { error: any }) {
|
|
17
|
-
createEffect(() => console.error(props.error));
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div style="padding: 16px">
|
|
21
|
-
<div
|
|
22
|
-
style={`
|
|
23
|
-
background-color: rgba(252, 165, 165);
|
|
24
|
-
color: rgb(153, 27, 27);
|
|
25
|
-
border-radius": 5px;
|
|
26
|
-
overflow: scroll;
|
|
27
|
-
padding: 16px;
|
|
28
|
-
margin-bottom: 8px;
|
|
29
|
-
`}
|
|
30
|
-
>
|
|
31
|
-
<p style="font-weight: bold" id="error-message">
|
|
32
|
-
{props.error.message}
|
|
33
|
-
</p>
|
|
34
|
-
<button
|
|
35
|
-
id="reset-errors"
|
|
36
|
-
onClick={resetErrorBoundaries}
|
|
37
|
-
style={`color: rgba(252, 165, 165);
|
|
38
|
-
background-color: rgb(153, 27, 27);
|
|
39
|
-
border-radius: 5px;
|
|
40
|
-
padding: 4px 8px`}
|
|
41
|
-
>
|
|
42
|
-
Clear errors and retry
|
|
43
|
-
</button>
|
|
44
|
-
<pre style={"margin-top: 8px;width:100%"}>{props.error.stack}</pre>
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
23
|
+
</DefaultErrorBoundary>
|
|
47
24
|
);
|
|
48
25
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as shikiji from 'shikiji';
|
|
2
|
+
import { createEffect, createResource, type JSX } from 'solid-js';
|
|
3
|
+
|
|
4
|
+
let HIGHLIGHTER: shikiji.Highlighter;
|
|
5
|
+
|
|
6
|
+
async function loadHighlighter() {
|
|
7
|
+
if (!HIGHLIGHTER) {
|
|
8
|
+
HIGHLIGHTER = await shikiji.getHighlighter({
|
|
9
|
+
themes: ['dark-plus'],
|
|
10
|
+
langs: ['js', 'ts', 'jsx', 'tsx'],
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return HIGHLIGHTER;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CodeViewProps {
|
|
17
|
+
fileName: string;
|
|
18
|
+
content: string;
|
|
19
|
+
line: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const RANGE = 8;
|
|
23
|
+
|
|
24
|
+
export function CodeView(props: CodeViewProps): JSX.Element | null {
|
|
25
|
+
const lines = () =>
|
|
26
|
+
props.content.split('\n').map((item, index) => ({
|
|
27
|
+
index: index + 1,
|
|
28
|
+
line: item,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const minLine = () => Math.max(props.line - (1 + RANGE), 0);
|
|
32
|
+
const maxLine = () => Math.min(props.line + RANGE, lines().length - 1);
|
|
33
|
+
|
|
34
|
+
let ref: HTMLDivElement | undefined;
|
|
35
|
+
|
|
36
|
+
const [data] = createResource(() => (
|
|
37
|
+
lines()
|
|
38
|
+
.slice(minLine(), maxLine())
|
|
39
|
+
.map(item => item.line)
|
|
40
|
+
.join('\n')
|
|
41
|
+
) ,async (value) => {
|
|
42
|
+
const highlighter = await loadHighlighter();
|
|
43
|
+
const lang = props.fileName
|
|
44
|
+
.split(/[#?]/)[0]
|
|
45
|
+
.split('.')
|
|
46
|
+
.pop()
|
|
47
|
+
?.trim() as shikiji.BuiltinLanguage;
|
|
48
|
+
await highlighter.loadLanguage(lang);
|
|
49
|
+
return highlighter.codeToHtml(value, {
|
|
50
|
+
theme: 'dark-plus',
|
|
51
|
+
lang,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
createEffect(() => {
|
|
56
|
+
const result = data();
|
|
57
|
+
if (ref && result) {
|
|
58
|
+
ref.innerHTML = result;
|
|
59
|
+
|
|
60
|
+
const lines = ref.querySelectorAll('span[class="line"]');
|
|
61
|
+
|
|
62
|
+
for (let i = 0, len = lines.length; i < len; i++) {
|
|
63
|
+
const el = lines[i];
|
|
64
|
+
if ((props.line - minLine() - 1) === i) {
|
|
65
|
+
el.innerHTML = `<mark style="background-color:#aaaaaa80">${el.innerHTML}</mark>`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return <div ref={ref} class="dev-overlay-code-view" style={{
|
|
72
|
+
'--dev-overlay-code-view-start': minLine() + 1,
|
|
73
|
+
}} />;
|
|
74
|
+
}
|