@inglorious/ssx 1.9.0 ā 1.10.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 +2 -1
- package/src/build/build.test.js +6 -1
- package/src/build/index.js +5 -1
- package/src/build/pages.js +7 -1
- package/src/dev/index.js +5 -1
- package/src/render/html.js +2 -2
- package/src/render/index.js +1 -1
- package/src/scripts/app.js +35 -13
- package/src/scripts/app.test.js +1 -1
- package/src/store/index.js +6 -23
- package/src/store/stuff.js +87 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/ssx",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.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,6 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"sideEffects": false,
|
|
50
50
|
"dependencies": {
|
|
51
|
+
"@inglorious/logo": "^2.1.0",
|
|
51
52
|
"@lit-labs/ssr": "^4.0.0",
|
|
52
53
|
"commander": "^14.0.2",
|
|
53
54
|
"connect": "^3.7.0",
|
package/src/build/build.test.js
CHANGED
|
@@ -56,7 +56,12 @@ describe("build", () => {
|
|
|
56
56
|
loadManifest.mockResolvedValue(null) // First build
|
|
57
57
|
getPages.mockResolvedValue([{ path: "/" }])
|
|
58
58
|
hashEntities.mockResolvedValue("hash")
|
|
59
|
-
generateStore.mockResolvedValue({
|
|
59
|
+
generateStore.mockResolvedValue({
|
|
60
|
+
store: {},
|
|
61
|
+
entities: {},
|
|
62
|
+
hasTypesFile: false,
|
|
63
|
+
hasEntitiesFile: false,
|
|
64
|
+
})
|
|
60
65
|
generatePages
|
|
61
66
|
.mockResolvedValueOnce([{ path: "/", html: "<html></html>" }])
|
|
62
67
|
.mockResolvedValueOnce([])
|
package/src/build/index.js
CHANGED
|
@@ -140,7 +140,11 @@ export async function build(options = {}) {
|
|
|
140
140
|
// 8. Always regenerate client-side JavaScript (it's cheap and ensures consistency)
|
|
141
141
|
console.log("\nš Generating client scripts...\n")
|
|
142
142
|
|
|
143
|
-
const app = generateApp(
|
|
143
|
+
const app = await generateApp(
|
|
144
|
+
allPages,
|
|
145
|
+
{ ...mergedOptions, isDev: false },
|
|
146
|
+
loader,
|
|
147
|
+
)
|
|
144
148
|
await fs.writeFile(path.join(outDir, "main.js"), app, "utf-8")
|
|
145
149
|
console.log(` ā main.js\n`)
|
|
146
150
|
|
package/src/build/pages.js
CHANGED
|
@@ -14,11 +14,16 @@ import { extractPageMetadata } from "./metadata.js"
|
|
|
14
14
|
* @param {Object} [options] - Generation options.
|
|
15
15
|
* @param {boolean} [options.shouldGenerateHtml=true] - Whether to generate HTML.
|
|
16
16
|
* @param {boolean} [options.shouldGenerateMetadata=true] - Whether to generate metadata.
|
|
17
|
+
* @param {Object} [options.originalEntities] - Original entities from entities.js.
|
|
17
18
|
* @param {Function} [loader] - Optional loader function.
|
|
18
19
|
* @returns {Promise<Array<Object>>} The processed pages with `html` and `metadata` properties added.
|
|
19
20
|
*/
|
|
20
21
|
export async function generatePages(store, pages, options = {}, loader) {
|
|
21
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
shouldGenerateHtml = true,
|
|
24
|
+
shouldGenerateMetadata = true,
|
|
25
|
+
originalEntities = {},
|
|
26
|
+
} = options
|
|
22
27
|
const load = loader || ((p) => import(pathToFileURL(path.resolve(p))))
|
|
23
28
|
|
|
24
29
|
const api = store._api
|
|
@@ -43,6 +48,7 @@ export async function generatePages(store, pages, options = {}, loader) {
|
|
|
43
48
|
if (shouldGenerateHtml) {
|
|
44
49
|
const html = await renderPage(store, page, entity, {
|
|
45
50
|
...options,
|
|
51
|
+
originalEntities,
|
|
46
52
|
wrap: true,
|
|
47
53
|
})
|
|
48
54
|
page.html = html
|
package/src/dev/index.js
CHANGED
|
@@ -80,7 +80,11 @@ export async function dev(options = {}) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// Generate and update the virtual app file BEFORE rendering
|
|
83
|
-
const app = generateApp(
|
|
83
|
+
const app = await generateApp(
|
|
84
|
+
pages,
|
|
85
|
+
{ ...mergedOptions, isDev: true },
|
|
86
|
+
loader,
|
|
87
|
+
)
|
|
84
88
|
virtualFiles.set("/main.js", app)
|
|
85
89
|
|
|
86
90
|
// Invalidate the virtual module to ensure Vite picks up changes
|
package/src/render/html.js
CHANGED
|
@@ -41,10 +41,10 @@ export async function toHTML(store, renderFn, options = {}) {
|
|
|
41
41
|
const layout = options.layout ?? defaultLayout
|
|
42
42
|
let html = layout(finalHTML, options)
|
|
43
43
|
|
|
44
|
-
if (options.
|
|
44
|
+
if (options.ssxPage) {
|
|
45
45
|
html = html.replace(
|
|
46
46
|
/<body[^>]*>/,
|
|
47
|
-
`$&<script type="application/json" id="
|
|
47
|
+
`$&<script type="application/json" id="__SSX_PAGE__">${JSON.stringify(options.ssxPage)}</script>`,
|
|
48
48
|
)
|
|
49
49
|
}
|
|
50
50
|
|
package/src/render/index.js
CHANGED
package/src/scripts/app.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
import { getStoreStuff } from "../store/stuff.js"
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Generates the client-side entry point script.
|
|
3
5
|
* This script hydrates the store with the initial state (entities) and sets up the router.
|
|
4
6
|
*
|
|
5
7
|
* @param {Array<Object>} pages - List of page objects to generate routes for.
|
|
6
8
|
* @param {Object} [options] - Runtime options.
|
|
9
|
+
* @param {boolean} [options.hasTypesFile] - Whether src/store/types.js exists.
|
|
10
|
+
* @param {boolean} [options.hasEntitiesFile] - Whether src/store/entities.js exists.
|
|
7
11
|
* @returns {string} The generated JavaScript code for the client entry point.
|
|
8
12
|
*/
|
|
9
|
-
export function generateApp(pages, options = {}) {
|
|
13
|
+
export async function generateApp(pages, options = {}, loader) {
|
|
10
14
|
const i18n = options.i18n || inferI18nFromPages(pages)
|
|
11
15
|
const isDev = Boolean(options.isDev)
|
|
12
16
|
|
|
17
|
+
const types = await getStoreStuff("types", options, loader)
|
|
18
|
+
const hasTypesFile = Object.keys(types).length
|
|
19
|
+
const entities = await getStoreStuff("entities", options, loader)
|
|
20
|
+
const hasEntitiesFile = Object.keys(entities).length
|
|
21
|
+
|
|
13
22
|
// Build client route map, including localized patterns (e.g. /it/about).
|
|
14
23
|
const routesByPattern = new Map()
|
|
15
24
|
for (const page of pages) {
|
|
@@ -22,11 +31,30 @@ export function generateApp(pages, options = {}) {
|
|
|
22
31
|
}
|
|
23
32
|
const routes = [...routesByPattern.values()]
|
|
24
33
|
|
|
34
|
+
const typesImport = hasTypesFile
|
|
35
|
+
? `import { types as additionalTypes } from "@/store/types.js"`
|
|
36
|
+
: ""
|
|
37
|
+
|
|
38
|
+
const entitiesImport = hasEntitiesFile
|
|
39
|
+
? `import { entities as additionalEntities } from "@/store/entities.js"`
|
|
40
|
+
: ""
|
|
41
|
+
|
|
42
|
+
const typesAssignment = hasTypesFile
|
|
43
|
+
? `Object.assign(types, additionalTypes)`
|
|
44
|
+
: ""
|
|
45
|
+
|
|
46
|
+
const entitiesAssignment = hasEntitiesFile
|
|
47
|
+
? `Object.assign(entities, additionalEntities)`
|
|
48
|
+
: ""
|
|
49
|
+
|
|
25
50
|
return `import "@inglorious/web/hydrate"
|
|
26
51
|
import { createDevtools, createStore, mount } from "@inglorious/web"
|
|
27
52
|
import { getRoute, router, setRoutes } from "@inglorious/web/router"
|
|
28
53
|
import { getLocaleFromPath } from "@inglorious/ssx/i18n"
|
|
29
54
|
|
|
55
|
+
${typesImport}
|
|
56
|
+
${entitiesImport}
|
|
57
|
+
|
|
30
58
|
const normalizePathname = (path = "/") => path.split("?")[0].split("#")[0]
|
|
31
59
|
const normalizeRoutePath = (path = "/") => {
|
|
32
60
|
const pathname = normalizePathname(path)
|
|
@@ -50,24 +78,18 @@ const path = normalizeRoutePath(window.location.pathname)
|
|
|
50
78
|
const page = pages.find((page) => normalizeRoutePath(page.path) === path)
|
|
51
79
|
|
|
52
80
|
const types = { router }
|
|
81
|
+
${typesAssignment}
|
|
53
82
|
|
|
54
83
|
const i18n = ${JSON.stringify(i18n, null, 2)}
|
|
55
84
|
const isDev = ${JSON.stringify(isDev)}
|
|
56
85
|
|
|
57
|
-
const ssxEntity = JSON.parse(document.getElementById("__SSX_ENTITY__").textContent)
|
|
58
|
-
|
|
59
86
|
const entities = {
|
|
60
|
-
router: {
|
|
61
|
-
|
|
62
|
-
path,
|
|
63
|
-
route: page?.moduleName,
|
|
64
|
-
},
|
|
65
|
-
i18n: {
|
|
66
|
-
type: "i18n",
|
|
67
|
-
...i18n,
|
|
68
|
-
},
|
|
69
|
-
...ssxEntity,
|
|
87
|
+
router: { type: "router", path, route: page?.moduleName },
|
|
88
|
+
i18n: { type: "i18n", ...i18n },
|
|
70
89
|
}
|
|
90
|
+
${entitiesAssignment}
|
|
91
|
+
Object.assign(entities, JSON.parse(document.getElementById("__SSX_PAGE__").textContent))
|
|
92
|
+
|
|
71
93
|
|
|
72
94
|
const middlewares = []
|
|
73
95
|
if (isDev) {
|
package/src/scripts/app.test.js
CHANGED
|
@@ -75,7 +75,7 @@ describe("generateApp", () => {
|
|
|
75
75
|
{ ...page, path: "/pt/hello", locale: "pt" },
|
|
76
76
|
]
|
|
77
77
|
|
|
78
|
-
const app = generateApp(localizedPages)
|
|
78
|
+
const app = await generateApp(localizedPages)
|
|
79
79
|
|
|
80
80
|
expect(app).toContain(`"/hello": () => import("@/pages/hello.js")`)
|
|
81
81
|
expect(app).toContain(`"/it/hello": () => import("@/pages/hello.js")`)
|
package/src/store/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { existsSync } from "node:fs"
|
|
2
|
-
import path from "node:path"
|
|
3
1
|
import { pathToFileURL } from "node:url"
|
|
4
2
|
|
|
5
3
|
import { createStore } from "@inglorious/web"
|
|
6
4
|
|
|
7
5
|
import { getModuleName } from "../utils/module.js"
|
|
6
|
+
import { getStoreStuff } from "./stuff.js"
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Generates the application store based on the provided pages and configuration.
|
|
11
10
|
* It loads page modules to register their exported entities as store types.
|
|
12
|
-
* It also attempts to load initial entities from an `entities.js` file
|
|
11
|
+
* It also attempts to load initial entities from an `entities.js` file and
|
|
12
|
+
* additional types from a `types.js` file in the store directory.
|
|
13
13
|
*
|
|
14
14
|
* @param {Array<Object>} pages - List of page objects containing file paths.
|
|
15
15
|
* @param {Object} options - Configuration options.
|
|
@@ -18,34 +18,17 @@ import { getModuleName } from "../utils/module.js"
|
|
|
18
18
|
* @returns {Promise<Object>} The initialized store instance.
|
|
19
19
|
*/
|
|
20
20
|
export async function generateStore(pages = [], options = {}, loader) {
|
|
21
|
-
const { rootDir = "." } = options
|
|
22
|
-
const srcDir = path.join(rootDir, "src")
|
|
23
|
-
|
|
24
21
|
const load = loader || ((p) => import(pathToFileURL(p)))
|
|
25
22
|
|
|
26
|
-
const types =
|
|
23
|
+
const types = await getStoreStuff("types", options, loader)
|
|
24
|
+
|
|
27
25
|
for (const page of pages) {
|
|
28
26
|
const pageModule = await load(page.filePath)
|
|
29
27
|
const name = getModuleName(pageModule)
|
|
30
28
|
types[name] = pageModule[name]
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
const extensions = ["js", "ts"]
|
|
35
|
-
|
|
36
|
-
for (const ext of extensions) {
|
|
37
|
-
const fullPath = path.join(srcDir, "store", `entities.${ext}`)
|
|
38
|
-
|
|
39
|
-
if (existsSync(fullPath)) {
|
|
40
|
-
try {
|
|
41
|
-
const module = await load(fullPath)
|
|
42
|
-
entities = module.entities
|
|
43
|
-
break
|
|
44
|
-
} catch {
|
|
45
|
-
// ignore and try next extension
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
31
|
+
const entities = await getStoreStuff("entities", options, loader)
|
|
49
32
|
|
|
50
33
|
const store = createStore({ types, entities, autoCreateEntities: true })
|
|
51
34
|
return store
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { pathToFileURL } from "node:url"
|
|
4
|
+
|
|
5
|
+
export async function getStoreStuff(name, options, loader) {
|
|
6
|
+
const { rootDir = "." } = options
|
|
7
|
+
const srcDir = path.join(rootDir, "src")
|
|
8
|
+
|
|
9
|
+
const load = loader || ((p) => import(pathToFileURL(p)))
|
|
10
|
+
|
|
11
|
+
const stuff = {}
|
|
12
|
+
const extensions = ["js", "ts"]
|
|
13
|
+
|
|
14
|
+
for (const ext of extensions) {
|
|
15
|
+
const stuffPath = path.join(srcDir, "store", `${name}.${ext}`)
|
|
16
|
+
|
|
17
|
+
if (existsSync(stuffPath)) {
|
|
18
|
+
try {
|
|
19
|
+
const module = await load(stuffPath)
|
|
20
|
+
if (module[name]) {
|
|
21
|
+
Object.assign(stuff, module[name])
|
|
22
|
+
}
|
|
23
|
+
break
|
|
24
|
+
} catch {
|
|
25
|
+
// ignore and try next extension
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return stuff
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function getTypes(options, loader) {
|
|
34
|
+
const { rootDir = "." } = options
|
|
35
|
+
const srcDir = path.join(rootDir, "src")
|
|
36
|
+
|
|
37
|
+
const load = loader || ((p) => import(pathToFileURL(p)))
|
|
38
|
+
|
|
39
|
+
const types = {}
|
|
40
|
+
const extensions = ["js", "ts"]
|
|
41
|
+
|
|
42
|
+
for (const ext of extensions) {
|
|
43
|
+
const typesPath = path.join(srcDir, "store", `types.${ext}`)
|
|
44
|
+
|
|
45
|
+
if (existsSync(typesPath)) {
|
|
46
|
+
try {
|
|
47
|
+
const module = await load(typesPath)
|
|
48
|
+
if (module.types) {
|
|
49
|
+
Object.assign(types, module.types)
|
|
50
|
+
}
|
|
51
|
+
break
|
|
52
|
+
} catch {
|
|
53
|
+
// ignore and try next extension
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return types
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function getEntities(options, loader) {
|
|
62
|
+
const { rootDir = "." } = options
|
|
63
|
+
const srcDir = path.join(rootDir, "src")
|
|
64
|
+
|
|
65
|
+
const load = loader || ((p) => import(pathToFileURL(p)))
|
|
66
|
+
|
|
67
|
+
const entities = {}
|
|
68
|
+
const extensions = ["js", "ts"]
|
|
69
|
+
|
|
70
|
+
for (const ext of extensions) {
|
|
71
|
+
const entitiesPath = path.join(srcDir, "store", `entities.${ext}`)
|
|
72
|
+
|
|
73
|
+
if (existsSync(entitiesPath)) {
|
|
74
|
+
try {
|
|
75
|
+
const module = await load(entitiesPath)
|
|
76
|
+
if (module.entities) {
|
|
77
|
+
Object.assign(entities, module.entities)
|
|
78
|
+
}
|
|
79
|
+
break
|
|
80
|
+
} catch {
|
|
81
|
+
// ignore and try next extension
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return entities
|
|
87
|
+
}
|