methanol 0.0.11 → 0.0.13

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Opinionated MDX-first static site generator powered by rEFui + Vite.
4
4
 
5
- For full documentation and examples, visit [Methanol Docs](https://methanol.netlify.app/).
5
+ For full documentation and examples, visit [Methanol Docs](https://methanol.sudomaker.com/).
6
6
 
7
7
  ## Quick start
8
8
 
@@ -41,9 +41,12 @@ export default () => ({
41
41
  enabled: true
42
42
  },
43
43
 
44
- // optional: code highlighting (Starry Night)
44
+ // optional: code highlighting (Starry Night, default: enabled)
45
45
  starryNight: false,
46
46
 
47
+ // optional: worker thread count (0 = auto)
48
+ jobs: 0,
49
+
47
50
  // optional: pwa support
48
51
  pwa: true,
49
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "methanol",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Static site generator powered by rEFui and MDX",
5
5
  "main": "./index.js",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "hast-util-is-element": "^3.0.0",
37
37
  "json5": "^2.2.3",
38
38
  "null-prototype-object": "^1.2.5",
39
- "refui": "^0.16.3",
39
+ "refui": "^0.16.4",
40
40
  "refurbish": "^0.1.8",
41
41
  "rehype-slug": "^6.0.0",
42
42
  "rehype-starry-night": "^2.2.0",
@@ -26,8 +26,8 @@ import { VitePWA } from 'vite-plugin-pwa'
26
26
  import { state, cli } from './state.js'
27
27
  import { resolveUserViteConfig } from './config.js'
28
28
  import { buildPagesContext } from './pages.js'
29
- import { renderHtml } from './mdx.js'
30
29
  import { buildComponentRegistry } from './components.js'
30
+ import { createBuildWorkers, runWorkerStage, terminateWorkers } from './workers/build-pool.js'
31
31
  import { methanolVirtualHtmlPlugin, methanolResolverPlugin } from './vite-plugins.js'
32
32
  import { createStageLogger } from './stage-logger.js'
33
33
  import { preparePublicAssets } from './public-assets.js'
@@ -40,7 +40,6 @@ const ensureDir = async (dir) => {
40
40
  }
41
41
 
42
42
  const isHtmlFile = (name) => name.endsWith('.html')
43
-
44
43
  const collectHtmlFiles = async (dir, basePath = '') => {
45
44
  const entries = await readdir(dir)
46
45
  const files = []
@@ -76,22 +75,14 @@ export const buildHtmlEntries = async () => {
76
75
  const stageLogger = createStageLogger(logEnabled)
77
76
  const themeComponentsDir = state.THEME_COMPONENTS_DIR
78
77
  const themeEnv = state.THEME_ENV
79
- const themeRegistry = themeComponentsDir
80
- ? await buildComponentRegistry({
81
- componentsDir: themeComponentsDir,
82
- client: themeEnv.client
83
- })
84
- : { components: {} }
85
- const themeComponents = {
86
- ...(themeRegistry.components || {}),
87
- ...(state.USER_THEME.components || {})
88
- }
89
- const { components } = await buildComponentRegistry()
90
- const mergedComponents = {
91
- ...themeComponents,
92
- ...components
78
+ if (themeComponentsDir) {
79
+ await buildComponentRegistry({
80
+ componentsDir: themeComponentsDir,
81
+ register: themeEnv.register
82
+ })
93
83
  }
94
- const pagesContext = await buildPagesContext()
84
+ await buildComponentRegistry()
85
+ const pagesContext = await buildPagesContext({ compileAll: false })
95
86
  const entry = {}
96
87
  const htmlCache = new Map()
97
88
  const resolveOutputName = (page) => {
@@ -102,42 +93,123 @@ export const buildHtmlEntries = async () => {
102
93
  return page.routePath.slice(1)
103
94
  }
104
95
 
105
- const renderToken = stageLogger.start('Rendering pages')
106
- const totalPages = pagesContext.pages.length
107
- for (let i = 0; i < pagesContext.pages.length; i++) {
108
- const page = pagesContext.pages[i]
109
- if (logEnabled) {
110
- stageLogger.update(
111
- renderToken,
112
- `Rendering pages [${i + 1}/${totalPages}] ${page.routePath || page.path}`
113
- )
96
+ const pages = pagesContext.pages || []
97
+ const totalPages = pages.length
98
+ const { workers, assignments } = createBuildWorkers(totalPages)
99
+ const excludedRoutes = Array.from(pagesContext.excludedRoutes || [])
100
+ const excludedDirs = Array.from(pagesContext.excludedDirs || [])
101
+ try {
102
+ await runWorkerStage({
103
+ workers,
104
+ stage: 'setPages',
105
+ messages: workers.map((worker) => ({
106
+ worker,
107
+ message: {
108
+ type: 'setPages',
109
+ stage: 'setPages',
110
+ pages,
111
+ excludedRoutes,
112
+ excludedDirs
113
+ }
114
+ }))
115
+ })
116
+
117
+ const compileToken = stageLogger.start('Compiling MDX')
118
+ let completed = 0
119
+ const updates = await runWorkerStage({
120
+ workers,
121
+ stage: 'compile',
122
+ messages: workers.map((worker, index) => ({
123
+ worker,
124
+ message: {
125
+ type: 'compile',
126
+ stage: 'compile',
127
+ ids: assignments[index]
128
+ }
129
+ })),
130
+ onProgress: (count) => {
131
+ if (!logEnabled) return
132
+ completed = count
133
+ stageLogger.update(compileToken, `Compiling MDX [${completed}/${totalPages}]`)
134
+ },
135
+ collect: (message) => message.updates || []
136
+ })
137
+ stageLogger.end(compileToken)
138
+
139
+ for (const update of updates) {
140
+ const page = pages[update.id]
141
+ if (!page) continue
142
+ if (update.title !== undefined) page.title = update.title
143
+ if (update.toc !== undefined) page.toc = update.toc
144
+ if (typeof pagesContext.setDerivedTitle === 'function') {
145
+ const shouldUseTocTitle = page.frontmatter?.title == null
146
+ pagesContext.setDerivedTitle(page.path, shouldUseTocTitle ? page.title : null, page.toc || null)
147
+ }
114
148
  }
115
- const html = await renderHtml({
116
- routePath: page.routePath,
117
- path: page.path,
118
- components: mergedComponents,
119
- pagesContext,
120
- pageMeta: page
149
+ pagesContext.refreshPagesTree?.()
150
+
151
+ const titleSnapshot = pages.map((page) => page.title)
152
+ await runWorkerStage({
153
+ workers,
154
+ stage: 'sync',
155
+ messages: workers.map((worker) => ({
156
+ worker,
157
+ message: {
158
+ type: 'sync',
159
+ stage: 'sync',
160
+ updates,
161
+ titles: titleSnapshot
162
+ }
163
+ }))
121
164
  })
122
- const name = resolveOutputName(page)
123
- const id = normalizePath(resolve(state.VIRTUAL_HTML_OUTPUT_ROOT, `${name}.html`))
124
- entry[name] = id
125
- htmlCache.set(id, html)
126
- if (state.INTERMEDIATE_DIR) {
127
- const outPath = resolve(state.INTERMEDIATE_DIR, `${name}.html`)
128
- await ensureDir(dirname(outPath))
129
- await writeFile(outPath, html)
165
+
166
+ const renderToken = stageLogger.start('Rendering pages')
167
+ completed = 0
168
+ const rendered = await runWorkerStage({
169
+ workers,
170
+ stage: 'render',
171
+ messages: workers.map((worker, index) => ({
172
+ worker,
173
+ message: {
174
+ type: 'render',
175
+ stage: 'render',
176
+ ids: assignments[index]
177
+ }
178
+ })),
179
+ onProgress: (count) => {
180
+ if (!logEnabled) return
181
+ completed = count
182
+ stageLogger.update(renderToken, `Rendering pages [${completed}/${totalPages}]`)
183
+ },
184
+ collect: (message) => message.results || []
185
+ })
186
+ stageLogger.end(renderToken)
187
+
188
+ for (const item of rendered) {
189
+ const page = pages[item.id]
190
+ if (!page) continue
191
+ const html = item.html
192
+ const name = resolveOutputName(page)
193
+ const id = normalizePath(resolve(state.VIRTUAL_HTML_OUTPUT_ROOT, `${name}.html`))
194
+ entry[name] = id
195
+ htmlCache.set(id, html)
196
+ if (state.INTERMEDIATE_DIR) {
197
+ const outPath = resolve(state.INTERMEDIATE_DIR, `${name}.html`)
198
+ await ensureDir(dirname(outPath))
199
+ await writeFile(outPath, html)
200
+ }
130
201
  }
202
+ } finally {
203
+ await terminateWorkers(workers)
131
204
  }
132
- stageLogger.end(renderToken)
133
205
 
134
206
  const htmlFiles = await collectHtmlFiles(state.PAGES_DIR)
135
- const excludedDirs = pagesContext.excludedDirs || new Set()
207
+ const htmlExcludedDirs = pagesContext.excludedDirs || new Set()
136
208
  const isHtmlExcluded = (relativePath) => {
137
- if (!excludedDirs.size) return false
209
+ if (!htmlExcludedDirs.size) return false
138
210
  const dir = relativePath.split('/').slice(0, -1).join('/')
139
211
  if (!dir) return false
140
- for (const excludedDir of excludedDirs) {
212
+ for (const excludedDir of htmlExcludedDirs) {
141
213
  if (!excludedDir) return true
142
214
  if (dir === excludedDir || dir.startsWith(`${excludedDir}/`)) {
143
215
  return true
package/src/components.js CHANGED
@@ -48,21 +48,24 @@ export const bumpComponentImportNonce = () => {
48
48
  }
49
49
 
50
50
  export const reframeEnv = env()
51
- export const client = reframeEnv.client
51
+ export const register = reframeEnv.register
52
52
  export const invalidateRegistryEntry = reframeEnv.invalidate
53
53
  export const genRegistryScript = reframeEnv.genRegistryScript
54
54
 
55
- const resolveComponentExport = (componentPath, ext) => {
55
+ const resolveComponentExport = (componentPath, exportName, ext) => {
56
56
  const staticCandidate = `${componentPath}.static${ext}`
57
57
  const clientCandidate = `${componentPath}.client${ext}`
58
58
  const genericCandidate = `${componentPath}${ext}`
59
- const ret = {}
59
+ const ret = { exportName }
60
+
60
61
  if (existsSync(staticCandidate)) {
61
62
  ret.staticPath = staticCandidate
62
63
  }
64
+
63
65
  if (existsSync(clientCandidate)) {
64
66
  ret.clientPath = clientCandidate
65
67
  }
68
+
66
69
  if (!ret.staticPath) {
67
70
  if (existsSync(genericCandidate)) {
68
71
  ret.staticPath = genericCandidate
@@ -70,34 +73,29 @@ const resolveComponentExport = (componentPath, ext) => {
70
73
  ret.staticPath = clientCandidate
71
74
  }
72
75
  }
73
- return ret
74
- }
75
76
 
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 }
77
+ if (ret.staticPath) {
78
+ ret.staticImportURL = `${pathToFileURL(ret.staticPath).href}?t=${componentImportNonce}`
80
79
  }
81
80
 
82
- let component = (await import(`${pathToFileURL(info.staticPath).href}?t=${componentImportNonce}`)).default
81
+ return ret
82
+ }
83
83
 
84
- if (!component) {
84
+ export const buildComponentEntry = async ({ dir, exportName, ext, register: registerFn = register }) => {
85
+ const info = resolveComponentExport(join(dir, exportName), exportName, ext)
86
+ if (!info.staticPath) {
85
87
  return { component: null, hasClient: false, staticPath: null, clientPath: null }
86
88
  }
87
89
 
88
- if (info.clientPath) {
89
- component = clientFn({ ...info, staticComponent: component, exportName })
90
- }
91
-
92
90
  return {
93
- component,
91
+ component: registerFn(info),
94
92
  hasClient: Boolean(info.clientPath),
95
93
  staticPath: info.staticPath,
96
94
  clientPath: info.clientPath || null
97
95
  }
98
96
  }
99
97
 
100
- export const buildComponentRegistry = async ({ componentsDir = state.COMPONENTS_DIR, client: clientFn = client } = {}) => {
98
+ export const buildComponentRegistry = async ({ componentsDir = state.COMPONENTS_DIR, register: registerFn = register } = {}) => {
101
99
  const components = {}
102
100
  const sources = new Map()
103
101
 
@@ -130,7 +128,7 @@ export const buildComponentRegistry = async ({ componentsDir = state.COMPONENTS_
130
128
  dir,
131
129
  exportName,
132
130
  ext: extname(entry),
133
- client: clientFn
131
+ register: registerFn
134
132
  })
135
133
  if (!component) continue
136
134
  components[exportName] = component
package/src/config.js CHANGED
@@ -191,12 +191,12 @@ const resolvePagefindBuild = (config) => {
191
191
  }
192
192
 
193
193
  const resolveStarryNightConfig = (value) => {
194
- if (value == null) return { enabled: false, options: null }
194
+ if (value == null) return { enabled: true, options: null }
195
195
  if (typeof value === 'boolean') {
196
196
  return { enabled: value, options: null }
197
197
  }
198
198
  if (typeof value !== 'object') {
199
- return { enabled: false, options: null }
199
+ return { enabled: true, options: null }
200
200
  }
201
201
  const { enabled, options, ...rest } = value
202
202
  if (enabled === false) return { enabled: false, options: null }
@@ -216,6 +216,14 @@ const normalizeHooks = (value) => {
216
216
  return []
217
217
  }
218
218
 
219
+ const normalizeJobs = (value) => {
220
+ if (value == null) return null
221
+ const parsed = Number(value)
222
+ if (!Number.isFinite(parsed)) return null
223
+ if (parsed <= 0) return 0
224
+ return Math.floor(parsed)
225
+ }
226
+
219
227
  const loadConfigModule = async (filePath) => {
220
228
  return import(`${pathToFileURL(filePath).href}?t=${Date.now()}`)
221
229
  }
@@ -425,6 +433,9 @@ export const applyConfig = async (config, mode) => {
425
433
  state.STARRY_NIGHT_OPTIONS = starryNight.enabled ? starryNight.options : null
426
434
  }
427
435
 
436
+ const jobsValue = cli.CLI_JOBS != null ? cli.CLI_JOBS : config.jobs
437
+ state.WORKER_JOBS = normalizeJobs(jobsValue) ?? 0
438
+
428
439
  if (cli.CLI_INTERMEDIATE_DIR) {
429
440
  state.INTERMEDIATE_DIR = resolveFromRoot(root, cli.CLI_INTERMEDIATE_DIR, 'build')
430
441
  } else if (config.intermediateDir) {
package/src/dev-server.js CHANGED
@@ -40,6 +40,8 @@ import { buildPagesContext, buildPageEntry, routePathFromFile } from './pages.js
40
40
  import { compilePageMdx, renderHtml } from './mdx.js'
41
41
  import { methanolResolverPlugin } from './vite-plugins.js'
42
42
  import { preparePublicAssets, updateAsset } from './public-assets.js'
43
+ import { createBuildWorkers, runWorkerStage, terminateWorkers } from './workers/build-pool.js'
44
+ import { style } from './logger.js'
43
45
 
44
46
  export const runViteDev = async () => {
45
47
  const baseFsAllow = [state.ROOT_DIR, state.USER_THEME.root].filter(Boolean)
@@ -127,7 +129,7 @@ export const runViteDev = async () => {
127
129
  const themeRegistry = themeComponentsDir
128
130
  ? await buildComponentRegistry({
129
131
  componentsDir: themeComponentsDir,
130
- client: themeEnv.client
132
+ register: themeEnv.register
131
133
  })
132
134
  : { components: {} }
133
135
  const themeComponents = {
@@ -152,19 +154,128 @@ export const runViteDev = async () => {
152
154
  htmlCache.clear()
153
155
  }
154
156
 
157
+ const logMdxError = (phase, error, page = null) => {
158
+ const target = page?.path || page?.routePath || 'unknown file'
159
+ console.error(style.red(`\n[methanol] ${phase} error in ${target}`))
160
+ console.error(error?.stack || error)
161
+ }
162
+
155
163
  const refreshPagesContext = async () => {
156
164
  setPagesContext(await buildPagesContext({ compileAll: false }))
157
165
  }
158
166
 
167
+ const prebuildHtmlCache = async (token) => {
168
+ if (!pagesContext || token !== pagesContextToken) return
169
+ const pages = pagesContext.pages || []
170
+ if (!pages.length) return
171
+ const { workers, assignments } = createBuildWorkers(pages.length, { command: 'serve' })
172
+ const excludedRoutes = Array.from(pagesContext.excludedRoutes || [])
173
+ const excludedDirs = Array.from(pagesContext.excludedDirs || [])
174
+ try {
175
+ await runWorkerStage({
176
+ workers,
177
+ stage: 'setPages',
178
+ messages: workers.map((worker) => ({
179
+ worker,
180
+ message: {
181
+ type: 'setPages',
182
+ stage: 'setPages',
183
+ pages,
184
+ excludedRoutes,
185
+ excludedDirs
186
+ }
187
+ }))
188
+ })
189
+ if (token !== pagesContextToken) return
190
+
191
+ const updates = await runWorkerStage({
192
+ workers,
193
+ stage: 'compile',
194
+ messages: workers.map((worker, index) => ({
195
+ worker,
196
+ message: {
197
+ type: 'compile',
198
+ stage: 'compile',
199
+ ids: assignments[index]
200
+ }
201
+ })),
202
+ collect: (message) => message.updates || []
203
+ })
204
+ if (token !== pagesContextToken) return
205
+
206
+ for (const update of updates) {
207
+ const page = pages[update.id]
208
+ if (!page) continue
209
+ if (update.title !== undefined) page.title = update.title
210
+ if (update.toc !== undefined) page.toc = update.toc
211
+ if (typeof pagesContext.setDerivedTitle === 'function') {
212
+ const shouldUseTocTitle = page.frontmatter?.title == null
213
+ pagesContext.setDerivedTitle(page.path, shouldUseTocTitle ? page.title : null, page.toc || null)
214
+ }
215
+ }
216
+ pagesContext.refreshPagesTree?.()
217
+ invalidateHtmlCache()
218
+ const renderEpoch = htmlCacheEpoch
219
+
220
+ const titleSnapshot = pages.map((page) => page.title)
221
+ await runWorkerStage({
222
+ workers,
223
+ stage: 'sync',
224
+ messages: workers.map((worker) => ({
225
+ worker,
226
+ message: {
227
+ type: 'sync',
228
+ stage: 'sync',
229
+ updates,
230
+ titles: titleSnapshot
231
+ }
232
+ }))
233
+ })
234
+ if (token !== pagesContextToken) return
235
+
236
+ const rendered = await runWorkerStage({
237
+ workers,
238
+ stage: 'render',
239
+ messages: workers.map((worker, index) => ({
240
+ worker,
241
+ message: {
242
+ type: 'render',
243
+ stage: 'render',
244
+ ids: assignments[index]
245
+ }
246
+ })),
247
+ collect: (message) => message.results || []
248
+ })
249
+ if (token !== pagesContextToken || renderEpoch !== htmlCacheEpoch) return
250
+ for (const item of rendered) {
251
+ const page = pages[item.id]
252
+ if (!page) continue
253
+ htmlCache.set(page.routePath, {
254
+ html: item.html,
255
+ path: page.path,
256
+ epoch: renderEpoch,
257
+ token
258
+ })
259
+ }
260
+ } finally {
261
+ await terminateWorkers(workers)
262
+ }
263
+ }
264
+
159
265
  const runInitialCompile = async () => {
160
266
  const token = pagesContextToken
161
267
  try {
162
- const nextContext = await buildPagesContext({ compileAll: true })
268
+ const nextContext = await buildPagesContext({ compileAll: false })
163
269
  if (token !== pagesContextToken) {
164
270
  return
165
271
  }
166
272
  setPagesContext(nextContext)
273
+ const nextToken = pagesContextToken
167
274
  invalidateHtmlCache()
275
+ await prebuildHtmlCache(nextToken)
276
+ if (nextToken !== pagesContextToken) {
277
+ return
278
+ }
168
279
  reload()
169
280
  } catch (err) {
170
281
  console.error(err)
@@ -257,10 +368,6 @@ export const runViteDev = async () => {
257
368
  return next()
258
369
  }
259
370
 
260
- if (pathname.includes('.') && !pathname.endsWith('.html')) {
261
- return next()
262
- }
263
-
264
371
  const accept = req.headers.accept || ''
265
372
  if (!pathname.endsWith('.html') && !accept.includes('text/html')) {
266
373
  return next()
@@ -274,6 +381,11 @@ export const runViteDev = async () => {
274
381
  }
275
382
  }
276
383
  const requestedPath = routePath
384
+ if (pathname.includes('.') && !pathname.endsWith('.html')) {
385
+ if (!pagesContext?.pagesByRoute?.has(requestedPath)) {
386
+ return next()
387
+ }
388
+ }
277
389
  const isExcludedPath = () => {
278
390
  const excludedRoutes = pagesContext.excludedRoutes
279
391
  if (excludedRoutes?.has(requestedPath)) return true
@@ -347,7 +459,12 @@ export const runViteDev = async () => {
347
459
  try {
348
460
  const renderEpoch = htmlCacheEpoch
349
461
  const cacheEntry = htmlCache.get(renderRoutePath)
350
- if (cacheEntry && cacheEntry.path === path && cacheEntry.epoch === htmlCacheEpoch) {
462
+ if (
463
+ cacheEntry &&
464
+ cacheEntry.path === path &&
465
+ cacheEntry.epoch === htmlCacheEpoch &&
466
+ cacheEntry.token === pagesContextToken
467
+ ) {
351
468
  res.statusCode = status
352
469
  res.setHeader('Content-Type', 'text/html')
353
470
  res.end(cacheEntry.html)
@@ -356,28 +473,37 @@ export const runViteDev = async () => {
356
473
 
357
474
  pageMeta ??= pagesContext.getPageByRoute(renderRoutePath, { path })
358
475
 
359
- const html = await renderHtml({
360
- routePath: renderRoutePath,
361
- path,
362
- components: {
363
- ...themeComponents,
364
- ...components
365
- },
366
- pagesContext,
367
- pageMeta
368
- })
476
+ let html = ''
477
+ try {
478
+ html = await renderHtml({
479
+ routePath: renderRoutePath,
480
+ path,
481
+ components: {
482
+ ...themeComponents,
483
+ ...components
484
+ },
485
+ pagesContext,
486
+ pageMeta
487
+ })
488
+ } catch (err) {
489
+ logMdxError('MDX render', err, pageMeta || { path, routePath: renderRoutePath })
490
+ res.statusCode = 500
491
+ res.end('Internal Server Error')
492
+ return
493
+ }
369
494
  if (renderEpoch === htmlCacheEpoch) {
370
495
  htmlCache.set(renderRoutePath, {
371
496
  html,
372
497
  path,
373
- epoch: renderEpoch
498
+ epoch: renderEpoch,
499
+ token: pagesContextToken
374
500
  })
375
501
  }
376
502
  res.statusCode = status
377
503
  res.setHeader('Content-Type', 'text/html')
378
504
  res.end(html)
379
505
  } catch (err) {
380
- console.error(err)
506
+ logMdxError('MDX render', err, pageMeta || { path, routePath: renderRoutePath })
381
507
  res.statusCode = 500
382
508
  res.end('Internal Server Error')
383
509
  }
@@ -532,12 +658,17 @@ export const runViteDev = async () => {
532
658
  pagesContext.refreshPagesTree?.()
533
659
  pagesContext.refreshLanguages?.()
534
660
  if (prevEntry.content && prevEntry.content.trim().length) {
535
- await compilePageMdx(prevEntry, pagesContext, {
536
- lazyPagesTree: true,
537
- refreshPagesTree: false
538
- })
539
- // Avoid caching a potentially stale render; recompile on request.
540
- prevEntry.mdxComponent = null
661
+ try {
662
+ await compilePageMdx(prevEntry, pagesContext, {
663
+ lazyPagesTree: true,
664
+ refreshPagesTree: false
665
+ })
666
+ // Avoid caching a potentially stale render; recompile on request.
667
+ prevEntry.mdxComponent = null
668
+ } catch (err) {
669
+ logMdxError('MDX compile', err, prevEntry)
670
+ prevEntry.mdxComponent = null
671
+ }
541
672
  }
542
673
  return true
543
674
  }
package/src/logger.js CHANGED
@@ -18,10 +18,22 @@
18
18
  * under the License.
19
19
  */
20
20
 
21
+ const resolveSupportColor = (stream) => {
22
+ if (typeof process === 'undefined') return false
23
+ const env = process.env || {}
24
+ if (env.NO_COLOR != null) return false
25
+ const force = env.FORCE_COLOR
26
+ if (force != null) {
27
+ return force !== '0'
28
+ }
29
+ if (stream && stream.isTTY) return true
30
+ const term = env.TERM || ''
31
+ if (!term || term.toLowerCase() === 'dumb') return false
32
+ return true
33
+ }
34
+
21
35
  const supportColor =
22
- typeof process !== 'undefined' &&
23
- process.stdout &&
24
- (process.stdout.isTTY || process.env.FORCE_COLOR)
36
+ resolveSupportColor(process.stdout) || resolveSupportColor(process.stderr)
25
37
 
26
38
  const formatter = (open, close, replace = open) =>
27
39
  supportColor