Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit f9a20ad

Browse files
author
Je
committed
feat: add virtual dom for ssr
1 parent f83713a commit f9a20ad

16 files changed

+3237
-23
lines changed

project.ts

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { compile } from './tsc/compile.ts'
88
import type { APIHandle, Config, Location, RouterURL } from './types.ts'
99
import util, { hashShort } from './util.ts'
1010
import './vendor/clean-css-builds/v4.2.2.js'
11+
import { Document } from './vendor/deno-dom/document.ts'
1112
import less from './vendor/less/less.js'
1213

1314
const reHttp = /^https?:\/\//i
@@ -162,8 +163,12 @@ export default class Project {
162163
if (path) {
163164
const importPath = '.' + path + '.js'
164165
if (this.#modules.has(importPath)) {
165-
const { default: handle } = await import("file://" + this.#modules.get(importPath)!.jsFile)
166-
return handle
166+
try {
167+
const { default: handle } = await import("file://" + this.#modules.get(importPath)!.jsFile)
168+
return handle
169+
} catch (error) {
170+
log.error(error)
171+
}
167172
}
168173
}
169174
return null
@@ -215,15 +220,19 @@ export default class Project {
215220
async getData() {
216221
const mod = this.#modules.get('./data.js') || this.#modules.get('./data/index.js')
217222
if (mod) {
218-
const { default: Data } = await import("file://" + mod.jsFile)
219-
let data: any = Data
220-
if (util.isFunction(Data)) {
221-
data = await Data()
222-
}
223-
if (util.isPlainObject(data)) {
224-
return data
225-
} else {
226-
log.warn(`module '${mod.url}' should return a plain object as default`)
223+
try {
224+
const { default: Data } = await import("file://" + mod.jsFile)
225+
let data: any = Data
226+
if (util.isFunction(Data)) {
227+
data = await Data()
228+
}
229+
if (util.isPlainObject(data)) {
230+
return data
231+
} else {
232+
log.warn(`module '${mod.url}' should return a plain object as default`)
233+
}
234+
} catch (error) {
235+
log.error(error)
227236
}
228237
}
229238
return {}
@@ -388,6 +397,9 @@ export default class Project {
388397
ALEPH_ENV: {
389398
appDir: this.rootDir,
390399
},
400+
document: new Document(),
401+
innerWidth: 1920,
402+
innerHeight: 1080,
391403
$RefreshReg$: () => { },
392404
$RefreshSig$: () => (type: any) => type,
393405
})
@@ -433,7 +445,7 @@ export default class Project {
433445
}
434446

435447
const preCompileUrls = [
436-
'https://deno.land/x/aleph/app.ts',
448+
'https://deno.land/x/aleph/bootstrap.ts',
437449
'https://deno.land/x/aleph/renderer.ts',
438450
'https://deno.land/x/aleph/vendor/tslib/tslib.js',
439451
]
@@ -646,7 +658,7 @@ export default class Project {
646658
module.jsContent = [
647659
this.isDev && 'import "./-/deno.land/x/aleph/hmr.js";',
648660
'import "./-/deno.land/x/aleph/vendor/tslib/tslib.js";',
649-
'import { bootstrap } from "./-/deno.land/x/aleph/app.js";',
661+
'import bootstrap from "./-/deno.land/x/aleph/bootstrap.js";',
650662
`bootstrap(${JSON.stringify(config, undefined, this.isDev ? 4 : undefined)});`
651663
].filter(Boolean).join(this.isDev ? '\n' : '')
652664
module.hash = (new Sha1()).update(module.jsContent).hex()
@@ -1036,18 +1048,28 @@ export default class Project {
10361048
const cache = page.rendered.get(url.pathname)!
10371049
return { ...cache }
10381050
}
1051+
Object.assign(window, {
1052+
location: {
1053+
protocol: 'http:',
1054+
host: 'localhost',
1055+
hostname: 'localhost',
1056+
port: '',
1057+
href: 'http://localhost' + url.pathname,
1058+
origin: 'http://localhost',
1059+
pathname: url.pathname,
1060+
search: '',
1061+
hash: '',
1062+
reload() { },
1063+
replace() { },
1064+
toString() { return this.href },
1065+
}
1066+
})
10391067
try {
10401068
const appModule = this.#modules.get('./app.js')
10411069
const pageModule = this.#modules.get(page.moduleId)!
1042-
const [
1043-
{ renderPage, renderHead },
1044-
{ default: App },
1045-
{ default: Page }
1046-
] = await Promise.all([
1047-
import("file://" + this.#modules.get('//deno.land/x/aleph/renderer.js')!.jsFile),
1048-
appModule ? await import("file://" + appModule.jsFile) : Promise.resolve({}),
1049-
await import("file://" + pageModule.jsFile)
1050-
])
1070+
const { renderPage, renderHead } = await import("file://" + this.#modules.get('//deno.land/x/aleph/renderer.js')!.jsFile)
1071+
const { default: App } = appModule ? await import("file://" + appModule.jsFile) : {} as any
1072+
const { default: Page } = await import("file://" + pageModule.jsFile)
10511073
const data = await this.getData()
10521074
const html = renderPage(data, url, appModule ? App : undefined, Page)
10531075
const head = renderHead([

vendor/deno-dom/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 b-fuze
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.

vendor/deno-dom/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Deno DOM
2+
3+
An implementation of the browser DOM—primarily for SSR—in Deno. Implemented with
4+
Rust, WASM, and obviously, Deno/TypeScript.
5+
6+
## Example
7+
```typescript
8+
import { DOMParser, Element } from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";
9+
10+
const doc = new DOMParser().parseFromString(`
11+
<h1>Hello World!</h1>
12+
<p>Hello from <a href="https://deno.land/">Deno!</a></p>
13+
`, "text/html")!;
14+
15+
const p = doc.querySelector("p")!;
16+
17+
console.log(p.textContent); // "Hello from Deno!"
18+
console.log(p.childNodes[1].textContent); // "Deno!"
19+
20+
p.innerHTML = "DOM in <b>Deno</b> is pretty cool";
21+
console.log(p.children[0].outerHTML); // "<b>Deno</b>"
22+
```
23+
24+
Deno DOM has **two** backends, WASM and native using Deno native plugins. Both
25+
APIs are **identical**, the difference being only in performance. The WASM
26+
backend works with all Deno restrictions, but the native backend requires
27+
the `--unstable --allow-plugin` flags. You can switch between them by
28+
importing either `deno-dom-wasm.ts` or `deno-dom-native.ts`.
29+
30+
Deno DOM is still under development, but is fairly usable for basic HTML
31+
manipulation needs.
32+
33+
## Goals
34+
35+
- HTML parser in Deno
36+
- Fast
37+
- Mirror most\* supported DOM APIs as closely as possible
38+
- Provide specific APIs in addition to DOM APIs to make certain operations more efficient, like controlling Shadow DOM (see Open Questions)
39+
- Use cutting-edge JS features like private class members, optional chaining, etc
40+
41+
## Non-Goals
42+
43+
- Headless browser implementation
44+
- Ability to run JS embedded in documents (`<script>` tags, `onload`, etc)
45+
- Parse CSS or JS (they're just text, but this may be supported in the future for CSSOM)
46+
- Support older (or even not so old) JS engines. In other words, there will be no support of transpilation to ES5, no support of polyfills, etc
47+
- Support special functionality of obsolete HTML elements (`<marquee>`, etc)
48+
49+
# Credits
50+
- html5ever developers for the HTML parser
51+
- nwsapi developers for the selector parser
52+

vendor/deno-dom/canvas.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export class CanvasContext2D {
2+
fillRect(x: number, y: number, width: number, height: number) {
3+
}
4+
}

vendor/deno-dom/constructor-lock.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Used to enforce illegal constructors
3+
*/
4+
let lock = true;
5+
6+
export function setLock(value: boolean) {
7+
lock = value;
8+
}
9+
10+
export function getLock(): boolean {
11+
return lock;
12+
}
13+

vendor/deno-dom/deserialize.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { setLock } from "./constructor-lock.ts";
2+
import { Element } from "./element.ts";
3+
import { Comment, Node, NodeType, Text } from "./node.ts";
4+
import { parse, parseFrag } from "./parser.ts";
5+
6+
export function nodesFromString(html: string): Node {
7+
setLock(false);
8+
const parsed = JSON.parse(parse(html));
9+
const node = nodeFromArray(parsed, null);
10+
setLock(true);
11+
12+
return node;
13+
}
14+
15+
export function fragmentNodesFromString(html: string): Node {
16+
setLock(false);
17+
const parsed = JSON.parse(parseFrag(html));
18+
const node = nodeFromArray(parsed, null);
19+
setLock(true);
20+
21+
return node;
22+
}
23+
24+
function nodeFromArray(data: any, parentNode: Node | null): Node {
25+
// For reference only:
26+
// type node = [NodeType, nodeName, attributes, node[]]
27+
// | [NodeType, characterData]
28+
const elm = new Element(data[1], parentNode, data[2]);
29+
const childNodes = elm._getChildNodesMutator();
30+
let childNode: Node;
31+
32+
for (const child of data.slice(3)) {
33+
switch (child[0]) {
34+
case NodeType.TEXT_NODE:
35+
childNode = new Text(child[1]);
36+
childNode.parentNode = childNode.parentElement = <Element>elm;
37+
childNodes.push(childNode);
38+
break;
39+
40+
case NodeType.COMMENT_NODE:
41+
childNode = new Comment(child[1]);
42+
childNode.parentNode = childNode.parentElement = <Element>elm;
43+
childNodes.push(childNode);
44+
break;
45+
46+
case NodeType.DOCUMENT_NODE:
47+
case NodeType.ELEMENT_NODE:
48+
nodeFromArray(child, elm);
49+
break;
50+
}
51+
}
52+
53+
return elm;
54+
}
55+

0 commit comments

Comments
 (0)