@bndynet/vue-site 1.2.1 → 1.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/README.md CHANGED
@@ -10,6 +10,7 @@ Configurable Vue 3 site framework: one package, `site.config.ts`, and Markdown p
10
10
  - Hash or HTML5 (`web`) router history, configurable in `site.config.ts`
11
11
  - Markdown (`?raw`) or Vue pages
12
12
  - highlight.js, light/dark theme + localStorage
13
+ - Custom shell actions for avatars, notifications, help menus, and other app controls
13
14
  - Built-in multi-language support (locale switcher, `LocalizedString` config, per-locale pages) — see [Internationalization](#internationalization-i18n)
14
15
  - Project `README.md` as Home
15
16
  - Full TypeScript types
@@ -29,6 +30,7 @@ import { defineConfig } from '@bndynet/vue-site'
29
30
 
30
31
  export default defineConfig({
31
32
  title: 'My Project',
33
+ favicon: '/favicon.ico',
32
34
  nav: [
33
35
  { label: 'Home', icon: 'home', page: () => import('./README.md?raw') },
34
36
  { label: 'Guide', icon: 'book-open', page: () => import('./pages/guide.md?raw') },
@@ -42,6 +44,7 @@ export default defineConfig({
42
44
  my-site/
43
45
  package.json
44
46
  site.config.ts
47
+ public/favicon.ico
45
48
  README.md
46
49
  pages/guide.md
47
50
  ```
@@ -72,13 +75,16 @@ Add `"dev": "vue-site dev"` (or `vs dev`) in `package.json` scripts if you like.
72
75
  |----------|-------------|
73
76
  | `title` | Site title (sidebar + tab). `LocalizedString` |
74
77
  | `nav` | `NavItem[]` |
78
+ | `navPosition` | Optional navigation placement: `'top'` forces the top/tiered layout, `'sidebar'` forces the full nav tree into the sidebar. Omit to keep automatic behavior based on nav depth |
75
79
  | `defaultPath` | Path the site opens at; `/` and unknown paths redirect here. Must match a registered route (a `nav` item's resolved path or a `pages` entry's `path`). Defaults to the first top-level `nav` item |
80
+ | `favicon` | Browser tab icon URL. Use a public asset path, remote URL, or imported asset URL |
76
81
  | `logo` | Logo URL or imported image |
77
82
  | `theme` | See `ThemeConfig` below; set to `false` to disable theming (hides the switcher, forces a fixed `light` palette, no localStorage persistence) |
78
83
  | `i18n` | Multi-language config (`I18nConfig`) — see [Internationalization](#internationalization-i18n) |
79
84
  | `footer` | Footer text. `LocalizedString` |
80
85
  | `readme` | Raw Home content if no `README.md` |
81
86
  | `links` | Header links: Lucide `icon` + `link`, optional `title` (`LocalizedString`) |
87
+ | `shell` | Shell action area config (`ShellConfig`) — see [Shell actions](#shell-actions-shellactions) |
82
88
  | `pages` | `StandalonePage[]` — full-screen routes outside the `nav` tree (no top bar/sidebar/footer) |
83
89
  | `auth` | Central authorization policy (`AuthConfig`) — see [Per-page authorization](#per-page-authorization-auth) |
84
90
  | `router` | History mode (`RouterConfig`) — `hash` (default) or HTML5 `web`; see [Router history](#router-history-router) |
@@ -95,11 +101,36 @@ Add `"dev": "vue-site dev"` (or `vs dev`) in `package.json` scripts if you like.
95
101
  | `icon` | [Lucide](https://lucide.dev/icons) name |
96
102
  | `page` | Page content. Simplest is a **file-path string** like `'./pages/AdminView.vue'` or `'./README.md'` (auto-loads per-locale siblings, falls back to the base file). Also accepts a loader (`() => import('./Page.vue')` / `() => import('./page.md?raw')`) or a `localizedPage(...)` result. See [Per-locale page content](#per-locale-page-content) and [Advanced page loaders](#advanced-page-loaders) |
97
103
  | `path` | Route path (derived from `label`'s default-locale value if omitted; stays stable across languages) |
104
+ | `layout` | Page content width: `'default'` keeps the standard centered reading width, `'wide'` uses a wider centered canvas, `'full'` fills the available parent container. Ignored for groups and links. |
98
105
  | `children` | Nested group |
99
106
  | `link` | Render as a hyperlink (internal route path or external URL) instead of a page route |
100
107
  | `visible` | `() => boolean \| Promise<boolean>`, awaited once at startup. Return `false` to hide the item from the nav and skip its route (not reachable by direct URL). A hidden parent hides its subtree; a group with no remaining children is pruned. Not reactive to later changes. |
101
108
  | `auth` | Authorization rule (`AuthRule`) interpreted by `auth.authorize`. Keeps the route registered and enforces it via a navigation guard (so direct URLs redirect to login). Requires `SiteConfig.auth`. See [Per-page authorization](#per-page-authorization-auth). |
102
109
 
110
+ ### `StandalonePage`
111
+
112
+ | Property | Description |
113
+ |----------|-------------|
114
+ | `path` | Route path, used as-is (e.g. `/landing`) |
115
+ | `page` | Page content. Same forms as `NavItem.page` |
116
+ | `layout` | Optional content width. Omit to keep the standalone default: a full-screen, unpadded canvas. |
117
+ | `visible` | `() => boolean \| Promise<boolean>`, awaited once at startup. Return `false` to skip registering the route. |
118
+ | `auth` | Authorization rule (`AuthRule`) enforced by the same navigation guard as `NavItem.auth`. |
119
+
120
+ ### Page layout
121
+
122
+ Set `layout` on a `NavItem` page when the default reading width is too narrow:
123
+
124
+ ```typescript
125
+ export default defineConfig({
126
+ nav: [
127
+ { label: 'Docs', icon: 'book', page: './pages/docs.md' },
128
+ { label: 'Reports', icon: 'table', layout: 'wide', page: './pages/reports.vue' },
129
+ { label: 'Dashboard', icon: 'gauge', layout: 'full', page: './pages/dashboard.vue' },
130
+ ],
131
+ })
132
+ ```
133
+
103
134
  ### `ThemeConfig`
104
135
 
105
136
  Built-in themes are `light`, `dark`, plus the always-on extras `sepia` and `ocean` (shown in the switcher for every site). Set `theme: false` on `SiteConfig` to disable theming entirely.
@@ -111,6 +142,45 @@ Built-in themes are `light`, `dark`, plus the always-on extras `sepia` and `ocea
111
142
  | `palettes` | — | Partial overrides for built-in light/dark only |
112
143
  | `extraThemes` | — | Extra themes: `id`, `label`, `icon`, optional `basedOn`, `palette`; reuse a built-in id (`sepia`/`ocean`) to override it. Import `builtinThemePalettes` for full defaults |
113
144
 
145
+ ## Shell actions (`shell.actions`)
146
+
147
+ Use `shell.actions` to render app-specific Vue components in the active layout's global toolbar.
148
+ In top/tiered navigation they appear at the end of the top header; in sidebar-only navigation they
149
+ appear in the sidebar footer after the built-in links, locale switcher, and theme switcher. This is
150
+ the recommended place for a signed-in user avatar, notification button, help menu, or tenant
151
+ switcher.
152
+
153
+ ```typescript
154
+ export default defineConfig({
155
+ title: 'My Site',
156
+ shell: {
157
+ actions: ['./components/UserAvatar.vue'],
158
+ align: 'left',
159
+ },
160
+ nav: [/* ... */],
161
+ })
162
+ ```
163
+
164
+ With the CLI, each path-like string is rewritten to a dynamic import. In library mode, pass an
165
+ imported component or loader instead:
166
+
167
+ ```typescript
168
+ import UserAvatar from './components/UserAvatar.vue'
169
+
170
+ export default defineConfig({
171
+ shell: {
172
+ actions: [UserAvatar, () => import('./components/HelpMenu.vue')],
173
+ align: 'center',
174
+ },
175
+ nav: [/* ... */],
176
+ })
177
+ ```
178
+
179
+ The framework only renders the component; auth state, user data, menus, and logout behavior stay in
180
+ your component so the framework does not need to define a user model.
181
+ Set `align` to `'left'`, `'center'`, or `'right'` for sidebar-footer placement; it defaults to
182
+ `'center'`. Top-header placement keeps the built-in toolbar flow.
183
+
114
184
  ## Internationalization (`i18n`)
115
185
 
116
186
  Set `i18n` to enable multi-language support. The framework adds a locale switcher to the header,
@@ -461,7 +531,7 @@ app.mount('#app')
461
531
 
462
532
  Use a top-level `await` in your entry (or an async IIFE): `createSiteApp` is async and **awaits** `configureApp` when it returns a `Promise`. If you set optional `bootstrap` in config, that module loads before the app is created; if you omit `bootstrap`, that step is skipped.
463
533
 
464
- Exports: `createSiteApp`, `defineConfig`, `useTheme`, `useSiteConfig`, `useLocale`, `useLocalize`, `tk`, `resolveLocalized`, `resolveField`, `resolveMessage`, `mergeCatalog`, `flattenMessages`, `isMessageRef`, `localizedPage`, `builtinMessages`, `themeRefKey`, `localeRefKey`. Types: `SiteConfig`, `SiteEnvConfig`, `SiteViteConfig`, `SiteExternalLink`, `NavItem`, `StandalonePage`, `AuthRule`, `AuthContext`, `AuthConfig`, `RouterConfig`, `ThemeConfig`, `ThemeOption`, `ThemePaletteVars`, `ResolvedNavItem`, `I18nConfig`, `LocaleOption`, `LocaleCode`, `LocalizedString`, `MessageRef`, `MessageTree`, `MessageCatalog`, `PageLoader`, `LocalizedPageOptions`.
534
+ Exports: `createSiteApp`, `defineConfig`, `useTheme`, `useSiteConfig`, `useLocale`, `useLocalize`, `tk`, `resolveLocalized`, `resolveField`, `resolveMessage`, `mergeCatalog`, `flattenMessages`, `isMessageRef`, `localizedPage`, `builtinMessages`, `themeRefKey`, `localeRefKey`. Types: `SiteConfig`, `SiteEnvConfig`, `SiteViteConfig`, `SiteExternalLink`, `ShellConfig`, `ShellAction`, `ShellActionLoader`, `NavItem`, `StandalonePage`, `PageLayout`, `AuthRule`, `AuthContext`, `AuthConfig`, `RouterConfig`, `ThemeConfig`, `ThemeOption`, `ThemePaletteVars`, `ResolvedNavItem`, `I18nConfig`, `LocaleOption`, `LocaleCode`, `LocalizedString`, `MessageRef`, `MessageTree`, `MessageCatalog`, `PageLoader`, `LocalizedPageOptions`.
465
535
 
466
536
  ### Theme in Vue pages (`useTheme`)
467
537
 
package/README.zh.md CHANGED
@@ -29,6 +29,7 @@ import { defineConfig } from '@bndynet/vue-site'
29
29
 
30
30
  export default defineConfig({
31
31
  title: 'My Project',
32
+ favicon: '/favicon.ico',
32
33
  nav: [
33
34
  { label: 'Home', icon: 'home', page: () => import('./README.md?raw') },
34
35
  { label: 'Guide', icon: 'book-open', page: () => import('./pages/guide.md?raw') },
@@ -42,6 +43,7 @@ export default defineConfig({
42
43
  my-site/
43
44
  package.json
44
45
  site.config.ts
46
+ public/favicon.ico
45
47
  README.md
46
48
  pages/guide.md
47
49
  ```
@@ -72,7 +74,9 @@ npx vue-site build --base /app/
72
74
  |----------|-------------|
73
75
  | `title` | 站点标题(侧边栏 + 标签页)。`LocalizedString` |
74
76
  | `nav` | `NavItem[]` |
77
+ | `navPosition` | 可选的导航位置:`'top'` 强制使用顶部/分层导航,`'sidebar'` 强制将完整导航树放在侧边栏。不传时保持基于导航深度的自动行为 |
75
78
  | `defaultPath` | 站点打开时的路径;`/` 和未知路径会重定向到此。必须匹配一个已注册的路由(某个 `nav` 条目解析后的路径,或某个 `pages` 条目的 `path`)。默认为第一个顶级 `nav` 条目 |
79
+ | `favicon` | 浏览器标签页图标 URL。可使用 public 资源路径、远程 URL 或导入后的资源 URL |
76
80
  | `logo` | Logo 的 URL 或导入的图片 |
77
81
  | `theme` | 见下方 `ThemeConfig`;设为 `false` 可禁用主题(隐藏切换器、强制使用固定的 `light` 调色板、不进行 localStorage 持久化) |
78
82
  | `i18n` | 多语言配置(`I18nConfig`)—— 参见[国际化](#国际化-i18n) |
@@ -95,11 +99,36 @@ npx vue-site build --base /app/
95
99
  | `icon` | [Lucide](https://lucide.dev/icons) 图标名 |
96
100
  | `page` | 页面内容。最简单的是一个**文件路径字符串**,如 `'./pages/AdminView.vue'` 或 `'./README.md'`(自动加载各语言的同名文件,找不到时回退到基础文件)。也接受加载器(`() => import('./Page.vue')` / `() => import('./page.md?raw')`)或 `localizedPage(...)` 的返回值。见[按语言区分的页面内容](#按语言区分的页面内容)与[高级页面加载器](#高级页面加载器) |
97
101
  | `path` | 路由路径(省略时从 `label` 的默认语言值派生;切换语言时保持稳定) |
102
+ | `layout` | 页面内容宽度:`'default'` 保持标准居中阅读宽度,`'wide'` 使用更宽的居中画布,`'full'` 填满可用父容器。对分组和链接无效。 |
98
103
  | `children` | 嵌套分组 |
99
104
  | `link` | 渲染为超链接(内部路由路径或外部 URL),而非页面路由 |
100
105
  | `visible` | `() => boolean \| Promise<boolean>`,在启动时等待执行一次。返回 `false` 会从导航中隐藏该条目并跳过其路由(无法通过直接 URL 访问)。被隐藏的父项会隐藏其整个子树;没有剩余子项的分组会被剪除。对后续变化不具响应性。 |
101
106
  | `auth` | 由 `auth.authorize` 解释的授权规则(`AuthRule`)。会保持路由注册并通过导航守卫强制执行(因此直接访问 URL 会重定向到登录页)。需要配置 `SiteConfig.auth`。参见[按页面授权](#按页面授权-auth)。 |
102
107
 
108
+ ### `StandalonePage`
109
+
110
+ | 属性 | 说明 |
111
+ |----------|-------------|
112
+ | `path` | 路由路径,按原样使用(如 `/landing`) |
113
+ | `page` | 页面内容。接受形式与 `NavItem.page` 相同 |
114
+ | `layout` | 可选内容宽度。省略时保持 standalone 默认行为:全屏、无内边距画布。 |
115
+ | `visible` | `() => boolean \| Promise<boolean>`,在启动时等待执行一次。返回 `false` 会跳过该路由注册。 |
116
+ | `auth` | 授权规则(`AuthRule`),由与 `NavItem.auth` 相同的导航守卫强制执行。 |
117
+
118
+ ### 页面布局
119
+
120
+ 当默认阅读宽度太窄时,可以在 `NavItem` 页面上设置 `layout`:
121
+
122
+ ```typescript
123
+ export default defineConfig({
124
+ nav: [
125
+ { label: 'Docs', icon: 'book', page: './pages/docs.md' },
126
+ { label: 'Reports', icon: 'table', layout: 'wide', page: './pages/reports.vue' },
127
+ { label: 'Dashboard', icon: 'gauge', layout: 'full', page: './pages/dashboard.vue' },
128
+ ],
129
+ })
130
+ ```
131
+
103
132
  ### `ThemeConfig`
104
133
 
105
134
  内置主题为 `light`、`dark`,外加始终可用的额外主题 `sepia` 和 `ocean`(在每个站点的切换器中都会显示)。在 `SiteConfig` 上设置 `theme: false` 可完全禁用主题。
@@ -468,7 +497,7 @@ app.mount('#app')
468
497
  返回 `Promise` 时会 **await** 它。如果你在配置中设置了可选的 `bootstrap`,该模块会在应用创建前加载;
469
498
  如果省略 `bootstrap`,则跳过该步骤。
470
499
 
471
- 导出:`createSiteApp`、`defineConfig`、`useTheme`、`useSiteConfig`、`useLocale`、`useLocalize`、`tk`、`resolveLocalized`、`resolveField`、`resolveMessage`、`mergeCatalog`、`flattenMessages`、`isMessageRef`、`localizedPage`、`builtinMessages`、`themeRefKey`、`localeRefKey`。类型:`SiteConfig`、`SiteEnvConfig`、`SiteViteConfig`、`SiteExternalLink`、`NavItem`、`StandalonePage`、`AuthRule`、`AuthContext`、`AuthConfig`、`RouterConfig`、`ThemeConfig`、`ThemeOption`、`ThemePaletteVars`、`ResolvedNavItem`、`I18nConfig`、`LocaleOption`、`LocaleCode`、`LocalizedString`、`MessageRef`、`MessageTree`、`MessageCatalog`、`PageLoader`、`LocalizedPageOptions`。
500
+ 导出:`createSiteApp`、`defineConfig`、`useTheme`、`useSiteConfig`、`useLocale`、`useLocalize`、`tk`、`resolveLocalized`、`resolveField`、`resolveMessage`、`mergeCatalog`、`flattenMessages`、`isMessageRef`、`localizedPage`、`builtinMessages`、`themeRefKey`、`localeRefKey`。类型:`SiteConfig`、`SiteEnvConfig`、`SiteViteConfig`、`SiteExternalLink`、`NavItem`、`StandalonePage`、`PageLayout`、`AuthRule`、`AuthContext`、`AuthConfig`、`RouterConfig`、`ThemeConfig`、`ThemeOption`、`ThemePaletteVars`、`ResolvedNavItem`、`I18nConfig`、`LocaleOption`、`LocaleCode`、`LocalizedString`、`MessageRef`、`MessageTree`、`MessageCatalog`、`PageLoader`、`LocalizedPageOptions`。
472
501
 
473
502
  ### 在 Vue 页面中使用主题(`useTheme`)
474
503
 
package/bin/vue-site.mjs CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  import { createServer, build, preview, mergeConfig } from 'vite'
4
4
  import vue from '@vitejs/plugin-vue'
5
+ import AutoImport from 'unplugin-auto-import/vite'
6
+ import Components from 'unplugin-vue-components/vite'
7
+ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
5
8
  import { resolve, dirname, basename } from 'path'
6
9
  import { fileURLToPath, pathToFileURL } from 'url'
7
10
  import { createRequire } from 'module'
@@ -169,6 +172,25 @@ function resolveBootstrapUrl(path) {
169
172
  return '/' + t.replace(/^\.\//, '')
170
173
  }
171
174
 
175
+ function escapeHtmlAttr(value) {
176
+ return String(value)
177
+ .replace(/&/g, '&amp;')
178
+ .replace(/"/g, '&quot;')
179
+ .replace(/</g, '&lt;')
180
+ }
181
+
182
+ function getConfiguredFavicon(siteConfig) {
183
+ const favicon = siteConfig?.favicon
184
+ return typeof favicon === 'string' ? favicon.trim() : ''
185
+ }
186
+
187
+ function buildFaviconLink(siteConfig) {
188
+ const favicon = getConfiguredFavicon(siteConfig)
189
+ return favicon
190
+ ? ` <link rel="icon" href="${escapeHtmlAttr(favicon)}" />\n`
191
+ : ''
192
+ }
193
+
172
194
  // Friendly display names for auto-discovered locale files (`/locales/<code>.json`). Used only when
173
195
  // the config doesn't declare `i18n.locales`; unknown codes fall back to the code itself.
174
196
  const LOCALE_LABELS = {
@@ -213,7 +235,6 @@ function buildBootstrapScript({ siteConfig, siteConfigSpecifier }) {
213
235
  : ''
214
236
  return [
215
237
  bootstrapImport,
216
- `import 'element-plus/dist/index.css'`,
217
238
  `import 'element-plus/theme-chalk/dark/css-vars.css'`,
218
239
  `import { createSiteApp } from '${FRAMEWORK_PACKAGE}'`,
219
240
  `import '${FRAMEWORK_PACKAGE}/style.css'`,
@@ -287,13 +308,13 @@ function buildEntryCode(siteConfig) {
287
308
  })
288
309
  }
289
310
 
290
- function buildHtmlShell(scriptTag) {
311
+ function buildHtmlShell(scriptTag, siteConfig) {
291
312
  return `<!DOCTYPE html>
292
313
  <html lang="en">
293
314
  <head>
294
315
  <meta charset="UTF-8" />
295
316
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
296
- <title></title>
317
+ ${buildFaviconLink(siteConfig)} <title></title>
297
318
  </head>
298
319
  <body>
299
320
  <div id="app"></div>
@@ -302,10 +323,6 @@ function buildHtmlShell(scriptTag) {
302
323
  </html>`
303
324
  }
304
325
 
305
- const htmlTemplate = buildHtmlShell(
306
- `<script type="module" src="/@id/__x00__${VIRTUAL_ENTRY}"></script>`,
307
- )
308
-
309
326
  // esbuild plugin stubbing asset / SFC / `?raw` imports (static or dynamic) to an empty default
310
327
  // export, so bundling the config for pre-load doesn't choke on resources Node can't load. Page
311
328
  // loaders are never invoked during pre-load (only build-time settings are read).
@@ -434,19 +451,41 @@ function fileToLocaleGlobExpr(rawPath) {
434
451
  // per-locale files get bundled. Two shapes are rewritten in the user's JS/TS under `cwd`:
435
452
  // localizedPage('./file.md') -> localizedPage(import.meta.glob([...], { query: '?raw' }))
436
453
  // page: './file.md' -> page: import.meta.glob([...], { query: '?raw' })
454
+ // shell.actions: ['./User.vue'] -> shell.actions: [() => import('./User.vue')]
437
455
  // Loader functions, locale maps (`localizedPage({ en, zh })`) and explicit `import.meta.glob` are
438
- // left untouched; the `page:` form only rewrites path-like values (starting with `.` or `/`).
439
- function localizedPageSugarPlugin() {
456
+ // left untouched; the string forms only rewrite path-like values (starting with `.` or `/`).
457
+ function configSugarPlugin() {
440
458
  const CALL_STRING_ARG = /(\blocalizedPage\s*\(\s*)(['"`])((?:\\.|(?!\2).)*)\2/g
441
459
  const PAGE_STRING_FIELD = /(\bpage\s*:\s*)(['"`])((?:\\.|(?!\2).)*)\2/g
460
+ const SHELL_ACTIONS_ARRAY = /(\bshell\s*:\s*\{[\s\S]*?\bactions\s*:\s*\[)([\s\S]*?)(\])/g
461
+ const STRING_LITERAL = /(['"`])((?:\\.|(?!\1).)*)\1/g
462
+
463
+ function rewriteShellActionArray(body) {
464
+ return body.replace(STRING_LITERAL, (match, _q, rawPath, offset) => {
465
+ if (!/^[./]/.test(rawPath)) return match
466
+
467
+ const before = body.slice(0, offset)
468
+ const after = body.slice(offset + match.length)
469
+ if (!/(^|,)\s*$/.test(before) || !/^\s*(,|$)/.test(after)) return match
470
+
471
+ return `() => import(${JSON.stringify(rawPath)})`
472
+ })
473
+ }
474
+
442
475
  return {
443
- name: 'vue-site:localized-page-sugar',
476
+ name: 'vue-site:config-sugar',
444
477
  enforce: 'pre',
445
478
  transform(code, id) {
446
479
  const file = id.split('?')[0]
447
480
  if (!/\.(?:[cm]?[jt]sx?)$/.test(file)) return
448
481
  if (!file.startsWith(cwd) || file.includes('/node_modules/')) return
449
- if (!code.includes('localizedPage(') && !/\bpage\s*:\s*['"`]/.test(code)) return
482
+ if (
483
+ !code.includes('localizedPage(') &&
484
+ !/\bpage\s*:\s*['"`]/.test(code) &&
485
+ !/\bshell\s*:\s*\{/.test(code)
486
+ ) {
487
+ return
488
+ }
450
489
  let changed = false
451
490
  let out = code.replace(CALL_STRING_ARG, (match, head, _q, rawPath) => {
452
491
  const expr = fileToLocaleGlobExpr(rawPath)
@@ -462,12 +501,18 @@ function localizedPageSugarPlugin() {
462
501
  changed = true
463
502
  return `${head}${expr}`
464
503
  })
504
+ out = out.replace(SHELL_ACTIONS_ARRAY, (match, head, body, tail) => {
505
+ const nextBody = rewriteShellActionArray(body)
506
+ if (nextBody === body) return match
507
+ changed = true
508
+ return `${head}${nextBody}${tail}`
509
+ })
465
510
  return changed ? { code: out, map: null } : undefined
466
511
  },
467
512
  }
468
513
  }
469
514
 
470
- function vueSitePlugin(entryCode) {
515
+ function vueSitePlugin(entryCode, htmlTemplate) {
471
516
  return [
472
517
  {
473
518
  name: 'vue-site:virtual-entry',
@@ -619,6 +664,10 @@ async function buildViteConfig(options = {}) {
619
664
  }
620
665
 
621
666
  const entryCode = buildEntryCode(siteConfig)
667
+ const htmlTemplate = buildHtmlShell(
668
+ `<script type="module" src="/@id/__x00__${VIRTUAL_ENTRY}"></script>`,
669
+ siteConfig,
670
+ )
622
671
 
623
672
  // When watchPackages uses entryPath, SCSS files imported inside the watched
624
673
  // package source (e.g. Lit web components with `import styles from './foo.scss'`
@@ -651,10 +700,27 @@ async function buildViteConfig(options = {}) {
651
700
  },
652
701
  ]
653
702
  : []
703
+ const elementPlusResolver = ElementPlusResolver({
704
+ importStyle: 'css',
705
+ })
654
706
 
655
707
  const baseConfig = {
656
708
  root: cwd,
657
- plugins: [localizedPageSugarPlugin(), vue(vueOpts), ...watchedScssPlugin, ...vueSitePlugin(entryCode), ...(userPlugins || [])],
709
+ plugins: [
710
+ configSugarPlugin(),
711
+ AutoImport({
712
+ resolvers: [elementPlusResolver],
713
+ dts: false,
714
+ }),
715
+ Components({
716
+ resolvers: [elementPlusResolver],
717
+ dts: false,
718
+ }),
719
+ vue(vueOpts),
720
+ ...watchedScssPlugin,
721
+ ...vueSitePlugin(entryCode, htmlTemplate),
722
+ ...(userPlugins || []),
723
+ ],
658
724
  resolve: {
659
725
  alias: [
660
726
  { find: /^@bndynet\/vue-site$/, replacement: frameworkEntry },
@@ -673,13 +739,6 @@ async function buildViteConfig(options = {}) {
673
739
  ],
674
740
  },
675
741
  optimizeDeps: {
676
- // Pre-bundle element-plus so Vite crawls its dependency graph and
677
- // discovers dayjs (a CJS/UMD package with no ESM default export).
678
- // Without this, dayjs.min.js is served raw to the browser and
679
- // Element Plus throws "does not provide an export named 'default'".
680
- // The virtual entry only imports element-plus CSS, so Vite's static
681
- // analysis never sees the JS side — this include bridges that gap.
682
- include: ['element-plus'],
683
742
  // The virtual entry and user-authored pages both import the framework.
684
743
  // Keep it out of dependency pre-bundling so Vite does not instantiate
685
744
  // `dist/index.es.js` once via /@fs and again via node_modules/.vite.
@@ -723,6 +782,7 @@ async function run() {
723
782
  })
724
783
  const buildHtml = buildHtmlShell(
725
784
  `<script type="module">\n${bootstrapScript}\n </script>`,
785
+ siteConfig,
726
786
  )
727
787
  fs.writeFileSync(tempHtml, buildHtml)
728
788
  }
@@ -0,0 +1,9 @@
1
+ type __VLS_Props = {
2
+ placement?: 'top' | 'sidebar';
3
+ };
4
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
5
+ placement: "top" | "sidebar";
6
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
7
+ root: HTMLDivElement;
8
+ }, any>;
9
+ export default _default;
package/dist/index.d.ts CHANGED
@@ -9,5 +9,5 @@ export { builtinMessages } from './i18n-messages';
9
9
  export { useSiteConfig } from './composables/useSiteConfig';
10
10
  export { builtinThemePalettes } from './theme/presets';
11
11
  export { ElMessage, ElMessageBox, ElNotification, } from 'element-plus';
12
- export type { SiteConfig, SiteEnvConfig, SiteViteConfig, SiteExternalLink, NavItem, StandalonePage, AuthRule, AuthContext, AuthConfig, RouterConfig, ThemeConfig, ThemeOption, ThemePaletteVars, ResolvedNavItem, LocaleCode, LocalizedString, MessageRef, MessageTree, LocaleOption, I18nConfig, PageLoader, } from './types';
12
+ export type { SiteConfig, SiteEnvConfig, SiteViteConfig, SiteExternalLink, ShellConfig, ShellAction, ShellActionLoader, NavItem, StandalonePage, PageLayout, AuthRule, AuthContext, AuthConfig, RouterConfig, ThemeConfig, ThemeOption, ThemePaletteVars, ResolvedNavItem, LocaleCode, LocalizedString, MessageRef, MessageTree, LocaleOption, I18nConfig, PageLoader, } from './types';
13
13
  export declare function defineConfig(config: SiteConfig): SiteConfig;