methanol 0.0.0 → 0.0.1

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.
Files changed (45) hide show
  1. package/.editorconfig +19 -0
  2. package/.prettierrc +10 -0
  3. package/LICENSE +203 -0
  4. package/banner.txt +6 -0
  5. package/bin/methanol.js +24 -0
  6. package/index.js +22 -0
  7. package/package.json +42 -9
  8. package/src/assets.js +30 -0
  9. package/src/build-system.js +200 -0
  10. package/src/components.js +145 -0
  11. package/src/config.js +355 -0
  12. package/src/dev-server.js +559 -0
  13. package/src/main.js +87 -0
  14. package/src/mdx.js +254 -0
  15. package/src/node-loader.js +88 -0
  16. package/src/pagefind.js +99 -0
  17. package/src/pages.js +638 -0
  18. package/src/preview-server.js +58 -0
  19. package/src/public-assets.js +73 -0
  20. package/src/register-loader.js +29 -0
  21. package/src/rehype-plugins/link-resolve.js +89 -0
  22. package/src/rehype-plugins/methanol-ctx.js +89 -0
  23. package/src/renderer.js +25 -0
  24. package/src/rewind.js +117 -0
  25. package/src/stage-logger.js +59 -0
  26. package/src/state.js +159 -0
  27. package/src/virtual-module/inject.js +30 -0
  28. package/src/virtual-module/loader.js +116 -0
  29. package/src/virtual-module/pagefind.js +108 -0
  30. package/src/vite-plugins.js +173 -0
  31. package/themes/default/components/ThemeColorSwitch.client.jsx +95 -0
  32. package/themes/default/components/ThemeColorSwitch.static.jsx +23 -0
  33. package/themes/default/components/ThemeSearchBox.client.jsx +287 -0
  34. package/themes/default/components/ThemeSearchBox.static.jsx +41 -0
  35. package/themes/default/components/ThemeToCContainer.client.jsx +154 -0
  36. package/themes/default/components/ThemeToCContainer.static.jsx +61 -0
  37. package/themes/default/components/pre.client.jsx +84 -0
  38. package/themes/default/components/pre.jsx +27 -0
  39. package/themes/default/heading.jsx +35 -0
  40. package/themes/default/index.js +50 -0
  41. package/themes/default/page.jsx +249 -0
  42. package/themes/default/pages/404.mdx +8 -0
  43. package/themes/default/pages/index.mdx +9 -0
  44. package/themes/default/public/logo.png +0 -0
  45. package/themes/default/resources/style.css +1089 -0
@@ -0,0 +1,145 @@
1
+ /* Copyright Yukino Song, SudoMaker Ltd.
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements. See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership. The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License. You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied. See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ */
20
+
21
+ import { readdir, stat } from 'fs/promises'
22
+ import { existsSync } from 'fs'
23
+ import { join, extname, basename } from 'path'
24
+ import { pathToFileURL } from 'url'
25
+ import { env } from './rewind.js'
26
+ import { state } from './state.js'
27
+
28
+ const normalizeComponentName = (value) => basename(value)
29
+ const isInternalComponentName = (name) => {
30
+ const normalized = normalizeComponentName(name)
31
+ return normalized.startsWith('_') || normalized.startsWith('.')
32
+ }
33
+ const isIgnoredEntry = (name) => name.startsWith('.')
34
+
35
+ const COMPONENT_NAME_PATTERN = /^[^.]+(?:\.(client|static))?\.(jsx?|tsx?)$/i
36
+
37
+ export const isComponentFile = (name) =>
38
+ /\.(jsx?|tsx?)$/i.test(normalizeComponentName(name)) &&
39
+ !isInternalComponentName(name) &&
40
+ COMPONENT_NAME_PATTERN.test(normalizeComponentName(name))
41
+ export const isClientComponent = (name) => /\.client\.(jsx?|tsx?)$/i.test(normalizeComponentName(name))
42
+ export const COMPONENT_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx']
43
+
44
+ let componentImportNonce = Date.now()
45
+ export const bumpComponentImportNonce = () => {
46
+ componentImportNonce = Date.now()
47
+ return componentImportNonce
48
+ }
49
+
50
+ export const rewindEnv = env()
51
+ export const client = rewindEnv.client
52
+ export const invalidateRegistryEntry = rewindEnv.invalidate
53
+ export const genRegistryScript = rewindEnv.genRegistryScript
54
+
55
+ const resolveComponentExport = (componentPath, ext) => {
56
+ const staticCandidate = `${componentPath}.static${ext}`
57
+ const clientCandidate = `${componentPath}.client${ext}`
58
+ const genericCandidate = `${componentPath}${ext}`
59
+ const ret = {}
60
+ if (existsSync(staticCandidate)) {
61
+ ret.staticPath = staticCandidate
62
+ }
63
+ if (existsSync(clientCandidate)) {
64
+ ret.clientPath = clientCandidate
65
+ }
66
+ if (!ret.staticPath) {
67
+ if (existsSync(genericCandidate)) {
68
+ ret.staticPath = genericCandidate
69
+ } else if (existsSync(clientCandidate)) {
70
+ ret.staticPath = clientCandidate
71
+ }
72
+ }
73
+ return ret
74
+ }
75
+
76
+ export const buildComponentEntry = async ({ dir, exportName, ext, client: clientFn = client }) => {
77
+ const info = resolveComponentExport(join(dir, exportName), ext)
78
+ if (!info.staticPath) {
79
+ return { component: null, hasClient: false, staticPath: null, clientPath: null }
80
+ }
81
+
82
+ let component = (await import(`${pathToFileURL(info.staticPath).href}?t=${componentImportNonce}`)).default
83
+
84
+ if (!component) {
85
+ return { component: null, hasClient: false, staticPath: null, clientPath: null }
86
+ }
87
+
88
+ if (info.clientPath) {
89
+ component = clientFn({ ...info, staticComponent: component, exportName })
90
+ }
91
+
92
+ return {
93
+ component,
94
+ hasClient: Boolean(info.clientPath),
95
+ staticPath: info.staticPath,
96
+ clientPath: info.clientPath || null
97
+ }
98
+ }
99
+
100
+ export const buildComponentRegistry = async ({ componentsDir = state.COMPONENTS_DIR, client: clientFn = client } = {}) => {
101
+ const components = {}
102
+ const sources = new Map()
103
+
104
+ if (!componentsDir || !existsSync(componentsDir)) {
105
+ return { components, sources }
106
+ }
107
+
108
+ const walk = async (dir) => {
109
+ const entries = await readdir(dir)
110
+ for (const entry of entries) {
111
+ if (isIgnoredEntry(entry)) {
112
+ continue
113
+ }
114
+ const fullPath = join(dir, entry)
115
+ const stats = await stat(fullPath)
116
+ if (stats.isDirectory()) {
117
+ await walk(fullPath)
118
+ continue
119
+ }
120
+ if (!isComponentFile(entry)) {
121
+ continue
122
+ }
123
+
124
+ const exportName = entry.split('.')[0]
125
+ if (sources.has(exportName)) {
126
+ continue
127
+ }
128
+
129
+ const { component, staticPath } = await buildComponentEntry({
130
+ dir,
131
+ exportName,
132
+ ext: extname(entry),
133
+ client: clientFn
134
+ })
135
+ if (!component) continue
136
+ components[exportName] = component
137
+ if (staticPath) {
138
+ sources.set(exportName, staticPath)
139
+ }
140
+ }
141
+ }
142
+
143
+ await walk(componentsDir)
144
+ return { components, sources }
145
+ }
package/src/config.js ADDED
@@ -0,0 +1,355 @@
1
+ /* Copyright Yukino Song, SudoMaker Ltd.
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements. See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership. The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License. You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied. See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ */
20
+
21
+ import { readFile } from 'fs/promises'
22
+ import { existsSync } from 'fs'
23
+ import { resolve, isAbsolute, extname, basename } from 'path'
24
+ import { pathToFileURL } from 'url'
25
+ import { mergeConfig } from 'vite'
26
+ import { cli, state } from './state.js'
27
+ import { HTMLRenderer } from './renderer.js'
28
+ import { rewindEnv } from './components.js'
29
+ import { env as createEnv } from './rewind.js'
30
+ import defaultTheme from '../themes/default/index.js'
31
+
32
+ const CONFIG_FILENAMES = [
33
+ 'methanol.config.js',
34
+ 'methanol.config.mjs',
35
+ 'methanol.config.cjs',
36
+ 'methanol.config.ts',
37
+ 'methanol.config.mts',
38
+ 'methanol.config.cts'
39
+ ]
40
+
41
+ const resolveRootPath = (value) => {
42
+ if (!value) {
43
+ return state.PROJECT_ROOT
44
+ }
45
+ return isAbsolute(value) ? value : resolve(state.PROJECT_ROOT, value)
46
+ }
47
+
48
+ const resolveFromRoot = (root, value, fallback) => {
49
+ if (!value) {
50
+ return resolve(root, fallback)
51
+ }
52
+ return isAbsolute(value) ? value : resolve(root, value)
53
+ }
54
+
55
+ const resolveOptionalPath = (root, value, fallback) => {
56
+ if (value === false) {
57
+ return false
58
+ }
59
+ return resolveFromRoot(root, value, fallback)
60
+ }
61
+
62
+ const resolveThemeComponentDir = (root, value) => {
63
+ if (value == null) return null
64
+ if (value === false) return false
65
+ return isAbsolute(value) ? value : resolve(root, value)
66
+ }
67
+
68
+ const resolveThemePagesDir = (root, value) => {
69
+ if (value == null) return null
70
+ if (value === false) return false
71
+ return isAbsolute(value) ? value : resolve(root, value)
72
+ }
73
+
74
+ const resolveThemePublicDir = (root, value) => {
75
+ if (value == null) return null
76
+ if (value === false) return false
77
+ return isAbsolute(value) ? value : resolve(root, value)
78
+ }
79
+
80
+ const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key)
81
+ const normalizeResources = (value, root) => {
82
+ if (!value) return []
83
+ const entries = []
84
+ const addEntry = (find, replacement) => {
85
+ if (!find || !replacement) return
86
+ let resolvedReplacement = replacement
87
+ if (typeof replacement === 'string' && !isAbsolute(replacement)) {
88
+ resolvedReplacement = resolve(root, replacement)
89
+ }
90
+ entries.push({ find, replacement: resolvedReplacement })
91
+ }
92
+ if (Array.isArray(value)) {
93
+ for (const entry of value) {
94
+ if (!entry) continue
95
+ if (typeof entry === 'object') {
96
+ addEntry(entry.find, entry.replacement)
97
+ }
98
+ }
99
+ return entries
100
+ }
101
+ if (typeof value === 'object') {
102
+ for (const [find, replacement] of Object.entries(value)) {
103
+ addEntry(find, replacement)
104
+ }
105
+ }
106
+ return entries
107
+ }
108
+
109
+ const resolvePagefindEnabled = (config) => {
110
+ if (config?.pagefind == null) return false
111
+ if (typeof config.pagefind === 'boolean') return config.pagefind
112
+ if (typeof config.pagefind === 'object') {
113
+ if (hasOwn(config.pagefind, 'enabled')) {
114
+ return config.pagefind.enabled !== false
115
+ }
116
+ }
117
+ return true
118
+ }
119
+
120
+ const resolvePagefindOptions = (config) => {
121
+ const value = config?.pagefind
122
+ if (!value || typeof value !== 'object') return null
123
+ const { enabled, options, ...rest } = value
124
+ if (options && typeof options === 'object') {
125
+ return { ...options }
126
+ }
127
+ if (Object.keys(rest).length) {
128
+ return { ...rest }
129
+ }
130
+ return null
131
+ }
132
+
133
+ const resolvePagefindBuildOptions = (config) => {
134
+ const value = config?.pagefind
135
+ if (!value || typeof value !== 'object') return null
136
+ const buildOptions = value.buildOptions || value.build
137
+ if (buildOptions && typeof buildOptions === 'object') {
138
+ return { ...buildOptions }
139
+ }
140
+ return null
141
+ }
142
+
143
+ const loadConfigModule = async (filePath) => {
144
+ return import(`${pathToFileURL(filePath).href}?t=${Date.now()}`)
145
+ }
146
+
147
+ const resolveConfigPath = (value) => {
148
+ if (!value) return null
149
+ return isAbsolute(value) ? value : resolve(state.PROJECT_ROOT, value)
150
+ }
151
+
152
+ const buildConfigContext = (mode) => ({
153
+ mode,
154
+ root: state.PROJECT_ROOT,
155
+ HTMLRenderer
156
+ })
157
+
158
+ export const loadUserConfig = async (mode, configPath = null) => {
159
+ if (configPath) {
160
+ const filePath = resolveConfigPath(configPath)
161
+ if (!filePath || !existsSync(filePath)) {
162
+ throw new Error(`Config file not found: ${configPath}`)
163
+ }
164
+ const mod = await loadConfigModule(filePath)
165
+ const config = mod.default ?? mod
166
+ if (typeof config !== 'function') {
167
+ throw new Error(`Config must export a function: ${filePath}`)
168
+ }
169
+ return (await config(buildConfigContext(mode))) || {}
170
+ }
171
+ for (const name of CONFIG_FILENAMES) {
172
+ const filePath = resolve(state.PROJECT_ROOT, name)
173
+ if (!existsSync(filePath)) {
174
+ continue
175
+ }
176
+ const mod = await loadConfigModule(filePath)
177
+ const config = mod.default ?? mod
178
+ if (typeof config !== 'function') {
179
+ throw new Error(`Config must export a function: ${filePath}`)
180
+ }
181
+ return (await config(buildConfigContext(mode))) || {}
182
+ }
183
+ return {}
184
+ }
185
+
186
+ export const applyConfig = async (config, mode) => {
187
+ const root = resolveRootPath(config.root)
188
+ state.ROOT_DIR = root
189
+ const configSiteName = cli.CLI_SITE_NAME ?? config.site?.name ?? null
190
+ state.SITE_NAME = configSiteName || basename(root) || 'Methanol Site'
191
+ if (mode) {
192
+ state.CURRENT_MODE = mode
193
+ }
194
+ const paths = config.paths || config.dirs || {}
195
+
196
+ const pagesDirValue = cli.CLI_PAGES_DIR || config.pagesDir || paths.pages
197
+ const componentsDirValue = cli.CLI_COMPONENTS_DIR || config.componentsDir || paths.components
198
+ const distDirValue = cli.CLI_OUTPUT_DIR || config.distDir || paths.dist
199
+ const publicDirValue = cli.CLI_ASSETS_DIR ?? config.publicDir ?? paths.public
200
+
201
+ const resolvePagesFallback = () => {
202
+ const pagesPath = resolveFromRoot(root, 'pages', 'pages')
203
+ if (existsSync(pagesPath)) return pagesPath
204
+ const docsPath = resolveFromRoot(root, 'docs', 'docs')
205
+ if (existsSync(docsPath)) return docsPath
206
+ return pagesPath
207
+ }
208
+ state.PAGES_DIR = pagesDirValue
209
+ ? resolveFromRoot(root, pagesDirValue, 'pages')
210
+ : resolvePagesFallback()
211
+ state.COMPONENTS_DIR = resolveFromRoot(root, componentsDirValue, 'components')
212
+ state.STATIC_DIR = resolveOptionalPath(root, publicDirValue, 'public')
213
+ state.BUILD_DIR = resolveFromRoot(root, config.buildDir || paths.build, 'build')
214
+ state.DIST_DIR = resolveFromRoot(root, distDirValue, 'dist')
215
+
216
+ const userSpecifiedPagesDir = cli.CLI_PAGES_DIR != null || hasOwn(config, 'pagesDir') || hasOwn(paths, 'pages')
217
+ if (userSpecifiedPagesDir && !existsSync(state.PAGES_DIR)) {
218
+ throw new Error(`Pages directory not found: ${state.PAGES_DIR}`)
219
+ }
220
+ const userSpecifiedComponentsDir = cli.CLI_COMPONENTS_DIR != null || hasOwn(config, 'componentsDir') || hasOwn(paths, 'components')
221
+ if (userSpecifiedComponentsDir && !existsSync(state.COMPONENTS_DIR)) {
222
+ throw new Error(`Components directory not found: ${state.COMPONENTS_DIR}`)
223
+ }
224
+ const userSpecifiedPublicDir = cli.CLI_ASSETS_DIR != null || hasOwn(config, 'publicDir') || hasOwn(paths, 'public')
225
+ if (userSpecifiedPublicDir && state.STATIC_DIR !== false && !existsSync(state.STATIC_DIR)) {
226
+ state.STATIC_DIR = resolveFromRoot(root, publicDirValue, 'public')
227
+ }
228
+ state.USER_PUBLIC_OVERRIDE = userSpecifiedPublicDir
229
+
230
+ state.VIRTUAL_HTML_OUTPUT_ROOT = state.PAGES_DIR
231
+
232
+ state.USER_THEME = config.theme || await defaultTheme()
233
+ if (!state.USER_THEME?.root && !config.theme?.root) {
234
+ throw new Error('Theme root is required.')
235
+ }
236
+ if (config.theme?.root) {
237
+ state.USER_THEME.root = resolveFromRoot(root, config.theme.root)
238
+ }
239
+ const themeEnv = state.USER_THEME.env || createEnv()
240
+ state.THEME_ENV = themeEnv
241
+ rewindEnv.setParent(themeEnv)
242
+ const themeRoot = state.USER_THEME.root || root
243
+ const themeComponentDirValue = hasOwn(state.USER_THEME, 'componentsDir')
244
+ ? state.USER_THEME.componentsDir
245
+ : './components'
246
+ state.THEME_COMPONENTS_DIR = resolveThemeComponentDir(themeRoot, themeComponentDirValue)
247
+ const themePagesDirValue = hasOwn(state.USER_THEME, 'pagesDir')
248
+ ? state.USER_THEME.pagesDir
249
+ : './pages'
250
+ state.THEME_PAGES_DIR = resolveThemePagesDir(themeRoot, themePagesDirValue)
251
+ const themePublicDirValue = hasOwn(state.USER_THEME, 'publicDir')
252
+ ? state.USER_THEME.publicDir
253
+ : './public'
254
+ state.THEME_PUBLIC_DIR = resolveThemePublicDir(themeRoot, themePublicDirValue)
255
+ if (hasOwn(state.USER_THEME, 'componentsDir') && state.THEME_COMPONENTS_DIR && !existsSync(state.THEME_COMPONENTS_DIR)) {
256
+ throw new Error(`Theme components directory not found: ${state.THEME_COMPONENTS_DIR}`)
257
+ }
258
+ if (hasOwn(state.USER_THEME, 'pagesDir') && state.THEME_PAGES_DIR && !existsSync(state.THEME_PAGES_DIR)) {
259
+ throw new Error(`Theme pages directory not found: ${state.THEME_PAGES_DIR}`)
260
+ }
261
+ if (hasOwn(state.USER_THEME, 'publicDir') && state.THEME_PUBLIC_DIR && !existsSync(state.THEME_PUBLIC_DIR)) {
262
+ throw new Error(`Theme public directory not found: ${state.THEME_PUBLIC_DIR}`)
263
+ }
264
+ if (
265
+ state.STATIC_DIR !== false &&
266
+ !userSpecifiedPublicDir &&
267
+ !existsSync(state.STATIC_DIR) &&
268
+ state.THEME_PUBLIC_DIR &&
269
+ existsSync(state.THEME_PUBLIC_DIR)
270
+ ) {
271
+ state.STATIC_DIR = state.THEME_PUBLIC_DIR
272
+ }
273
+ state.RESOURCES = normalizeResources(state.USER_THEME.resources, themeRoot)
274
+ state.USER_VITE_CONFIG = config.vite || null
275
+ state.USER_MDX_CONFIG = config.mdx || null
276
+ state.RESOLVED_MDX_CONFIG = undefined
277
+ state.RESOLVED_VITE_CONFIG = undefined
278
+ state.PAGEFIND_ENABLED = resolvePagefindEnabled(config)
279
+ state.PAGEFIND_OPTIONS = resolvePagefindOptions(config)
280
+ state.PAGEFIND_BUILD_OPTIONS = resolvePagefindBuildOptions(config)
281
+
282
+ if (cli.CLI_INTERMEDIATE_DIR) {
283
+ state.INTERMEDIATE_DIR = resolveFromRoot(root, cli.CLI_INTERMEDIATE_DIR, 'build')
284
+ } else if (config.intermediateDir) {
285
+ state.INTERMEDIATE_DIR = resolveFromRoot(root, config.intermediateDir, 'build')
286
+ } else if (cli.CLI_EMIT_INTERMEDIATE || config.emitIntermediate) {
287
+ state.INTERMEDIATE_DIR = state.BUILD_DIR
288
+ } else {
289
+ state.INTERMEDIATE_DIR = null
290
+ }
291
+ }
292
+
293
+ export const resolveUserMdxConfig = async () => {
294
+ if (state.RESOLVED_MDX_CONFIG !== undefined) {
295
+ return state.RESOLVED_MDX_CONFIG
296
+ }
297
+ const resolveConfig = async (config) => {
298
+ if (!config) return {}
299
+ if (typeof config === 'function') {
300
+ return (
301
+ (await config({
302
+ mode: state.CURRENT_MODE,
303
+ root: state.ROOT_DIR
304
+ })) || {}
305
+ )
306
+ }
307
+ return config || {}
308
+ }
309
+ const themeConfig = await resolveConfig(state.USER_THEME.mdx)
310
+ const userConfig = await resolveConfig(state.USER_MDX_CONFIG)
311
+ const merged = { ...themeConfig, ...userConfig }
312
+ const themePlugins = themeConfig?.rehypePlugins
313
+ const userPlugins = userConfig?.rehypePlugins
314
+ if (themePlugins || userPlugins) {
315
+ const normalize = (value) => (Array.isArray(value) ? value : value ? [value] : [])
316
+ merged.rehypePlugins = [...normalize(themePlugins), ...normalize(userPlugins)]
317
+ } else {
318
+ merged.rehypePlugins = []
319
+ }
320
+ state.RESOLVED_MDX_CONFIG = merged
321
+ return state.RESOLVED_MDX_CONFIG
322
+ }
323
+
324
+ export const resolveUserViteConfig = async (command) => {
325
+ if (state.RESOLVED_VITE_CONFIG !== undefined) {
326
+ return state.RESOLVED_VITE_CONFIG
327
+ }
328
+ const resolveConfig = async (config) => {
329
+ if (!config) return null
330
+ if (typeof config === 'function') {
331
+ const isPreview = command === 'preview'
332
+ return (
333
+ (await config({
334
+ mode: state.CURRENT_MODE,
335
+ root: state.ROOT_DIR,
336
+ command: isPreview ? 'serve' : command,
337
+ isPreview
338
+ })) || null
339
+ )
340
+ }
341
+ return config || null
342
+ }
343
+ const themeConfig = await resolveConfig(state.USER_THEME.vite)
344
+ const userConfig = await resolveConfig(state.USER_VITE_CONFIG)
345
+ if (!themeConfig && !userConfig) {
346
+ state.RESOLVED_VITE_CONFIG = null
347
+ return null
348
+ }
349
+ state.RESOLVED_VITE_CONFIG = themeConfig
350
+ ? userConfig
351
+ ? mergeConfig(themeConfig, userConfig)
352
+ : themeConfig
353
+ : userConfig
354
+ return state.RESOLVED_VITE_CONFIG
355
+ }