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.
- package/.editorconfig +19 -0
- package/.prettierrc +10 -0
- package/LICENSE +203 -0
- package/banner.txt +6 -0
- package/bin/methanol.js +24 -0
- package/index.js +22 -0
- package/package.json +42 -9
- package/src/assets.js +30 -0
- package/src/build-system.js +200 -0
- package/src/components.js +145 -0
- package/src/config.js +355 -0
- package/src/dev-server.js +559 -0
- package/src/main.js +87 -0
- package/src/mdx.js +254 -0
- package/src/node-loader.js +88 -0
- package/src/pagefind.js +99 -0
- package/src/pages.js +638 -0
- package/src/preview-server.js +58 -0
- package/src/public-assets.js +73 -0
- package/src/register-loader.js +29 -0
- package/src/rehype-plugins/link-resolve.js +89 -0
- package/src/rehype-plugins/methanol-ctx.js +89 -0
- package/src/renderer.js +25 -0
- package/src/rewind.js +117 -0
- package/src/stage-logger.js +59 -0
- package/src/state.js +159 -0
- package/src/virtual-module/inject.js +30 -0
- package/src/virtual-module/loader.js +116 -0
- package/src/virtual-module/pagefind.js +108 -0
- package/src/vite-plugins.js +173 -0
- package/themes/default/components/ThemeColorSwitch.client.jsx +95 -0
- package/themes/default/components/ThemeColorSwitch.static.jsx +23 -0
- package/themes/default/components/ThemeSearchBox.client.jsx +287 -0
- package/themes/default/components/ThemeSearchBox.static.jsx +41 -0
- package/themes/default/components/ThemeToCContainer.client.jsx +154 -0
- package/themes/default/components/ThemeToCContainer.static.jsx +61 -0
- package/themes/default/components/pre.client.jsx +84 -0
- package/themes/default/components/pre.jsx +27 -0
- package/themes/default/heading.jsx +35 -0
- package/themes/default/index.js +50 -0
- package/themes/default/page.jsx +249 -0
- package/themes/default/pages/404.mdx +8 -0
- package/themes/default/pages/index.mdx +9 -0
- package/themes/default/public/logo.png +0 -0
- package/themes/default/resources/style.css +1089 -0
|
@@ -0,0 +1,559 @@
|
|
|
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 { existsSync } from 'fs'
|
|
22
|
+
import { resolve, dirname, extname, join, basename } from 'path'
|
|
23
|
+
import { fileURLToPath } from 'url'
|
|
24
|
+
import chokidar from 'chokidar'
|
|
25
|
+
import { createServer, mergeConfig } from 'vite'
|
|
26
|
+
import { refurbish } from 'refurbish/vite'
|
|
27
|
+
import { state, cli } from './state.js'
|
|
28
|
+
import { resolveUserViteConfig } from './config.js'
|
|
29
|
+
import {
|
|
30
|
+
buildComponentRegistry,
|
|
31
|
+
buildComponentEntry,
|
|
32
|
+
invalidateRegistryEntry,
|
|
33
|
+
bumpComponentImportNonce,
|
|
34
|
+
isComponentFile,
|
|
35
|
+
isClientComponent,
|
|
36
|
+
COMPONENT_EXTENSIONS
|
|
37
|
+
} from './components.js'
|
|
38
|
+
import { buildPagesContext, buildPageEntry, routePathFromFile } from './pages.js'
|
|
39
|
+
import { compilePageMdx, renderHtml } from './mdx.js'
|
|
40
|
+
import { methanolResolverPlugin } from './vite-plugins.js'
|
|
41
|
+
import { copyPublicDir } from './public-assets.js'
|
|
42
|
+
|
|
43
|
+
export const runViteDev = async () => {
|
|
44
|
+
const baseFsAllow = [state.ROOT_DIR, state.USER_THEME.root].filter(Boolean)
|
|
45
|
+
const baseConfig = {
|
|
46
|
+
configFile: false,
|
|
47
|
+
root: state.PAGES_DIR,
|
|
48
|
+
publicDir: state.STATIC_DIR === false ? false : state.STATIC_DIR,
|
|
49
|
+
server: {
|
|
50
|
+
fs: {
|
|
51
|
+
allow: baseFsAllow
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
esbuild: {
|
|
55
|
+
jsx: 'automatic',
|
|
56
|
+
jsxImportSource: 'refui'
|
|
57
|
+
},
|
|
58
|
+
resolve: {
|
|
59
|
+
dedupe: ['refui', 'methanol']
|
|
60
|
+
},
|
|
61
|
+
plugins: [methanolResolverPlugin(), refurbish()]
|
|
62
|
+
}
|
|
63
|
+
const userConfig = await resolveUserViteConfig('serve')
|
|
64
|
+
const finalConfig = userConfig ? mergeConfig(baseConfig, userConfig) : baseConfig
|
|
65
|
+
if (state.STATIC_DIR !== false) {
|
|
66
|
+
await copyPublicDir({
|
|
67
|
+
sourceDir: state.THEME_PUBLIC_DIR,
|
|
68
|
+
targetDir: state.STATIC_DIR,
|
|
69
|
+
label: 'theme public'
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
if (cli.CLI_PORT != null) {
|
|
73
|
+
finalConfig.server = { ...(finalConfig.server || {}), port: cli.CLI_PORT }
|
|
74
|
+
}
|
|
75
|
+
if (cli.CLI_HOST !== null) {
|
|
76
|
+
finalConfig.server = { ...(finalConfig.server || {}), host: cli.CLI_HOST }
|
|
77
|
+
}
|
|
78
|
+
if (baseFsAllow.length) {
|
|
79
|
+
const fsConfig = finalConfig.server?.fs || {}
|
|
80
|
+
const allow = Array.isArray(fsConfig.allow) ? fsConfig.allow : []
|
|
81
|
+
for (const dir of baseFsAllow) {
|
|
82
|
+
if (!allow.includes(dir)) {
|
|
83
|
+
allow.push(dir)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
finalConfig.server = {
|
|
87
|
+
...(finalConfig.server || {}),
|
|
88
|
+
fs: {
|
|
89
|
+
...fsConfig,
|
|
90
|
+
allow
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const server = await createServer(finalConfig)
|
|
95
|
+
|
|
96
|
+
const themeComponentsDir = state.THEME_COMPONENTS_DIR
|
|
97
|
+
const themeEnv = state.THEME_ENV
|
|
98
|
+
const themeRegistry = themeComponentsDir
|
|
99
|
+
? await buildComponentRegistry({
|
|
100
|
+
componentsDir: themeComponentsDir,
|
|
101
|
+
client: themeEnv.client
|
|
102
|
+
})
|
|
103
|
+
: { components: {} }
|
|
104
|
+
const themeComponents = {
|
|
105
|
+
...(themeRegistry.components || {}),
|
|
106
|
+
...(state.USER_THEME.components || {})
|
|
107
|
+
}
|
|
108
|
+
const initialRegistry = await buildComponentRegistry()
|
|
109
|
+
let components = initialRegistry.components
|
|
110
|
+
const componentSources = new Map(initialRegistry.sources)
|
|
111
|
+
let pagesContext = null
|
|
112
|
+
let pagesContextToken = 0
|
|
113
|
+
const setPagesContext = (next) => {
|
|
114
|
+
pagesContext = next
|
|
115
|
+
pagesContextToken += 1
|
|
116
|
+
}
|
|
117
|
+
setPagesContext(await buildPagesContext({ compileAll: false }))
|
|
118
|
+
const htmlCache = new Map()
|
|
119
|
+
let htmlCacheEpoch = 0
|
|
120
|
+
|
|
121
|
+
const invalidateHtmlCache = () => {
|
|
122
|
+
htmlCacheEpoch += 1
|
|
123
|
+
htmlCache.clear()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const refreshPagesContext = async () => {
|
|
127
|
+
setPagesContext(await buildPagesContext({ compileAll: false }))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const runInitialCompile = async () => {
|
|
131
|
+
const token = pagesContextToken
|
|
132
|
+
try {
|
|
133
|
+
const nextContext = await buildPagesContext({ compileAll: true })
|
|
134
|
+
if (token !== pagesContextToken) {
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
setPagesContext(nextContext)
|
|
138
|
+
invalidateHtmlCache()
|
|
139
|
+
reload()
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error(err)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const resolvePageFile = (routePath) => {
|
|
146
|
+
const name = routePath === '/' ? 'index' : routePath.slice(1)
|
|
147
|
+
const mdxPath = resolve(state.PAGES_DIR, `${name}.mdx`)
|
|
148
|
+
if (existsSync(mdxPath)) return mdxPath
|
|
149
|
+
const mdPath = resolve(state.PAGES_DIR, `${name}.md`)
|
|
150
|
+
if (existsSync(mdPath)) return mdPath
|
|
151
|
+
return mdxPath
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const htmlMiddleware = async (req, res, next) => {
|
|
155
|
+
if (!req.url || req.method !== 'GET') {
|
|
156
|
+
return next()
|
|
157
|
+
}
|
|
158
|
+
if (req.url.startsWith('/@vite') || req.url.startsWith('/__vite')) {
|
|
159
|
+
return next()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const url = new URL(req.url, 'http://methanol')
|
|
163
|
+
let pathname = url.pathname
|
|
164
|
+
try {
|
|
165
|
+
pathname = decodeURIComponent(pathname)
|
|
166
|
+
} catch {}
|
|
167
|
+
const originalPathname = pathname
|
|
168
|
+
const hasTrailingSlash = originalPathname.endsWith('/') && originalPathname !== '/'
|
|
169
|
+
|
|
170
|
+
if (pathname.includes('.') && !pathname.endsWith('.html')) {
|
|
171
|
+
return next()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const accept = req.headers.accept || ''
|
|
175
|
+
if (!pathname.endsWith('.html') && !accept.includes('text/html')) {
|
|
176
|
+
return next()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let routePath = pathname
|
|
180
|
+
if (routePath.endsWith('.html')) {
|
|
181
|
+
routePath = routePath.slice(0, -'.html'.length)
|
|
182
|
+
if (routePath === '') {
|
|
183
|
+
routePath = '/'
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (routePath.endsWith('/') && routePath !== '/') {
|
|
187
|
+
routePath = routePath.slice(0, -1)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const requestedPath = routePath
|
|
191
|
+
const isExcludedPath = () => {
|
|
192
|
+
const excludedRoutes = pagesContext?.excludedRoutes
|
|
193
|
+
if (excludedRoutes?.has(requestedPath)) return true
|
|
194
|
+
const excludedDirPaths = pagesContext?.excludedDirPaths
|
|
195
|
+
if (excludedDirPaths?.size) {
|
|
196
|
+
for (const dirPath of excludedDirPaths) {
|
|
197
|
+
if (requestedPath === dirPath || requestedPath.startsWith(`${dirPath}/`)) {
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
const notFoundPage = pagesContext?.pagesByRoute?.get('/404') ?? null
|
|
205
|
+
const pageMeta = hasTrailingSlash
|
|
206
|
+
? (pagesContext?.pagesByRouteIndex?.get(requestedPath) ?? pagesContext?.pagesByRoute?.get(requestedPath) ?? null)
|
|
207
|
+
: (pagesContext?.pagesByRoute?.get(requestedPath) ?? null)
|
|
208
|
+
let filePath = pageMeta?.filePath || resolvePageFile(requestedPath)
|
|
209
|
+
let status = 200
|
|
210
|
+
let renderRoutePath = requestedPath
|
|
211
|
+
|
|
212
|
+
if (isExcludedPath()) {
|
|
213
|
+
if (notFoundPage) {
|
|
214
|
+
filePath = notFoundPage.filePath
|
|
215
|
+
renderRoutePath = '/404'
|
|
216
|
+
status = 404
|
|
217
|
+
} else {
|
|
218
|
+
return next()
|
|
219
|
+
}
|
|
220
|
+
} else if (requestedPath === '/404' && notFoundPage) {
|
|
221
|
+
filePath = notFoundPage.filePath
|
|
222
|
+
renderRoutePath = '/404'
|
|
223
|
+
status = 404
|
|
224
|
+
} else if (!pageMeta && !existsSync(filePath)) {
|
|
225
|
+
if (notFoundPage) {
|
|
226
|
+
filePath = notFoundPage.filePath
|
|
227
|
+
renderRoutePath = '/404'
|
|
228
|
+
status = 404
|
|
229
|
+
} else {
|
|
230
|
+
return next()
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const renderEpoch = htmlCacheEpoch
|
|
236
|
+
const cacheEntry = htmlCache.get(renderRoutePath)
|
|
237
|
+
if (cacheEntry && cacheEntry.filePath === filePath && cacheEntry.epoch === htmlCacheEpoch) {
|
|
238
|
+
res.statusCode = status
|
|
239
|
+
res.setHeader('Content-Type', 'text/html')
|
|
240
|
+
res.end(cacheEntry.html)
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const html = await renderHtml({
|
|
245
|
+
routePath: renderRoutePath,
|
|
246
|
+
filePath,
|
|
247
|
+
components: {
|
|
248
|
+
...themeComponents,
|
|
249
|
+
...components
|
|
250
|
+
},
|
|
251
|
+
pagesContext,
|
|
252
|
+
pageMeta
|
|
253
|
+
})
|
|
254
|
+
if (renderEpoch === htmlCacheEpoch) {
|
|
255
|
+
htmlCache.set(renderRoutePath, {
|
|
256
|
+
html,
|
|
257
|
+
filePath,
|
|
258
|
+
epoch: renderEpoch
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
res.statusCode = status
|
|
262
|
+
res.setHeader('Content-Type', 'text/html')
|
|
263
|
+
res.end(html)
|
|
264
|
+
} catch (err) {
|
|
265
|
+
console.error(err)
|
|
266
|
+
res.statusCode = 500
|
|
267
|
+
res.end('Internal Server Error')
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (Array.isArray(server.middlewares.stack)) {
|
|
272
|
+
server.middlewares.stack.unshift({ route: '', handle: htmlMiddleware })
|
|
273
|
+
} else {
|
|
274
|
+
server.middlewares.use(htmlMiddleware)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
await server.listen()
|
|
278
|
+
server.printUrls()
|
|
279
|
+
|
|
280
|
+
const invalidateRewindInject = () => {
|
|
281
|
+
const registryModule = server.moduleGraph.getModuleById('\0/.methanol_virtual_module/registry.js')
|
|
282
|
+
if (registryModule) {
|
|
283
|
+
server.moduleGraph.invalidateModule(registryModule)
|
|
284
|
+
}
|
|
285
|
+
const injectModule = server.moduleGraph.getModuleById('\0/.methanol_virtual_module/inject.js')
|
|
286
|
+
if (injectModule) {
|
|
287
|
+
server.moduleGraph.invalidateModule(injectModule)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let queue = Promise.resolve()
|
|
292
|
+
const enqueue = (task) => {
|
|
293
|
+
queue = queue.then(task).catch((err) => {
|
|
294
|
+
console.error(err)
|
|
295
|
+
})
|
|
296
|
+
return queue
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const reload = () => {
|
|
300
|
+
server.ws.send({ type: 'full-reload' })
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
runInitialCompile()
|
|
304
|
+
|
|
305
|
+
const refreshPages = async () => {
|
|
306
|
+
await refreshPagesContext()
|
|
307
|
+
invalidateHtmlCache()
|
|
308
|
+
reload()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const getExportName = (filePath) => basename(filePath).split('.')[0]
|
|
312
|
+
|
|
313
|
+
const findComponentExt = (dir, exportName) => {
|
|
314
|
+
for (const ext of COMPONENT_EXTENSIONS) {
|
|
315
|
+
if (
|
|
316
|
+
existsSync(join(dir, `${exportName}${ext}`)) ||
|
|
317
|
+
existsSync(join(dir, `${exportName}.client${ext}`)) ||
|
|
318
|
+
existsSync(join(dir, `${exportName}.static${ext}`))
|
|
319
|
+
) {
|
|
320
|
+
return ext
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return null
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const updateComponentEntry = async (filePath, { fallback = false } = {}) => {
|
|
327
|
+
bumpComponentImportNonce()
|
|
328
|
+
const exportName = getExportName(filePath)
|
|
329
|
+
const dir = dirname(filePath)
|
|
330
|
+
let ext = extname(filePath)
|
|
331
|
+
let { component, hasClient, staticPath } = await buildComponentEntry({
|
|
332
|
+
dir,
|
|
333
|
+
exportName,
|
|
334
|
+
ext
|
|
335
|
+
})
|
|
336
|
+
if (!component && fallback) {
|
|
337
|
+
ext = findComponentExt(dir, exportName)
|
|
338
|
+
if (ext) {
|
|
339
|
+
;({ component, hasClient, staticPath } = await buildComponentEntry({
|
|
340
|
+
dir,
|
|
341
|
+
exportName,
|
|
342
|
+
ext
|
|
343
|
+
}))
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (!component) {
|
|
347
|
+
delete components[exportName]
|
|
348
|
+
componentSources.delete(exportName)
|
|
349
|
+
invalidateRegistryEntry(exportName)
|
|
350
|
+
return { hasClient: false }
|
|
351
|
+
}
|
|
352
|
+
if (!hasClient) {
|
|
353
|
+
invalidateRegistryEntry(exportName)
|
|
354
|
+
}
|
|
355
|
+
components[exportName] = component
|
|
356
|
+
if (staticPath) {
|
|
357
|
+
componentSources.set(exportName, staticPath)
|
|
358
|
+
}
|
|
359
|
+
return { hasClient }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const pageWatchPaths = [state.PAGES_DIR, state.THEME_PAGES_DIR].filter(Boolean)
|
|
363
|
+
const pageWatcher = chokidar.watch(pageWatchPaths, {
|
|
364
|
+
ignoreInitial: true
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
const PAGE_UPDATE_DEBOUNCE_MS = 30
|
|
368
|
+
const pageUpdateTimers = new Map()
|
|
369
|
+
|
|
370
|
+
const schedulePageUpdate = (filePath, kind) => {
|
|
371
|
+
const existing = pageUpdateTimers.get(filePath)
|
|
372
|
+
if (existing?.timer) {
|
|
373
|
+
clearTimeout(existing.timer)
|
|
374
|
+
}
|
|
375
|
+
const entry = {
|
|
376
|
+
kind,
|
|
377
|
+
timer: setTimeout(() => {
|
|
378
|
+
pageUpdateTimers.delete(filePath)
|
|
379
|
+
enqueue(() => handlePageUpdate(filePath, kind))
|
|
380
|
+
}, PAGE_UPDATE_DEBOUNCE_MS)
|
|
381
|
+
}
|
|
382
|
+
pageUpdateTimers.set(filePath, entry)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const resolveWatchedSource = (filePath) => {
|
|
386
|
+
const inUserPages = routePathFromFile(filePath, state.PAGES_DIR)
|
|
387
|
+
if (inUserPages) {
|
|
388
|
+
return { pagesDir: state.PAGES_DIR, source: 'user', routePath: inUserPages }
|
|
389
|
+
}
|
|
390
|
+
if (state.THEME_PAGES_DIR) {
|
|
391
|
+
const inThemePages = routePathFromFile(filePath, state.THEME_PAGES_DIR)
|
|
392
|
+
if (inThemePages) {
|
|
393
|
+
return { pagesDir: state.THEME_PAGES_DIR, source: 'theme', routePath: inThemePages }
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return null
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const updatePageEntry = async (filePath, resolved) => {
|
|
400
|
+
if (!pagesContext || !resolved) return false
|
|
401
|
+
pagesContext.clearDerivedTitle?.(filePath)
|
|
402
|
+
const nextEntry = await buildPageEntry({
|
|
403
|
+
filePath,
|
|
404
|
+
pagesDir: resolved.pagesDir,
|
|
405
|
+
source: resolved.source
|
|
406
|
+
})
|
|
407
|
+
if (!nextEntry) return false
|
|
408
|
+
const prevEntry = pagesContext.pages?.find?.((page) => page.filePath === filePath) || null
|
|
409
|
+
if (!prevEntry) return false
|
|
410
|
+
if (prevEntry.exclude !== nextEntry.exclude) return false
|
|
411
|
+
if (prevEntry.isIndex !== nextEntry.isIndex || prevEntry.dir !== nextEntry.dir) return false
|
|
412
|
+
Object.assign(prevEntry, nextEntry)
|
|
413
|
+
prevEntry.mdxComponent = null
|
|
414
|
+
prevEntry.toc = null
|
|
415
|
+
pagesContext.refreshPagesTree?.()
|
|
416
|
+
pagesContext.refreshLanguages?.()
|
|
417
|
+
if (prevEntry.frontmatter?.title == null) {
|
|
418
|
+
if (prevEntry.content && prevEntry.content.trim().length) {
|
|
419
|
+
await compilePageMdx(prevEntry, pagesContext)
|
|
420
|
+
// Avoid caching a potentially stale render; recompile on request.
|
|
421
|
+
prevEntry.mdxComponent = null
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return true
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const isUserHeadAsset = (filePath) => {
|
|
428
|
+
const name = basename(filePath)
|
|
429
|
+
if (name !== 'style.css' && name !== 'index.js' && name !== 'index.ts') {
|
|
430
|
+
return false
|
|
431
|
+
}
|
|
432
|
+
const root = resolve(state.PAGES_DIR || '')
|
|
433
|
+
return root && resolve(dirname(filePath)) === root
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const handlePageUpdate = async (filePath, kind) => {
|
|
437
|
+
if (isUserHeadAsset(filePath)) {
|
|
438
|
+
invalidateHtmlCache()
|
|
439
|
+
reload()
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
const resolved = resolveWatchedSource(filePath)
|
|
443
|
+
if (kind === 'unlink') {
|
|
444
|
+
if (resolved?.routePath) {
|
|
445
|
+
htmlCache.delete(resolved.routePath)
|
|
446
|
+
} else {
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
await refreshPages()
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
const updated = await updatePageEntry(filePath, resolved)
|
|
453
|
+
if (updated) {
|
|
454
|
+
invalidateHtmlCache()
|
|
455
|
+
reload()
|
|
456
|
+
return
|
|
457
|
+
}
|
|
458
|
+
if (!resolved?.routePath) {
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
await refreshPages()
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
pageWatcher.on('change', (filePath) => {
|
|
465
|
+
schedulePageUpdate(filePath, 'change')
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
pageWatcher.on('add', (filePath) => {
|
|
469
|
+
schedulePageUpdate(filePath, 'add')
|
|
470
|
+
})
|
|
471
|
+
pageWatcher.on('unlink', (filePath) => {
|
|
472
|
+
schedulePageUpdate(filePath, 'unlink')
|
|
473
|
+
})
|
|
474
|
+
pageWatcher.on('addDir', () => {
|
|
475
|
+
enqueue(refreshPages)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
pageWatcher.on('unlinkDir', () => {
|
|
479
|
+
enqueue(refreshPages)
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
const componentWatcher = chokidar.watch(state.COMPONENTS_DIR, {
|
|
483
|
+
ignoreInitial: true
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
componentWatcher.on('add', (filePath) => {
|
|
487
|
+
if (!isComponentFile(filePath)) {
|
|
488
|
+
return
|
|
489
|
+
}
|
|
490
|
+
if (isClientComponent(filePath)) {
|
|
491
|
+
enqueue(async () => {
|
|
492
|
+
const { hasClient } = await updateComponentEntry(filePath)
|
|
493
|
+
if (hasClient) {
|
|
494
|
+
invalidateRewindInject()
|
|
495
|
+
}
|
|
496
|
+
invalidateHtmlCache()
|
|
497
|
+
reload()
|
|
498
|
+
})
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
enqueue(async () => {
|
|
502
|
+
const { hasClient } = await updateComponentEntry(filePath)
|
|
503
|
+
invalidateHtmlCache()
|
|
504
|
+
if (hasClient) {
|
|
505
|
+
invalidateRewindInject()
|
|
506
|
+
}
|
|
507
|
+
reload()
|
|
508
|
+
})
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
componentWatcher.on('change', (filePath) => {
|
|
512
|
+
if (!isComponentFile(filePath)) {
|
|
513
|
+
return
|
|
514
|
+
}
|
|
515
|
+
if (isClientComponent(filePath)) {
|
|
516
|
+
enqueue(async () => {
|
|
517
|
+
await updateComponentEntry(filePath)
|
|
518
|
+
invalidateHtmlCache()
|
|
519
|
+
})
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
enqueue(async () => {
|
|
523
|
+
const { hasClient } = await updateComponentEntry(filePath)
|
|
524
|
+
invalidateHtmlCache()
|
|
525
|
+
if (hasClient) {
|
|
526
|
+
invalidateRewindInject()
|
|
527
|
+
}
|
|
528
|
+
reload()
|
|
529
|
+
})
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
componentWatcher.on('unlink', (filePath) => {
|
|
533
|
+
if (!isComponentFile(filePath)) return
|
|
534
|
+
if (isClientComponent(filePath)) {
|
|
535
|
+
enqueue(async () => {
|
|
536
|
+
await updateComponentEntry(filePath, { fallback: true })
|
|
537
|
+
invalidateRewindInject()
|
|
538
|
+
invalidateHtmlCache()
|
|
539
|
+
reload()
|
|
540
|
+
})
|
|
541
|
+
return
|
|
542
|
+
}
|
|
543
|
+
const exportName = getExportName(filePath)
|
|
544
|
+
const currentSource = componentSources.get(exportName)
|
|
545
|
+
if (currentSource && currentSource !== filePath && existsSync(currentSource)) {
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
enqueue(async () => {
|
|
549
|
+
const { hasClient } = await updateComponentEntry(filePath, {
|
|
550
|
+
fallback: true
|
|
551
|
+
})
|
|
552
|
+
invalidateHtmlCache()
|
|
553
|
+
if (hasClient) {
|
|
554
|
+
invalidateRewindInject()
|
|
555
|
+
}
|
|
556
|
+
reload()
|
|
557
|
+
})
|
|
558
|
+
})
|
|
559
|
+
}
|
package/src/main.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
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 { loadUserConfig, applyConfig } from './config.js'
|
|
22
|
+
import { runViteDev } from './dev-server.js'
|
|
23
|
+
import { buildHtmlEntries, runViteBuild } from './build-system.js'
|
|
24
|
+
import { runPagefind } from './pagefind.js'
|
|
25
|
+
import { runVitePreview } from './preview-server.js'
|
|
26
|
+
import { cli, state } from './state.js'
|
|
27
|
+
import { readFile } from 'fs/promises'
|
|
28
|
+
|
|
29
|
+
const printBanner = async () => {
|
|
30
|
+
try {
|
|
31
|
+
const pkgUrl = new URL('../package.json', import.meta.url)
|
|
32
|
+
const raw = await readFile(pkgUrl, 'utf-8')
|
|
33
|
+
const pkg = JSON.parse(raw)
|
|
34
|
+
const version = pkg?.version ? `v${pkg.version}` : ''
|
|
35
|
+
const isTty = Boolean(process.stdout && process.stdout.isTTY)
|
|
36
|
+
const label = `Methanol ${version}`.trim()
|
|
37
|
+
if (isTty) {
|
|
38
|
+
const bannerUrl = new URL('../banner.txt', import.meta.url)
|
|
39
|
+
const banner = await readFile(bannerUrl, 'utf-8')
|
|
40
|
+
console.log(banner.trimEnd())
|
|
41
|
+
console.log(`\n\t${label}\n`)
|
|
42
|
+
} else {
|
|
43
|
+
console.log(label)
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
console.log('Methanol')
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const main = async () => {
|
|
51
|
+
await printBanner()
|
|
52
|
+
const command = cli.command
|
|
53
|
+
if (!command) {
|
|
54
|
+
cli.showHelp()
|
|
55
|
+
process.exit(1)
|
|
56
|
+
}
|
|
57
|
+
const normalizedCommand = command === 'preview' ? 'serve' : command
|
|
58
|
+
const isDev = normalizedCommand === 'dev'
|
|
59
|
+
const isPreview = normalizedCommand === 'serve'
|
|
60
|
+
const isBuild = normalizedCommand === 'build'
|
|
61
|
+
const mode = isDev ? 'development' : 'production'
|
|
62
|
+
const config = await loadUserConfig(mode, cli.CLI_CONFIG_PATH)
|
|
63
|
+
await applyConfig(config, mode)
|
|
64
|
+
if (isDev) {
|
|
65
|
+
await runViteDev()
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
if (isPreview) {
|
|
69
|
+
await runVitePreview()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
if (isBuild) {
|
|
73
|
+
const { entry, htmlCache } = await buildHtmlEntries()
|
|
74
|
+
await runViteBuild(entry, htmlCache)
|
|
75
|
+
if (state.PAGEFIND_ENABLED) {
|
|
76
|
+
await runPagefind()
|
|
77
|
+
}
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
cli.showHelp()
|
|
81
|
+
process.exit(1)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main().catch((err) => {
|
|
85
|
+
console.error(err)
|
|
86
|
+
process.exit(1)
|
|
87
|
+
})
|