refactor: split valaxy plugins & fixed search btn

This commit is contained in:
YunYouJun 2023-07-20 16:30:02 +08:00
parent 60fe76b22f
commit 2c0ef59894
16 changed files with 525 additions and 519 deletions

View File

@ -1,8 +1,6 @@
import { defineValaxyConfig } from 'valaxy'
import type { ThemeConfig } from 'valaxy-theme-yun'
// import { VitePWA } from 'vite-plugin-pwa'
import Inspect from 'vite-plugin-inspect'
import { addonAlgolia } from 'valaxy-addon-algolia'
// import { addonTwikoo } from 'valaxy-addon-twikoo'
@ -62,16 +60,6 @@ export default defineValaxyConfig<ThemeConfig>({
},
},
vite: {
plugins: [
// VitePWA(),
// https://github.com/antfu/vite-plugin-inspect
// Visit http://localhost:3333/__inspect/ to see the inspector
Inspect(),
],
},
unocss: {
safelist,
},

17
demo/yun/vite.config.ts Normal file
View File

@ -0,0 +1,17 @@
import { defineConfig } from 'vite'
// vite plugins
// import { VitePWA } from 'vite-plugin-pwa'
// import VueDevTools from 'vite-plugin-vue-devtools'
// import Inspect from 'vite-plugin-inspect'
export default defineConfig({
plugins: [
// not works, to be fixed
// VueDevTools(),
// VitePWA(),
// https://github.com/antfu/vite-plugin-inspect
// Visit http://localhost:3333/__inspect/ to see the inspector
// Inspect(),
],
})

View File

@ -13,6 +13,9 @@ import {
// https://vitepress.dev/reference/site-config
export default defineConfig({
// remove it after migrate finish
ignoreDeadLinks: true,
title: 'Valaxy',
description: 'Docs for Valaxy',
themeConfig: {

View File

@ -20,6 +20,7 @@
"devDependencies": {
"nodemon": "^3.0.1",
"vite": "^4.4.4",
"vitepress": "1.0.0-beta.5"
"vitepress": "1.0.0-beta.5",
"vue": "^3.3.4"
}
}

View File

@ -27,12 +27,9 @@ themes:
很高兴你看到这里,这里是 Valaxy 主题橱窗,我将会为提交主题(符合基础使用质量)的前五位作者赠送[「小云立牌」](https://twitter.com/YunYouJun/status/1633116052174299137) :P。
欢迎 [提交主题](https://github.com/YunYouJun/valaxy/blob/main/docs/pages/themes/gallery.md)。
:::
::: en
::: tip
Nice to see you here. This is the Valaxy Themes Gallery, and I will give away [「小云立牌」](https://twitter.com/YunYouJun/status/1633116052174299137) to the top five authors who submitted the theme (meeting the basic usage quality) :P.
@ -40,6 +37,5 @@ Nice to see you here. This is the Valaxy Themes Gallery, and I will give away [
Feel free to [submit your theme](https://github.com/YunYouJun/valaxy/blob/main/docs/pages/themes/gallery.md).
:::
<ThemeGallery :themes="frontmatter.themes" />
<ThemeGallery :themes="$frontmatter.themes" />
<br />

View File

@ -33,6 +33,7 @@
"dev": "pnpm -r --filter=./packages/** --parallel run dev",
"docs": "pnpm -C docs run dev",
"docs:dev": "pnpm -C docs run docs:dev",
"docs:build": "pnpm -C docs run docs:build",
"lint": "eslint .",
"prepublishOnly": "npm run build",
"release": "tsx scripts/release.ts",

View File

@ -11,8 +11,19 @@ const { t } = useI18n()
</script>
<template>
<button class="search-btn popup-trigger yun-icon-btn" :title="t('menu.search')">
<button class="yun-search-btn popup-trigger yun-icon-btn" :title="t('menu.search')">
<div v-if="!open" i-ri-search-line />
<div v-else text="!2xl" i-ri-close-line />
</button>
</template>
<style lang="scss">
.yun-search-btn {
position: fixed;
top: 0.6rem;
right: 0.8rem;
color: var(--va-c-primary);
z-index: var(--yun-z-search-btn);
}
</style>

View File

@ -32,14 +32,3 @@ const YunAlgoliaSearch = isAlgolia.value
<YunAlgoliaSearch v-if="isAlgolia" :open="open" @close="open = false" />
<YunFuseSearch v-else-if="isFuse" :open="open" @close="open = false" />
</template>
<style lang="scss">
.search-btn {
position: absolute;
top: 0.6rem;
right: 0.8rem;
color: var(--va-c-primary);
z-index: var(--yun-z-search-btn);
}
</style>

View File

@ -311,7 +311,7 @@ function injectPageDataCode(
export default {
name:'${data.relativePath}',
data() {
return { data, frontmatter: data.frontmatter }
return { data, frontmatter: data.frontmatter, $frontmatter: data.frontmatter }
},
setup() {
provide('pageData', data)

View File

@ -18,7 +18,7 @@ import {
getHighlighter,
} from 'shiki-processor'
import type { Logger } from 'vite'
import type { ThemeOptions } from '..'
import type { ThemeOptions } from '../types'
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)

View File

@ -1,308 +1 @@
import { join, relative, resolve } from 'node:path'
import fs from 'fs-extra'
import type { Plugin, ResolvedConfig } from 'vite'
// import consola from 'consola'
import { pascalCase } from 'pascal-case'
import { defu } from 'defu'
import { defaultSiteConfig } from '../config'
import type { ResolvedValaxyOptions, ValaxyServerOptions } from '../options'
import { processValaxyOptions, resolveOptions, resolveThemeValaxyConfig } from '../options'
import { mergeValaxyConfig, resolveImportPath, slash, toAtFS } from '../utils'
import { createMarkdownToVueRenderFn } from '../markdown/markdownToVue'
import type { PageDataPayload, SiteConfig } from '../../types'
import type { ValaxyNodeConfig } from '../types'
import { checkMd } from '../markdown/check'
import { resolveSiteConfig } from '../config/site'
/**
* for /@valaxyjs/styles
* @param roots
* @returns
*/
function generateStyles(roots: string[], options: ResolvedValaxyOptions) {
const imports: string[] = []
// katex
if (options.config.features?.katex) {
imports.push(`import "${toAtFS(resolveImportPath('katex/dist/katex.min.css', true))}"`)
imports.push(`import "${toAtFS(join(options.clientRoot, 'styles/third/katex.scss'))}"`)
}
for (const root of roots) {
const styles: string[] = []
const autoloadNames = ['index', 'css-vars']
autoloadNames.forEach((name) => {
styles.push(join(root, 'styles', `${name}.css`))
styles.push(join(root, 'styles', `${name}.scss`))
})
for (const style of styles) {
if (fs.existsSync(style))
imports.push(`import "${toAtFS(style)}"`)
}
}
return imports.join('\n')
}
function generateLocales(roots: string[]) {
const imports: string[] = [
'const messages = { "zh-CN": {}, en: {} }',
]
const languages = ['zh-CN', 'en']
roots.forEach((root, i) => {
languages.forEach((lang) => {
const langYml = `${root}/locales/${lang}.yml`
if (fs.existsSync(langYml) && fs.readFileSync(langYml, 'utf-8')) {
const varName = lang.replace('-', '') + i
imports.unshift(`import ${varName} from "${toAtFS(langYml)}"`)
imports.push(`Object.assign(messages['${lang}'], ${varName})`)
}
})
})
imports.push('export default messages')
return imports.join('\n')
}
function generateAddons(options: ResolvedValaxyOptions) {
const globalAddonComponents = options.addons
.filter(v => v.global)
.filter(v => fs.existsSync(join(v.root, './App.vue')))
const spliceImportName = (str: string) => `Addon${pascalCase(str)}App`
const imports = globalAddonComponents
.map(addon => `import ${spliceImportName(addon.name)} from "${addon.name}/App.vue"`)
.join('\n')
const components = globalAddonComponents
.map(addon => `{ component: ${spliceImportName(addon.name)}, props: ${JSON.stringify(addon.props)} }`)
.join(',')
return `${imports}\n` + `export default [${components}]`
}
/**
* vue component render null
*/
const nullVue = 'import { defineComponent } from "vue"; export default defineComponent({ render: () => null });'
/**
* generate app vue from root/app.vue
* @param root
* @returns
*/
function generateAppVue(root: string) {
const appVue = join(root, 'App.vue')
if (!fs.existsSync(appVue))
return nullVue
const scripts = [
`import AppVue from "${toAtFS(appVue)}"`,
'export default AppVue',
]
return scripts.join('\n')
}
/**
* create valaxy plugin (virtual modules)
* @internal
* @param options
* @param serverOptions
* @returns
*/
export function createValaxyPlugin(options: ResolvedValaxyOptions, serverOptions: ValaxyServerOptions = {}): Plugin {
let { config: valaxyConfig } = options
const valaxyPrefix = '/@valaxy'
const roots = options.roots
let markdownToVue: Awaited<ReturnType<typeof createMarkdownToVueRenderFn>>
let hasDeadLinks = false
let viteConfig: ResolvedConfig
return {
name: 'valaxy:loader',
enforce: 'pre',
async configResolved(resolvedConfig) {
viteConfig = resolvedConfig
markdownToVue = await createMarkdownToVueRenderFn(
options.userRoot,
valaxyConfig.markdown || {
katex: {},
},
options.pages,
viteConfig.define,
viteConfig.command === 'build',
options.config.siteConfig.lastUpdated,
)
},
configureServer(server) {
server.watcher.add([
options.configFile,
options.clientRoot,
options.themeRoot,
options.userRoot,
])
},
resolveId(id) {
if (id.startsWith(valaxyPrefix))
return id
return null
},
load(id) {
if (id === '/@valaxyjs/config')
// stringify twice for \"
return `export default ${JSON.stringify(JSON.stringify(valaxyConfig))}`
if (id === '/@valaxyjs/context') {
return `export default ${JSON.stringify(JSON.stringify({
userRoot: options.userRoot,
// clientRoot: options.clientRoot,
}))}`
}
// generate styles
if (id === '/@valaxyjs/styles')
return generateStyles(roots, options)
if (id === '/@valaxyjs/locales')
return generateLocales(roots)
if (id === '/@valaxyjs/addons')
return generateAddons(options)
if (id === '/@valaxyjs/UserAppVue')
return generateAppVue(options.userRoot)
if (id === '/@valaxyjs/ThemeAppVue')
return generateAppVue(options.themeRoot)
if (id.startsWith(valaxyPrefix)) {
return {
code: '',
map: { mappings: '' },
}
}
},
async transform(code, id) {
if (id.endsWith('.md')) {
checkMd(code, id)
code.replace('{%', '\{\%')
code.replace('%}', '\%\}')
// transform .md files into vueSrc so plugin-vue can handle it
const { vueSrc, deadLinks, includes } = await markdownToVue(
code,
id,
viteConfig.publicDir,
)
if (deadLinks.length)
hasDeadLinks = true
if (includes.length) {
includes.forEach((i) => {
this.addWatchFile(i)
})
}
return vueSrc
}
},
renderStart() {
if (hasDeadLinks && !valaxyConfig.ignoreDeadLinks)
throw new Error('One or more pages contain dead links.')
},
/**
* handle config hmr
* @param ctx
* @returns
*/
async handleHotUpdate(ctx) {
const { file, server, read } = ctx
const reloadConfigAndEntries = (config: ValaxyNodeConfig) => {
serverOptions.onConfigReload?.(config, options.config)
Object.assign(options.config, config)
valaxyConfig = config
const moduleIds = ['/@valaxyjs/config', '/@valaxyjs/context']
const moduleEntries = [
...Array.from(moduleIds).map(id => server.moduleGraph.getModuleById(id)),
].filter(<T>(item: T): item is NonNullable<T> => !!item)
return moduleEntries
}
const configFiles = [options.configFile]
// handle valaxy.config.ts hmr
if (configFiles.includes(file)) {
const { config } = await resolveOptions({ userRoot: options.userRoot })
return reloadConfigAndEntries(config)
}
// siteConfig
if (file === options.siteConfigFile) {
const { siteConfig } = await resolveSiteConfig(options.userRoot)
valaxyConfig.siteConfig = defu<SiteConfig, [SiteConfig]>(siteConfig, defaultSiteConfig)
return reloadConfigAndEntries(valaxyConfig)
}
// themeConfig
if (file === options.themeConfigFile) {
const { config } = await resolveOptions({ userRoot: options.userRoot })
return reloadConfigAndEntries(config)
}
if (file === resolve(options.themeRoot, 'valaxy.config.ts')) {
const themeValaxyConfig = resolveThemeValaxyConfig(options)
const valaxyConfig = mergeValaxyConfig(options.config, themeValaxyConfig)
const { config } = await processValaxyOptions(options, valaxyConfig)
return reloadConfigAndEntries(config)
}
// if (file === options.siteConfigFile) {}
// send headers
if (file.endsWith('.md')) {
const content = await read()
const { pageData, vueSrc } = await markdownToVue(
content,
file,
join(options.userRoot, 'public'),
)
const path = `/${slash(relative(`${options.userRoot}/pages`, file))}`
const payload: PageDataPayload = {
// path: `/${slash(relative(srcDir, file))}`,
path,
pageData,
}
server.ws.send({
type: 'custom',
event: 'valaxy:pageData',
data: payload,
})
// overwrite src so vue plugin can handle the HMR
ctx.read = () => vueSrc
}
},
}
}
export * from './valaxy'

View File

@ -0,0 +1,150 @@
import Pages from 'vite-plugin-pages'
import fs from 'fs-extra'
import matter from 'gray-matter'
import { isDate } from '@antfu/utils'
import { convert } from 'html-to-text'
import type { PageFrontMatter } from 'valaxy/types'
import type { ResolvedValaxyOptions } from '../options'
import type { ValaxyExtendConfig } from '../types'
import { EXCERPT_SEPARATOR } from '../constants'
import { mdIt } from './preset'
import { presetStatistics } from './presets/statistics'
/**
* get excerpt by type
* @param excerpt
* @param type
* @returns
*/
function getExcerptByType(excerpt = '', type: 'md' | 'html' | 'text' = 'html') {
switch (type) {
case 'md':
return excerpt
case 'html':
return mdIt.render(excerpt)
case 'text':
return convert(mdIt.render(excerpt))
}
}
/**
* @see https://github.com/hannoeru/vite-plugin-pages
* @param options
* @returns
*/
export function createPagesPlugin(options: ResolvedValaxyOptions) {
const { roots, config: valaxyConfig } = options
return Pages({
extensions: ['vue', 'md'],
dirs: roots.map(root => `${root}/pages`),
...valaxyConfig.pages,
/**
* we need get frontmatter before route, so write it in Pages.extendRoute
*/
async extendRoute(
route: Parameters<Required<ValaxyExtendConfig>['extendMd']>[0]['route'],
parent,
) {
let path: string = route.component
const defaultFrontmatter = {}
if (!route.meta) {
route.meta = {
frontmatter: defaultFrontmatter,
}
}
else if (!route.meta.frontmatter) {
// set default frontmatter
route.meta.frontmatter = defaultFrontmatter
}
// encode for chinese filename
route.path = encodeURI(route.path)
// add default layout for home, can be overrode
if (route.path === '/')
route.meta.layout = 'home'
// find page path
roots.forEach((root) => {
const pagePath = root + route.component
if (fs.existsSync(pagePath))
path = pagePath
})
// page is post
if (route.path.startsWith('/posts/'))
route.meta.layout = 'post'
if (path.endsWith('.md')) {
const md = fs.readFileSync(path, 'utf-8')
const { data, excerpt, content } = matter(md, {
excerpt_separator: EXCERPT_SEPARATOR,
})
const mdFm = data as PageFrontMatter
// todo, optimize it to cache or on demand
// https://github.com/hannoeru/vite-plugin-pages/issues/257
const lastUpdated = options.config.siteConfig.lastUpdated
if (!data.date)
data.date = fs.statSync(path).mtime
// format
if (lastUpdated) {
if (!data.updated)
data.updated = fs.statSync(path).ctime
}
if (!isDate(mdFm.date))
mdFm.date = new Date(mdFm.date)
if (!isDate(mdFm.updated))
mdFm.updated = new Date(mdFm.updated)
// set route meta
route.meta = Object.assign(route.meta, {
frontmatter: Object.assign(defaultFrontmatter, data),
excerpt: excerpt ? getExcerptByType(excerpt, data.excerpt_type) : '',
})
// set layout
if (data.layout)
route.meta.layout = data.layout
// set default updated
if (!route.meta.frontmatter?.updated)
route.meta.frontmatter.updated = route.meta.frontmatter.date
// adapt for tags
if (route.meta.frontmatter.tags) {
const tags = route.meta.frontmatter.tags
if (typeof tags === 'string')
route.meta.frontmatter.tags = [tags]
}
if (valaxyConfig.siteConfig.statistics.enable) {
presetStatistics({
options: valaxyConfig.siteConfig.statistics,
route,
})
}
valaxyConfig.extendMd?.({
route,
data: data as Readonly<Record<string, any>>,
excerpt,
content,
path,
})
}
valaxyConfig.pages?.extendRoute?.(route, parent)
return route
},
})
}

View File

@ -1,49 +1,25 @@
import fs from 'fs-extra'
import type { PluginOption } from 'vite'
import { splitVendorChunkPlugin } from 'vite'
import MarkdownIt from 'markdown-it'
import matter from 'gray-matter'
import Vue from '@vitejs/plugin-vue'
import Pages from 'vite-plugin-pages'
import Layouts from 'vite-plugin-vue-layouts'
import Components from 'unplugin-vue-components/vite'
import VueI18n from '@intlify/unplugin-vue-i18n/vite'
import { convert } from 'html-to-text'
import type { PageFrontMatter } from 'valaxy/types'
import { isDate } from '@antfu/utils'
import type { ValaxyExtendConfig } from '../types'
import type { ResolvedValaxyOptions, ValaxyServerOptions } from '../options'
import { setupMarkdownPlugins } from '../markdown'
import { EXCERPT_SEPARATOR } from '../constants'
import { createUnocssPlugin } from './unocss'
import { createConfigPlugin } from './extendConfig'
import { createClientSetupPlugin } from './setupClient'
import { createFixPlugins } from './patchTransform'
import { presetStatistics } from './presets/statistics'
import { createValaxyPlugin } from '.'
import { createPagesPlugin } from './pages'
import { createValaxyPlugin } from './valaxy'
// for render markdown excerpt
const mdIt = new MarkdownIt({ html: true })
/**
* get excerpt by type
* @param excerpt
* @param type
* @returns
*/
function getExcerptByType(excerpt = '', type: 'md' | 'html' | 'text' = 'html') {
switch (type) {
case 'md':
return excerpt
case 'html':
return mdIt.render(excerpt)
case 'text':
return convert(mdIt.render(excerpt))
}
}
export const mdIt = new MarkdownIt({ html: true })
export async function ViteValaxyPlugins(
options: ResolvedValaxyOptions,
@ -51,11 +27,6 @@ export async function ViteValaxyPlugins(
): Promise<(PluginOption | PluginOption[])[] | undefined> {
const { roots, config: valaxyConfig } = options
// const MarkdownPlugin = createMarkdownPlugin(options)
const UnocssPlugin = await createUnocssPlugin(options)
const ValaxyPlugin = createValaxyPlugin(options, serverOptions)
// setup mdIt
await setupMarkdownPlugins(mdIt, valaxyConfig.markdown, true)
@ -109,120 +80,10 @@ export async function ViteValaxyPlugins(
createConfigPlugin(options),
createClientSetupPlugin(options),
ValaxyPlugin,
createValaxyPlugin(options, serverOptions),
// https://github.com/hannoeru/vite-plugin-pages
Pages({
extensions: ['vue', 'md'],
dirs: roots.map(root => `${root}/pages`),
...valaxyConfig.pages,
/**
* we need get frontmatter before route, so write it in Pages.extendRoute
*/
async extendRoute(
route: Parameters<Required<ValaxyExtendConfig>['extendMd']>[0]['route'],
parent,
) {
let path: string = route.component
const defaultFrontmatter = {}
if (!route.meta) {
route.meta = {
frontmatter: defaultFrontmatter,
}
}
else if (!route.meta.frontmatter) {
// set default frontmatter
route.meta.frontmatter = defaultFrontmatter
}
// encode for chinese filename
route.path = encodeURI(route.path)
// add default layout for home, can be overrode
if (route.path === '/')
route.meta.layout = 'home'
// find page path
roots.forEach((root) => {
const pagePath = root + route.component
if (fs.existsSync(pagePath))
path = pagePath
})
// page is post
if (route.path.startsWith('/posts/'))
route.meta.layout = 'post'
if (path.endsWith('.md')) {
const md = fs.readFileSync(path, 'utf-8')
const { data, excerpt, content } = matter(md, {
excerpt_separator: EXCERPT_SEPARATOR,
})
const mdFm = data as PageFrontMatter
// todo, optimize it to cache or on demand
// https://github.com/hannoeru/vite-plugin-pages/issues/257
const lastUpdated = options.config.siteConfig.lastUpdated
if (!data.date)
data.date = fs.statSync(path).mtime
// format
if (lastUpdated) {
if (!data.updated)
data.updated = fs.statSync(path).ctime
}
if (!isDate(mdFm.date))
mdFm.date = new Date(mdFm.date)
if (!isDate(mdFm.updated))
mdFm.updated = new Date(mdFm.updated)
// set route meta
route.meta = Object.assign(route.meta, {
frontmatter: Object.assign(defaultFrontmatter, data),
excerpt: excerpt ? getExcerptByType(excerpt, data.excerpt_type) : '',
})
// set layout
if (data.layout)
route.meta.layout = data.layout
// set default updated
if (!route.meta.frontmatter?.updated)
route.meta.frontmatter.updated = route.meta.frontmatter.date
// adapt for tags
if (route.meta.frontmatter.tags) {
const tags = route.meta.frontmatter.tags
if (typeof tags === 'string')
route.meta.frontmatter.tags = [tags]
}
if (valaxyConfig.siteConfig.statistics.enable) {
presetStatistics({
options: valaxyConfig.siteConfig.statistics,
route,
})
}
valaxyConfig.extendMd?.({
route,
data: data as Readonly<Record<string, any>>,
excerpt,
content,
path,
})
}
valaxyConfig.pages?.extendRoute?.(route, parent)
return route
},
}),
createPagesPlugin(options),
// https://github.com/JohnCampionJr/vite-plugin-vue-layouts
Layouts({
@ -252,7 +113,7 @@ export async function ViteValaxyPlugins(
// https://github.com/antfu/unocss
// UnocssPlugin,
UnocssPlugin,
await createUnocssPlugin(options),
// ...MarkdownPlugin,

View File

@ -0,0 +1,312 @@
/**
* @packageDocumentation valaxy plugin
*/
import { join, relative, resolve } from 'node:path'
import fs from 'fs-extra'
import type { Plugin, ResolvedConfig } from 'vite'
// import consola from 'consola'
import { pascalCase } from 'pascal-case'
import { defu } from 'defu'
import { defaultSiteConfig } from '../config'
import type { ResolvedValaxyOptions, ValaxyServerOptions } from '../options'
import { processValaxyOptions, resolveOptions, resolveThemeValaxyConfig } from '../options'
import { mergeValaxyConfig, resolveImportPath, slash, toAtFS } from '../utils'
import { createMarkdownToVueRenderFn } from '../markdown/markdownToVue'
import type { PageDataPayload, SiteConfig } from '../../types'
import type { ValaxyNodeConfig } from '../types'
import { checkMd } from '../markdown/check'
import { resolveSiteConfig } from '../config/site'
/**
* for /@valaxyjs/styles
* @param roots
* @returns
*/
function generateStyles(roots: string[], options: ResolvedValaxyOptions) {
const imports: string[] = []
// katex
if (options.config.features?.katex) {
imports.push(`import "${toAtFS(resolveImportPath('katex/dist/katex.min.css', true))}"`)
imports.push(`import "${toAtFS(join(options.clientRoot, 'styles/third/katex.scss'))}"`)
}
for (const root of roots) {
const styles: string[] = []
const autoloadNames = ['index', 'css-vars']
autoloadNames.forEach((name) => {
styles.push(join(root, 'styles', `${name}.css`))
styles.push(join(root, 'styles', `${name}.scss`))
})
for (const style of styles) {
if (fs.existsSync(style))
imports.push(`import "${toAtFS(style)}"`)
}
}
return imports.join('\n')
}
function generateLocales(roots: string[]) {
const imports: string[] = [
'const messages = { "zh-CN": {}, en: {} }',
]
const languages = ['zh-CN', 'en']
roots.forEach((root, i) => {
languages.forEach((lang) => {
const langYml = `${root}/locales/${lang}.yml`
if (fs.existsSync(langYml) && fs.readFileSync(langYml, 'utf-8')) {
const varName = lang.replace('-', '') + i
imports.unshift(`import ${varName} from "${toAtFS(langYml)}"`)
imports.push(`Object.assign(messages['${lang}'], ${varName})`)
}
})
})
imports.push('export default messages')
return imports.join('\n')
}
function generateAddons(options: ResolvedValaxyOptions) {
const globalAddonComponents = options.addons
.filter(v => v.global)
.filter(v => fs.existsSync(join(v.root, './App.vue')))
const spliceImportName = (str: string) => `Addon${pascalCase(str)}App`
const imports = globalAddonComponents
.map(addon => `import ${spliceImportName(addon.name)} from "${addon.name}/App.vue"`)
.join('\n')
const components = globalAddonComponents
.map(addon => `{ component: ${spliceImportName(addon.name)}, props: ${JSON.stringify(addon.props)} }`)
.join(',')
return `${imports}\n` + `export default [${components}]`
}
/**
* vue component render null
*/
const nullVue = 'import { defineComponent } from "vue"; export default defineComponent({ render: () => null });'
/**
* generate app vue from root/app.vue
* @param root
* @returns
*/
function generateAppVue(root: string) {
const appVue = join(root, 'App.vue')
if (!fs.existsSync(appVue))
return nullVue
const scripts = [
`import AppVue from "${toAtFS(appVue)}"`,
'export default AppVue',
]
return scripts.join('\n')
}
/**
* create valaxy plugin (virtual modules)
* @internal
* @param options
* @param serverOptions
* @returns
*/
export function createValaxyPlugin(options: ResolvedValaxyOptions, serverOptions: ValaxyServerOptions = {}): Plugin {
let { config: valaxyConfig } = options
const valaxyPrefix = '/@valaxy'
const roots = options.roots
let markdownToVue: Awaited<ReturnType<typeof createMarkdownToVueRenderFn>>
let hasDeadLinks = false
let viteConfig: ResolvedConfig
return {
name: 'valaxy:loader',
enforce: 'pre',
async configResolved(resolvedConfig) {
viteConfig = resolvedConfig
markdownToVue = await createMarkdownToVueRenderFn(
options.userRoot,
valaxyConfig.markdown || {
katex: {},
},
options.pages,
viteConfig.define,
viteConfig.command === 'build',
options.config.siteConfig.lastUpdated,
)
},
configureServer(server) {
server.watcher.add([
options.configFile,
options.clientRoot,
options.themeRoot,
options.userRoot,
])
},
resolveId(id) {
if (id.startsWith(valaxyPrefix))
return id
return null
},
load(id) {
if (id === '/@valaxyjs/config')
// stringify twice for \"
return `export default ${JSON.stringify(JSON.stringify(valaxyConfig))}`
if (id === '/@valaxyjs/context') {
return `export default ${JSON.stringify(JSON.stringify({
userRoot: options.userRoot,
// clientRoot: options.clientRoot,
}))}`
}
// generate styles
if (id === '/@valaxyjs/styles')
return generateStyles(roots, options)
if (id === '/@valaxyjs/locales')
return generateLocales(roots)
if (id === '/@valaxyjs/addons')
return generateAddons(options)
if (id === '/@valaxyjs/UserAppVue')
return generateAppVue(options.userRoot)
if (id === '/@valaxyjs/ThemeAppVue')
return generateAppVue(options.themeRoot)
if (id.startsWith(valaxyPrefix)) {
return {
code: '',
map: { mappings: '' },
}
}
},
async transform(code, id) {
if (id.endsWith('.md')) {
checkMd(code, id)
code.replace('{%', '\{\%')
code.replace('%}', '\%\}')
// transform .md files into vueSrc so plugin-vue can handle it
const { vueSrc, deadLinks, includes } = await markdownToVue(
code,
id,
viteConfig.publicDir,
)
if (deadLinks.length)
hasDeadLinks = true
if (includes.length) {
includes.forEach((i) => {
this.addWatchFile(i)
})
}
return vueSrc
}
},
renderStart() {
if (hasDeadLinks && !valaxyConfig.ignoreDeadLinks)
throw new Error('One or more pages contain dead links.')
},
/**
* handle config hmr
* @param ctx
* @returns
*/
async handleHotUpdate(ctx) {
const { file, server, read } = ctx
const reloadConfigAndEntries = (config: ValaxyNodeConfig) => {
serverOptions.onConfigReload?.(config, options.config)
Object.assign(options.config, config)
valaxyConfig = config
const moduleIds = ['/@valaxyjs/config', '/@valaxyjs/context']
const moduleEntries = [
...Array.from(moduleIds).map(id => server.moduleGraph.getModuleById(id)),
].filter(<T>(item: T): item is NonNullable<T> => !!item)
return moduleEntries
}
const configFiles = [options.configFile]
// handle valaxy.config.ts hmr
if (configFiles.includes(file)) {
const { config } = await resolveOptions({ userRoot: options.userRoot })
return reloadConfigAndEntries(config)
}
// siteConfig
if (file === options.siteConfigFile) {
const { siteConfig } = await resolveSiteConfig(options.userRoot)
valaxyConfig.siteConfig = defu<SiteConfig, [SiteConfig]>(siteConfig, defaultSiteConfig)
return reloadConfigAndEntries(valaxyConfig)
}
// themeConfig
if (file === options.themeConfigFile) {
const { config } = await resolveOptions({ userRoot: options.userRoot })
return reloadConfigAndEntries(config)
}
if (file === resolve(options.themeRoot, 'valaxy.config.ts')) {
const themeValaxyConfig = resolveThemeValaxyConfig(options)
const valaxyConfig = mergeValaxyConfig(options.config, themeValaxyConfig)
const { config } = await processValaxyOptions(options, valaxyConfig)
return reloadConfigAndEntries(config)
}
// if (file === options.siteConfigFile) {}
// send headers
if (file.endsWith('.md')) {
const content = await read()
const { pageData, vueSrc } = await markdownToVue(
content,
file,
join(options.userRoot, 'public'),
)
const path = `/${slash(relative(`${options.userRoot}/pages`, file))}`
const payload: PageDataPayload = {
// path: `/${slash(relative(srcDir, file))}`,
path,
pageData,
}
server.ws.send({
type: 'custom',
event: 'valaxy:pageData',
data: payload,
})
// overwrite src so vue plugin can handle the HMR
ctx.read = () => vueSrc
}
},
}
}

View File

@ -13,14 +13,13 @@ export async function createServer(
// default editor vscode
process.env.EDITOR = process.env.EDITOR || 'code'
const server = await createViteServer(
mergeViteConfig(
viteConfig,
{
plugins: await ViteValaxyPlugins(options, serverOptions),
},
),
const plugins = await ViteValaxyPlugins(options, serverOptions)
const mergedViteConfig = mergeViteConfig(
viteConfig,
{
plugins,
},
)
const server = await createViteServer(mergedViteConfig)
return server
}

View File

@ -163,6 +163,9 @@ importers:
vitepress:
specifier: 1.0.0-beta.5
version: 1.0.0-beta.5(@types/node@20.4.2)(search-insights@2.7.0)
vue:
specifier: ^3.3.4
version: 3.3.4
packages/create-valaxy:
dependencies:
@ -974,20 +977,10 @@ packages:
'@babel/types': 7.22.5
dev: true
/@babel/helper-string-parser@7.19.4:
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
engines: {node: '>=6.9.0'}
dev: false
/@babel/helper-string-parser@7.22.5:
resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==}
engines: {node: '>=6.9.0'}
/@babel/helper-validator-identifier@7.19.1:
resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
engines: {node: '>=6.9.0'}
dev: false
/@babel/helper-validator-identifier@7.22.5:
resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==}
engines: {node: '>=6.9.0'}
@ -1031,7 +1024,7 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.21.4
'@babel/types': 7.22.5
dev: false
/@babel/parser@7.22.7:
@ -1926,15 +1919,6 @@ packages:
- supports-color
dev: true
/@babel/types@7.21.4:
resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.19.4
'@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
dev: false
/@babel/types@7.22.5:
resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==}
engines: {node: '>=6.9.0'}
@ -3714,7 +3698,7 @@ packages:
/@vue/compiler-core@3.2.47:
resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==}
dependencies:
'@babel/parser': 7.21.4
'@babel/parser': 7.22.7
'@vue/shared': 3.2.47
estree-walker: 2.0.2
source-map: 0.6.1
@ -3767,7 +3751,7 @@ packages:
'@vue/shared': 3.3.4
estree-walker: 2.0.2
magic-string: 0.30.1
postcss: 8.4.25
postcss: 8.4.26
source-map-js: 1.0.2
/@vue/compiler-ssr@3.2.47:
@ -3808,7 +3792,7 @@ packages:
/@vue/reactivity-transform@3.2.47:
resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==}
dependencies:
'@babel/parser': 7.21.4
'@babel/parser': 7.22.7
'@vue/compiler-core': 3.2.47
'@vue/shared': 3.2.47
estree-walker: 2.0.2
@ -5377,7 +5361,7 @@ packages:
/eslint-import-resolver-node@0.3.7:
resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==}
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
is-core-module: 2.12.1
resolve: 1.22.2
transitivePeerDependencies:
@ -5406,7 +5390,7 @@ packages:
optional: true
dependencies:
'@typescript-eslint/parser': 6.0.0(eslint@8.45.0)(typescript@5.1.6)
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
eslint: 8.45.0
eslint-import-resolver-node: 0.3.7
transitivePeerDependencies:
@ -5466,7 +5450,7 @@ packages:
peerDependencies:
eslint: ^7.2.0 || ^8
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
doctrine: 2.1.0
eslint: 8.45.0
eslint-import-resolver-node: 0.3.7
@ -8080,6 +8064,7 @@ packages:
nanoid: 3.3.6
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
/postcss@8.4.26:
resolution: {integrity: sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==}