@inglorious/ssx 0.4.0 ā 1.0.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 +380 -677
- package/package.json +2 -2
- package/src/build.js +8 -7
- package/src/dev.js +1 -1
- package/src/render.js +35 -18
- package/src/render.test.js +17 -13
- package/src/router.js +3 -1
- package/src/router.test.js +5 -5
- package/src/scripts/app.js +9 -4
- package/src/scripts/app.test.js +3 -3
- package/src/store.js +9 -3
- package/src/store.test.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/ssx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Server-Side-X. Xecution? Xperience? Who knows.",
|
|
5
5
|
"author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"glob": "^13.0.0",
|
|
48
48
|
"rollup-plugin-minify-template-literals": "^1.1.7",
|
|
49
49
|
"vite": "^7.1.3",
|
|
50
|
-
"@inglorious/web": "4.0.
|
|
50
|
+
"@inglorious/web": "4.0.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"prettier": "^3.6.2",
|
package/src/build.js
CHANGED
|
@@ -27,7 +27,7 @@ export async function build(options = {}) {
|
|
|
27
27
|
const store = await generateStore(pages, options)
|
|
28
28
|
|
|
29
29
|
// Render all pages
|
|
30
|
-
const
|
|
30
|
+
const htmls = await renderPages(store, pages, options)
|
|
31
31
|
|
|
32
32
|
// Write all pages to disk
|
|
33
33
|
console.log("\nš¾ Writing files...\n")
|
|
@@ -35,10 +35,11 @@ export async function build(options = {}) {
|
|
|
35
35
|
const app = generateApp(store, pages)
|
|
36
36
|
await fs.writeFile(path.join(outDir, "main.js"), app, "utf-8")
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
pages.forEach(async (page, index) => {
|
|
39
|
+
const html = htmls[index]
|
|
39
40
|
const filePath = await writePageToDisk(page.path, html, outDir)
|
|
40
41
|
console.log(` ā ${filePath}`)
|
|
41
|
-
}
|
|
42
|
+
})
|
|
42
43
|
|
|
43
44
|
// Bundle with Vite
|
|
44
45
|
console.log("\nš¦ Bundling with Vite...\n")
|
|
@@ -50,10 +51,10 @@ export async function build(options = {}) {
|
|
|
50
51
|
|
|
51
52
|
console.log("\n⨠Build complete!\n")
|
|
52
53
|
|
|
53
|
-
return { pages:
|
|
54
|
+
return { pages: htmls.length, outDir }
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
async function
|
|
57
|
+
async function renderPages(store, pages, options = {}) {
|
|
57
58
|
const { renderOptions } = options
|
|
58
59
|
|
|
59
60
|
const renderedPages = []
|
|
@@ -62,11 +63,11 @@ async function generatePages(store, pages, options = {}) {
|
|
|
62
63
|
console.log(` Rendering ${page.path}...`)
|
|
63
64
|
|
|
64
65
|
const module = await import(pathToFileURL(page.filePath))
|
|
65
|
-
const html = await renderPage(store, module, {
|
|
66
|
+
const html = await renderPage(store, page, module, {
|
|
66
67
|
...renderOptions,
|
|
67
68
|
wrap: true,
|
|
68
69
|
})
|
|
69
|
-
renderedPages.push(
|
|
70
|
+
renderedPages.push(html)
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
return renderedPages
|
package/src/dev.js
CHANGED
|
@@ -48,7 +48,7 @@ export async function dev(options = {}) {
|
|
|
48
48
|
if (!page) return next()
|
|
49
49
|
|
|
50
50
|
const module = await viteServer.ssrLoadModule(page.filePath)
|
|
51
|
-
const html = await renderPage(store, module, {
|
|
51
|
+
const html = await renderPage(store, page, module, {
|
|
52
52
|
...renderOptions,
|
|
53
53
|
wrap: true,
|
|
54
54
|
})
|
package/src/render.js
CHANGED
|
@@ -1,31 +1,48 @@
|
|
|
1
1
|
import { toHTML } from "./html.js"
|
|
2
2
|
import { getModuleName } from "./module.js"
|
|
3
3
|
|
|
4
|
-
export async function renderPage(store,
|
|
5
|
-
const
|
|
4
|
+
export async function renderPage(store, page, module, options = {}) {
|
|
5
|
+
const { title = "", meta = {}, scripts = [], styles = [] } = options
|
|
6
|
+
|
|
7
|
+
const name = getModuleName(module)
|
|
6
8
|
const api = store._api
|
|
7
9
|
const entity = api.getEntity(name)
|
|
8
10
|
|
|
9
|
-
if (
|
|
10
|
-
await
|
|
11
|
+
if (module.load) {
|
|
12
|
+
await module.load(entity, page, store._api)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const pageTitle = module.title
|
|
16
|
+
? typeof module.title === "function"
|
|
17
|
+
? module.title(entity, api)
|
|
18
|
+
: module.title
|
|
19
|
+
: title
|
|
20
|
+
|
|
21
|
+
const pageMeta = {
|
|
22
|
+
...meta,
|
|
23
|
+
...(typeof module.meta === "function"
|
|
24
|
+
? module.meta(entity, api)
|
|
25
|
+
: (module.meta ?? {})),
|
|
11
26
|
}
|
|
12
27
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
const pageScripts = [
|
|
29
|
+
...scripts,
|
|
30
|
+
...(typeof module.scripts === "function"
|
|
31
|
+
? module.scripts(entity, api)
|
|
32
|
+
: (module.scripts ?? [])),
|
|
33
|
+
]
|
|
34
|
+
const pageStyles = [
|
|
35
|
+
...styles,
|
|
36
|
+
...(typeof module.styles === "function"
|
|
37
|
+
? module.styles(entity, api)
|
|
38
|
+
: (module.styles ?? [])),
|
|
39
|
+
]
|
|
23
40
|
|
|
24
41
|
return toHTML(store, (api) => api.render(name, { allowType: true }), {
|
|
25
42
|
...options,
|
|
26
|
-
title,
|
|
27
|
-
meta,
|
|
28
|
-
scripts,
|
|
29
|
-
styles,
|
|
43
|
+
title: pageTitle,
|
|
44
|
+
meta: pageMeta,
|
|
45
|
+
scripts: pageScripts,
|
|
46
|
+
styles: pageStyles,
|
|
30
47
|
})
|
|
31
48
|
}
|
package/src/render.test.js
CHANGED
|
@@ -11,6 +11,7 @@ const PAGES_DIR = path.join(ROOT_DIR, "pages")
|
|
|
11
11
|
const DEFAULT_OPTIONS = { stripLitMarkers: true }
|
|
12
12
|
|
|
13
13
|
it("should render a static page fragment", async () => {
|
|
14
|
+
const page = { path: "/" }
|
|
14
15
|
const module = await import(path.resolve(path.join(PAGES_DIR, "about.js")))
|
|
15
16
|
|
|
16
17
|
const store = createStore({
|
|
@@ -18,51 +19,54 @@ it("should render a static page fragment", async () => {
|
|
|
18
19
|
updateMode: "manual",
|
|
19
20
|
})
|
|
20
21
|
|
|
21
|
-
const html = await renderPage(store, module, DEFAULT_OPTIONS)
|
|
22
|
+
const html = await renderPage(store, page, module, DEFAULT_OPTIONS)
|
|
22
23
|
|
|
23
24
|
expect(html).toMatchSnapshot()
|
|
24
25
|
})
|
|
25
26
|
|
|
26
|
-
it("should render a
|
|
27
|
+
it("should render a page with entity", async () => {
|
|
28
|
+
const page = { path: "/about" }
|
|
27
29
|
const module = await import(path.resolve(path.join(PAGES_DIR, "about.js")))
|
|
28
30
|
|
|
29
31
|
const store = createStore({
|
|
30
32
|
types: { about: module.about },
|
|
33
|
+
entities: { about: { type: "about", name: "Us" } },
|
|
31
34
|
updateMode: "manual",
|
|
32
35
|
})
|
|
33
36
|
|
|
34
|
-
const html = await renderPage(store, module,
|
|
35
|
-
...DEFAULT_OPTIONS,
|
|
36
|
-
wrap: true,
|
|
37
|
-
})
|
|
37
|
+
const html = await renderPage(store, page, module, DEFAULT_OPTIONS)
|
|
38
38
|
|
|
39
39
|
expect(html).toMatchSnapshot()
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
-
it("should render a page with
|
|
42
|
+
it("should render a page with metadata", async () => {
|
|
43
|
+
const page = { path: "/about" }
|
|
43
44
|
const module = await import(path.resolve(path.join(PAGES_DIR, "about.js")))
|
|
44
45
|
|
|
45
46
|
const store = createStore({
|
|
46
47
|
types: { about: module.about },
|
|
47
|
-
entities: { about: { type: "about", name: "Us" } },
|
|
48
48
|
updateMode: "manual",
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
-
const html = await renderPage(store, module,
|
|
51
|
+
const html = await renderPage(store, page, module, {
|
|
52
|
+
...DEFAULT_OPTIONS,
|
|
53
|
+
wrap: true,
|
|
54
|
+
})
|
|
52
55
|
|
|
53
56
|
expect(html).toMatchSnapshot()
|
|
54
57
|
})
|
|
55
58
|
|
|
56
59
|
it("should render a page with pre-fetched data", async () => {
|
|
57
|
-
const
|
|
60
|
+
const page = { path: "/blog" }
|
|
61
|
+
const module = await import(path.resolve(path.join(PAGES_DIR, "blog.js")))
|
|
58
62
|
|
|
59
63
|
const store = createStore({
|
|
60
|
-
types: {
|
|
61
|
-
entities: {
|
|
64
|
+
types: { blog: module.blog },
|
|
65
|
+
entities: { blog: { type: "blog", name: "Antony", posts: [] } },
|
|
62
66
|
updateMode: "manual",
|
|
63
67
|
})
|
|
64
68
|
|
|
65
|
-
const html = await renderPage(store, module, DEFAULT_OPTIONS)
|
|
69
|
+
const html = await renderPage(store, page, module, DEFAULT_OPTIONS)
|
|
66
70
|
|
|
67
71
|
expect(html).toMatchSnapshot()
|
|
68
72
|
})
|
package/src/router.js
CHANGED
|
@@ -35,6 +35,7 @@ export async function getPages(pagesDir = "pages") {
|
|
|
35
35
|
const params = extractParams(route, urlPath)
|
|
36
36
|
|
|
37
37
|
pages.push({
|
|
38
|
+
pattern: route.pattern,
|
|
38
39
|
path: urlPath,
|
|
39
40
|
modulePath: route.modulePath,
|
|
40
41
|
filePath: route.filePath,
|
|
@@ -51,7 +52,8 @@ export async function getPages(pagesDir = "pages") {
|
|
|
51
52
|
} else {
|
|
52
53
|
// Static route - add directly
|
|
53
54
|
pages.push({
|
|
54
|
-
|
|
55
|
+
pattern: route.pattern,
|
|
56
|
+
path: route.pattern || "/",
|
|
55
57
|
modulePath: route.modulePath,
|
|
56
58
|
filePath: route.filePath,
|
|
57
59
|
moduleName,
|
package/src/router.test.js
CHANGED
|
@@ -25,8 +25,8 @@ describe("router", () => {
|
|
|
25
25
|
|
|
26
26
|
const patterns = routes.map((r) => r.pattern)
|
|
27
27
|
|
|
28
|
-
expect(patterns).toContain("/posts/:
|
|
29
|
-
expect(patterns).toContain("/blog
|
|
28
|
+
expect(patterns).toContain("/posts/:slug")
|
|
29
|
+
expect(patterns).toContain("/blog")
|
|
30
30
|
expect(patterns).toContain("/about")
|
|
31
31
|
expect(patterns).toContain("/")
|
|
32
32
|
expect(patterns).toContain("/api/*")
|
|
@@ -39,7 +39,7 @@ describe("router", () => {
|
|
|
39
39
|
// Root usually comes after specific paths but before catch-all if it was a catch-all root,
|
|
40
40
|
// but here / is static.
|
|
41
41
|
// Let's just check that we found them.
|
|
42
|
-
expect(routes).toHaveLength(
|
|
42
|
+
expect(routes).toHaveLength(5)
|
|
43
43
|
})
|
|
44
44
|
})
|
|
45
45
|
|
|
@@ -59,9 +59,9 @@ describe("router", () => {
|
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
it("should resolve dynamic page with params", async () => {
|
|
62
|
-
const page = await resolvePage("/
|
|
62
|
+
const page = await resolvePage("/posts/hello-world", PAGES_DIR)
|
|
63
63
|
expect(page).not.toBeNull()
|
|
64
|
-
expect(page.filePath).toContain("
|
|
64
|
+
expect(page.filePath).toContain("posts")
|
|
65
65
|
expect(page.params).toEqual({ slug: "hello-world" })
|
|
66
66
|
})
|
|
67
67
|
|
package/src/scripts/app.js
CHANGED
|
@@ -6,9 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export function generateApp(store, pages) {
|
|
8
8
|
// Collect all unique page modules and their exports
|
|
9
|
-
const routes = pages
|
|
10
|
-
(page
|
|
11
|
-
|
|
9
|
+
const routes = pages
|
|
10
|
+
.filter((page, index) => {
|
|
11
|
+
return pages.findIndex((p) => p.pattern === page.pattern) === index
|
|
12
|
+
})
|
|
13
|
+
.map(
|
|
14
|
+
(page) =>
|
|
15
|
+
` "${page.pattern}": () => import("@/pages/${page.modulePath}")`,
|
|
16
|
+
)
|
|
12
17
|
|
|
13
18
|
return `import { createDevtools, createStore, mount } from "@inglorious/web"
|
|
14
19
|
import { getRoute, router, setRoutes } from "@inglorious/web/router"
|
|
@@ -37,7 +42,7 @@ setRoutes({
|
|
|
37
42
|
${routes.join(",\n")}
|
|
38
43
|
})
|
|
39
44
|
|
|
40
|
-
const module = await getRoute(page.
|
|
45
|
+
const module = await getRoute(page.pattern)()
|
|
41
46
|
const type = module[page.moduleName]
|
|
42
47
|
types[page.moduleName] = type
|
|
43
48
|
|
package/src/scripts/app.test.js
CHANGED
|
@@ -35,9 +35,9 @@ it("should generate the app script for a page with an entity", async () => {
|
|
|
35
35
|
|
|
36
36
|
it("should generate the app script for a page that has metadata", async () => {
|
|
37
37
|
const page = {
|
|
38
|
-
path: "/
|
|
39
|
-
modulePath: "
|
|
40
|
-
filePath: path.join(ROOT_DIR, "pages", "
|
|
38
|
+
path: "/blog",
|
|
39
|
+
modulePath: "blog.js",
|
|
40
|
+
filePath: path.join(ROOT_DIR, "pages", "blog.js"),
|
|
41
41
|
}
|
|
42
42
|
const store = await generateStore([page], { rootDir: ROOT_DIR })
|
|
43
43
|
|
package/src/store.js
CHANGED
|
@@ -15,9 +15,15 @@ export async function generateStore(pages = [], options = {}) {
|
|
|
15
15
|
types[name] = pageModule[name]
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
let entities = {}
|
|
19
|
+
try {
|
|
20
|
+
const module = await import(
|
|
21
|
+
pathToFileURL(path.join(rootDir, "entities.js"))
|
|
22
|
+
)
|
|
23
|
+
entities = module.entities
|
|
24
|
+
} catch {
|
|
25
|
+
entities = {}
|
|
26
|
+
}
|
|
21
27
|
|
|
22
28
|
return createStore({ types, entities, updateMode: "manual" })
|
|
23
29
|
}
|
package/src/store.test.js
CHANGED
|
@@ -30,11 +30,11 @@ it("should generate the proper types and entities from a page with an entity", a
|
|
|
30
30
|
|
|
31
31
|
it("should generate the proper types and entities from a page that has metadata", async () => {
|
|
32
32
|
const page = {
|
|
33
|
-
filePath: path.join(ROOT_DIR, "pages", "
|
|
33
|
+
filePath: path.join(ROOT_DIR, "pages", "blog.js"),
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const store = await generateStore([page], { rootDir: ROOT_DIR })
|
|
37
37
|
|
|
38
|
-
expect(store.getType("
|
|
38
|
+
expect(store.getType("blog").render).toBeDefined()
|
|
39
39
|
expect(store.getState()).toMatchSnapshot()
|
|
40
40
|
})
|