@inglorious/ssx 1.11.1 → 2.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 CHANGED
@@ -71,7 +71,7 @@ import { html } from "@inglorious/web"
71
71
  // You can import API for type safety, though it's optional
72
72
  // import type { API } from "@inglorious/web"
73
73
 
74
- export const index = {
74
+ export const Index = {
75
75
  render(/* entity: any, api: API */) {
76
76
  return html`
77
77
  <div>
@@ -92,7 +92,7 @@ export const index = {
92
92
  // src/pages/index.js
93
93
  import { html } from "@inglorious/web"
94
94
 
95
- export const index = {
95
+ export const Index = {
96
96
  render() {
97
97
  return html`
98
98
  <div>
@@ -242,7 +242,7 @@ const messages = {
242
242
  pt: "Olá mundo!",
243
243
  }
244
244
 
245
- export const hello = {
245
+ export const Hello = {
246
246
  render(entity) {
247
247
  return html`<h1>${messages[entity.locale] ?? messages.en}</h1>`
248
248
  },
@@ -270,7 +270,7 @@ Notes:
270
270
  // src/pages/about.js
271
271
  import { html } from "@inglorious/web"
272
272
 
273
- export const about = {
273
+ export const About = {
274
274
  click(entity) {
275
275
  entity.name += "!"
276
276
  },
@@ -290,7 +290,7 @@ export const about = {
290
290
  // src/store/entities.js
291
291
  export const entities = {
292
292
  about: {
293
- type: "about",
293
+ type: "About",
294
294
  name: "Us",
295
295
  },
296
296
  }
@@ -304,7 +304,7 @@ Load data at build time with the `load` export:
304
304
  // src/pages/blog.js
305
305
  import { html } from "@inglorious/web"
306
306
 
307
- export const blog = {
307
+ export const Blog = {
308
308
  render(entity) {
309
309
  return html`
310
310
  <h1>Blog Posts</h1>
@@ -342,7 +342,7 @@ Generate multiple pages from data:
342
342
  // src/pages/posts/_slug.js
343
343
  import { html } from "@inglorious/web"
344
344
 
345
- export const post = {
345
+ export const Post = {
346
346
  render(entity) {
347
347
  return html`
348
348
  <article>
@@ -385,7 +385,7 @@ export const metadata = (entity) => ({
385
385
  Export metadata for HTML `<head>`. The `metadata` export can be a plain object or a function:
386
386
 
387
387
  ```javascript
388
- export const index = {
388
+ export const Index = {
389
389
  render() {
390
390
  return html`<h1>Home</h1>`
391
391
  },
@@ -415,7 +415,7 @@ export const metadata = (entity) => ({
415
415
  Pages hydrate automatically with lit-html. Interactivity works immediately:
416
416
 
417
417
  ```javascript
418
- export const counter = {
418
+ export const Counter = {
419
419
  click(entity) {
420
420
  entity.count++
421
421
  },
@@ -558,7 +558,7 @@ Fetch from your API in `routeChange` for client-side navigation, and use direct
558
558
  // src/pages/posts/_slug.js
559
559
  import { html } from "@inglorious/web"
560
560
 
561
- export const post = {
561
+ export const Post = {
562
562
  async routeChange(entity, { route, params }, api) {
563
563
  if (route !== entity.type) return
564
564
 
@@ -778,7 +778,7 @@ Create a fallback route:
778
778
 
779
779
  ```javascript
780
780
  // src/pages/404.js
781
- export const notFound = {
781
+ export const NotFound = {
782
782
  render() {
783
783
  return html`
784
784
  <div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/ssx",
3
- "version": "1.11.1",
3
+ "version": "2.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",
@@ -48,7 +48,6 @@
48
48
  },
49
49
  "sideEffects": false,
50
50
  "dependencies": {
51
- "@inglorious/logo": "^2.1.1",
52
51
  "@lit-labs/ssr": "^4.0.0",
53
52
  "commander": "^14.0.2",
54
53
  "connect": "^3.7.0",
@@ -65,12 +64,14 @@
65
64
  "svgo": "^4.0.0",
66
65
  "vite": "^7.1.3",
67
66
  "vite-plugin-image-optimizer": "^2.0.3",
68
- "@inglorious/web": "4.5.0"
67
+ "@inglorious/utils": "3.8.0",
68
+ "@inglorious/web": "5.0.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "prettier": "^3.6.2",
72
72
  "vitest": "^1.6.1",
73
- "@inglorious/eslint-config": "1.1.2"
73
+ "@inglorious/eslint-config": "1.1.2",
74
+ "@inglorious/logo": "3.0.0"
74
75
  },
75
76
  "engines": {
76
77
  "node": ">= 22"
@@ -26,8 +26,6 @@ export async function generatePages(store, pages, options = {}, loader) {
26
26
  } = options
27
27
  const load = loader || ((p) => import(pathToFileURL(path.resolve(p))))
28
28
 
29
- const api = store._api
30
-
31
29
  for (const page of pages) {
32
30
  console.log(
33
31
  ` Generating ${shouldGenerateHtml ? "HTML" : ""}${shouldGenerateHtml && shouldGenerateMetadata ? " and " : ""}${shouldGenerateMetadata ? "metadata" : ""} for ${page.path}...`,
@@ -36,7 +34,7 @@ export async function generatePages(store, pages, options = {}, loader) {
36
34
  const module = await load(page.filePath)
37
35
  page.module = module
38
36
 
39
- const entity = api.getEntity(page.moduleName)
37
+ const [entity] = store._api.getEntities(page.moduleName)
40
38
  if (page.locale) {
41
39
  entity.locale = page.locale
42
40
  }
@@ -37,7 +37,9 @@ describe("generatePages", () => {
37
37
  })
38
38
 
39
39
  it("should generate HTML and metadata by default", async () => {
40
- const store = { _api: { getEntity: vi.fn(() => ({})) } }
40
+ const store = {
41
+ _api: { getEntities: vi.fn(() => []), getEntity: vi.fn(() => ({})) },
42
+ }
41
43
  const pages = [{ path: "/p1", filePath: pageFile, moduleName: "p1" }]
42
44
 
43
45
  renderPage.mockResolvedValue("<html></html>")
@@ -53,7 +55,9 @@ describe("generatePages", () => {
53
55
  })
54
56
 
55
57
  it("should skip HTML generation when disabled", async () => {
56
- const store = { _api: { getEntity: vi.fn(() => ({})) } }
58
+ const store = {
59
+ _api: { getEntities: vi.fn(() => []), getEntity: vi.fn(() => ({})) },
60
+ }
57
61
  const pages = [{ path: "/p2", filePath: pageFile, moduleName: "p2" }]
58
62
 
59
63
  vi.clearAllMocks()
@@ -67,7 +71,9 @@ describe("generatePages", () => {
67
71
  })
68
72
 
69
73
  it("should skip metadata generation when disabled", async () => {
70
- const store = { _api: { getEntity: vi.fn(() => ({})) } }
74
+ const store = {
75
+ _api: { getEntities: vi.fn(() => []), getEntity: vi.fn(() => ({})) },
76
+ }
71
77
  const pages = [{ path: "/p3", filePath: pageFile, moduleName: "p3" }]
72
78
 
73
79
  vi.clearAllMocks()
@@ -83,7 +89,12 @@ describe("generatePages", () => {
83
89
 
84
90
  it("should set entity.locale from page.locale even without load()", async () => {
85
91
  const entity = {}
86
- const store = { _api: { getEntity: vi.fn(() => entity) } }
92
+ const store = {
93
+ _api: {
94
+ getEntities: vi.fn(() => [entity]),
95
+ getEntity: vi.fn(() => entity),
96
+ },
97
+ }
87
98
  const pages = [
88
99
  {
89
100
  path: "/it/p4",
@@ -105,7 +116,12 @@ describe("generatePages", () => {
105
116
 
106
117
  it("should overwrite entity.locale for each localized page render", async () => {
107
118
  const entity = { locale: "en" }
108
- const store = { _api: { getEntity: vi.fn(() => entity) } }
119
+ const store = {
120
+ _api: {
121
+ getEntities: vi.fn(() => [entity]),
122
+ getEntity: vi.fn(() => entity),
123
+ },
124
+ }
109
125
  const pages = [
110
126
  {
111
127
  path: "/it/p5",
package/src/dev/index.js CHANGED
@@ -70,7 +70,7 @@ export async function dev(options = {}) {
70
70
  // Generate store for THIS request (to pick up changes)
71
71
  const store = await generateStore(pages, mergedOptions, loader)
72
72
 
73
- const entity = store._api.getEntity(page.moduleName)
73
+ const [entity] = store._api.getEntities(page.moduleName)
74
74
  if (page.locale) {
75
75
  entity.locale = page.locale
76
76
  }
@@ -58,7 +58,7 @@ describe("await toHTML", () => {
58
58
  },
59
59
  },
60
60
  entities: {
61
- greeting: { type: "message", text: "Hello from store" },
61
+ greeting: { type: "Message", text: "Hello from store" },
62
62
  },
63
63
  })
64
64
 
@@ -77,9 +77,9 @@ describe("await toHTML", () => {
77
77
  },
78
78
  },
79
79
  entities: {
80
- item1: { type: "item", name: "First" },
81
- item2: { type: "item", name: "Second" },
82
- item3: { type: "item", name: "Third" },
80
+ item1: { type: "Item", name: "First" },
81
+ item2: { type: "Item", name: "Second" },
82
+ item3: { type: "Item", name: "Third" },
83
83
  },
84
84
  })
85
85
 
@@ -104,7 +104,7 @@ describe("await toHTML", () => {
104
104
  },
105
105
  },
106
106
  entities: {
107
- content: { type: "content", isVisible: true },
107
+ content: { type: "Content", isVisible: true },
108
108
  },
109
109
  })
110
110
 
@@ -227,8 +227,8 @@ describe("await toHTML", () => {
227
227
  item: { render: (entity) => html`<span>${entity.label}</span>` },
228
228
  },
229
229
  entities: {
230
- myWrapper: { type: "wrapper" },
231
- myItem: { type: "item", label: "Test Item" },
230
+ myWrapper: { type: "Wrapper" },
231
+ myItem: { type: "Item", label: "Test Item" },
232
232
  },
233
233
  })
234
234
 
@@ -250,8 +250,8 @@ describe("await toHTML", () => {
250
250
  },
251
251
  },
252
252
  entities: {
253
- msg1: { type: "message", text: "First message" },
254
- msg2: { type: "message", text: "Second message" },
253
+ msg1: { type: "Message", text: "First message" },
254
+ msg2: { type: "Message", text: "Second message" },
255
255
  },
256
256
  })
257
257
 
@@ -272,7 +272,7 @@ describe("await toHTML", () => {
272
272
  types: {
273
273
  header: { render: () => html`<header><h1>My Website</h1></header>` },
274
274
  },
275
- entities: { header: { type: "header" } },
275
+ entities: { header: { type: "Header" } },
276
276
  })
277
277
 
278
278
  const renderFn = (api) =>
@@ -309,7 +309,7 @@ describe("await toHTML", () => {
309
309
  },
310
310
  },
311
311
  entities: {
312
- myButton: { type: "button", id: "myButton" },
312
+ myButton: { type: "Button", id: "myButton" },
313
313
  },
314
314
  })
315
315
 
@@ -337,7 +337,7 @@ describe("await toHTML", () => {
337
337
  },
338
338
  },
339
339
  entities: {
340
- counter1: { type: "counter", id: "counter1", count: 5 },
340
+ counter1: { type: "Counter", id: "counter1", count: 5 },
341
341
  },
342
342
  })
343
343
 
@@ -1,3 +1,5 @@
1
+ import { toCamelCase } from "@inglorious/utils/data-structures/string.js"
2
+
1
3
  import { createGetPageOption } from "../utils/page-options.js"
2
4
  import { toHTML } from "./html.js"
3
5
 
@@ -24,6 +26,7 @@ const DEFAULT_OPTIONS = {
24
26
  */
25
27
  export async function renderPage(store, page, entity, options = {}) {
26
28
  const { moduleName, module } = page
29
+ const entityId = entity?.id ?? toCamelCase(moduleName)
27
30
 
28
31
  const getPageOption = createGetPageOption(store, module, entity)
29
32
 
@@ -42,7 +45,7 @@ export async function renderPage(store, page, entity, options = {}) {
42
45
  ...getPageOption("scripts", DEFAULT_OPTIONS),
43
46
  ]
44
47
 
45
- return toHTML(store, (api) => api.render(moduleName), {
48
+ return toHTML(store, (api) => api.render(entityId), {
46
49
  ...options,
47
50
  lang,
48
51
  charset,
@@ -52,6 +55,6 @@ export async function renderPage(store, page, entity, options = {}) {
52
55
  styles,
53
56
  head,
54
57
  scripts,
55
- ssxPage: { [moduleName]: entity },
58
+ ssxPage: { [entityId]: entity },
56
59
  })
57
60
  }
@@ -17,7 +17,7 @@ describe("renderPage", () => {
17
17
  const page = { path: "/", moduleName: "index", module }
18
18
 
19
19
  const store = createStore({
20
- types: { index: module.index },
20
+ types: { Index: module.Index },
21
21
  updateMode: "manual",
22
22
  })
23
23
  store.update()
@@ -30,10 +30,10 @@ describe("renderPage", () => {
30
30
  it("should render a page with entity", async () => {
31
31
  const module = await import(pathToFileURL(path.join(PAGES_DIR, "about.js")))
32
32
  const page = { path: "/about", moduleName: "about", module }
33
- const entity = { type: "about", name: "Us" }
33
+ const entity = { type: "About", name: "Us" }
34
34
 
35
35
  const store = createStore({
36
- types: { about: module.about },
36
+ types: { About: module.About },
37
37
  entities: { about: entity },
38
38
  updateMode: "manual",
39
39
  })
@@ -46,10 +46,10 @@ describe("renderPage", () => {
46
46
  it("should render a page with metadata", async () => {
47
47
  const module = await import(pathToFileURL(path.join(PAGES_DIR, "about.js")))
48
48
  const page = { path: "/about", moduleName: "about", module }
49
- const entity = { type: "about", name: "Us" }
49
+ const entity = { type: "About", name: "Us" }
50
50
 
51
51
  const store = createStore({
52
- types: { about: module.about },
52
+ types: { About: module.About },
53
53
  entities: { about: entity },
54
54
  updateMode: "manual",
55
55
  })
@@ -66,7 +66,7 @@ describe("renderPage", () => {
66
66
  const module = await import(pathToFileURL(path.join(PAGES_DIR, "blog.js")))
67
67
  const page = { path: "/blog", moduleName: "blog", module }
68
68
  const entity = {
69
- type: "blog",
69
+ type: "Blog",
70
70
  name: "Antony",
71
71
  posts: [
72
72
  { id: 1, title: "First Post" },
@@ -76,7 +76,7 @@ describe("renderPage", () => {
76
76
  }
77
77
 
78
78
  const store = createStore({
79
- types: { blog: module.blog },
79
+ types: { Blog: module.Blog },
80
80
  entities: { blog: entity },
81
81
  updateMode: "manual",
82
82
  })
@@ -90,9 +90,9 @@ describe("renderPage", () => {
90
90
  const module = await import(
91
91
  pathToFileURL(path.join(PAGES_DIR, "posts", "_slug.js"))
92
92
  )
93
- const page = { path: "/posts/1", moduleName: "post", module }
93
+ const page = { path: "/posts/1", moduleName: "Post", module }
94
94
  const entity = {
95
- type: "blog",
95
+ type: "Post",
96
96
  name: "Antony",
97
97
  post: {
98
98
  id: 1,
@@ -103,12 +103,12 @@ describe("renderPage", () => {
103
103
  }
104
104
 
105
105
  const store = createStore({
106
- types: { blog: module.post },
106
+ types: { Post: module.Post },
107
107
  entities: { post: entity },
108
108
  updateMode: "manual",
109
109
  })
110
110
 
111
- const html = await renderPage(store, page, module, DEFAULT_OPTIONS)
111
+ const html = await renderPage(store, page, entity, DEFAULT_OPTIONS)
112
112
 
113
113
  expect(html).toMatchSnapshot()
114
114
  })
@@ -49,7 +49,7 @@ export async function generateApp(pages, options = {}, loader) {
49
49
 
50
50
  return `import "@inglorious/web/hydrate"
51
51
  import { createDevtools, createStore, mount } from "@inglorious/web"
52
- import { getRoute, router, setRoutes } from "@inglorious/web/router"
52
+ import { Router, getRoute, setRoutes } from "@inglorious/web/router"
53
53
  import { getLocaleFromPath } from "@inglorious/ssx/i18n"
54
54
 
55
55
  ${typesImport}
@@ -77,15 +77,20 @@ const pages = ${JSON.stringify(
77
77
  const path = normalizeRoutePath(window.location.pathname)
78
78
  const page = pages.find((page) => normalizeRoutePath(page.path) === path)
79
79
 
80
- const types = { router }
80
+ const types = { Router }
81
81
  ${typesAssignment}
82
82
 
83
83
  const i18n = ${JSON.stringify(i18n, null, 2)}
84
84
  const isDev = ${JSON.stringify(isDev)}
85
85
 
86
+ const toCamelCase = (str) => {
87
+ const [firstChar, ...rest] = str
88
+ return [firstChar.toLowerCase(), ...rest].join("")
89
+ }
90
+
86
91
  const entities = {
87
- router: { type: "router", path, route: page?.moduleName },
88
- i18n: { type: "i18n", ...i18n },
92
+ router: { type: "Router", path, route: toCamelCase(page?.moduleName) },
93
+ i18n: { type: "I18n", ...i18n },
89
94
  }
90
95
  ${entitiesAssignment}
91
96
  Object.assign(entities, JSON.parse(document.getElementById("__SSX_PAGE__").textContent))
@@ -18,7 +18,7 @@ describe("generateStore", () => {
18
18
 
19
19
  const store = await generateStore([page], { rootDir: ROOT_DIR })
20
20
 
21
- expect(store.getType("index").render).toBeDefined()
21
+ expect(store.getType("Index").render).toBeDefined()
22
22
  expect(store.getState()).toMatchSnapshot()
23
23
  })
24
24
 
@@ -29,7 +29,7 @@ describe("generateStore", () => {
29
29
 
30
30
  const store = await generateStore([page], { rootDir: ROOT_DIR })
31
31
 
32
- expect(store.getType("about").render).toBeDefined()
32
+ expect(store.getType("About").render).toBeDefined()
33
33
  expect(store.getState()).toMatchSnapshot()
34
34
  })
35
35
 
@@ -40,7 +40,7 @@ describe("generateStore", () => {
40
40
 
41
41
  const store = await generateStore([page], { rootDir: ROOT_DIR })
42
42
 
43
- expect(store.getType("blog").render).toBeDefined()
43
+ expect(store.getType("Blog").render).toBeDefined()
44
44
  expect(store.getState()).toMatchSnapshot()
45
45
  })
46
46