methanol 0.0.20 → 0.0.22
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/bin/methanol.js +1 -2
- package/package.json +6 -4
- package/src/base.js +36 -0
- package/src/build-system.js +337 -113
- package/src/client/sw.js +227 -180
- package/src/client/virtual-module/pwa-inject.js +25 -3
- package/src/config.js +19 -35
- package/src/dev-server.js +4 -2
- package/src/entry.js +22 -0
- package/src/feed.js +3 -12
- package/src/html/build-html.js +221 -0
- package/src/html/utils.js +125 -0
- package/src/html/worker-html.js +591 -0
- package/src/main.js +97 -6
- package/src/mdx.js +35 -2
- package/src/pages-index.js +11 -3
- package/src/pages.js +26 -11
- package/src/pwa.js +240 -0
- package/src/state.js +15 -3
- package/src/utils.js +1 -1
- package/src/vite-plugins.js +6 -2
- package/src/workers/build-pool.js +1 -1
- package/src/workers/build-worker.js +157 -18
- package/src/workers/entry-build-worker.js +22 -0
- package/src/workers/entry-mdx-compile-worker.js +22 -0
- package/src/workers/mdx-compile-worker.js +0 -1
- package/themes/benchmark/README.md +5 -0
- package/themes/benchmark/index.js +33 -0
- package/themes/benchmark/src/page.jsx +25 -0
- package/themes/blog/src/page.jsx +0 -2
- package/themes/default/src/nav-tree.jsx +3 -2
- package/themes/default/src/page.jsx +0 -2
package/bin/methanol.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "methanol",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "Static site generator powered by rEFui and MDX",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -29,21 +29,23 @@
|
|
|
29
29
|
"@mdx-js/mdx": "^3.1.1",
|
|
30
30
|
"@sindresorhus/fnv1a": "^3.1.0",
|
|
31
31
|
"@stefanprobst/rehype-extract-toc": "^3.0.0",
|
|
32
|
-
"@wooorm/starry-night": "^3.
|
|
32
|
+
"@wooorm/starry-night": "^3.9.0",
|
|
33
33
|
"chokidar": "^5.0.0",
|
|
34
34
|
"esbuild": "^0.27.2",
|
|
35
|
+
"fast-glob": "^3.3.3",
|
|
35
36
|
"gray-matter": "^4.0.3",
|
|
36
37
|
"hast-util-is-element": "^3.0.0",
|
|
38
|
+
"htmlparser2": "^10.1.0",
|
|
37
39
|
"json5": "^2.2.3",
|
|
38
40
|
"null-prototype-object": "^1.2.5",
|
|
41
|
+
"picomatch": "^4.0.3",
|
|
39
42
|
"refui": "^0.17.1",
|
|
40
43
|
"refurbish": "^0.1.8",
|
|
41
44
|
"rehype-slug": "^6.0.0",
|
|
42
45
|
"rehype-starry-night": "^2.2.0",
|
|
43
46
|
"remark-gfm": "^4.0.1",
|
|
44
|
-
"unist-util-visit": "^5.
|
|
47
|
+
"unist-util-visit": "^5.1.0",
|
|
45
48
|
"vite": "^7.3.1",
|
|
46
|
-
"vite-plugin-pwa": "^1.2.0",
|
|
47
49
|
"workbox-core": "^7.4.0",
|
|
48
50
|
"workbox-routing": "^7.4.0",
|
|
49
51
|
"workbox-strategies": "^7.4.0",
|
package/src/base.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
export const normalizeBasePrefix = (value) => {
|
|
22
|
+
if (!value || value === '/' || value === './') return ''
|
|
23
|
+
if (typeof value !== 'string') return ''
|
|
24
|
+
let base = value.trim()
|
|
25
|
+
if (!base || base === '/' || base === './') return ''
|
|
26
|
+
if (base.startsWith('http://') || base.startsWith('https://')) {
|
|
27
|
+
try {
|
|
28
|
+
base = new URL(base).pathname
|
|
29
|
+
} catch {
|
|
30
|
+
return ''
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!base.startsWith('/')) return ''
|
|
34
|
+
if (base.endsWith('/')) base = base.slice(0, -1)
|
|
35
|
+
return base
|
|
36
|
+
}
|
package/src/build-system.js
CHANGED
|
@@ -18,20 +18,22 @@
|
|
|
18
18
|
* under the License.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import { existsSync } from 'fs'
|
|
21
22
|
import { writeFile, mkdir, rm, readFile, readdir, stat } from 'fs/promises'
|
|
22
|
-
import { resolve, dirname, join } from 'path'
|
|
23
|
+
import { resolve, dirname, join, basename } from 'path'
|
|
24
|
+
import { createHash } from 'crypto'
|
|
23
25
|
import { fileURLToPath } from 'url'
|
|
24
26
|
import { build as viteBuild, mergeConfig, normalizePath } from 'vite'
|
|
25
|
-
import { VitePWA } from 'vite-plugin-pwa'
|
|
26
27
|
import { state, cli } from './state.js'
|
|
27
28
|
import { resolveUserViteConfig } from './config.js'
|
|
28
29
|
import { buildPagesContext } from './pages.js'
|
|
29
30
|
import { selectFeedPages } from './feed.js'
|
|
30
31
|
import { buildComponentRegistry } from './components.js'
|
|
31
32
|
import { createBuildWorkers, runWorkerStage, terminateWorkers } from './workers/build-pool.js'
|
|
32
|
-
import {
|
|
33
|
+
import { methanolResolverPlugin } from './vite-plugins.js'
|
|
33
34
|
import { createStageLogger } from './stage-logger.js'
|
|
34
35
|
import { preparePublicAssets } from './public-assets.js'
|
|
36
|
+
export { scanHtmlEntries, rewriteHtmlEntries } from './html/build-html.js'
|
|
35
37
|
|
|
36
38
|
const __filename = fileURLToPath(import.meta.url)
|
|
37
39
|
const __dirname = dirname(__filename)
|
|
@@ -40,11 +42,29 @@ const ensureDir = async (dir) => {
|
|
|
40
42
|
await mkdir(dir, { recursive: true })
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
const ensureSwEntry = async () => {
|
|
46
|
+
const entriesDir = resolve(resolveMethanolDir(), ENTRY_DIR)
|
|
47
|
+
await ensureDir(entriesDir)
|
|
48
|
+
const swEntryPath = resolve(entriesDir, 'sw-entry.js')
|
|
49
|
+
const swSource = normalizePath(resolve(__dirname, 'client', 'sw.js'))
|
|
50
|
+
await writeFile(swEntryPath, `import ${JSON.stringify(swSource)}\n`)
|
|
51
|
+
return swEntryPath
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const INLINE_DIR = 'inline'
|
|
55
|
+
const ENTRY_DIR = 'entries'
|
|
56
|
+
const WRITE_CONCURRENCY_LIMIT = 32
|
|
57
|
+
|
|
58
|
+
const resolveMethanolDir = () => resolve(state.PAGES_DIR, '.methanol')
|
|
59
|
+
|
|
43
60
|
const isHtmlFile = (name) => name.endsWith('.html')
|
|
44
61
|
const collectHtmlFiles = async (dir, basePath = '') => {
|
|
45
62
|
const entries = await readdir(dir)
|
|
46
63
|
const files = []
|
|
47
64
|
for (const entry of entries.sort()) {
|
|
65
|
+
if (entry.startsWith('.') || entry.startsWith('_')) {
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
48
68
|
const fullPath = resolve(dir, entry)
|
|
49
69
|
const stats = await stat(fullPath)
|
|
50
70
|
if (stats.isDirectory()) {
|
|
@@ -65,11 +85,18 @@ const collectHtmlFiles = async (dir, basePath = '') => {
|
|
|
65
85
|
return files
|
|
66
86
|
}
|
|
67
87
|
|
|
68
|
-
|
|
88
|
+
const hashKey = (value) =>
|
|
89
|
+
createHash('md5').update(value).digest('hex')
|
|
90
|
+
|
|
91
|
+
const makeInputKey = (prefix, value) => `${prefix}-${hashKey(value).slice(0, 12)}`
|
|
92
|
+
|
|
93
|
+
export const buildHtmlEntries = async (options = {}) => {
|
|
94
|
+
const keepWorkers = Boolean(options.keepWorkers)
|
|
69
95
|
await resolveUserViteConfig('build') // Prepare `base`
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
await
|
|
96
|
+
const htmlStageDir = state.INTERMEDIATE_DIR || resolve(state.PAGES_DIR, '.methanol/html')
|
|
97
|
+
if (htmlStageDir) {
|
|
98
|
+
await rm(htmlStageDir, { recursive: true, force: true })
|
|
99
|
+
await ensureDir(htmlStageDir)
|
|
73
100
|
}
|
|
74
101
|
|
|
75
102
|
const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build' && !cli.CLI_VERBOSE
|
|
@@ -84,8 +111,13 @@ export const buildHtmlEntries = async () => {
|
|
|
84
111
|
}
|
|
85
112
|
await buildComponentRegistry()
|
|
86
113
|
const pagesContext = await buildPagesContext({ compileAll: false })
|
|
87
|
-
const
|
|
88
|
-
const
|
|
114
|
+
const htmlEntries = []
|
|
115
|
+
const htmlEntryNames = new Set()
|
|
116
|
+
const inlineDir = resolve(resolveMethanolDir(), INLINE_DIR)
|
|
117
|
+
await rm(inlineDir, { recursive: true, force: true })
|
|
118
|
+
await ensureDir(inlineDir)
|
|
119
|
+
const renderScans = new Map()
|
|
120
|
+
const renderScansById = new Map()
|
|
89
121
|
const resolveOutputName = (page) => {
|
|
90
122
|
if (page.routePath === '/') return 'index'
|
|
91
123
|
if (page.isIndex && page.dir) {
|
|
@@ -94,13 +126,16 @@ export const buildHtmlEntries = async () => {
|
|
|
94
126
|
return page.routePath.slice(1)
|
|
95
127
|
}
|
|
96
128
|
|
|
97
|
-
const pages = pagesContext.pages || []
|
|
129
|
+
const pages = pagesContext.pagesAll || pagesContext.pages || []
|
|
98
130
|
const totalPages = pages.length
|
|
99
131
|
const { workers, assignments } = createBuildWorkers(totalPages)
|
|
132
|
+
const writeConcurrency = Math.max(1, Math.floor(WRITE_CONCURRENCY_LIMIT / workers.length))
|
|
100
133
|
const excludedRoutes = Array.from(pagesContext.excludedRoutes || [])
|
|
101
134
|
const excludedDirs = Array.from(pagesContext.excludedDirs || [])
|
|
102
135
|
const rssContent = new Map()
|
|
103
|
-
|
|
136
|
+
let feedIds = []
|
|
137
|
+
let feedAssignments = null
|
|
138
|
+
let completedRun = false
|
|
104
139
|
try {
|
|
105
140
|
await runWorkerStage({
|
|
106
141
|
workers,
|
|
@@ -168,6 +203,18 @@ export const buildHtmlEntries = async () => {
|
|
|
168
203
|
}
|
|
169
204
|
}))
|
|
170
205
|
})
|
|
206
|
+
if (state.RSS_ENABLED) {
|
|
207
|
+
const feedPages = selectFeedPages(pages, state.RSS_OPTIONS || {})
|
|
208
|
+
const pageIndex = new Map(pages.map((page, index) => [page, index]))
|
|
209
|
+
feedIds = feedPages.map((page) => pageIndex.get(page)).filter((id) => id != null)
|
|
210
|
+
if (feedIds.length) {
|
|
211
|
+
feedAssignments = Array.from({ length: workers.length }, () => [])
|
|
212
|
+
for (const id of feedIds) {
|
|
213
|
+
feedAssignments[id % workers.length].push(id)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
171
218
|
const renderToken = stageLogger.start('Rendering pages')
|
|
172
219
|
completed = 0
|
|
173
220
|
await runWorkerStage({
|
|
@@ -178,7 +225,10 @@ export const buildHtmlEntries = async () => {
|
|
|
178
225
|
message: {
|
|
179
226
|
type: 'render',
|
|
180
227
|
stage: 'render',
|
|
181
|
-
ids: assignments[index]
|
|
228
|
+
ids: assignments[index],
|
|
229
|
+
feedIds: feedAssignments ? feedAssignments[index] : [],
|
|
230
|
+
htmlStageDir,
|
|
231
|
+
writeConcurrency
|
|
182
232
|
}
|
|
183
233
|
})),
|
|
184
234
|
onProgress: (count) => {
|
|
@@ -190,74 +240,36 @@ export const buildHtmlEntries = async () => {
|
|
|
190
240
|
if (!result || typeof result.id !== 'number') return
|
|
191
241
|
const page = pages[result.id]
|
|
192
242
|
if (!page) return
|
|
193
|
-
|
|
243
|
+
if (result.scan) {
|
|
244
|
+
renderScansById.set(result.id, result.scan)
|
|
245
|
+
}
|
|
194
246
|
const name = resolveOutputName(page)
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
247
|
+
const outPath = htmlStageDir
|
|
248
|
+
? (result.stagePath || resolve(htmlStageDir, `${name}.html`))
|
|
249
|
+
: `${name}.html`
|
|
250
|
+
htmlEntryNames.add(name)
|
|
251
|
+
htmlEntries.push({ name, routePath: page.routePath, stagePath: outPath, source: 'rendered' })
|
|
252
|
+
if (result.feedContent != null) {
|
|
253
|
+
const key = page.path || page.routePath
|
|
254
|
+
if (key) {
|
|
255
|
+
rssContent.set(key, result.feedContent || '')
|
|
256
|
+
}
|
|
200
257
|
}
|
|
201
258
|
}
|
|
202
259
|
})
|
|
203
260
|
stageLogger.end(renderToken)
|
|
204
261
|
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (id != null) {
|
|
212
|
-
feedIds.push(id)
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (feedIds.length) {
|
|
216
|
-
const rssToken = stageLogger.start('Rendering feed')
|
|
217
|
-
completed = 0
|
|
218
|
-
const rssAssignments = Array.from({ length: workers.length }, () => [])
|
|
219
|
-
for (let i = 0; i < feedIds.length; i += 1) {
|
|
220
|
-
rssAssignments[i % workers.length].push(feedIds[i])
|
|
221
|
-
}
|
|
222
|
-
await runWorkerStage({
|
|
223
|
-
workers,
|
|
224
|
-
stage: 'rss',
|
|
225
|
-
messages: workers.map((worker, index) => ({
|
|
226
|
-
worker,
|
|
227
|
-
message: {
|
|
228
|
-
type: 'rss',
|
|
229
|
-
stage: 'rss',
|
|
230
|
-
ids: rssAssignments[index]
|
|
231
|
-
}
|
|
232
|
-
})),
|
|
233
|
-
onProgress: (count) => {
|
|
234
|
-
if (!logEnabled) return
|
|
235
|
-
completed = count
|
|
236
|
-
stageLogger.update(rssToken, `Rendering feed [${completed}/${feedIds.length}]`)
|
|
237
|
-
},
|
|
238
|
-
onResult: (result) => {
|
|
239
|
-
if (!result || typeof result.id !== 'number') return
|
|
240
|
-
const page = pages[result.id]
|
|
241
|
-
if (!page) return
|
|
242
|
-
const key = page.path || page.routePath
|
|
243
|
-
if (key) {
|
|
244
|
-
rssContent.set(key, result.content || '')
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
})
|
|
248
|
-
stageLogger.end(rssToken)
|
|
249
|
-
}
|
|
262
|
+
for (const [id, scan] of renderScansById.entries()) {
|
|
263
|
+
const page = pages[id]
|
|
264
|
+
if (!page || !scan) continue
|
|
265
|
+
const name = resolveOutputName(page)
|
|
266
|
+
const stagePath = htmlStageDir ? resolve(htmlStageDir, `${name}.html`) : `${name}.html`
|
|
267
|
+
renderScans.set(stagePath, scan)
|
|
250
268
|
}
|
|
269
|
+
completedRun = true
|
|
251
270
|
} finally {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (state.INTERMEDIATE_DIR) {
|
|
255
|
-
for (const output of intermediateOutputs) {
|
|
256
|
-
const html = htmlCache.get(output.id)
|
|
257
|
-
if (typeof html !== 'string') continue
|
|
258
|
-
const outPath = resolve(state.INTERMEDIATE_DIR, `${output.name}.html`)
|
|
259
|
-
await ensureDir(dirname(outPath))
|
|
260
|
-
await writeFile(outPath, html)
|
|
271
|
+
if (!keepWorkers || !completedRun) {
|
|
272
|
+
await terminateWorkers(workers)
|
|
261
273
|
}
|
|
262
274
|
}
|
|
263
275
|
|
|
@@ -281,27 +293,133 @@ export const buildHtmlEntries = async () => {
|
|
|
281
293
|
}
|
|
282
294
|
const name = file.relativePath.replace(/\.html$/, '')
|
|
283
295
|
const outputName = name === 'index' ? 'index' : name
|
|
284
|
-
if (
|
|
296
|
+
if (htmlEntryNames.has(outputName)) {
|
|
285
297
|
continue
|
|
286
298
|
}
|
|
287
299
|
const html = await readFile(file.fullPath, 'utf-8')
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
htmlCache.set(id, html)
|
|
291
|
-
if (state.INTERMEDIATE_DIR) {
|
|
292
|
-
const outPath = resolve(state.INTERMEDIATE_DIR, file.relativePath)
|
|
300
|
+
const outPath = htmlStageDir ? resolve(htmlStageDir, file.relativePath) : null
|
|
301
|
+
if (outPath) {
|
|
293
302
|
await ensureDir(dirname(outPath))
|
|
294
303
|
await writeFile(outPath, html)
|
|
295
304
|
}
|
|
305
|
+
htmlEntryNames.add(outputName)
|
|
306
|
+
htmlEntries.push({
|
|
307
|
+
name: outputName,
|
|
308
|
+
routePath: outputName === 'index'
|
|
309
|
+
? '/'
|
|
310
|
+
: outputName.endsWith('/index')
|
|
311
|
+
? `/${outputName.slice(0, -'/index'.length)}/`
|
|
312
|
+
: `/${outputName}`,
|
|
313
|
+
stagePath: outPath,
|
|
314
|
+
inputPath: file.fullPath,
|
|
315
|
+
source: 'static'
|
|
316
|
+
})
|
|
296
317
|
}
|
|
297
318
|
|
|
298
|
-
return {
|
|
319
|
+
return {
|
|
320
|
+
htmlEntries,
|
|
321
|
+
htmlStageDir,
|
|
322
|
+
pagesContext,
|
|
323
|
+
rssContent,
|
|
324
|
+
renderScans,
|
|
325
|
+
renderScansById,
|
|
326
|
+
workers: keepWorkers ? workers : null,
|
|
327
|
+
assignments: keepWorkers ? assignments : null
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export const rewriteHtmlEntriesInWorkers = async ({
|
|
332
|
+
pages = [],
|
|
333
|
+
htmlStageDir,
|
|
334
|
+
manifest,
|
|
335
|
+
scanResult,
|
|
336
|
+
renderScansById,
|
|
337
|
+
onProgress,
|
|
338
|
+
workers: existingWorkers = null,
|
|
339
|
+
assignments: existingAssignments = null
|
|
340
|
+
}) => {
|
|
341
|
+
const totalPages = pages.length
|
|
342
|
+
if (!totalPages) return
|
|
343
|
+
const useExisting = Array.isArray(existingWorkers) && Array.isArray(existingAssignments)
|
|
344
|
+
const { workers, assignments } = useExisting
|
|
345
|
+
? { workers: existingWorkers, assignments: existingAssignments }
|
|
346
|
+
: createBuildWorkers(totalPages)
|
|
347
|
+
try {
|
|
348
|
+
if (!useExisting) {
|
|
349
|
+
await runWorkerStage({
|
|
350
|
+
workers,
|
|
351
|
+
stage: 'setPagesLite',
|
|
352
|
+
messages: workers.map((worker) => ({
|
|
353
|
+
worker,
|
|
354
|
+
message: {
|
|
355
|
+
type: 'setPagesLite',
|
|
356
|
+
stage: 'setPagesLite',
|
|
357
|
+
pages
|
|
358
|
+
}
|
|
359
|
+
}))
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const entryModules = Array.isArray(scanResult?.entryModules) ? scanResult.entryModules : []
|
|
364
|
+
const commonScripts = Array.isArray(scanResult?.commonScripts) ? scanResult.commonScripts : []
|
|
365
|
+
const commonEntry = scanResult?.commonScriptEntry?.manifestKey
|
|
366
|
+
? manifest?.[scanResult.commonScriptEntry.manifestKey] || manifest?.[`/${scanResult.commonScriptEntry.manifestKey}`]
|
|
367
|
+
: null
|
|
368
|
+
|
|
369
|
+
await runWorkerStage({
|
|
370
|
+
workers,
|
|
371
|
+
stage: 'rewrite',
|
|
372
|
+
messages: workers.map((worker, index) => {
|
|
373
|
+
const ids = assignments[index] || []
|
|
374
|
+
const scans = {}
|
|
375
|
+
if (renderScansById) {
|
|
376
|
+
for (const id of ids) {
|
|
377
|
+
const scan = renderScansById.get(id)
|
|
378
|
+
if (scan) scans[id] = scan
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
worker,
|
|
383
|
+
message: {
|
|
384
|
+
type: 'rewrite',
|
|
385
|
+
stage: 'rewrite',
|
|
386
|
+
ids,
|
|
387
|
+
htmlStageDir,
|
|
388
|
+
manifest,
|
|
389
|
+
entryModules,
|
|
390
|
+
commonScripts,
|
|
391
|
+
commonEntry,
|
|
392
|
+
scans
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}),
|
|
396
|
+
onProgress: (count) => {
|
|
397
|
+
if (typeof onProgress === 'function') {
|
|
398
|
+
onProgress(count, totalPages)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
} finally {
|
|
403
|
+
if (!useExisting) {
|
|
404
|
+
await terminateWorkers(workers)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
299
407
|
}
|
|
300
408
|
|
|
301
|
-
export const runViteBuild = async (
|
|
409
|
+
export const runViteBuild = async (inputs) => {
|
|
302
410
|
const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build' && !cli.CLI_VERBOSE
|
|
303
411
|
const stageLogger = createStageLogger(logEnabled)
|
|
304
412
|
const token = stageLogger.start('Building bundle')
|
|
413
|
+
const rewriteOptions = inputs.rewrite || null
|
|
414
|
+
const preWrite = typeof inputs.preWrite === 'function' ? inputs.preWrite : null
|
|
415
|
+
const postWrite = typeof inputs.postWrite === 'function' ? inputs.postWrite : null
|
|
416
|
+
let manifestData = null
|
|
417
|
+
let bundleEnded = false
|
|
418
|
+
const endBundleStage = () => {
|
|
419
|
+
if (bundleEnded) return
|
|
420
|
+
bundleEnded = true
|
|
421
|
+
stageLogger.end(token)
|
|
422
|
+
}
|
|
305
423
|
|
|
306
424
|
if (state.STATIC_DIR !== false && state.MERGED_ASSETS_DIR) {
|
|
307
425
|
await preparePublicAssets({
|
|
@@ -311,20 +429,53 @@ export const runViteBuild = async (entry, htmlCache) => {
|
|
|
311
429
|
})
|
|
312
430
|
}
|
|
313
431
|
const copyPublicDirEnabled = state.STATIC_DIR !== false
|
|
314
|
-
const
|
|
315
|
-
const
|
|
432
|
+
const entryModules = Array.isArray(inputs.entryModules) ? inputs.entryModules : []
|
|
433
|
+
const entryInputs = entryModules
|
|
434
|
+
.filter((entry) => entry && entry.kind !== 'style')
|
|
435
|
+
.map((entry) => entry.fsPath)
|
|
436
|
+
.filter(Boolean)
|
|
437
|
+
.sort()
|
|
438
|
+
const htmlEntries = Array.isArray(inputs.htmlEntries) ? inputs.htmlEntries : []
|
|
439
|
+
const htmlInputs = htmlEntries
|
|
440
|
+
.filter((entry) => entry?.source === 'static' && entry.inputPath)
|
|
441
|
+
.map((entry) => entry.inputPath)
|
|
442
|
+
.sort()
|
|
443
|
+
if (cli.CLI_VERBOSE && entryInputs.length === 0) {
|
|
444
|
+
console.log('Vite pipeline: no wrapper entries detected (no module scripts/stylesheets found)')
|
|
445
|
+
}
|
|
446
|
+
const rollupInput = {}
|
|
447
|
+
for (const entryPath of entryInputs) {
|
|
448
|
+
const normalized = normalizePath(entryPath)
|
|
449
|
+
rollupInput[makeInputKey('chunk', normalized)] = normalized
|
|
450
|
+
}
|
|
451
|
+
for (const htmlPath of htmlInputs) {
|
|
452
|
+
const normalized = normalizePath(htmlPath)
|
|
453
|
+
rollupInput[makeInputKey('html', normalized)] = normalized
|
|
454
|
+
}
|
|
455
|
+
let swEntryPath = null
|
|
456
|
+
if (state.PWA_ENABLED) {
|
|
457
|
+
swEntryPath = await ensureSwEntry()
|
|
458
|
+
if (swEntryPath) {
|
|
459
|
+
const normalized = normalizePath(swEntryPath)
|
|
460
|
+
rollupInput['sw'] = normalized
|
|
461
|
+
}
|
|
462
|
+
}
|
|
316
463
|
const baseConfig = {
|
|
317
464
|
configFile: false,
|
|
318
465
|
root: state.PAGES_DIR,
|
|
319
|
-
appType: '
|
|
466
|
+
appType: 'custom',
|
|
320
467
|
publicDir: state.STATIC_DIR === false ? false : state.STATIC_DIR,
|
|
321
468
|
logLevel: cli.CLI_VERBOSE ? 'info' : 'silent',
|
|
322
469
|
build: {
|
|
323
470
|
outDir: state.DIST_DIR,
|
|
324
471
|
emptyOutDir: true,
|
|
325
472
|
rollupOptions: {
|
|
326
|
-
input:
|
|
473
|
+
input: rollupInput,
|
|
474
|
+
output: {
|
|
475
|
+
entryFileNames: (chunk) => (chunk.name === 'sw' ? 'sw.js' : 'assets/[name]-[hash].js')
|
|
476
|
+
}
|
|
327
477
|
},
|
|
478
|
+
manifest: true,
|
|
328
479
|
copyPublicDir: copyPublicDirEnabled,
|
|
329
480
|
minify: true
|
|
330
481
|
},
|
|
@@ -335,42 +486,115 @@ export const runViteBuild = async (entry, htmlCache) => {
|
|
|
335
486
|
resolve: {
|
|
336
487
|
dedupe: ['refui', 'methanol']
|
|
337
488
|
},
|
|
338
|
-
plugins: [
|
|
339
|
-
methanolVirtualHtmlPlugin(htmlCache),
|
|
340
|
-
methanolResolverPlugin(),
|
|
341
|
-
state.PWA_ENABLED
|
|
342
|
-
? VitePWA({
|
|
343
|
-
injectRegister: 'auto',
|
|
344
|
-
registerType: 'autoUpdate',
|
|
345
|
-
strategies: 'injectManifest',
|
|
346
|
-
srcDir: resolve(__dirname, 'client'),
|
|
347
|
-
filename: 'sw.js',
|
|
348
|
-
manifest: resolvedManifest,
|
|
349
|
-
injectManifest: {
|
|
350
|
-
globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
|
|
351
|
-
...(state.PWA_OPTIONS?.injectManifest || {})
|
|
352
|
-
}
|
|
353
|
-
})
|
|
354
|
-
: null
|
|
355
|
-
]
|
|
489
|
+
plugins: [methanolResolverPlugin()]
|
|
356
490
|
}
|
|
357
491
|
const userConfig = await resolveUserViteConfig('build')
|
|
358
492
|
const finalConfig = userConfig ? mergeConfig(baseConfig, userConfig) : baseConfig
|
|
359
493
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
494
|
+
// Keep the pipeline deterministic: do not let user configs override the build root/output/inputs.
|
|
495
|
+
finalConfig.root = state.PAGES_DIR
|
|
496
|
+
finalConfig.appType = 'custom'
|
|
497
|
+
finalConfig.publicDir = state.STATIC_DIR === false ? false : state.STATIC_DIR
|
|
498
|
+
finalConfig.build = {
|
|
499
|
+
...(finalConfig.build || {}),
|
|
500
|
+
outDir: state.DIST_DIR,
|
|
501
|
+
emptyOutDir: true,
|
|
502
|
+
manifest: finalConfig.build?.manifest === undefined ? true : finalConfig.build.manifest,
|
|
503
|
+
copyPublicDir: copyPublicDirEnabled,
|
|
504
|
+
rollupOptions: {
|
|
505
|
+
...((finalConfig.build && finalConfig.build.rollupOptions) || {}),
|
|
506
|
+
input: rollupInput,
|
|
507
|
+
output: (() => {
|
|
508
|
+
const existing = finalConfig.build?.rollupOptions?.output
|
|
509
|
+
const outputConfig = Array.isArray(existing) ? existing[0] || {} : existing || {}
|
|
510
|
+
return {
|
|
511
|
+
...outputConfig,
|
|
512
|
+
entryFileNames: (chunk) => (chunk.name === 'sw' ? 'sw.js' : 'assets/[name]-[hash].js')
|
|
513
|
+
}
|
|
514
|
+
})()
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const manifestFileName = typeof finalConfig.build?.manifest === 'string'
|
|
519
|
+
? finalConfig.build.manifest
|
|
520
|
+
: '.vite/manifest.json'
|
|
521
|
+
const manifestPath = resolve(state.DIST_DIR, manifestFileName.replace(/^\//, ''))
|
|
522
|
+
const manifestEnabled = finalConfig.build?.manifest !== false
|
|
523
|
+
let rewriteDone = false
|
|
524
|
+
let preWriteDone = false
|
|
525
|
+
let postWriteDone = false
|
|
526
|
+
const loadManifest = async () => {
|
|
527
|
+
if (!manifestEnabled) return null
|
|
528
|
+
if (!existsSync(manifestPath)) return null
|
|
529
|
+
const raw = await readFile(manifestPath, 'utf-8')
|
|
530
|
+
return JSON.parse(raw)
|
|
531
|
+
}
|
|
532
|
+
const runPreWrite = async () => {
|
|
533
|
+
if (rewriteOptions) {
|
|
534
|
+
endBundleStage()
|
|
535
|
+
}
|
|
536
|
+
if (preWrite) {
|
|
537
|
+
await preWrite({ manifest: manifestData })
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const runPostWrite = async () => {
|
|
541
|
+
if (postWrite) {
|
|
542
|
+
await postWrite({ manifest: manifestData })
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const runRewrite = async () => {
|
|
546
|
+
if (!rewriteOptions || rewriteDone || !manifestData) return
|
|
547
|
+
const rewriteToken = logEnabled ? stageLogger.start('Rewriting HTML') : null
|
|
548
|
+
try {
|
|
549
|
+
await rewriteHtmlEntriesInWorkers({
|
|
550
|
+
...rewriteOptions,
|
|
551
|
+
manifest: manifestData,
|
|
552
|
+
onProgress: (done, total) => {
|
|
553
|
+
if (!rewriteToken) return
|
|
554
|
+
stageLogger.update(rewriteToken, `Rewriting HTML [${done}/${total}]`)
|
|
555
|
+
}
|
|
556
|
+
})
|
|
557
|
+
} finally {
|
|
558
|
+
rewriteDone = true
|
|
559
|
+
if (rewriteToken) {
|
|
560
|
+
stageLogger.end(rewriteToken)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const postBundlePlugin = {
|
|
566
|
+
name: 'methanol:post-bundle',
|
|
567
|
+
apply: 'build',
|
|
568
|
+
enforce: 'post',
|
|
569
|
+
async writeBundle() {
|
|
570
|
+
manifestData = await loadManifest()
|
|
571
|
+
await runPreWrite()
|
|
572
|
+
await runRewrite()
|
|
573
|
+
await runPostWrite()
|
|
574
|
+
}
|
|
365
575
|
}
|
|
366
576
|
|
|
577
|
+
finalConfig.plugins = [...(finalConfig.plugins || []), postBundlePlugin]
|
|
578
|
+
|
|
579
|
+
await viteBuild(finalConfig)
|
|
580
|
+
endBundleStage()
|
|
581
|
+
const methanolManifestDir = resolve(state.PAGES_DIR, '.methanol')
|
|
582
|
+
const methanolManifestPath = resolve(methanolManifestDir, 'manifest.json')
|
|
367
583
|
try {
|
|
368
|
-
await
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
584
|
+
const parsed = manifestData || (manifestEnabled ? JSON.parse(await readFile(manifestPath, 'utf-8')) : null)
|
|
585
|
+
if (!parsed) return {}
|
|
586
|
+
await ensureDir(methanolManifestDir)
|
|
587
|
+
await writeFile(methanolManifestPath, JSON.stringify(parsed, null, 2))
|
|
588
|
+
await rm(manifestPath, { force: true })
|
|
589
|
+
const manifestDir = dirname(manifestPath)
|
|
590
|
+
if (basename(manifestDir) === '.vite') {
|
|
591
|
+
await rm(manifestDir, { recursive: true, force: true })
|
|
592
|
+
}
|
|
593
|
+
return parsed
|
|
594
|
+
} catch (error) {
|
|
595
|
+
if (cli.CLI_VERBOSE) {
|
|
596
|
+
console.log(`Vite pipeline: failed to read manifest at ${manifestPath}`)
|
|
373
597
|
}
|
|
598
|
+
return {}
|
|
374
599
|
}
|
|
375
|
-
stageLogger.end(token)
|
|
376
600
|
}
|