@inglorious/ssx 1.5.0 → 1.5.1
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/bin/ssx.js +4 -4
- package/package.json +1 -1
- package/src/build/index.js +4 -2
- package/src/build/vite-config.js +7 -10
- package/src/dev/index.js +20 -12
- package/src/dev/vite-config.js +6 -8
- package/src/dev/vite-config.test.js +3 -1
- package/src/render/render.test.js +1 -1
- package/src/router/router.test.js +1 -1
- package/src/scripts/app.test.js +6 -5
- package/src/store/index.js +4 -2
- package/src/store/store.test.js +7 -6
- package/src/utils/config.js +3 -2
package/bin/ssx.js
CHANGED
|
@@ -27,8 +27,8 @@ program
|
|
|
27
27
|
program
|
|
28
28
|
.command("dev")
|
|
29
29
|
.description("Start development server with hot reload")
|
|
30
|
-
.option("-c, --config <file>", "config file
|
|
31
|
-
.option("-r, --root <dir>", "
|
|
30
|
+
.option("-c, --config <file>", "config file name", "site.config.js")
|
|
31
|
+
.option("-r, --root <dir>", "root directory", ".")
|
|
32
32
|
.option("-p, --port <port>", "dev server port", 3000)
|
|
33
33
|
.action(async (options) => {
|
|
34
34
|
const cwd = process.cwd()
|
|
@@ -54,8 +54,8 @@ program
|
|
|
54
54
|
program
|
|
55
55
|
.command("build")
|
|
56
56
|
.description("Build site from pages directory")
|
|
57
|
-
.option("-c, --config <file>", "config file
|
|
58
|
-
.option("-r, --root <dir>", "
|
|
57
|
+
.option("-c, --config <file>", "config file name", "site.config.js")
|
|
58
|
+
.option("-r, --root <dir>", "root directory", ".")
|
|
59
59
|
.option("-o, --out <dir>", "output directory", "dist")
|
|
60
60
|
.option("-i, --incremental", "enable incremental builds", true)
|
|
61
61
|
.option("-f, --force", "force clean build (ignore cache)", false)
|
package/package.json
CHANGED
package/src/build/index.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function build(options = {}) {
|
|
|
37
37
|
|
|
38
38
|
const mergedOptions = { ...config, ...options }
|
|
39
39
|
const {
|
|
40
|
-
rootDir = "
|
|
40
|
+
rootDir = ".",
|
|
41
41
|
outDir = "dist",
|
|
42
42
|
incremental = true,
|
|
43
43
|
clean = false,
|
|
@@ -45,6 +45,8 @@ export async function build(options = {}) {
|
|
|
45
45
|
rss,
|
|
46
46
|
} = mergedOptions
|
|
47
47
|
|
|
48
|
+
const pagesDir = path.join(rootDir, "src", "pages")
|
|
49
|
+
|
|
48
50
|
console.log("🔨 Starting build...\n")
|
|
49
51
|
|
|
50
52
|
// Create a temporary Vite server to load modules (supports TS)
|
|
@@ -56,7 +58,7 @@ export async function build(options = {}) {
|
|
|
56
58
|
const loader = (p) => vite.ssrLoadModule(p)
|
|
57
59
|
|
|
58
60
|
// 0. Get all pages to build (Fail fast if source is broken)
|
|
59
|
-
const allPages = await getPages(
|
|
61
|
+
const allPages = await getPages(pagesDir, loader)
|
|
60
62
|
console.log(`📄 Found ${allPages.length} pages\n`)
|
|
61
63
|
|
|
62
64
|
// Load previous build manifest
|
package/src/build/vite-config.js
CHANGED
|
@@ -11,18 +11,15 @@ import { markdownPlugin } from "../utils/markdown.js"
|
|
|
11
11
|
* Generate Vite config for building the client bundle
|
|
12
12
|
*/
|
|
13
13
|
export function createViteConfig(options = {}) {
|
|
14
|
-
const {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
vite = {},
|
|
19
|
-
markdown = {},
|
|
20
|
-
} = options
|
|
14
|
+
const { rootDir = ".", outDir = "dist", vite = {}, markdown = {} } = options
|
|
15
|
+
|
|
16
|
+
const srcDir = path.resolve(process.cwd(), rootDir, "src")
|
|
17
|
+
const publicDir = path.resolve(process.cwd(), rootDir, "public")
|
|
21
18
|
|
|
22
19
|
return mergeConfig(
|
|
23
20
|
{
|
|
24
|
-
root:
|
|
25
|
-
publicDir:
|
|
21
|
+
root: process.cwd(),
|
|
22
|
+
publicDir: publicDir,
|
|
26
23
|
plugins: [
|
|
27
24
|
// minifyTemplateLiterals(), // TODO: minification breaks hydration. The footprint difference is minimal after all
|
|
28
25
|
ViteImageOptimizer({
|
|
@@ -52,7 +49,7 @@ export function createViteConfig(options = {}) {
|
|
|
52
49
|
},
|
|
53
50
|
resolve: {
|
|
54
51
|
alias: {
|
|
55
|
-
"@":
|
|
52
|
+
"@": srcDir,
|
|
56
53
|
},
|
|
57
54
|
},
|
|
58
55
|
},
|
package/src/dev/index.js
CHANGED
|
@@ -21,7 +21,9 @@ export async function dev(options = {}) {
|
|
|
21
21
|
const config = await loadConfig(options)
|
|
22
22
|
|
|
23
23
|
const mergedOptions = { ...config, ...options }
|
|
24
|
-
const { rootDir = "
|
|
24
|
+
const { rootDir = "." } = mergedOptions
|
|
25
|
+
|
|
26
|
+
const pagesDir = path.join(rootDir, "src", "pages")
|
|
25
27
|
|
|
26
28
|
console.log("🚀 Starting dev server...\n")
|
|
27
29
|
|
|
@@ -30,16 +32,8 @@ export async function dev(options = {}) {
|
|
|
30
32
|
const viteServer = await createServer(viteConfig)
|
|
31
33
|
const loader = (p) => viteServer.ssrLoadModule(p)
|
|
32
34
|
|
|
33
|
-
// Get all pages once at startup
|
|
34
|
-
const pages = await getPages(path.join(rootDir, "pages"), loader)
|
|
35
|
-
console.log(`📄 Found ${pages.length} pages\n`)
|
|
36
|
-
|
|
37
|
-
// Generate store config once for all pages
|
|
38
|
-
const store = await generateStore(pages, mergedOptions, loader)
|
|
39
|
-
|
|
40
35
|
// Use Vite's middleware first (handles HMR, static files, etc.)
|
|
41
36
|
const connectServer = connect()
|
|
42
|
-
|
|
43
37
|
connectServer.use(viteServer.middlewares)
|
|
44
38
|
|
|
45
39
|
// Add SSR middleware
|
|
@@ -56,6 +50,9 @@ export async function dev(options = {}) {
|
|
|
56
50
|
return next() // Let Vite serve it
|
|
57
51
|
}
|
|
58
52
|
|
|
53
|
+
// Get all pages on each request (in dev mode, pages might be added/removed)
|
|
54
|
+
const pages = await getPages(pagesDir, loader)
|
|
55
|
+
|
|
59
56
|
// Find matching page
|
|
60
57
|
const page = pages.find((p) => matchRoute(p.path, url))
|
|
61
58
|
if (!page) return next()
|
|
@@ -63,19 +60,30 @@ export async function dev(options = {}) {
|
|
|
63
60
|
const module = await loader(page.filePath)
|
|
64
61
|
page.module = module
|
|
65
62
|
|
|
63
|
+
// Generate store for THIS request (to pick up changes)
|
|
64
|
+
const store = await generateStore(pages, mergedOptions, loader)
|
|
65
|
+
|
|
66
66
|
const entity = store._api.getEntity(page.moduleName)
|
|
67
67
|
if (module.load) {
|
|
68
68
|
await module.load(entity, page)
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
// Generate and update the virtual app file BEFORE rendering
|
|
72
|
+
const app = generateApp(store, pages)
|
|
73
|
+
virtualFiles.set("/main.js", app)
|
|
74
|
+
|
|
75
|
+
// Invalidate the virtual module to ensure Vite picks up changes
|
|
76
|
+
const virtualModule = viteServer.moduleGraph.getModuleById("/main.js")
|
|
77
|
+
if (virtualModule) {
|
|
78
|
+
viteServer.moduleGraph.invalidateModule(virtualModule)
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
const html = await renderPage(store, page, entity, {
|
|
71
82
|
...mergedOptions,
|
|
72
83
|
wrap: true,
|
|
73
84
|
isDev: true,
|
|
74
85
|
})
|
|
75
86
|
|
|
76
|
-
const app = generateApp(store, pages)
|
|
77
|
-
virtualFiles.set("/main.js", app)
|
|
78
|
-
|
|
79
87
|
res.setHeader("Content-Type", "text/html")
|
|
80
88
|
res.end(html)
|
|
81
89
|
} catch (error) {
|
package/src/dev/vite-config.js
CHANGED
|
@@ -15,24 +15,22 @@ import { markdownPlugin } from "../utils/markdown.js"
|
|
|
15
15
|
* @returns {Object} The merged Vite configuration.
|
|
16
16
|
*/
|
|
17
17
|
export function createViteConfig(options = {}) {
|
|
18
|
-
const {
|
|
19
|
-
rootDir = "src",
|
|
20
|
-
publicDir = "public",
|
|
21
|
-
vite = {},
|
|
22
|
-
markdown = {},
|
|
23
|
-
} = options
|
|
18
|
+
const { rootDir = ".", vite = {}, markdown = {} } = options
|
|
24
19
|
const { port = 3000 } = vite.dev ?? {}
|
|
25
20
|
|
|
21
|
+
const srcDir = path.resolve(process.cwd(), rootDir, "src")
|
|
22
|
+
const publicDir = path.resolve(process.cwd(), rootDir, "public")
|
|
23
|
+
|
|
26
24
|
return mergeConfig(
|
|
27
25
|
{
|
|
28
26
|
root: process.cwd(),
|
|
29
|
-
publicDir
|
|
27
|
+
publicDir,
|
|
30
28
|
server: { port, middlewareMode: true },
|
|
31
29
|
appType: "custom",
|
|
32
30
|
plugins: [virtualPlugin(), markdownPlugin(markdown)],
|
|
33
31
|
resolve: {
|
|
34
32
|
alias: {
|
|
35
|
-
"@":
|
|
33
|
+
"@": srcDir,
|
|
36
34
|
},
|
|
37
35
|
},
|
|
38
36
|
},
|
|
@@ -14,7 +14,9 @@ describe("createViteConfig", () => {
|
|
|
14
14
|
|
|
15
15
|
it("should respect custom rootDir", () => {
|
|
16
16
|
const config = createViteConfig({ rootDir: "app" })
|
|
17
|
-
expect(config.resolve.alias["@"]).toBe(
|
|
17
|
+
expect(config.resolve.alias["@"]).toBe(
|
|
18
|
+
path.resolve(process.cwd(), "app", "src"),
|
|
19
|
+
)
|
|
18
20
|
})
|
|
19
21
|
|
|
20
22
|
it("should merge custom vite config", () => {
|
|
@@ -7,7 +7,7 @@ import { describe, expect, it } from "vitest"
|
|
|
7
7
|
import { renderPage } from "."
|
|
8
8
|
|
|
9
9
|
const ROOT_DIR = path.join(import.meta.dirname, "..", "__fixtures__")
|
|
10
|
-
const PAGES_DIR = path.join(ROOT_DIR, "pages")
|
|
10
|
+
const PAGES_DIR = path.join(ROOT_DIR, "src", "pages")
|
|
11
11
|
|
|
12
12
|
const DEFAULT_OPTIONS = { stripLitMarkers: true }
|
|
13
13
|
|
|
@@ -5,7 +5,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"
|
|
|
5
5
|
import { getPages, getRoutes, matchRoute, resolvePage } from "./index.js"
|
|
6
6
|
|
|
7
7
|
const ROOT_DIR = path.join(import.meta.dirname, "..", "__fixtures__")
|
|
8
|
-
const PAGES_DIR = path.join(ROOT_DIR, "pages")
|
|
8
|
+
const PAGES_DIR = path.join(ROOT_DIR, "src", "pages")
|
|
9
9
|
|
|
10
10
|
describe("router", () => {
|
|
11
11
|
afterEach(() => {
|
package/src/scripts/app.test.js
CHANGED
|
@@ -5,7 +5,8 @@ import { describe, expect, it } from "vitest"
|
|
|
5
5
|
import { generateStore } from "../store"
|
|
6
6
|
import { generateApp } from "./app"
|
|
7
7
|
|
|
8
|
-
const ROOT_DIR = path.join(
|
|
8
|
+
const ROOT_DIR = path.join(import.meta.dirname, "..", "__fixtures__")
|
|
9
|
+
const PAGES_DIR = path.join(ROOT_DIR, "src", "pages")
|
|
9
10
|
|
|
10
11
|
describe("generateApp", () => {
|
|
11
12
|
it("should generate the app script for a static page", async () => {
|
|
@@ -13,7 +14,7 @@ describe("generateApp", () => {
|
|
|
13
14
|
pattern: "/",
|
|
14
15
|
path: "/",
|
|
15
16
|
modulePath: "index.js",
|
|
16
|
-
filePath:
|
|
17
|
+
filePath: PAGES_DIR,
|
|
17
18
|
}
|
|
18
19
|
const store = await generateStore([page], { rootDir: ROOT_DIR })
|
|
19
20
|
|
|
@@ -27,7 +28,7 @@ describe("generateApp", () => {
|
|
|
27
28
|
pattern: "/about",
|
|
28
29
|
path: "/about",
|
|
29
30
|
modulePath: "about.js",
|
|
30
|
-
filePath: path.join(
|
|
31
|
+
filePath: path.join(PAGES_DIR, "about.js"),
|
|
31
32
|
}
|
|
32
33
|
const store = await generateStore([page], { rootDir: ROOT_DIR })
|
|
33
34
|
|
|
@@ -41,7 +42,7 @@ describe("generateApp", () => {
|
|
|
41
42
|
pattern: "/blog",
|
|
42
43
|
path: "/blog",
|
|
43
44
|
modulePath: "blog.js",
|
|
44
|
-
filePath: path.join(
|
|
45
|
+
filePath: path.join(PAGES_DIR, "blog.js"),
|
|
45
46
|
}
|
|
46
47
|
const store = await generateStore([page], { rootDir: ROOT_DIR })
|
|
47
48
|
|
|
@@ -55,7 +56,7 @@ describe("generateApp", () => {
|
|
|
55
56
|
pattern: "/posts/:slug",
|
|
56
57
|
path: "/posts/my-first-post",
|
|
57
58
|
modulePath: "post.js",
|
|
58
|
-
filePath: path.join(
|
|
59
|
+
filePath: path.join(PAGES_DIR, "posts", "_slug.js"),
|
|
59
60
|
}
|
|
60
61
|
const store = await generateStore([page], { rootDir: ROOT_DIR })
|
|
61
62
|
|
package/src/store/index.js
CHANGED
|
@@ -17,7 +17,9 @@ import { getModuleName } from "../utils/module.js"
|
|
|
17
17
|
* @returns {Promise<Object>} The initialized store instance.
|
|
18
18
|
*/
|
|
19
19
|
export async function generateStore(pages = [], options = {}, loader) {
|
|
20
|
-
const { rootDir = "
|
|
20
|
+
const { rootDir = "." } = options
|
|
21
|
+
const srcDir = path.join(rootDir, "src")
|
|
22
|
+
|
|
21
23
|
const load = loader || ((p) => import(pathToFileURL(p)))
|
|
22
24
|
|
|
23
25
|
const types = {}
|
|
@@ -32,7 +34,7 @@ export async function generateStore(pages = [], options = {}, loader) {
|
|
|
32
34
|
|
|
33
35
|
for (const ext of extensions) {
|
|
34
36
|
try {
|
|
35
|
-
const module = await load(path.join(
|
|
37
|
+
const module = await load(path.join(srcDir, "store", `entities.${ext}`))
|
|
36
38
|
entities = module.entities
|
|
37
39
|
break
|
|
38
40
|
} catch {
|
package/src/store/store.test.js
CHANGED
|
@@ -5,11 +5,12 @@ import { describe, expect, it, vi } from "vitest"
|
|
|
5
5
|
import { generateStore } from "."
|
|
6
6
|
|
|
7
7
|
const ROOT_DIR = path.join(import.meta.dirname, "..", "__fixtures__")
|
|
8
|
+
const PAGES_DIR = path.join(ROOT_DIR, "src", "pages")
|
|
8
9
|
|
|
9
10
|
describe("generateStore", () => {
|
|
10
11
|
it("should generate the proper types and entities from a static page", async () => {
|
|
11
12
|
const page = {
|
|
12
|
-
filePath: path.join(
|
|
13
|
+
filePath: path.join(PAGES_DIR, "index.js"),
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
const store = await generateStore([page], { rootDir: ROOT_DIR })
|
|
@@ -20,7 +21,7 @@ describe("generateStore", () => {
|
|
|
20
21
|
|
|
21
22
|
it("should generate the proper types and entities from a page with an entity", async () => {
|
|
22
23
|
const page = {
|
|
23
|
-
filePath: path.join(
|
|
24
|
+
filePath: path.join(PAGES_DIR, "about.js"),
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const store = await generateStore([page], { rootDir: ROOT_DIR })
|
|
@@ -31,7 +32,7 @@ describe("generateStore", () => {
|
|
|
31
32
|
|
|
32
33
|
it("should generate the proper types and entities from a page that has metadata", async () => {
|
|
33
34
|
const page = {
|
|
34
|
-
filePath: path.join(
|
|
35
|
+
filePath: path.join(PAGES_DIR, "blog.js"),
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const store = await generateStore([page], { rootDir: ROOT_DIR })
|
|
@@ -42,7 +43,7 @@ describe("generateStore", () => {
|
|
|
42
43
|
|
|
43
44
|
it("should handle missing entities.js gracefully", async () => {
|
|
44
45
|
const page = {
|
|
45
|
-
filePath: path.join(
|
|
46
|
+
filePath: path.join(PAGES_DIR, "index.js"),
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// Point to a directory that doesn't contain entities.js
|
|
@@ -64,8 +65,8 @@ describe("generateStore", () => {
|
|
|
64
65
|
throw new Error("MODULE_NOT_FOUND")
|
|
65
66
|
})
|
|
66
67
|
|
|
67
|
-
const page = { filePath: path.join(
|
|
68
|
-
await generateStore([page], { rootDir: "
|
|
68
|
+
const page = { filePath: path.join(PAGES_DIR, "index.js") }
|
|
69
|
+
await generateStore([page], { rootDir: "." }, loader)
|
|
69
70
|
|
|
70
71
|
expect(loader).toHaveBeenCalledWith(page.filePath)
|
|
71
72
|
expect(loader).toHaveBeenCalledWith(
|
package/src/utils/config.js
CHANGED
|
@@ -2,10 +2,11 @@ import path from "node:path"
|
|
|
2
2
|
import { pathToFileURL } from "node:url"
|
|
3
3
|
|
|
4
4
|
export async function loadConfig(options) {
|
|
5
|
-
const { rootDir = "
|
|
5
|
+
const { rootDir = ".", configPath = "site.config.js" } = options
|
|
6
|
+
const srcDir = path.join(rootDir, "src")
|
|
6
7
|
|
|
7
8
|
try {
|
|
8
|
-
const config = await import(pathToFileURL(path.join(
|
|
9
|
+
const config = await import(pathToFileURL(path.join(srcDir, configPath)))
|
|
9
10
|
return config.default || config
|
|
10
11
|
} catch (error) {
|
|
11
12
|
if (error.code === "MODULE_NOT_FOUND") {
|