Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(build): show progress during render, log duration for each task, use humanized format #3385

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -139,6 +139,7 @@
"@types/debug": "^4.1.12",
"@types/escape-html": "^1.0.4",
"@types/fs-extra": "^11.0.4",
"@types/humanize-duration": "^3.27.3",
"@types/lodash.template": "^4.5.3",
"@types/mark.js": "^8.11.12",
"@types/markdown-it-attrs": "^4.1.3",
Expand All @@ -162,6 +163,7 @@
"fs-extra": "^11.2.0",
"get-port": "^7.0.0",
"gray-matter": "^4.0.3",
"humanize-duration": "^3.31.0",
"lint-staged": "^15.2.0",
"lodash.template": "^4.5.0",
"lru-cache": "^10.1.0",
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 12 additions & 6 deletions src/node/build/build.ts
Expand Up @@ -15,12 +15,13 @@ import { task } from '../utils/task'
import { bundle } from './bundle'
import { generateSitemap } from './generateSitemap'
import { renderPage } from './render'
import humanizeDuration from 'humanize-duration'

export async function build(
root?: string,
buildOptions: BuildOptions & { base?: string; mpa?: string } = {}
) {
const start = Date.now()
const timeStart = performance.now()

process.env.NODE_ENV = 'production'
const siteConfig = await resolveConfig(root, 'build', 'production')
Expand Down Expand Up @@ -56,7 +57,7 @@ export async function build(
pathToFileURL(entryPath).toString() + '?t=' + Date.now()
)

await task('rendering pages', async () => {
await task('rendering pages', async (updateProgress) => {
const appChunk =
clientResult &&
(clientResult.output.find(
Expand Down Expand Up @@ -110,8 +111,10 @@ export async function build(
}
}

const pages = ['404.md', ...siteConfig.pages]
let count_done = 0
await pMap(
['404.md', ...siteConfig.pages],
pages,
async (page) => {
await renderPage(
render,
Expand All @@ -125,6 +128,7 @@ export async function build(
metadataScript,
additionalHeadTags
)
updateProgress(++count_done, pages.length)
},
{ concurrency: siteConfig.buildConcurrency }
)
Expand All @@ -145,9 +149,11 @@ export async function build(
await siteConfig.buildEnd?.(siteConfig)
clearCache()

siteConfig.logger.info(
`build complete in ${((Date.now() - start) / 1000).toFixed(2)}s.`
)
const timeEnd = performance.now()
const duration = humanizeDuration(timeEnd - timeStart, {
maxDecimalPoints: 2
})
siteConfig.logger.info(`build complete in ${duration}.`)
}

function linkVue() {
Expand Down
63 changes: 32 additions & 31 deletions src/node/build/bundle.ts
Expand Up @@ -167,40 +167,41 @@ export async function bundle(
configFile: config.vite?.configFile
})

let clientResult!: Rollup.RollupOutput | null
let serverResult!: Rollup.RollupOutput

await task('building client + server bundles', async () => {
clientResult = config.mpa
? null
: ((await build(await resolveViteConfig(false))) as Rollup.RollupOutput)
serverResult = (await build(
await resolveViteConfig(true)
)) as Rollup.RollupOutput
})
const serverResult = await task(
'building server bundle',
() => resolveViteConfig(true).then(build) as Promise<Rollup.RollupOutput>
)

if (config.mpa) {
// in MPA mode, we need to copy over the non-js asset files from the
// server build since there is no client-side build.
await Promise.all(
serverResult.output.map(async (chunk) => {
if (!chunk.fileName.endsWith('.js')) {
const tempPath = path.resolve(config.tempDir, chunk.fileName)
const outPath = path.resolve(config.outDir, chunk.fileName)
await fs.copy(tempPath, outPath)
const clientResult = !config.mpa
? await task(
'building client bundle',
() =>
resolveViteConfig(false).then(build) as Promise<Rollup.RollupOutput>
)
: await task('building client bundle (MPA)', async () => {
// in MPA mode, we need to copy over the non-js asset files from the
// server build since there is no client-side build.
await Promise.all(
serverResult.output.map(async (chunk) => {
if (!chunk.fileName.endsWith('.js')) {
const tempPath = path.resolve(config.tempDir, chunk.fileName)
const outPath = path.resolve(config.outDir, chunk.fileName)
await fs.copy(tempPath, outPath)
}
})
)
// also copy over public dir
const publicDir = path.resolve(config.srcDir, 'public')
if (fs.existsSync(publicDir)) {
await fs.copy(publicDir, config.outDir)
}
// build <script client> bundle
if (Object.keys(clientJSMap).length) {
return buildMPAClient(clientJSMap, config)
} else {
return null
}
})
)
// also copy over public dir
const publicDir = path.resolve(config.srcDir, 'public')
if (fs.existsSync(publicDir)) {
await fs.copy(publicDir, config.outDir)
}
// build <script client> bundle
if (Object.keys(clientJSMap).length) {
clientResult = await buildMPAClient(clientJSMap, config)
}
}

return { clientResult, serverResult, pageToHashMap }
}
Expand Down
66 changes: 59 additions & 7 deletions src/node/utils/task.ts
@@ -1,18 +1,70 @@
import ora from 'ora'
import humanizeDuration from 'humanize-duration'
import c from 'picocolors'

export const okMark = '\x1b[32m✓\x1b[0m'
export const failMark = '\x1b[31m✖\x1b[0m'
export const okMark = c.green('✓')
export const failMark = c.red('✖')
export const clearLine = '\x1b[2K\r'

export async function task(taskName: string, task: () => Promise<void>) {
export type UpdateHandle = (
done?: number,
total?: number,
subtask?: string
) => any

let updateHandle: UpdateHandle | null = null

export const updateCurrentTask: UpdateHandle = (...args) => {
if (updateHandle) updateHandle(...args)
else if (!process.stderr.isTTY) {
return
} else if (args.length === 0) {
process.stderr.write(clearLine)
} else {
const name = args[2] || 'unknown task'
process.stderr.write(
`${clearLine}${name} [${args.slice(0, 2).join(' / ')}]`
)
}
}

export async function task<T>(
taskName: string,
task: (update: UpdateHandle) => Promise<T>
): Promise<T> {
const spinner = ora({ discardStdin: false })
spinner.start(taskName + '...')

updateHandle = (done, total, subtask) => {
const taskFullName = subtask ? `${taskName} - ${subtask}` : taskName
if (done === undefined) {
spinner.text = taskFullName + '...'
} else if (total === undefined) {
spinner.text = `${taskFullName} [ ${done} ]`
} else {
// match length to display them in same width
const _total = `${total}`
const _done = `${done}`.padStart(_total.length, ' ')
spinner.text = `${taskFullName} [ ${_done} / ${_total} ]`
}
}

const timeStart = performance.now()
let success = true

try {
await task()
return await task(updateHandle)
} catch (e) {
spinner.stopAndPersist({ symbol: failMark })
success = false
throw e
} finally {
updateHandle = null
const timeEnd = performance.now()
const duration = humanizeDuration(timeEnd - timeStart, {
maxDecimalPoints: 2
})
const text = `${taskName} - ${duration}`
const symbol = success ? okMark : failMark
spinner.stopAndPersist({ symbol, text })
}

spinner.stopAndPersist({ symbol: okMark })
}