@jasonshimmy/vite-plugin-cer-app 0.2.0 → 0.3.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/CHANGELOG.md +4 -0
- package/README.md +2 -0
- package/commits.txt +1 -1
- package/dist/cli/create/index.js +7 -3
- package/dist/cli/create/index.js.map +1 -1
- package/dist/cli/create/templates/spa/.gitignore.tpl +25 -0
- package/dist/cli/create/templates/spa/index.html.tpl +1 -1
- package/dist/cli/create/templates/ssg/.gitignore.tpl +25 -0
- package/dist/cli/create/templates/ssg/index.html.tpl +1 -1
- package/dist/cli/create/templates/ssr/.gitignore.tpl +25 -0
- package/dist/cli/create/templates/ssr/cer.config.ts.tpl +0 -1
- package/dist/cli/create/templates/ssr/index.html.tpl +1 -1
- package/dist/plugin/dev-server.d.ts +0 -1
- package/dist/plugin/dev-server.d.ts.map +1 -1
- package/dist/plugin/generated-dir.d.ts +5 -11
- package/dist/plugin/generated-dir.d.ts.map +1 -1
- package/dist/plugin/generated-dir.js +43 -31
- package/dist/plugin/generated-dir.js.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +0 -1
- package/dist/plugin/index.js.map +1 -1
- package/dist/runtime/app-template.d.ts +5 -4
- package/dist/runtime/app-template.d.ts.map +1 -1
- package/dist/runtime/app-template.js +6 -5
- package/dist/runtime/app-template.js.map +1 -1
- package/dist/types/config.d.ts +0 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js.map +1 -1
- package/docs/cli.md +1 -1
- package/docs/configuration.md +2 -11
- package/docs/getting-started.md +2 -100
- package/docs/rendering-modes.md +2 -3
- package/package.json +1 -1
- package/src/__tests__/plugin/dev-server.test.ts +1 -1
- package/src/__tests__/plugin/generated-dir.test.ts +8 -39
- package/src/__tests__/plugin/resolve-config.test.ts +0 -5
- package/src/__tests__/types/config.test.ts +1 -1
- package/src/cli/create/index.ts +12 -8
- package/src/cli/create/templates/spa/.gitignore.tpl +25 -0
- package/src/cli/create/templates/spa/index.html.tpl +1 -1
- package/src/cli/create/templates/ssg/.gitignore.tpl +25 -0
- package/src/cli/create/templates/ssg/index.html.tpl +1 -1
- package/src/cli/create/templates/ssr/.gitignore.tpl +25 -0
- package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -1
- package/src/cli/create/templates/ssr/index.html.tpl +1 -1
- package/src/plugin/dev-server.ts +1 -1
- package/src/plugin/generated-dir.ts +44 -31
- package/src/plugin/index.ts +0 -1
- package/src/runtime/app-template.ts +6 -5
- package/src/types/config.ts +0 -1
- package/dist/cli/create/templates/spa/app/app.ts.tpl +0 -93
- package/dist/cli/create/templates/ssg/app/app.ts.tpl +0 -97
- package/dist/cli/create/templates/ssr/app/app.ts.tpl +0 -97
- package/src/cli/create/templates/spa/app/app.ts.tpl +0 -93
- package/src/cli/create/templates/ssg/app/app.ts.tpl +0 -97
- package/src/cli/create/templates/ssr/app/app.ts.tpl +0 -97
package/docs/configuration.md
CHANGED
|
@@ -13,7 +13,6 @@ export default defineConfig({
|
|
|
13
13
|
|
|
14
14
|
ssr: {
|
|
15
15
|
dsd: true,
|
|
16
|
-
streaming: false,
|
|
17
16
|
},
|
|
18
17
|
|
|
19
18
|
ssg: {
|
|
@@ -87,8 +86,7 @@ Controls SSR rendering behavior.
|
|
|
87
86
|
|
|
88
87
|
```ts
|
|
89
88
|
ssr: {
|
|
90
|
-
dsd: true,
|
|
91
|
-
streaming: false // Reserved for future use
|
|
89
|
+
dsd: true, // Emit Declarative Shadow DOM
|
|
92
90
|
}
|
|
93
91
|
```
|
|
94
92
|
|
|
@@ -99,13 +97,6 @@ ssr: {
|
|
|
99
97
|
|
|
100
98
|
When `true`, renders components with [Declarative Shadow DOM](https://developer.chrome.com/docs/css-ui/declarative-shadow-dom) markup. This eliminates Flash of Unstyled Content (FOUC) because styles are embedded directly in the HTML.
|
|
101
99
|
|
|
102
|
-
### `ssr.streaming`
|
|
103
|
-
|
|
104
|
-
**Type:** `boolean`
|
|
105
|
-
**Default:** `false`
|
|
106
|
-
|
|
107
|
-
Reserved for future use. Currently, the SSR renderer always collects the full HTML string before sending the response.
|
|
108
|
-
|
|
109
100
|
---
|
|
110
101
|
|
|
111
102
|
## `ssg` options
|
|
@@ -247,7 +238,7 @@ export default defineConfig({
|
|
|
247
238
|
plugins: [
|
|
248
239
|
cerApp({
|
|
249
240
|
mode: 'ssr',
|
|
250
|
-
ssr: { dsd: true
|
|
241
|
+
ssr: { dsd: true },
|
|
251
242
|
}),
|
|
252
243
|
],
|
|
253
244
|
})
|
package/docs/getting-started.md
CHANGED
|
@@ -128,110 +128,12 @@ component('layout-default', () => {
|
|
|
128
128
|
</head>
|
|
129
129
|
<body>
|
|
130
130
|
<cer-layout-view></cer-layout-view>
|
|
131
|
-
<script type="module" src="/app
|
|
131
|
+
<script type="module" src="/.cer/app.ts"></script>
|
|
132
132
|
</body>
|
|
133
133
|
</html>
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
The framework generates this file when you scaffold a new project. It bootstraps the router, registers all auto-discovered components, runs plugins, and mounts the app:
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
// app/app.ts
|
|
142
|
-
import '@jasonshimmy/custom-elements-runtime/css'
|
|
143
|
-
import 'virtual:cer-jit-css'
|
|
144
|
-
import 'virtual:cer-components'
|
|
145
|
-
import routes from 'virtual:cer-routes'
|
|
146
|
-
import layouts from 'virtual:cer-layouts'
|
|
147
|
-
import plugins from 'virtual:cer-plugins'
|
|
148
|
-
import { hasLoading, loadingTag } from 'virtual:cer-loading'
|
|
149
|
-
import { hasError, errorTag } from 'virtual:cer-error'
|
|
150
|
-
import {
|
|
151
|
-
component, ref, provide,
|
|
152
|
-
useOnConnected, useOnDisconnected,
|
|
153
|
-
registerBuiltinComponents,
|
|
154
|
-
} from '@jasonshimmy/custom-elements-runtime'
|
|
155
|
-
import { initRouter } from '@jasonshimmy/custom-elements-runtime/router'
|
|
156
|
-
import { enableJITCSS } from '@jasonshimmy/custom-elements-runtime/jit-css'
|
|
157
|
-
|
|
158
|
-
registerBuiltinComponents()
|
|
159
|
-
enableJITCSS()
|
|
160
|
-
|
|
161
|
-
const router = initRouter({ routes })
|
|
162
|
-
|
|
163
|
-
const isNavigating = ref(false)
|
|
164
|
-
const currentError = ref(null)
|
|
165
|
-
;(globalThis as any).resetError = () => {
|
|
166
|
-
currentError.value = null
|
|
167
|
-
router.replace(router.getCurrent().path)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const _push = router.push.bind(router)
|
|
171
|
-
const _replace = router.replace.bind(router)
|
|
172
|
-
router.push = async (path) => {
|
|
173
|
-
isNavigating.value = true; currentError.value = null
|
|
174
|
-
try { await _push(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false }
|
|
175
|
-
}
|
|
176
|
-
router.replace = async (path) => {
|
|
177
|
-
isNavigating.value = true; currentError.value = null
|
|
178
|
-
try { await _replace(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false }
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// _pluginProvides is populated by plugin setup and forwarded into the component
|
|
182
|
-
// context tree via provide() inside cer-layout-view so inject() works in all modes.
|
|
183
|
-
// Also exposed on globalThis for the SSG timing edge case — see docs/plugins.md.
|
|
184
|
-
const _pluginProvides = new Map<string, unknown>()
|
|
185
|
-
;(globalThis as any).__cerPluginProvides = _pluginProvides
|
|
186
|
-
|
|
187
|
-
component('cer-layout-view', () => {
|
|
188
|
-
for (const [key, value] of _pluginProvides) { provide(key, value) }
|
|
189
|
-
|
|
190
|
-
const current = ref(router.getCurrent())
|
|
191
|
-
let unsub: (() => void) | undefined
|
|
192
|
-
useOnConnected(() => { unsub = router.subscribe((s) => { current.value = s }) })
|
|
193
|
-
useOnDisconnected(() => { unsub?.(); unsub = undefined })
|
|
194
|
-
|
|
195
|
-
if (currentError.value !== null) {
|
|
196
|
-
if (hasError && errorTag) return { tag: errorTag, props: { attrs: { error: String(currentError.value) } }, children: [] }
|
|
197
|
-
return { tag: 'div', props: { attrs: { style: 'padding:2rem;font-family:monospace' } }, children: String(currentError.value) }
|
|
198
|
-
}
|
|
199
|
-
if (isNavigating.value && hasLoading && loadingTag) return { tag: loadingTag, props: {}, children: [] }
|
|
200
|
-
|
|
201
|
-
const matched = router.matchRoute(current.value.path)
|
|
202
|
-
const layoutName = (matched?.route as any)?.meta?.layout ?? 'default'
|
|
203
|
-
const layoutTag = (layouts as Record<string, string>)[layoutName]
|
|
204
|
-
const routerView = { tag: 'router-view', props: {}, children: [] }
|
|
205
|
-
return layoutTag ? { tag: layoutTag, props: {}, children: [routerView] } : routerView
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
// Plugins run AFTER cer-layout-view is defined so provide() calls from plugins
|
|
209
|
-
// are forwarded into the component tree on the very first render.
|
|
210
|
-
for (const plugin of plugins ?? []) {
|
|
211
|
-
if (plugin && typeof plugin.setup === 'function') {
|
|
212
|
-
await plugin.setup({ router, provide: (key, value) => { _pluginProvides.set(key, value) }, config: {} })
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Pre-load the current page's route chunk AFTER plugins run.
|
|
217
|
-
// This ensures cer-layout-view's first render (and its provide() calls) completes
|
|
218
|
-
// before page component modules are imported and their renders are scheduled.
|
|
219
|
-
if (typeof window !== 'undefined') {
|
|
220
|
-
const _initMatch = router.matchRoute(window.location.pathname)
|
|
221
|
-
if (_initMatch?.route?.load) {
|
|
222
|
-
try { await _initMatch.route.load() } catch { /* non-fatal */ }
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (typeof window !== 'undefined') {
|
|
227
|
-
await _replace(window.location.pathname + window.location.search + window.location.hash)
|
|
228
|
-
delete (globalThis as any).__CER_DATA__
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export { router }
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
> **Note:** Do not move the plugin loop before `component('cer-layout-view', …)`. The layout component must be defined first so that when plugins call `app.provide()`, the values are available to the component tree from the very first render. See [Plugins](plugins.md) for details.
|
|
136
|
+
> **Note:** The framework bootstrap lives in `.cer/app.ts` and is regenerated automatically on every dev server start and build. You never edit or own this file — updates to the plugin propagate to it immediately, just like Nuxt's `.nuxt/` directory.
|
|
235
137
|
|
|
236
138
|
---
|
|
237
139
|
|
package/docs/rendering-modes.md
CHANGED
|
@@ -93,8 +93,7 @@ export default handler
|
|
|
93
93
|
export default defineConfig({
|
|
94
94
|
mode: 'ssr',
|
|
95
95
|
ssr: {
|
|
96
|
-
dsd: true,
|
|
97
|
-
streaming: false, // true = stream response; false = buffer full HTML
|
|
96
|
+
dsd: true, // Declarative Shadow DOM (eliminates FOUC)
|
|
98
97
|
},
|
|
99
98
|
})
|
|
100
99
|
```
|
|
@@ -242,7 +241,7 @@ Any CDN or static host. Upload the entire `dist/` directory (excluding `dist/ser
|
|
|
242
241
|
| Dynamic routes | Yes | Yes | Requires `ssg.paths` |
|
|
243
242
|
| API routes | Separate deploy | Same process | Separate deploy |
|
|
244
243
|
| `useHead()` SSR injection | No | Yes | Yes |
|
|
245
|
-
| Streaming | No |
|
|
244
|
+
| Streaming | No | No | No |
|
|
246
245
|
|
|
247
246
|
---
|
|
248
247
|
|
package/package.json
CHANGED
|
@@ -48,7 +48,7 @@ function makeConfig(overrides: Partial<ResolvedCerConfig> = {}): ResolvedCerConf
|
|
|
48
48
|
serverApiDir: '/project/server/api',
|
|
49
49
|
serverMiddlewareDir: '/project/server/middleware',
|
|
50
50
|
port: 3000,
|
|
51
|
-
ssr: { dsd: true
|
|
51
|
+
ssr: { dsd: true },
|
|
52
52
|
ssg: { routes: 'auto', concurrency: 2, fallback: false },
|
|
53
53
|
router: {},
|
|
54
54
|
jitCss: { content: [], extendedColors: false },
|
|
@@ -17,7 +17,6 @@ import { existsSync, writeFileSync, mkdirSync, readFileSync, appendFileSync } fr
|
|
|
17
17
|
import {
|
|
18
18
|
GENERATED_DIR_NAME,
|
|
19
19
|
getGeneratedDir,
|
|
20
|
-
resolveAppEntry,
|
|
21
20
|
resolveHtmlEntry,
|
|
22
21
|
generateDefaultHtml,
|
|
23
22
|
writeGeneratedDir,
|
|
@@ -49,18 +48,6 @@ describe('getGeneratedDir', () => {
|
|
|
49
48
|
})
|
|
50
49
|
})
|
|
51
50
|
|
|
52
|
-
describe('resolveAppEntry', () => {
|
|
53
|
-
it('returns user app/app.ts when it exists', () => {
|
|
54
|
-
vi.mocked(existsSync).mockReturnValue(true)
|
|
55
|
-
expect(resolveAppEntry(mockConfig)).toBe(`${ROOT}/app/app.ts`)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('returns .cer/app.ts when user app/app.ts is absent', () => {
|
|
59
|
-
vi.mocked(existsSync).mockReturnValue(false)
|
|
60
|
-
expect(resolveAppEntry(mockConfig)).toBe(`${ROOT}/.cer/app.ts`)
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
51
|
describe('resolveHtmlEntry', () => {
|
|
65
52
|
it('returns user index.html when it exists', () => {
|
|
66
53
|
vi.mocked(existsSync).mockReturnValue(true)
|
|
@@ -74,26 +61,19 @@ describe('resolveHtmlEntry', () => {
|
|
|
74
61
|
})
|
|
75
62
|
|
|
76
63
|
describe('generateDefaultHtml', () => {
|
|
77
|
-
it('references /app
|
|
78
|
-
|
|
79
|
-
const html = generateDefaultHtml(mockConfig)
|
|
80
|
-
expect(html).toContain('/app/app.ts')
|
|
81
|
-
expect(html).not.toContain('/.cer/app.ts')
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('references /.cer/app.ts when user entry is absent', () => {
|
|
85
|
-
vi.mocked(existsSync).mockReturnValue(false)
|
|
86
|
-
const html = generateDefaultHtml(mockConfig)
|
|
64
|
+
it('always references /.cer/app.ts', () => {
|
|
65
|
+
const html = generateDefaultHtml()
|
|
87
66
|
expect(html).toContain('/.cer/app.ts')
|
|
67
|
+
expect(html).not.toContain('/app/app.ts')
|
|
88
68
|
})
|
|
89
69
|
|
|
90
70
|
it('includes <cer-layout-view> mount point', () => {
|
|
91
|
-
const html = generateDefaultHtml(
|
|
71
|
+
const html = generateDefaultHtml()
|
|
92
72
|
expect(html).toContain('<cer-layout-view>')
|
|
93
73
|
})
|
|
94
74
|
|
|
95
75
|
it('is valid HTML with doctype', () => {
|
|
96
|
-
const html = generateDefaultHtml(
|
|
76
|
+
const html = generateDefaultHtml()
|
|
97
77
|
expect(html).toContain('<!DOCTYPE html>')
|
|
98
78
|
})
|
|
99
79
|
})
|
|
@@ -112,28 +92,17 @@ describe('writeGeneratedDir', () => {
|
|
|
112
92
|
expect(mkdirSync).not.toHaveBeenCalled()
|
|
113
93
|
})
|
|
114
94
|
|
|
115
|
-
it('writes .cer/app.ts
|
|
116
|
-
// Only the .cer dir check needs to return true to skip mkdirSync — but we
|
|
117
|
-
// want the user entry check to return false. Use a counter.
|
|
118
|
-
let callCount = 0
|
|
119
|
-
vi.mocked(existsSync).mockImplementation(() => {
|
|
120
|
-
callCount++
|
|
121
|
-
// First call: .cer/ dir → true (already exists, skip mkdir)
|
|
122
|
-
// Second call: app/app.ts → false (absent, write template)
|
|
123
|
-
// Subsequent calls: false (no .gitignore)
|
|
124
|
-
return callCount === 1
|
|
125
|
-
})
|
|
95
|
+
it('always writes .cer/app.ts', () => {
|
|
126
96
|
writeGeneratedDir(mockConfig)
|
|
127
97
|
const paths = vi.mocked(writeFileSync).mock.calls.map(([p]) => String(p))
|
|
128
98
|
expect(paths.some(p => p.endsWith('/.cer/app.ts'))).toBe(true)
|
|
129
99
|
})
|
|
130
100
|
|
|
131
|
-
it('
|
|
132
|
-
// existsSync always returns true — dir exists, user entry exists
|
|
101
|
+
it('always writes .cer/app.ts even when .cer/ dir already exists', () => {
|
|
133
102
|
vi.mocked(existsSync).mockReturnValue(true)
|
|
134
103
|
writeGeneratedDir(mockConfig)
|
|
135
104
|
const paths = vi.mocked(writeFileSync).mock.calls.map(([p]) => String(p))
|
|
136
|
-
expect(paths.some(p => p.endsWith('/.cer/app.ts'))).toBe(
|
|
105
|
+
expect(paths.some(p => p.endsWith('/.cer/app.ts'))).toBe(true)
|
|
137
106
|
})
|
|
138
107
|
|
|
139
108
|
it('always writes .cer/index.html', () => {
|
|
@@ -84,11 +84,6 @@ describe('resolveConfig', () => {
|
|
|
84
84
|
expect(cfg.ssr.dsd).toBe(false)
|
|
85
85
|
})
|
|
86
86
|
|
|
87
|
-
it('defaults ssr.streaming to false', () => {
|
|
88
|
-
const cfg = resolveConfig({}, ROOT)
|
|
89
|
-
expect(cfg.ssr.streaming).toBe(false)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
87
|
it('defaults ssg.routes to "auto"', () => {
|
|
93
88
|
const cfg = resolveConfig({}, ROOT)
|
|
94
89
|
expect(cfg.ssg.routes).toBe('auto')
|
package/src/cli/create/index.ts
CHANGED
|
@@ -179,6 +179,10 @@ async function generateInlineTemplate(
|
|
|
179
179
|
): Promise<void> {
|
|
180
180
|
await mkdir(join(targetDir, 'app/pages'), { recursive: true })
|
|
181
181
|
await mkdir(join(targetDir, 'app/layouts'), { recursive: true })
|
|
182
|
+
await mkdir(join(targetDir, 'app/components'), { recursive: true })
|
|
183
|
+
await mkdir(join(targetDir, 'app/composables'), { recursive: true })
|
|
184
|
+
await mkdir(join(targetDir, 'app/plugins'), { recursive: true })
|
|
185
|
+
await mkdir(join(targetDir, 'app/middleware'), { recursive: true })
|
|
182
186
|
|
|
183
187
|
// package.json
|
|
184
188
|
await writeFile(
|
|
@@ -215,13 +219,6 @@ async function generateInlineTemplate(
|
|
|
215
219
|
'utf-8',
|
|
216
220
|
)
|
|
217
221
|
|
|
218
|
-
// app/app.ts
|
|
219
|
-
await writeFile(
|
|
220
|
-
join(targetDir, 'app/app.ts'),
|
|
221
|
-
`import '@jasonshimmy/custom-elements-runtime/css'\nimport 'virtual:cer-jit-css'\nimport 'virtual:cer-components'\nimport routes from 'virtual:cer-routes'\nimport layouts from 'virtual:cer-layouts'\nimport plugins from 'virtual:cer-plugins'\nimport { hasLoading, loadingTag } from 'virtual:cer-loading'\nimport { hasError, errorTag } from 'virtual:cer-error'\nimport { component, ref, provide, useOnConnected, useOnDisconnected, registerBuiltinComponents } from '@jasonshimmy/custom-elements-runtime'\nimport { initRouter } from '@jasonshimmy/custom-elements-runtime/router'\nimport { enableJITCSS } from '@jasonshimmy/custom-elements-runtime/jit-css'\n\nregisterBuiltinComponents()\nenableJITCSS()\n\nconst router = initRouter({ routes })\n\nconst isNavigating = ref(false)\nconst currentError = ref(null)\n;(globalThis as any).resetError = () => { currentError.value = null; router.replace(router.getCurrent().path) }\nconst _push = router.push.bind(router)\nconst _replace = router.replace.bind(router)\nrouter.push = async (path) => { isNavigating.value = true; currentError.value = null; try { await _push(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false } }\nrouter.replace = async (path) => { isNavigating.value = true; currentError.value = null; try { await _replace(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false } }\n\nconst _pluginProvides = new Map()\n;(globalThis as any).__cerPluginProvides = _pluginProvides\n\ncomponent('cer-layout-view', () => {\n for (const [key, value] of _pluginProvides) { provide(key, value) }\n const current = ref(router.getCurrent())\n let unsub: (() => void) | undefined\n useOnConnected(() => { unsub = router.subscribe((s: typeof current.value) => { current.value = s }) })\n useOnDisconnected(() => { unsub?.(); unsub = undefined })\n if (currentError.value !== null) {\n if (hasError && errorTag) return { tag: errorTag, props: { attrs: { error: String(currentError.value) } }, children: [] }\n return { tag: 'div', props: { attrs: { style: 'padding:2rem;font-family:monospace' } }, children: [String(currentError.value)] }\n }\n if (isNavigating.value && hasLoading && loadingTag) return { tag: loadingTag, props: {}, children: [] }\n const matched = router.matchRoute(current.value.path)\n const layoutName = (matched?.route as any)?.meta?.layout ?? 'default'\n const layoutTag = (layouts as Record<string, string>)[layoutName]\n const routerView = { tag: 'router-view', props: {}, children: [] }\n return layoutTag ? { tag: layoutTag, props: {}, children: [routerView] } : routerView\n})\n\nfor (const plugin of plugins ?? []) {\n if (plugin && typeof plugin.setup === 'function') {\n await plugin.setup({ router, provide: (key: string, value: unknown) => { _pluginProvides.set(key, value) }, config: {} })\n }\n}\n\nif (typeof window !== 'undefined') {\n const _initMatch = router.matchRoute(window.location.pathname)\n if (_initMatch?.route?.load) {\n try { await _initMatch.route.load() } catch { /* non-fatal */ }\n }\n}\n\nif (typeof window !== 'undefined') {\n await _replace(window.location.pathname + window.location.search + window.location.hash)\n delete (globalThis as any).__CER_DATA__\n}\n\nexport { router }\n`,
|
|
222
|
-
'utf-8',
|
|
223
|
-
)
|
|
224
|
-
|
|
225
222
|
// app/pages/index.ts
|
|
226
223
|
await writeFile(
|
|
227
224
|
join(targetDir, 'app/pages/index.ts'),
|
|
@@ -236,10 +233,17 @@ async function generateInlineTemplate(
|
|
|
236
233
|
'utf-8',
|
|
237
234
|
)
|
|
238
235
|
|
|
236
|
+
// .gitignore
|
|
237
|
+
await writeFile(
|
|
238
|
+
join(targetDir, '.gitignore'),
|
|
239
|
+
`# Dependencies\nnode_modules/\n\n# Build output\ndist/\n\n# CER App generated directory\n.cer/\n\n# Environment variables\n.env.local\n.env.*.local\n\n# Editor\n.vscode/\n.idea/\n*.suo\n*.sw?\n\n# OS\n.DS_Store\nThumbs.db\n\n# Logs\n*.log\n`,
|
|
240
|
+
'utf-8',
|
|
241
|
+
)
|
|
242
|
+
|
|
239
243
|
// index.html
|
|
240
244
|
await writeFile(
|
|
241
245
|
join(targetDir, 'index.html'),
|
|
242
|
-
`<!DOCTYPE html>\n<html lang="en">\n <head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <title>${projectName}</title>\n </head>\n <body>\n <cer-layout-view></cer-layout-view>\n <script type="module" src="/app
|
|
246
|
+
`<!DOCTYPE html>\n<html lang="en">\n <head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <title>${projectName}</title>\n </head>\n <body>\n <cer-layout-view></cer-layout-view>\n <script type="module" src="/.cer/app.ts"></script>\n </body>\n</html>\n`,
|
|
243
247
|
'utf-8',
|
|
244
248
|
)
|
|
245
249
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
|
|
7
|
+
# CER App generated directory
|
|
8
|
+
.cer/
|
|
9
|
+
|
|
10
|
+
# Environment variables
|
|
11
|
+
.env.local
|
|
12
|
+
.env.*.local
|
|
13
|
+
|
|
14
|
+
# Editor
|
|
15
|
+
.vscode/
|
|
16
|
+
.idea/
|
|
17
|
+
*.suo
|
|
18
|
+
*.sw?
|
|
19
|
+
|
|
20
|
+
# OS
|
|
21
|
+
.DS_Store
|
|
22
|
+
Thumbs.db
|
|
23
|
+
|
|
24
|
+
# Logs
|
|
25
|
+
*.log
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
|
|
7
|
+
# CER App generated directory
|
|
8
|
+
.cer/
|
|
9
|
+
|
|
10
|
+
# Environment variables
|
|
11
|
+
.env.local
|
|
12
|
+
.env.*.local
|
|
13
|
+
|
|
14
|
+
# Editor
|
|
15
|
+
.vscode/
|
|
16
|
+
.idea/
|
|
17
|
+
*.suo
|
|
18
|
+
*.sw?
|
|
19
|
+
|
|
20
|
+
# OS
|
|
21
|
+
.DS_Store
|
|
22
|
+
Thumbs.db
|
|
23
|
+
|
|
24
|
+
# Logs
|
|
25
|
+
*.log
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
|
|
7
|
+
# CER App generated directory
|
|
8
|
+
.cer/
|
|
9
|
+
|
|
10
|
+
# Environment variables
|
|
11
|
+
.env.local
|
|
12
|
+
.env.*.local
|
|
13
|
+
|
|
14
|
+
# Editor
|
|
15
|
+
.vscode/
|
|
16
|
+
.idea/
|
|
17
|
+
*.suo
|
|
18
|
+
*.sw?
|
|
19
|
+
|
|
20
|
+
# OS
|
|
21
|
+
.DS_Store
|
|
22
|
+
Thumbs.db
|
|
23
|
+
|
|
24
|
+
# Logs
|
|
25
|
+
*.log
|
package/src/plugin/dev-server.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface ResolvedCerConfig {
|
|
|
15
15
|
serverApiDir: string
|
|
16
16
|
serverMiddlewareDir: string
|
|
17
17
|
port: number
|
|
18
|
-
ssr: { dsd: boolean
|
|
18
|
+
ssr: { dsd: boolean }
|
|
19
19
|
ssg: { routes: 'auto' | string[]; concurrency: number; fallback: boolean }
|
|
20
20
|
router: { base?: string; scrollToFragment?: boolean | object }
|
|
21
21
|
jitCss: { content: string[]; extendedColors: boolean }
|
|
@@ -13,16 +13,6 @@ export function getGeneratedDir(root: string): string {
|
|
|
13
13
|
return join(root, GENERATED_DIR_NAME)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Returns the app entry file path to use for builds and the dev server.
|
|
18
|
-
* Prefers the consumer's `app/app.ts` when it exists; falls back to `.cer/app.ts`.
|
|
19
|
-
*/
|
|
20
|
-
export function resolveAppEntry(config: ResolvedCerConfig): string {
|
|
21
|
-
const userEntry = join(config.srcDir, 'app.ts')
|
|
22
|
-
if (existsSync(userEntry)) return userEntry
|
|
23
|
-
return join(getGeneratedDir(config.root), 'app.ts')
|
|
24
|
-
}
|
|
25
|
-
|
|
26
16
|
/**
|
|
27
17
|
* Returns the HTML entry path to use for builds.
|
|
28
18
|
* Prefers the consumer's root-level `index.html` when it exists;
|
|
@@ -35,13 +25,10 @@ export function resolveHtmlEntry(config: ResolvedCerConfig): string {
|
|
|
35
25
|
}
|
|
36
26
|
|
|
37
27
|
/**
|
|
38
|
-
* Generates the content for
|
|
39
|
-
*
|
|
40
|
-
* otherwise to the generated `.cer/app.ts`.
|
|
28
|
+
* Generates the content for the default `.cer/index.html`.
|
|
29
|
+
* Always points to the generated `/.cer/app.ts` entry.
|
|
41
30
|
*/
|
|
42
|
-
export function generateDefaultHtml(
|
|
43
|
-
const userEntry = join(config.srcDir, 'app.ts')
|
|
44
|
-
const scriptSrc = existsSync(userEntry) ? '/app/app.ts' : '/.cer/app.ts'
|
|
31
|
+
export function generateDefaultHtml(): string {
|
|
45
32
|
return `<!DOCTYPE html>
|
|
46
33
|
<html lang="en">
|
|
47
34
|
<head>
|
|
@@ -51,34 +38,61 @@ export function generateDefaultHtml(config: ResolvedCerConfig): string {
|
|
|
51
38
|
</head>
|
|
52
39
|
<body>
|
|
53
40
|
<cer-layout-view></cer-layout-view>
|
|
54
|
-
<script type="module" src="
|
|
41
|
+
<script type="module" src="/.cer/app.ts"></script>
|
|
55
42
|
</body>
|
|
56
43
|
</html>
|
|
57
44
|
`
|
|
58
45
|
}
|
|
59
46
|
|
|
47
|
+
const GITIGNORE_DEFAULTS = `# Dependencies
|
|
48
|
+
node_modules/
|
|
49
|
+
|
|
50
|
+
# Build output
|
|
51
|
+
dist/
|
|
52
|
+
|
|
53
|
+
# CER App generated directory
|
|
54
|
+
.cer/
|
|
55
|
+
|
|
56
|
+
# Environment variables
|
|
57
|
+
.env.local
|
|
58
|
+
.env.*.local
|
|
59
|
+
|
|
60
|
+
# Editor
|
|
61
|
+
.vscode/
|
|
62
|
+
.idea/
|
|
63
|
+
*.suo
|
|
64
|
+
*.sw?
|
|
65
|
+
|
|
66
|
+
# OS
|
|
67
|
+
.DS_Store
|
|
68
|
+
Thumbs.db
|
|
69
|
+
|
|
70
|
+
# Logs
|
|
71
|
+
*.log
|
|
72
|
+
`
|
|
73
|
+
|
|
60
74
|
/**
|
|
61
|
-
* Ensures `.cer
|
|
62
|
-
* Creates `.gitignore` if it does not exist.
|
|
75
|
+
* Ensures `.cer/`, `node_modules/`, `dist/`, and other common entries are
|
|
76
|
+
* listed in the project's `.gitignore`. Creates `.gitignore` if it does not exist.
|
|
63
77
|
*/
|
|
64
78
|
function ensureGitignore(root: string): void {
|
|
65
79
|
const gitignorePath = join(root, '.gitignore')
|
|
66
|
-
const
|
|
80
|
+
const cerEntry = `${GENERATED_DIR_NAME}/`
|
|
67
81
|
|
|
68
82
|
if (existsSync(gitignorePath)) {
|
|
69
83
|
const content = readFileSync(gitignorePath, 'utf-8')
|
|
70
|
-
if (!content.includes(
|
|
71
|
-
appendFileSync(gitignorePath, `\n# CER App generated directory\n${
|
|
84
|
+
if (!content.includes(cerEntry) && !content.includes(`${GENERATED_DIR_NAME}\n`)) {
|
|
85
|
+
appendFileSync(gitignorePath, `\n# CER App generated directory\n${cerEntry}\n`)
|
|
72
86
|
}
|
|
73
87
|
} else {
|
|
74
|
-
writeFileSync(gitignorePath,
|
|
88
|
+
writeFileSync(gitignorePath, GITIGNORE_DEFAULTS)
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
|
|
78
92
|
/**
|
|
79
93
|
* Writes all generated files to `.cer/`:
|
|
80
|
-
* - `.cer/app.ts`
|
|
81
|
-
* - `.cer/index.html`
|
|
94
|
+
* - `.cer/app.ts` — framework entry (always regenerated)
|
|
95
|
+
* - `.cer/index.html` — default HTML shell (always regenerated)
|
|
82
96
|
* - `.cer/tsconfig.json` — written by dts-generator via writeTsconfigPaths
|
|
83
97
|
*
|
|
84
98
|
* Also ensures `.cer/` is listed in `.gitignore`.
|
|
@@ -89,14 +103,13 @@ export function writeGeneratedDir(config: ResolvedCerConfig): void {
|
|
|
89
103
|
mkdirSync(dir, { recursive: true })
|
|
90
104
|
}
|
|
91
105
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
106
|
+
// Always write the generated app.ts — this is the framework entry point and
|
|
107
|
+
// is never user-owned. Regenerating it on every dev/build ensures consumers
|
|
108
|
+
// automatically get the latest bootstrap code on plugin update (Nuxt-style).
|
|
109
|
+
writeFileSync(join(dir, 'app.ts'), APP_ENTRY_TEMPLATE, 'utf-8')
|
|
97
110
|
|
|
98
111
|
// Always write the default index.html so builds and the dev server can use it.
|
|
99
|
-
writeFileSync(join(dir, 'index.html'), generateDefaultHtml(
|
|
112
|
+
writeFileSync(join(dir, 'index.html'), generateDefaultHtml(), 'utf-8')
|
|
100
113
|
|
|
101
114
|
ensureGitignore(config.root)
|
|
102
115
|
}
|
package/src/plugin/index.ts
CHANGED
|
@@ -62,7 +62,6 @@ export function resolveConfig(userConfig: CerAppConfig, root: string = process.c
|
|
|
62
62
|
port: userConfig.port ?? 3000,
|
|
63
63
|
ssr: {
|
|
64
64
|
dsd: userConfig.ssr?.dsd ?? true,
|
|
65
|
-
streaming: userConfig.ssr?.streaming ?? false,
|
|
66
65
|
},
|
|
67
66
|
ssg: {
|
|
68
67
|
routes: userConfig.ssg?.routes ?? 'auto',
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Template string for
|
|
2
|
+
* Template string for `.cer/app.ts` — the framework client entry point.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Always written to `.cer/app.ts` on every dev/build so consumers
|
|
5
|
+
* automatically receive the latest bootstrap code on plugin update.
|
|
6
|
+
* This file is gitignored and should never be edited directly.
|
|
6
7
|
*/
|
|
7
|
-
export const APP_ENTRY_TEMPLATE = `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app
|
|
8
|
-
//
|
|
8
|
+
export const APP_ENTRY_TEMPLATE = `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app — do not edit.
|
|
9
|
+
// Regenerated automatically on every dev server start and build.
|
|
9
10
|
|
|
10
11
|
import '@jasonshimmy/custom-elements-runtime/css'
|
|
11
12
|
import 'virtual:cer-jit-css'
|