@bndynet/vue-site 1.2.0 → 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 +71 -1
- package/README.zh.md +30 -1
- package/bin/vue-site.mjs +104 -27
- package/dist/components/ShellActions.vue.d.ts +9 -0
- package/dist/composables/useSiteConfig.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +2806 -2717
- package/dist/style.css +1 -1
- package/dist/types.d.ts +60 -0
- package/package.json +3 -1
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'
|
|
@@ -12,6 +15,9 @@ const __filename = fileURLToPath(import.meta.url)
|
|
|
12
15
|
const __dirname = dirname(__filename)
|
|
13
16
|
const pkgDir = resolve(__dirname, '..')
|
|
14
17
|
const require = createRequire(import.meta.url)
|
|
18
|
+
const FRAMEWORK_PACKAGE = '@bndynet/vue-site'
|
|
19
|
+
const frameworkEntry = resolve(pkgDir, 'dist/index.es.js')
|
|
20
|
+
const frameworkStyle = resolve(pkgDir, 'dist/style.css')
|
|
15
21
|
|
|
16
22
|
function resolvePkgDir(pkg) {
|
|
17
23
|
return dirname(require.resolve(`${pkg}/package.json`))
|
|
@@ -166,6 +172,25 @@ function resolveBootstrapUrl(path) {
|
|
|
166
172
|
return '/' + t.replace(/^\.\//, '')
|
|
167
173
|
}
|
|
168
174
|
|
|
175
|
+
function escapeHtmlAttr(value) {
|
|
176
|
+
return String(value)
|
|
177
|
+
.replace(/&/g, '&')
|
|
178
|
+
.replace(/"/g, '"')
|
|
179
|
+
.replace(/</g, '<')
|
|
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
|
+
|
|
169
194
|
// Friendly display names for auto-discovered locale files (`/locales/<code>.json`). Used only when
|
|
170
195
|
// the config doesn't declare `i18n.locales`; unknown codes fall back to the code itself.
|
|
171
196
|
const LOCALE_LABELS = {
|
|
@@ -208,13 +233,11 @@ function buildBootstrapScript({ siteConfig, siteConfigSpecifier }) {
|
|
|
208
233
|
bs != null && String(bs).trim() !== ''
|
|
209
234
|
? `import '${resolveBootstrapUrl(bs)}'\n`
|
|
210
235
|
: ''
|
|
211
|
-
const pkgDirUrl = pkgDir.replace(/\\/g, '/')
|
|
212
236
|
return [
|
|
213
237
|
bootstrapImport,
|
|
214
|
-
`import 'element-plus/dist/index.css'`,
|
|
215
238
|
`import 'element-plus/theme-chalk/dark/css-vars.css'`,
|
|
216
|
-
`import { createSiteApp } from '${
|
|
217
|
-
`import '${
|
|
239
|
+
`import { createSiteApp } from '${FRAMEWORK_PACKAGE}'`,
|
|
240
|
+
`import '${FRAMEWORK_PACKAGE}/style.css'`,
|
|
218
241
|
`import siteConfig from '${siteConfigSpecifier}'`,
|
|
219
242
|
`import { repositoryUrl } from '${VIRTUAL_PACKAGE}'`,
|
|
220
243
|
``,
|
|
@@ -285,13 +308,13 @@ function buildEntryCode(siteConfig) {
|
|
|
285
308
|
})
|
|
286
309
|
}
|
|
287
310
|
|
|
288
|
-
function buildHtmlShell(scriptTag) {
|
|
311
|
+
function buildHtmlShell(scriptTag, siteConfig) {
|
|
289
312
|
return `<!DOCTYPE html>
|
|
290
313
|
<html lang="en">
|
|
291
314
|
<head>
|
|
292
315
|
<meta charset="UTF-8" />
|
|
293
316
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
294
|
-
<title></title>
|
|
317
|
+
${buildFaviconLink(siteConfig)} <title></title>
|
|
295
318
|
</head>
|
|
296
319
|
<body>
|
|
297
320
|
<div id="app"></div>
|
|
@@ -300,10 +323,6 @@ function buildHtmlShell(scriptTag) {
|
|
|
300
323
|
</html>`
|
|
301
324
|
}
|
|
302
325
|
|
|
303
|
-
const htmlTemplate = buildHtmlShell(
|
|
304
|
-
`<script type="module" src="/@id/__x00__${VIRTUAL_ENTRY}"></script>`,
|
|
305
|
-
)
|
|
306
|
-
|
|
307
326
|
// esbuild plugin stubbing asset / SFC / `?raw` imports (static or dynamic) to an empty default
|
|
308
327
|
// export, so bundling the config for pre-load doesn't choke on resources Node can't load. Page
|
|
309
328
|
// loaders are never invoked during pre-load (only build-time settings are read).
|
|
@@ -432,19 +451,41 @@ function fileToLocaleGlobExpr(rawPath) {
|
|
|
432
451
|
// per-locale files get bundled. Two shapes are rewritten in the user's JS/TS under `cwd`:
|
|
433
452
|
// localizedPage('./file.md') -> localizedPage(import.meta.glob([...], { query: '?raw' }))
|
|
434
453
|
// page: './file.md' -> page: import.meta.glob([...], { query: '?raw' })
|
|
454
|
+
// shell.actions: ['./User.vue'] -> shell.actions: [() => import('./User.vue')]
|
|
435
455
|
// Loader functions, locale maps (`localizedPage({ en, zh })`) and explicit `import.meta.glob` are
|
|
436
|
-
// left untouched; the
|
|
437
|
-
function
|
|
456
|
+
// left untouched; the string forms only rewrite path-like values (starting with `.` or `/`).
|
|
457
|
+
function configSugarPlugin() {
|
|
438
458
|
const CALL_STRING_ARG = /(\blocalizedPage\s*\(\s*)(['"`])((?:\\.|(?!\2).)*)\2/g
|
|
439
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
|
+
|
|
440
475
|
return {
|
|
441
|
-
name: 'vue-site:
|
|
476
|
+
name: 'vue-site:config-sugar',
|
|
442
477
|
enforce: 'pre',
|
|
443
478
|
transform(code, id) {
|
|
444
479
|
const file = id.split('?')[0]
|
|
445
480
|
if (!/\.(?:[cm]?[jt]sx?)$/.test(file)) return
|
|
446
481
|
if (!file.startsWith(cwd) || file.includes('/node_modules/')) return
|
|
447
|
-
if (
|
|
482
|
+
if (
|
|
483
|
+
!code.includes('localizedPage(') &&
|
|
484
|
+
!/\bpage\s*:\s*['"`]/.test(code) &&
|
|
485
|
+
!/\bshell\s*:\s*\{/.test(code)
|
|
486
|
+
) {
|
|
487
|
+
return
|
|
488
|
+
}
|
|
448
489
|
let changed = false
|
|
449
490
|
let out = code.replace(CALL_STRING_ARG, (match, head, _q, rawPath) => {
|
|
450
491
|
const expr = fileToLocaleGlobExpr(rawPath)
|
|
@@ -460,12 +501,18 @@ function localizedPageSugarPlugin() {
|
|
|
460
501
|
changed = true
|
|
461
502
|
return `${head}${expr}`
|
|
462
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
|
+
})
|
|
463
510
|
return changed ? { code: out, map: null } : undefined
|
|
464
511
|
},
|
|
465
512
|
}
|
|
466
513
|
}
|
|
467
514
|
|
|
468
|
-
function vueSitePlugin(entryCode) {
|
|
515
|
+
function vueSitePlugin(entryCode, htmlTemplate) {
|
|
469
516
|
return [
|
|
470
517
|
{
|
|
471
518
|
name: 'vue-site:virtual-entry',
|
|
@@ -617,6 +664,10 @@ async function buildViteConfig(options = {}) {
|
|
|
617
664
|
}
|
|
618
665
|
|
|
619
666
|
const entryCode = buildEntryCode(siteConfig)
|
|
667
|
+
const htmlTemplate = buildHtmlShell(
|
|
668
|
+
`<script type="module" src="/@id/__x00__${VIRTUAL_ENTRY}"></script>`,
|
|
669
|
+
siteConfig,
|
|
670
|
+
)
|
|
620
671
|
|
|
621
672
|
// When watchPackages uses entryPath, SCSS files imported inside the watched
|
|
622
673
|
// package source (e.g. Lit web components with `import styles from './foo.scss'`
|
|
@@ -649,24 +700,49 @@ async function buildViteConfig(options = {}) {
|
|
|
649
700
|
},
|
|
650
701
|
]
|
|
651
702
|
: []
|
|
703
|
+
const elementPlusResolver = ElementPlusResolver({
|
|
704
|
+
importStyle: 'css',
|
|
705
|
+
})
|
|
652
706
|
|
|
653
707
|
const baseConfig = {
|
|
654
708
|
root: cwd,
|
|
655
|
-
plugins: [
|
|
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
|
+
],
|
|
656
724
|
resolve: {
|
|
657
|
-
alias:
|
|
658
|
-
vue:
|
|
659
|
-
|
|
660
|
-
|
|
725
|
+
alias: [
|
|
726
|
+
{ find: /^@bndynet\/vue-site$/, replacement: frameworkEntry },
|
|
727
|
+
{
|
|
728
|
+
find: /^@bndynet\/vue-site\/style\.css$/,
|
|
729
|
+
replacement: frameworkStyle,
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
find: 'vue',
|
|
733
|
+
replacement: resolve(vuePath, 'dist/vue.runtime.esm-bundler.js'),
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
find: 'vue-router',
|
|
737
|
+
replacement: resolve(vueRouterPath, 'dist/vue-router.mjs'),
|
|
738
|
+
},
|
|
739
|
+
],
|
|
661
740
|
},
|
|
662
741
|
optimizeDeps: {
|
|
663
|
-
//
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
|
|
667
|
-
// The virtual entry only imports element-plus CSS, so Vite's static
|
|
668
|
-
// analysis never sees the JS side — this include bridges that gap.
|
|
669
|
-
include: ['element-plus'],
|
|
742
|
+
// The virtual entry and user-authored pages both import the framework.
|
|
743
|
+
// Keep it out of dependency pre-bundling so Vite does not instantiate
|
|
744
|
+
// `dist/index.es.js` once via /@fs and again via node_modules/.vite.
|
|
745
|
+
exclude: [FRAMEWORK_PACKAGE],
|
|
670
746
|
},
|
|
671
747
|
server: {
|
|
672
748
|
open: true,
|
|
@@ -706,6 +782,7 @@ async function run() {
|
|
|
706
782
|
})
|
|
707
783
|
const buildHtml = buildHtmlShell(
|
|
708
784
|
`<script type="module">\n${bootstrapScript}\n </script>`,
|
|
785
|
+
siteConfig,
|
|
709
786
|
)
|
|
710
787
|
fs.writeFileSync(tempHtml, buildHtml)
|
|
711
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;
|
|
@@ -4,6 +4,7 @@ export interface SiteContext {
|
|
|
4
4
|
config: SiteConfig;
|
|
5
5
|
resolvedNav: ResolvedNavItem[];
|
|
6
6
|
}
|
|
7
|
+
/** Use `Symbol.for` so pages still inject context if Vite resolves the package twice. */
|
|
7
8
|
export declare const siteContextKey: InjectionKey<SiteContext>;
|
|
8
9
|
export declare function provideSiteConfig(context: SiteContext): void;
|
|
9
10
|
export declare function useSiteConfig(): SiteContext;
|
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;
|