@jasonshimmy/vite-plugin-cer-app 0.1.0 → 0.1.2
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/CHANGELOG.md +8 -0
- package/IMPLEMENTATION_PLAN.md +1 -1
- package/README.md +7 -7
- package/VITE_PLUGIN_FRAMEWORK_PLAN.md +12 -12
- package/commits.txt +1 -3
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +2 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/preview.d.ts.map +1 -1
- package/dist/cli/commands/preview.js +21 -2
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/create/index.js +2 -2
- package/dist/cli/create/index.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/plugin/build-ssg.d.ts.map +1 -1
- package/dist/plugin/build-ssg.js +10 -21
- package/dist/plugin/build-ssg.js.map +1 -1
- package/dist/plugin/build-ssr.d.ts.map +1 -1
- package/dist/plugin/build-ssr.js +151 -28
- package/dist/plugin/build-ssr.js.map +1 -1
- package/dist/plugin/dts-generator.js +4 -4
- package/dist/plugin/dts-generator.js.map +1 -1
- package/dist/plugin/index.js +2 -2
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/transforms/auto-import.js +3 -3
- package/dist/plugin/transforms/auto-import.js.map +1 -1
- package/dist/plugin/virtual/components.js +3 -3
- package/dist/plugin/virtual/components.js.map +1 -1
- package/dist/plugin/virtual/composables.js +3 -3
- package/dist/plugin/virtual/composables.js.map +1 -1
- package/dist/plugin/virtual/error.js +2 -2
- package/dist/plugin/virtual/error.js.map +1 -1
- package/dist/plugin/virtual/layouts.js +3 -3
- package/dist/plugin/virtual/layouts.js.map +1 -1
- package/dist/plugin/virtual/loading.js +2 -2
- package/dist/plugin/virtual/loading.js.map +1 -1
- package/dist/plugin/virtual/middleware.js +3 -3
- package/dist/plugin/virtual/middleware.js.map +1 -1
- package/dist/plugin/virtual/plugins.js +3 -3
- package/dist/plugin/virtual/plugins.js.map +1 -1
- package/dist/plugin/virtual/routes.d.ts.map +1 -1
- package/dist/plugin/virtual/routes.js +14 -4
- package/dist/plugin/virtual/routes.js.map +1 -1
- package/dist/plugin/virtual/server-api.js +3 -3
- package/dist/plugin/virtual/server-api.js.map +1 -1
- package/dist/plugin/virtual/server-middleware.js +3 -3
- package/dist/plugin/virtual/server-middleware.js.map +1 -1
- package/dist/runtime/app-template.d.ts +1 -1
- package/dist/runtime/app-template.d.ts.map +1 -1
- package/dist/runtime/app-template.js +6 -0
- package/dist/runtime/app-template.js.map +1 -1
- package/dist/runtime/composables/use-page-data.d.ts +15 -6
- package/dist/runtime/composables/use-page-data.d.ts.map +1 -1
- package/dist/runtime/composables/use-page-data.js +30 -9
- package/dist/runtime/composables/use-page-data.js.map +1 -1
- package/dist/runtime/entry-server-template.d.ts +1 -1
- package/dist/runtime/entry-server-template.d.ts.map +1 -1
- package/dist/runtime/entry-server-template.js +138 -17
- package/dist/runtime/entry-server-template.js.map +1 -1
- package/docs/cli.md +2 -2
- package/docs/configuration.md +4 -4
- package/docs/data-loading.md +8 -7
- package/docs/getting-started.md +5 -5
- package/docs/head-management.md +3 -3
- package/docs/middleware.md +2 -2
- package/docs/plugins.md +1 -1
- package/docs/rendering-modes.md +1 -1
- package/docs/routing.md +1 -1
- package/docs/server-api.md +10 -1
- package/docs/testing.md +4 -4
- package/package.json +1 -1
- package/src/__tests__/index.test.ts +21 -0
- package/src/__tests__/plugin/build-ssg.test.ts +265 -0
- package/src/__tests__/plugin/build-ssr.test.ts +180 -0
- package/src/__tests__/plugin/cer-app-plugin.test.ts +409 -0
- package/src/__tests__/plugin/dts-generator.test.ts +246 -0
- package/src/__tests__/plugin/resolve-config.test.ts +158 -0
- package/src/__tests__/plugin/transforms/auto-import.test.ts +1 -1
- package/src/__tests__/plugin/virtual/components.test.ts +1 -1
- package/src/__tests__/plugin/virtual/composables.test.ts +1 -1
- package/src/__tests__/plugin/virtual/error.test.ts +71 -0
- package/src/__tests__/plugin/virtual/layouts.test.ts +1 -1
- package/src/__tests__/plugin/virtual/loading.test.ts +72 -0
- package/src/__tests__/plugin/virtual/middleware.test.ts +1 -1
- package/src/__tests__/plugin/virtual/plugins.test.ts +1 -1
- package/src/__tests__/plugin/virtual/routes.test.ts +1 -1
- package/src/__tests__/plugin/virtual/server-api.test.ts +1 -1
- package/src/__tests__/plugin/virtual/server-middleware.test.ts +102 -0
- package/src/__tests__/runtime/use-page-data.test.ts +81 -5
- package/src/__tests__/types/config.test.ts +23 -0
- package/src/cli/commands/generate.ts +2 -0
- package/src/cli/commands/preview.ts +21 -2
- package/src/cli/create/index.ts +2 -2
- package/src/cli/create/templates/spa/cer.config.ts.tpl +1 -1
- package/src/cli/create/templates/spa/package.json.tpl +1 -1
- package/src/cli/create/templates/ssg/cer.config.ts.tpl +1 -1
- package/src/cli/create/templates/ssg/package.json.tpl +1 -1
- package/src/cli/create/templates/ssr/cer.config.ts.tpl +1 -1
- package/src/cli/create/templates/ssr/package.json.tpl +1 -1
- package/src/cli/index.ts +1 -1
- package/src/plugin/build-ssg.ts +9 -22
- package/src/plugin/build-ssr.ts +150 -28
- package/src/plugin/dts-generator.ts +4 -4
- package/src/plugin/index.ts +2 -2
- package/src/plugin/transforms/auto-import.ts +3 -3
- package/src/plugin/virtual/components.ts +3 -3
- package/src/plugin/virtual/composables.ts +3 -3
- package/src/plugin/virtual/error.ts +2 -2
- package/src/plugin/virtual/layouts.ts +3 -3
- package/src/plugin/virtual/loading.ts +2 -2
- package/src/plugin/virtual/middleware.ts +3 -3
- package/src/plugin/virtual/plugins.ts +3 -3
- package/src/plugin/virtual/routes.ts +15 -4
- package/src/plugin/virtual/server-api.ts +3 -3
- package/src/plugin/virtual/server-middleware.ts +3 -3
- package/src/runtime/app-template.ts +6 -0
- package/src/runtime/composables/use-page-data.ts +31 -9
- package/src/runtime/entry-server-template.ts +138 -17
- package/tsconfig.build.json +1 -1
- package/dist/__tests__/plugin/path-utils.test.d.ts +0 -2
- package/dist/__tests__/plugin/path-utils.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/path-utils.test.js +0 -305
- package/dist/__tests__/plugin/path-utils.test.js.map +0 -1
- package/dist/__tests__/plugin/scanner.test.d.ts +0 -2
- package/dist/__tests__/plugin/scanner.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/scanner.test.js +0 -143
- package/dist/__tests__/plugin/scanner.test.js.map +0 -1
- package/dist/__tests__/plugin/transforms/auto-import.test.d.ts +0 -2
- package/dist/__tests__/plugin/transforms/auto-import.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/transforms/auto-import.test.js +0 -151
- package/dist/__tests__/plugin/transforms/auto-import.test.js.map +0 -1
- package/dist/__tests__/plugin/transforms/head-inject.test.d.ts +0 -2
- package/dist/__tests__/plugin/transforms/head-inject.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/transforms/head-inject.test.js +0 -151
- package/dist/__tests__/plugin/transforms/head-inject.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/components.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/components.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/components.test.js +0 -47
- package/dist/__tests__/plugin/virtual/components.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/composables.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/composables.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/composables.test.js +0 -48
- package/dist/__tests__/plugin/virtual/composables.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/layouts.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/layouts.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/layouts.test.js +0 -59
- package/dist/__tests__/plugin/virtual/layouts.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/middleware.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/middleware.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/middleware.test.js +0 -58
- package/dist/__tests__/plugin/virtual/middleware.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/plugins.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/plugins.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/plugins.test.js +0 -73
- package/dist/__tests__/plugin/virtual/plugins.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/routes.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/routes.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/routes.test.js +0 -167
- package/dist/__tests__/plugin/virtual/routes.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/server-api.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/server-api.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/server-api.test.js +0 -72
- package/dist/__tests__/plugin/virtual/server-api.test.js.map +0 -1
- package/dist/__tests__/runtime/use-head.test.d.ts +0 -2
- package/dist/__tests__/runtime/use-head.test.d.ts.map +0 -1
- package/dist/__tests__/runtime/use-head.test.js +0 -202
- package/dist/__tests__/runtime/use-head.test.js.map +0 -1
- package/dist/__tests__/runtime/use-page-data.test.d.ts +0 -2
- package/dist/__tests__/runtime/use-page-data.test.d.ts.map +0 -1
- package/dist/__tests__/runtime/use-page-data.test.js +0 -41
- package/dist/__tests__/runtime/use-page-data.test.js.map +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
vi.mock('node:fs', () => ({ existsSync: vi.fn() }))
|
|
4
|
+
vi.mock('../../../plugin/scanner.js', () => ({ scanDirectory: vi.fn() }))
|
|
5
|
+
|
|
6
|
+
import { existsSync } from 'node:fs'
|
|
7
|
+
import { scanDirectory } from '../../../plugin/scanner.js'
|
|
8
|
+
import { generateServerMiddlewareCode } from '../../../plugin/virtual/server-middleware.js'
|
|
9
|
+
|
|
10
|
+
const MIDDLEWARE_DIR = '/project/server/middleware'
|
|
11
|
+
|
|
12
|
+
describe('generateServerMiddlewareCode', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.mocked(existsSync).mockReturnValue(true)
|
|
15
|
+
vi.mocked(scanDirectory).mockResolvedValue([])
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('returns empty serverMiddleware when directory does not exist', async () => {
|
|
19
|
+
vi.mocked(existsSync).mockReturnValue(false)
|
|
20
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
21
|
+
expect(code).toContain('export const serverMiddleware = []')
|
|
22
|
+
expect(code).toContain('export default serverMiddleware')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('returns empty serverMiddleware when no files found', async () => {
|
|
26
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
27
|
+
expect(code).toContain('export const serverMiddleware = []')
|
|
28
|
+
expect(code).toContain('export default serverMiddleware')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('includes AUTO-GENERATED comment', async () => {
|
|
32
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/logger.ts`])
|
|
33
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
34
|
+
expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('generates import for a middleware file', async () => {
|
|
38
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/logger.ts`])
|
|
39
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
40
|
+
expect(code).toContain(`import _sm_logger from "${MIDDLEWARE_DIR}/logger.ts"`)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('registers middleware with its name and handler', async () => {
|
|
44
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/logger.ts`])
|
|
45
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
46
|
+
expect(code).toContain('name: "logger"')
|
|
47
|
+
expect(code).toContain('handler: _sm_logger?.default ?? _sm_logger')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('exports serverMiddleware as both named and default export', async () => {
|
|
51
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/logger.ts`])
|
|
52
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
53
|
+
expect(code).toContain('export const serverMiddleware = [')
|
|
54
|
+
expect(code).toContain('export default serverMiddleware')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('replaces dashes with underscores in the alias identifier', async () => {
|
|
58
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/check-auth.ts`])
|
|
59
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
60
|
+
expect(code).toContain('_sm_check_auth')
|
|
61
|
+
expect(code).toContain('name: "check-auth"')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('prepends underscore to identifiers starting with a digit', async () => {
|
|
65
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/1first.ts`])
|
|
66
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
67
|
+
expect(code).toContain('_sm__1first')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('handles multiple middleware files', async () => {
|
|
71
|
+
vi.mocked(scanDirectory).mockResolvedValue([
|
|
72
|
+
`${MIDDLEWARE_DIR}/logger.ts`,
|
|
73
|
+
`${MIDDLEWARE_DIR}/auth.ts`,
|
|
74
|
+
])
|
|
75
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
76
|
+
expect(code).toContain('name: "auth"')
|
|
77
|
+
expect(code).toContain('name: "logger"')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('sorts middleware files alphabetically', async () => {
|
|
81
|
+
vi.mocked(scanDirectory).mockResolvedValue([
|
|
82
|
+
`${MIDDLEWARE_DIR}/zz-last.ts`,
|
|
83
|
+
`${MIDDLEWARE_DIR}/aa-first.ts`,
|
|
84
|
+
])
|
|
85
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
86
|
+
const authIdx = code.indexOf('aa-first')
|
|
87
|
+
const loggerIdx = code.indexOf('zz-last')
|
|
88
|
+
expect(authIdx).toBeLessThan(loggerIdx)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('ends with a newline', async () => {
|
|
92
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/logger.ts`])
|
|
93
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
94
|
+
expect(code.endsWith('\n')).toBe(true)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('returns AUTO-GENERATED comment even when directory is missing', async () => {
|
|
98
|
+
vi.mocked(existsSync).mockReturnValue(false)
|
|
99
|
+
const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
|
|
100
|
+
expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
|
|
101
|
+
})
|
|
102
|
+
})
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest'
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
3
|
import { usePageData } from '../../runtime/composables/use-page-data.js'
|
|
3
4
|
|
|
4
5
|
describe('usePageData', () => {
|
|
5
6
|
beforeEach(() => {
|
|
6
7
|
// Clean up global state between tests
|
|
7
8
|
delete (globalThis as Record<string, unknown>)['__CER_DATA__']
|
|
9
|
+
delete (globalThis as Record<string, unknown>)['__CER_DATA_STORE__']
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
delete (globalThis as Record<string, unknown>)['__CER_DATA_STORE__']
|
|
8
14
|
})
|
|
9
15
|
|
|
10
16
|
it('returns null when no SSR data is present', () => {
|
|
@@ -17,16 +23,16 @@ describe('usePageData', () => {
|
|
|
17
23
|
expect(usePageData()).toEqual(data)
|
|
18
24
|
})
|
|
19
25
|
|
|
20
|
-
it('
|
|
26
|
+
it('does NOT clear __CER_DATA__ on read (cleared by app.ts after router.replace)', () => {
|
|
21
27
|
;(globalThis as Record<string, unknown>)['__CER_DATA__'] = { title: 'Hello' }
|
|
22
28
|
usePageData()
|
|
23
|
-
expect((globalThis as Record<string, unknown>)['__CER_DATA__']).
|
|
29
|
+
expect((globalThis as Record<string, unknown>)['__CER_DATA__']).toEqual({ title: 'Hello' })
|
|
24
30
|
})
|
|
25
31
|
|
|
26
|
-
it('returns
|
|
32
|
+
it('returns the same data on subsequent calls (not consumed on first read)', () => {
|
|
27
33
|
;(globalThis as Record<string, unknown>)['__CER_DATA__'] = { title: 'Hello' }
|
|
28
34
|
usePageData()
|
|
29
|
-
expect(usePageData()).
|
|
35
|
+
expect(usePageData()).toEqual({ title: 'Hello' })
|
|
30
36
|
})
|
|
31
37
|
|
|
32
38
|
it('is generic and preserves the typed shape', () => {
|
|
@@ -43,3 +49,73 @@ describe('usePageData', () => {
|
|
|
43
49
|
expect(usePageData()).toBeNull()
|
|
44
50
|
})
|
|
45
51
|
})
|
|
52
|
+
|
|
53
|
+
describe('usePageData — AsyncLocalStorage (server-side)', () => {
|
|
54
|
+
let store: AsyncLocalStorage<unknown>
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
delete (globalThis as Record<string, unknown>)['__CER_DATA__']
|
|
58
|
+
store = new AsyncLocalStorage()
|
|
59
|
+
;(globalThis as Record<string, unknown>)['__CER_DATA_STORE__'] = store
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
delete (globalThis as Record<string, unknown>)['__CER_DATA_STORE__']
|
|
64
|
+
delete (globalThis as Record<string, unknown>)['__CER_DATA__']
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('reads data from the ALS store when available', () => {
|
|
68
|
+
const data = { title: 'SSR Post', body: 'Hello from ALS' }
|
|
69
|
+
store.run(data, () => {
|
|
70
|
+
expect(usePageData()).toEqual(data)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('returns null when ALS store is empty (null)', () => {
|
|
75
|
+
store.run(null, () => {
|
|
76
|
+
expect(usePageData()).toBeNull()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('prefers ALS store over globalThis.__CER_DATA__', () => {
|
|
81
|
+
const alsData = { source: 'als' }
|
|
82
|
+
const globalData = { source: 'global' }
|
|
83
|
+
;(globalThis as Record<string, unknown>)['__CER_DATA__'] = globalData
|
|
84
|
+
store.run(alsData, () => {
|
|
85
|
+
expect(usePageData()).toEqual(alsData)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('does NOT delete __CER_DATA__ when returning ALS data', () => {
|
|
90
|
+
const globalData = { source: 'global' }
|
|
91
|
+
;(globalThis as Record<string, unknown>)['__CER_DATA__'] = globalData
|
|
92
|
+
store.run({ source: 'als' }, () => {
|
|
93
|
+
usePageData()
|
|
94
|
+
})
|
|
95
|
+
// global data should still be present — ALS path doesn't consume it
|
|
96
|
+
expect((globalThis as Record<string, unknown>)['__CER_DATA__']).toEqual(globalData)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('falls back to __CER_DATA__ when ALS store has no value outside run()', () => {
|
|
100
|
+
// Outside any store.run() context, getStore() returns undefined
|
|
101
|
+
const data = { fallback: true }
|
|
102
|
+
;(globalThis as Record<string, unknown>)['__CER_DATA__'] = data
|
|
103
|
+
// store is set but we're outside a run() context → getStore() === undefined
|
|
104
|
+
expect(usePageData()).toEqual(data)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('returns null when both ALS and __CER_DATA__ are absent', () => {
|
|
108
|
+
store.run(null, () => {
|
|
109
|
+
expect(usePageData()).toBeNull()
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('is safe to call multiple times inside same ALS context', () => {
|
|
114
|
+
const data = { count: 42 }
|
|
115
|
+
store.run(data, () => {
|
|
116
|
+
expect(usePageData()).toEqual(data)
|
|
117
|
+
// ALS data is NOT consumed on read, so a second call still returns it
|
|
118
|
+
expect(usePageData()).toEqual(data)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { defineConfig } from '../../types/config.js'
|
|
3
|
+
|
|
4
|
+
describe('defineConfig', () => {
|
|
5
|
+
it('returns the config object unchanged', () => {
|
|
6
|
+
const config = { mode: 'spa' as const, port: 4000 }
|
|
7
|
+
expect(defineConfig(config)).toBe(config)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('accepts an empty config object', () => {
|
|
11
|
+
expect(defineConfig({})).toEqual({})
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('preserves nested ssr config', () => {
|
|
15
|
+
const config = { ssr: { dsd: false, streaming: true } }
|
|
16
|
+
expect(defineConfig(config)).toEqual(config)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('preserves nested ssg config', () => {
|
|
20
|
+
const config = { ssg: { routes: ['/a', '/b'], concurrency: 2 } }
|
|
21
|
+
expect(defineConfig(config)).toEqual(config)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -95,9 +95,13 @@ export function previewCommand(): Command {
|
|
|
95
95
|
const distDir = join(root, 'dist')
|
|
96
96
|
const serverBundle = join(distDir, 'server/server.js')
|
|
97
97
|
|
|
98
|
-
// Check if SSR server bundle exists
|
|
98
|
+
// Check if SSR server bundle exists.
|
|
99
|
+
// An SSG build also produces a server bundle, but previewing SSG means serving
|
|
100
|
+
// the pre-rendered static HTML — detect the SSG manifest to avoid switching to
|
|
101
|
+
// live SSR mode for SSG builds.
|
|
99
102
|
const hasServerBundle = existsSync(serverBundle)
|
|
100
|
-
const
|
|
103
|
+
const hasSsgManifest = existsSync(join(distDir, 'ssg-manifest.json'))
|
|
104
|
+
const useSSR = options.ssr || (hasServerBundle && !hasSsgManifest)
|
|
101
105
|
|
|
102
106
|
if (useSSR && hasServerBundle) {
|
|
103
107
|
console.log('[cer-app] Starting SSR preview server...')
|
|
@@ -200,6 +204,21 @@ export function previewCommand(): Command {
|
|
|
200
204
|
}
|
|
201
205
|
|
|
202
206
|
const server = createHttpServer((req: IncomingMessage, res: ServerResponse) => {
|
|
207
|
+
const urlPath = (req.url ?? '/').split('?')[0]
|
|
208
|
+
// SSG builds put assets in dist/client/ while HTML lives in dist/.
|
|
209
|
+
// For requests with a non-HTML file extension, check dist/client/ first
|
|
210
|
+
// so the static server finds the Vite-built JS/CSS bundles.
|
|
211
|
+
const clientDist = join(distDir, 'client')
|
|
212
|
+
const ext = extname(urlPath).toLowerCase()
|
|
213
|
+
if (ext && ext !== '.html' && existsSync(clientDist)) {
|
|
214
|
+
const assetPath = join(clientDist, urlPath)
|
|
215
|
+
if (existsSync(assetPath) && !statSync(assetPath).isDirectory()) {
|
|
216
|
+
res.setHeader('Content-Type', getMimeType(assetPath))
|
|
217
|
+
res.setHeader('Cache-Control', 'no-cache')
|
|
218
|
+
createReadStream(assetPath).pipe(res)
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
}
|
|
203
222
|
const served = serveStaticFile(req, res, distDir)
|
|
204
223
|
if (!served) {
|
|
205
224
|
res.statusCode = 404
|
package/src/cli/create/index.ts
CHANGED
|
@@ -198,7 +198,7 @@ async function generateInlineTemplate(
|
|
|
198
198
|
},
|
|
199
199
|
devDependencies: {
|
|
200
200
|
vite: '^5.0.0',
|
|
201
|
-
'vite-plugin-cer-app': '^0.1.0',
|
|
201
|
+
'@jasonshimmy/vite-plugin-cer-app': '^0.1.0',
|
|
202
202
|
typescript: '^5.4.0',
|
|
203
203
|
},
|
|
204
204
|
},
|
|
@@ -211,7 +211,7 @@ async function generateInlineTemplate(
|
|
|
211
211
|
// cer.config.ts
|
|
212
212
|
await writeFile(
|
|
213
213
|
join(targetDir, 'cer.config.ts'),
|
|
214
|
-
`import { defineConfig } from 'vite-plugin-cer-app'\n\nexport default defineConfig({\n mode: '${mode}',\n autoImports: { components: true, composables: true, directives: true, runtime: true },\n})\n`,
|
|
214
|
+
`import { defineConfig } from '@jasonshimmy/vite-plugin-cer-app'\n\nexport default defineConfig({\n mode: '${mode}',\n autoImports: { components: true, composables: true, directives: true, runtime: true },\n})\n`,
|
|
215
215
|
'utf-8',
|
|
216
216
|
)
|
|
217
217
|
|
package/src/cli/index.ts
CHANGED
package/src/plugin/build-ssg.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFile, mkdir
|
|
1
|
+
import { writeFile, mkdir } from 'node:fs/promises'
|
|
2
2
|
import { existsSync } from 'node:fs'
|
|
3
3
|
import { join } from 'pathe'
|
|
4
4
|
import { createServer, build, type UserConfig } from 'vite'
|
|
@@ -101,14 +101,13 @@ let _serverMod: Record<string, unknown> | null = null
|
|
|
101
101
|
/**
|
|
102
102
|
* Renders a single path using the SSR server bundle and returns the HTML.
|
|
103
103
|
*
|
|
104
|
-
* The server bundle
|
|
105
|
-
*
|
|
106
|
-
*
|
|
104
|
+
* The server bundle's handler already merges the SSR output with
|
|
105
|
+
* dist/client/index.html internally (via the _mergeWithClientTemplate helper
|
|
106
|
+
* it embeds at build time), so this function simply captures the response.
|
|
107
107
|
*/
|
|
108
108
|
async function renderPath(
|
|
109
109
|
path: string,
|
|
110
110
|
serverBundlePath: string,
|
|
111
|
-
clientDistDir: string,
|
|
112
111
|
): Promise<string> {
|
|
113
112
|
// Load server bundle once
|
|
114
113
|
if (!_serverMod) {
|
|
@@ -130,28 +129,17 @@ async function renderPath(
|
|
|
130
129
|
return ''
|
|
131
130
|
}
|
|
132
131
|
|
|
133
|
-
// Mock req/res for the Express-style handler
|
|
132
|
+
// Mock req/res for the Express-style handler.
|
|
133
|
+
// The handler internally merges with dist/client/index.html, so we just
|
|
134
|
+
// capture whatever it ends with.
|
|
134
135
|
const mockReq = { url: path, headers: {} }
|
|
135
|
-
|
|
136
|
+
return new Promise<string>((resolve, reject) => {
|
|
136
137
|
const mockRes = {
|
|
137
138
|
setHeader: () => {},
|
|
138
|
-
// Intentionally omit write() so the handler uses the non-streaming code path
|
|
139
139
|
end: (body: string) => resolve(body),
|
|
140
140
|
}
|
|
141
141
|
;(handlerFn as Function)(mockReq, mockRes).catch(reject)
|
|
142
142
|
})
|
|
143
|
-
|
|
144
|
-
// Read the client index.html template and inject the server-rendered body
|
|
145
|
-
const templatePath = join(clientDistDir, 'index.html')
|
|
146
|
-
if (!existsSync(templatePath)) return html
|
|
147
|
-
|
|
148
|
-
const template = await readFile(templatePath, 'utf-8')
|
|
149
|
-
// The handler already emits a full document; just return it as-is
|
|
150
|
-
// If it only emits a fragment we fall back to injecting into the template
|
|
151
|
-
if (html.trimStart().startsWith('<!DOCTYPE') || html.trimStart().startsWith('<html')) {
|
|
152
|
-
return html
|
|
153
|
-
}
|
|
154
|
-
return template.replace('<div id="app"></div>', `<div id="app">${html}</div>`)
|
|
155
143
|
}
|
|
156
144
|
|
|
157
145
|
/**
|
|
@@ -190,7 +178,6 @@ export async function buildSSG(
|
|
|
190
178
|
viteUserConfig: UserConfig = {},
|
|
191
179
|
): Promise<void> {
|
|
192
180
|
const distDir = join(config.root, 'dist')
|
|
193
|
-
const clientDistDir = join(distDir, 'client')
|
|
194
181
|
const serverDistDir = join(distDir, 'server')
|
|
195
182
|
const serverBundlePath = join(serverDistDir, 'server.js')
|
|
196
183
|
|
|
@@ -222,7 +209,7 @@ export async function buildSSG(
|
|
|
222
209
|
const results = await Promise.allSettled(
|
|
223
210
|
chunk.map(async (path) => {
|
|
224
211
|
console.log(`[cer-app] Generating: ${path}`)
|
|
225
|
-
const html = await renderPath(path, serverBundlePath
|
|
212
|
+
const html = await renderPath(path, serverBundlePath)
|
|
226
213
|
await writeRenderedPath(path, html, distDir)
|
|
227
214
|
return path
|
|
228
215
|
}),
|