@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/ssx",
3
- "version": "0.4.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.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 renderedPages = await generatePages(store, pages, options)
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
- for (const { page, html } of renderedPages) {
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: renderedPages.length, outDir }
54
+ return { pages: htmls.length, outDir }
54
55
  }
55
56
 
56
- async function generatePages(store, pages, options = {}) {
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({ page, module, html })
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, pageModule, options) {
5
- const name = getModuleName(pageModule)
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 (pageModule.load) {
10
- await pageModule.load(entity, store._api)
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 title =
14
- typeof pageModule.title === "function"
15
- ? pageModule.title(entity, api)
16
- : pageModule.title
17
- const meta =
18
- typeof options.meta === "function"
19
- ? options.meta(entity, api)
20
- : options.meta
21
- const scripts = pageModule.scripts
22
- const styles = pageModule.styles
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
  }
@@ -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 whole static page", async () => {
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 entity", async () => {
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, DEFAULT_OPTIONS)
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 module = await import(path.resolve(path.join(PAGES_DIR, "posts.js")))
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: { posts: module.posts },
61
- entities: { posts: { type: "posts", name: "Antony", posts: [] } },
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
- path: route.pattern === "" ? "/" : route.pattern,
55
+ pattern: route.pattern,
56
+ path: route.pattern || "/",
55
57
  modulePath: route.modulePath,
56
58
  filePath: route.filePath,
57
59
  moduleName,
@@ -25,8 +25,8 @@ describe("router", () => {
25
25
 
26
26
  const patterns = routes.map((r) => r.pattern)
27
27
 
28
- expect(patterns).toContain("/posts/:id")
29
- expect(patterns).toContain("/blog/:slug")
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(6)
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("/blog/hello-world", PAGES_DIR)
62
+ const page = await resolvePage("/posts/hello-world", PAGES_DIR)
63
63
  expect(page).not.toBeNull()
64
- expect(page.filePath).toContain("blog")
64
+ expect(page.filePath).toContain("posts")
65
65
  expect(page.params).toEqual({ slug: "hello-world" })
66
66
  })
67
67
 
@@ -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.map(
10
- (page) => ` "${page.path}": () => import("@/pages/${page.modulePath}")`,
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.path)()
45
+ const module = await getRoute(page.pattern)()
41
46
  const type = module[page.moduleName]
42
47
  types[page.moduleName] = type
43
48
 
@@ -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: "/posts",
39
- modulePath: "posts.js",
40
- filePath: path.join(ROOT_DIR, "pages", "posts.js"),
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
- const { entities } = await import(
19
- pathToFileURL(path.join(rootDir, "entities.js"))
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", "posts.js"),
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("posts").render).toBeDefined()
38
+ expect(store.getType("blog").render).toBeDefined()
39
39
  expect(store.getState()).toMatchSnapshot()
40
40
  })