@jasonshimmy/vite-plugin-cer-app 0.1.6 → 0.2.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/.github/workflows/publish.yml +56 -5
- package/CHANGELOG.md +4 -0
- package/commits.txt +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +19 -5
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/dev.js +1 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/preview.d.ts.map +1 -1
- package/dist/cli/commands/preview.js +0 -1
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/plugin/build-ssg.d.ts.map +1 -1
- package/dist/plugin/build-ssg.js.map +1 -1
- package/dist/plugin/build-ssr.d.ts +10 -0
- package/dist/plugin/build-ssr.d.ts.map +1 -1
- package/dist/plugin/build-ssr.js +21 -8
- package/dist/plugin/build-ssr.js.map +1 -1
- package/dist/plugin/dev-server.d.ts.map +1 -1
- package/dist/plugin/dev-server.js +0 -2
- package/dist/plugin/dev-server.js.map +1 -1
- package/dist/plugin/dts-generator.d.ts +4 -4
- package/dist/plugin/dts-generator.d.ts.map +1 -1
- package/dist/plugin/dts-generator.js +39 -19
- package/dist/plugin/dts-generator.js.map +1 -1
- package/dist/plugin/generated-dir.d.ts +34 -0
- package/dist/plugin/generated-dir.d.ts.map +1 -0
- package/dist/plugin/generated-dir.js +94 -0
- package/dist/plugin/generated-dir.js.map +1 -0
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +27 -0
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/path-utils.js.map +1 -1
- package/dist/plugin/virtual/loading.d.ts.map +1 -1
- package/dist/plugin/virtual/loading.js.map +1 -1
- package/dist/runtime/app-template.d.ts +8 -0
- package/dist/runtime/app-template.d.ts.map +1 -0
- package/dist/runtime/app-template.js +158 -0
- package/dist/runtime/app-template.js.map +1 -0
- package/docs/configuration.md +2 -2
- package/docs/rendering-modes.md +2 -2
- package/docs/routing.md +1 -1
- package/e2e/kitchen-sink/tsconfig.json +3 -0
- package/eslint.config.ts +22 -0
- package/package.json +6 -1
- package/src/__tests__/plugin/build-ssr.test.ts +24 -10
- package/src/__tests__/plugin/cer-app-plugin.test.ts +35 -0
- package/src/__tests__/plugin/dts-generator.test.ts +15 -6
- package/src/__tests__/plugin/generated-dir.test.ts +168 -0
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/dev.ts +2 -2
- package/src/cli/commands/preview.ts +7 -5
- package/src/plugin/build-ssg.ts +2 -2
- package/src/plugin/build-ssr.ts +22 -8
- package/src/plugin/dev-server.ts +4 -3
- package/src/plugin/dts-generator.ts +43 -19
- package/src/plugin/generated-dir.ts +102 -0
- package/src/plugin/index.ts +32 -1
- package/src/plugin/path-utils.ts +1 -1
- package/src/plugin/virtual/loading.ts +0 -1
- package/{e2e/kitchen-sink/app/app.ts → src/runtime/app-template.ts} +23 -7
- package/e2e/kitchen-sink/index.html +0 -12
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { writeFileSync, existsSync, mkdirSync, readFileSync, appendFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'pathe'
|
|
3
|
+
import type { ResolvedCerConfig } from './dev-server.js'
|
|
4
|
+
import { APP_ENTRY_TEMPLATE } from '../runtime/app-template.js'
|
|
5
|
+
|
|
6
|
+
/** The name of the generated directory relative to the project root. */
|
|
7
|
+
export const GENERATED_DIR_NAME = '.cer'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns the absolute path to the .cer/ generated directory.
|
|
11
|
+
*/
|
|
12
|
+
export function getGeneratedDir(root: string): string {
|
|
13
|
+
return join(root, GENERATED_DIR_NAME)
|
|
14
|
+
}
|
|
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
|
+
/**
|
|
27
|
+
* Returns the HTML entry path to use for builds.
|
|
28
|
+
* Prefers the consumer's root-level `index.html` when it exists;
|
|
29
|
+
* falls back to `.cer/index.html`.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveHtmlEntry(config: ResolvedCerConfig): string {
|
|
32
|
+
const userHtml = join(config.root, 'index.html')
|
|
33
|
+
if (existsSync(userHtml)) return userHtml
|
|
34
|
+
return join(getGeneratedDir(config.root), 'index.html')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generates the content for a default `index.html`.
|
|
39
|
+
* The script src points to the consumer's `app/app.ts` if it exists,
|
|
40
|
+
* otherwise to the generated `.cer/app.ts`.
|
|
41
|
+
*/
|
|
42
|
+
export function generateDefaultHtml(config: ResolvedCerConfig): string {
|
|
43
|
+
const userEntry = join(config.srcDir, 'app.ts')
|
|
44
|
+
const scriptSrc = existsSync(userEntry) ? '/app/app.ts' : '/.cer/app.ts'
|
|
45
|
+
return `<!DOCTYPE html>
|
|
46
|
+
<html lang="en">
|
|
47
|
+
<head>
|
|
48
|
+
<meta charset="UTF-8" />
|
|
49
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
50
|
+
<title>CER App</title>
|
|
51
|
+
</head>
|
|
52
|
+
<body>
|
|
53
|
+
<cer-layout-view></cer-layout-view>
|
|
54
|
+
<script type="module" src="${scriptSrc}"></script>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
57
|
+
`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Ensures `.cer/` is listed in the project's `.gitignore`.
|
|
62
|
+
* Creates `.gitignore` if it does not exist.
|
|
63
|
+
*/
|
|
64
|
+
function ensureGitignore(root: string): void {
|
|
65
|
+
const gitignorePath = join(root, '.gitignore')
|
|
66
|
+
const entry = `${GENERATED_DIR_NAME}/`
|
|
67
|
+
|
|
68
|
+
if (existsSync(gitignorePath)) {
|
|
69
|
+
const content = readFileSync(gitignorePath, 'utf-8')
|
|
70
|
+
if (!content.includes(entry) && !content.includes(`${GENERATED_DIR_NAME}\n`)) {
|
|
71
|
+
appendFileSync(gitignorePath, `\n# CER App generated directory\n${entry}\n`)
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
writeFileSync(gitignorePath, `# CER App generated directory\n${entry}\n`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Writes all generated files to `.cer/`:
|
|
80
|
+
* - `.cer/app.ts` — default entry (only when `app/app.ts` does not exist)
|
|
81
|
+
* - `.cer/index.html` — default HTML shell
|
|
82
|
+
* - `.cer/tsconfig.json` — written by dts-generator via writeTsconfigPaths
|
|
83
|
+
*
|
|
84
|
+
* Also ensures `.cer/` is listed in `.gitignore`.
|
|
85
|
+
*/
|
|
86
|
+
export function writeGeneratedDir(config: ResolvedCerConfig): void {
|
|
87
|
+
const dir = getGeneratedDir(config.root)
|
|
88
|
+
if (!existsSync(dir)) {
|
|
89
|
+
mkdirSync(dir, { recursive: true })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Write default app.ts only when the consumer has not provided one.
|
|
93
|
+
const userEntry = join(config.srcDir, 'app.ts')
|
|
94
|
+
if (!existsSync(userEntry)) {
|
|
95
|
+
writeFileSync(join(dir, 'app.ts'), APP_ENTRY_TEMPLATE, 'utf-8')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Always write the default index.html so builds and the dev server can use it.
|
|
99
|
+
writeFileSync(join(dir, 'index.html'), generateDefaultHtml(config), 'utf-8')
|
|
100
|
+
|
|
101
|
+
ensureGitignore(config.root)
|
|
102
|
+
}
|
package/src/plugin/index.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { resolve, join } from 'pathe'
|
|
2
|
-
import
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
3
|
+
import type { Plugin, ViteDevServer } from 'vite'
|
|
3
4
|
import type { CerAppConfig } from '../types/config.js'
|
|
4
5
|
import type { ResolvedCerConfig } from './dev-server.js'
|
|
5
6
|
import { cerPlugin } from '@jasonshimmy/custom-elements-runtime/vite-plugin'
|
|
6
7
|
import { autoImportTransform } from './transforms/auto-import.js'
|
|
7
8
|
import { scanComposableExports, writeAutoImportDts, writeTsconfigPaths } from './dts-generator.js'
|
|
8
9
|
import { configureCerDevServer } from './dev-server.js'
|
|
10
|
+
import { writeGeneratedDir, getGeneratedDir } from './generated-dir.js'
|
|
9
11
|
import { generateRoutesCode } from './virtual/routes.js'
|
|
10
12
|
import { generateLayoutsCode } from './virtual/layouts.js'
|
|
11
13
|
import { generateComponentsCode } from './virtual/components.js'
|
|
@@ -256,11 +258,38 @@ export function cerApp(userConfig: CerAppConfig = {}): Plugin[] {
|
|
|
256
258
|
// config might not be set yet; resolve with cwd
|
|
257
259
|
config = resolveConfig(userConfig, process.cwd())
|
|
258
260
|
}
|
|
261
|
+
|
|
262
|
+
// Write .cer/ generated files (app.ts fallback, index.html, .gitignore)
|
|
263
|
+
writeGeneratedDir(config)
|
|
264
|
+
|
|
259
265
|
// Scan composables and write .d.ts + tsconfig paths on dev server start
|
|
260
266
|
composableExports = await scanComposableExports(config.composablesDir)
|
|
261
267
|
await writeAutoImportDts(config.root, config.composablesDir, composableExports)
|
|
262
268
|
writeTsconfigPaths(config.root, config.srcDir)
|
|
263
269
|
|
|
270
|
+
// Serve a generated index.html for HTML requests when the consumer has
|
|
271
|
+
// not provided one. This runs BEFORE configureCerDevServer so that the
|
|
272
|
+
// Vite HTML pipeline (HMR injection, module preprocessing) is applied.
|
|
273
|
+
const userHtml = resolve(config.root, 'index.html')
|
|
274
|
+
if (!existsSync(userHtml)) {
|
|
275
|
+
const cerHtmlPath = join(getGeneratedDir(config.root), 'index.html')
|
|
276
|
+
server.middlewares.use(async (req, res, next) => {
|
|
277
|
+
const url = (req as { url?: string }).url ?? '/'
|
|
278
|
+
const isHtmlRequest =
|
|
279
|
+
url === '/' ||
|
|
280
|
+
url === '/index.html' ||
|
|
281
|
+
(!url.includes('.') && !url.startsWith('/api/'))
|
|
282
|
+
if (isHtmlRequest && existsSync(cerHtmlPath)) {
|
|
283
|
+
const rawHtml = readFileSync(cerHtmlPath, 'utf-8')
|
|
284
|
+
const transformed = await server.transformIndexHtml(url, rawHtml)
|
|
285
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
286
|
+
res.end(transformed)
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
next()
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
264
293
|
// Watch app/ and server/ directories for file changes
|
|
265
294
|
const watchDirs = [
|
|
266
295
|
config.pagesDir,
|
|
@@ -302,6 +331,8 @@ export function cerApp(userConfig: CerAppConfig = {}): Plugin[] {
|
|
|
302
331
|
if (!config) {
|
|
303
332
|
config = resolveConfig(userConfig, process.cwd())
|
|
304
333
|
}
|
|
334
|
+
// Write .cer/ generated files before the build begins
|
|
335
|
+
writeGeneratedDir(config)
|
|
305
336
|
// Scan composables and generate type declarations + tsconfig paths
|
|
306
337
|
composableExports = await scanComposableExports(config.composablesDir)
|
|
307
338
|
await writeAutoImportDts(config.root, config.composablesDir, composableExports)
|
package/src/plugin/path-utils.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template string for the default `.cer/app.ts` client entry point.
|
|
3
|
+
*
|
|
4
|
+
* Written to `.cer/app.ts` when the consumer does not provide `app/app.ts`.
|
|
5
|
+
* Consumers can override by creating their own `app/app.ts`.
|
|
6
|
+
*/
|
|
7
|
+
export const APP_ENTRY_TEMPLATE = `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app
|
|
8
|
+
// This is the default client entry point. Create app/app.ts to override it.
|
|
9
|
+
|
|
1
10
|
import '@jasonshimmy/custom-elements-runtime/css'
|
|
2
11
|
import 'virtual:cer-jit-css'
|
|
3
12
|
import 'virtual:cer-components'
|
|
@@ -27,10 +36,11 @@ const router = initRouter({ routes })
|
|
|
27
36
|
const isNavigating = ref(false)
|
|
28
37
|
const currentError = ref(null)
|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
const resetError = (): void => {
|
|
31
40
|
currentError.value = null
|
|
32
|
-
router.replace(router.getCurrent().path)
|
|
41
|
+
void router.replace(router.getCurrent().path)
|
|
33
42
|
}
|
|
43
|
+
;(globalThis as Record<string, unknown>).resetError = resetError
|
|
34
44
|
|
|
35
45
|
const _push = router.push.bind(router)
|
|
36
46
|
const _replace = router.replace.bind(router)
|
|
@@ -68,8 +78,8 @@ router.replace = async (path) => {
|
|
|
68
78
|
// synchronously, calling the render function immediately.
|
|
69
79
|
const _pluginProvides = new Map<string, unknown>()
|
|
70
80
|
// Expose plugin provides globally so page components can read them synchronously
|
|
71
|
-
// regardless of render order
|
|
72
|
-
;(globalThis as
|
|
81
|
+
// regardless of render order.
|
|
82
|
+
;(globalThis as Record<string, unknown>).__cerPluginProvides = _pluginProvides
|
|
73
83
|
|
|
74
84
|
// ─── <cer-layout-view> ───────────────────────────────────────────────────────
|
|
75
85
|
|
|
@@ -100,7 +110,8 @@ component('cer-layout-view', () => {
|
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
const matched = router.matchRoute(current.value.path)
|
|
103
|
-
const
|
|
113
|
+
const routeMeta = matched?.route?.meta as { layout?: string } | undefined
|
|
114
|
+
const layoutName = routeMeta?.layout ?? 'default'
|
|
104
115
|
const layoutTag = (layouts as Record<string, string>)[layoutName]
|
|
105
116
|
const routerView = { tag: 'router-view', props: {}, children: [] }
|
|
106
117
|
|
|
@@ -110,7 +121,11 @@ component('cer-layout-view', () => {
|
|
|
110
121
|
|
|
111
122
|
for (const plugin of plugins) {
|
|
112
123
|
if (plugin && typeof plugin.setup === 'function') {
|
|
113
|
-
await plugin.setup({
|
|
124
|
+
await plugin.setup({
|
|
125
|
+
router,
|
|
126
|
+
provide: (key: string, value: unknown) => { _pluginProvides.set(key, value) },
|
|
127
|
+
config: {},
|
|
128
|
+
})
|
|
114
129
|
}
|
|
115
130
|
}
|
|
116
131
|
|
|
@@ -135,7 +150,8 @@ if (typeof window !== 'undefined') {
|
|
|
135
150
|
await _replace(window.location.pathname + window.location.search + window.location.hash)
|
|
136
151
|
// Clear SSR loader data after initial navigation so subsequent client-side
|
|
137
152
|
// navigations don't accidentally reuse stale server data.
|
|
138
|
-
delete (globalThis as
|
|
153
|
+
delete (globalThis as Record<string, unknown>).__CER_DATA__
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
export { router }
|
|
157
|
+
`
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>Kitchen Sink</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<cer-layout-view></cer-layout-view>
|
|
10
|
-
<script type="module" src="/app/app.ts"></script>
|
|
11
|
-
</body>
|
|
12
|
-
</html>
|