methanol 0.0.21 → 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 +319 -69
- package/src/client/sw.js +227 -180
- package/src/client/virtual-module/pwa-inject.js +25 -3
- package/src/config.js +16 -35
- package/src/dev-server.js +4 -2
- package/src/entry.js +22 -0
- 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 +33 -1
- package/src/pages-index.js +1 -2
- package/src/pages.js +26 -11
- package/src/pwa.js +240 -0
- package/src/state.js +6 -0
- 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 +165 -3
- 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/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,15 +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
|
-
const intermediateOutputs = []
|
|
104
136
|
let feedIds = []
|
|
105
137
|
let feedAssignments = null
|
|
138
|
+
let completedRun = false
|
|
106
139
|
try {
|
|
107
140
|
await runWorkerStage({
|
|
108
141
|
workers,
|
|
@@ -193,7 +226,9 @@ export const buildHtmlEntries = async () => {
|
|
|
193
226
|
type: 'render',
|
|
194
227
|
stage: 'render',
|
|
195
228
|
ids: assignments[index],
|
|
196
|
-
feedIds: feedAssignments ? feedAssignments[index] : []
|
|
229
|
+
feedIds: feedAssignments ? feedAssignments[index] : [],
|
|
230
|
+
htmlStageDir,
|
|
231
|
+
writeConcurrency
|
|
197
232
|
}
|
|
198
233
|
})),
|
|
199
234
|
onProgress: (count) => {
|
|
@@ -205,14 +240,15 @@ export const buildHtmlEntries = async () => {
|
|
|
205
240
|
if (!result || typeof result.id !== 'number') return
|
|
206
241
|
const page = pages[result.id]
|
|
207
242
|
if (!page) return
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const id = normalizePath(resolve(state.VIRTUAL_HTML_OUTPUT_ROOT, `${name}.html`))
|
|
211
|
-
entry[name] = id
|
|
212
|
-
htmlCache.set(id, html)
|
|
213
|
-
if (state.INTERMEDIATE_DIR) {
|
|
214
|
-
intermediateOutputs.push({ name, id })
|
|
243
|
+
if (result.scan) {
|
|
244
|
+
renderScansById.set(result.id, result.scan)
|
|
215
245
|
}
|
|
246
|
+
const name = resolveOutputName(page)
|
|
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' })
|
|
216
252
|
if (result.feedContent != null) {
|
|
217
253
|
const key = page.path || page.routePath
|
|
218
254
|
if (key) {
|
|
@@ -222,16 +258,18 @@ export const buildHtmlEntries = async () => {
|
|
|
222
258
|
}
|
|
223
259
|
})
|
|
224
260
|
stageLogger.end(renderToken)
|
|
261
|
+
|
|
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)
|
|
268
|
+
}
|
|
269
|
+
completedRun = true
|
|
225
270
|
} finally {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (state.INTERMEDIATE_DIR) {
|
|
229
|
-
for (const output of intermediateOutputs) {
|
|
230
|
-
const html = htmlCache.get(output.id)
|
|
231
|
-
if (typeof html !== 'string') continue
|
|
232
|
-
const outPath = resolve(state.INTERMEDIATE_DIR, `${output.name}.html`)
|
|
233
|
-
await ensureDir(dirname(outPath))
|
|
234
|
-
await writeFile(outPath, html)
|
|
271
|
+
if (!keepWorkers || !completedRun) {
|
|
272
|
+
await terminateWorkers(workers)
|
|
235
273
|
}
|
|
236
274
|
}
|
|
237
275
|
|
|
@@ -255,27 +293,133 @@ export const buildHtmlEntries = async () => {
|
|
|
255
293
|
}
|
|
256
294
|
const name = file.relativePath.replace(/\.html$/, '')
|
|
257
295
|
const outputName = name === 'index' ? 'index' : name
|
|
258
|
-
if (
|
|
296
|
+
if (htmlEntryNames.has(outputName)) {
|
|
259
297
|
continue
|
|
260
298
|
}
|
|
261
299
|
const html = await readFile(file.fullPath, 'utf-8')
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
htmlCache.set(id, html)
|
|
265
|
-
if (state.INTERMEDIATE_DIR) {
|
|
266
|
-
const outPath = resolve(state.INTERMEDIATE_DIR, file.relativePath)
|
|
300
|
+
const outPath = htmlStageDir ? resolve(htmlStageDir, file.relativePath) : null
|
|
301
|
+
if (outPath) {
|
|
267
302
|
await ensureDir(dirname(outPath))
|
|
268
303
|
await writeFile(outPath, html)
|
|
269
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
|
+
})
|
|
270
317
|
}
|
|
271
318
|
|
|
272
|
-
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
|
+
}
|
|
273
407
|
}
|
|
274
408
|
|
|
275
|
-
export const runViteBuild = async (
|
|
409
|
+
export const runViteBuild = async (inputs) => {
|
|
276
410
|
const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build' && !cli.CLI_VERBOSE
|
|
277
411
|
const stageLogger = createStageLogger(logEnabled)
|
|
278
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
|
+
}
|
|
279
423
|
|
|
280
424
|
if (state.STATIC_DIR !== false && state.MERGED_ASSETS_DIR) {
|
|
281
425
|
await preparePublicAssets({
|
|
@@ -285,20 +429,53 @@ export const runViteBuild = async (entry, htmlCache) => {
|
|
|
285
429
|
})
|
|
286
430
|
}
|
|
287
431
|
const copyPublicDirEnabled = state.STATIC_DIR !== false
|
|
288
|
-
const
|
|
289
|
-
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
|
+
}
|
|
290
463
|
const baseConfig = {
|
|
291
464
|
configFile: false,
|
|
292
465
|
root: state.PAGES_DIR,
|
|
293
|
-
appType: '
|
|
466
|
+
appType: 'custom',
|
|
294
467
|
publicDir: state.STATIC_DIR === false ? false : state.STATIC_DIR,
|
|
295
468
|
logLevel: cli.CLI_VERBOSE ? 'info' : 'silent',
|
|
296
469
|
build: {
|
|
297
470
|
outDir: state.DIST_DIR,
|
|
298
471
|
emptyOutDir: true,
|
|
299
472
|
rollupOptions: {
|
|
300
|
-
input:
|
|
473
|
+
input: rollupInput,
|
|
474
|
+
output: {
|
|
475
|
+
entryFileNames: (chunk) => (chunk.name === 'sw' ? 'sw.js' : 'assets/[name]-[hash].js')
|
|
476
|
+
}
|
|
301
477
|
},
|
|
478
|
+
manifest: true,
|
|
302
479
|
copyPublicDir: copyPublicDirEnabled,
|
|
303
480
|
minify: true
|
|
304
481
|
},
|
|
@@ -309,42 +486,115 @@ export const runViteBuild = async (entry, htmlCache) => {
|
|
|
309
486
|
resolve: {
|
|
310
487
|
dedupe: ['refui', 'methanol']
|
|
311
488
|
},
|
|
312
|
-
plugins: [
|
|
313
|
-
methanolVirtualHtmlPlugin(htmlCache),
|
|
314
|
-
methanolResolverPlugin(),
|
|
315
|
-
state.PWA_ENABLED
|
|
316
|
-
? VitePWA({
|
|
317
|
-
injectRegister: 'auto',
|
|
318
|
-
registerType: 'autoUpdate',
|
|
319
|
-
strategies: 'injectManifest',
|
|
320
|
-
srcDir: resolve(__dirname, 'client'),
|
|
321
|
-
filename: 'sw.js',
|
|
322
|
-
manifest: resolvedManifest,
|
|
323
|
-
injectManifest: {
|
|
324
|
-
globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
|
|
325
|
-
...(state.PWA_OPTIONS?.injectManifest || {})
|
|
326
|
-
}
|
|
327
|
-
})
|
|
328
|
-
: null
|
|
329
|
-
]
|
|
489
|
+
plugins: [methanolResolverPlugin()]
|
|
330
490
|
}
|
|
331
491
|
const userConfig = await resolveUserViteConfig('build')
|
|
332
492
|
const finalConfig = userConfig ? mergeConfig(baseConfig, userConfig) : baseConfig
|
|
333
493
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
+
}
|
|
339
575
|
}
|
|
340
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')
|
|
341
583
|
try {
|
|
342
|
-
await
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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}`)
|
|
347
597
|
}
|
|
598
|
+
return {}
|
|
348
599
|
}
|
|
349
|
-
stageLogger.end(token)
|
|
350
600
|
}
|