Skip to content

Commit

Permalink
add support parallel builds (#11984)
Browse files Browse the repository at this point in the history
* 支持并发生成

* feat: Add support parallel builds

* feat: read concurrency config

* changeset

* fix: Explicit undefined is unnecessary on an optional parameter

* pnpm-lock.yaml rebuild

* fix: add innerPrevTimeEnd

* fix: modification time calculation

* update pnpm-lock.yaml

* Rewrite with p-limit

* update

* clean

* Update changeset

* typo [skip ci]

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <[email protected]>

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <[email protected]>

* Update packages/astro/src/core/config/schema.ts

* formatting

* merge main and update the lock file

* Update packages/astro/src/@types/astro.ts

Co-authored-by: Sarah Rainsberger <[email protected]>

* Update packages/astro/src/@types/astro.ts

Co-authored-by: Sarah Rainsberger <[email protected]>

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <[email protected]>

---------

Co-authored-by: bluwy <[email protected]>
Co-authored-by: Emanuele Stoppa <[email protected]>
Co-authored-by: Sarah Rainsberger <[email protected]>
  • Loading branch information
4 people authored Oct 9, 2024
1 parent 116c533 commit 3ac2263
Show file tree
Hide file tree
Showing 5 changed files with 878 additions and 658 deletions.
25 changes: 25 additions & 0 deletions .changeset/ten-emus-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'astro': minor
---

Adds a new `build.concurreny` configuration option to specify the number of pages to build in parallel

**In most cases, you should not change the default value of `1`.**

Use this option only when other attempts to reduce the overall rendering time (e.g. batch or cache long running tasks like fetch calls or data access) are not possible or are insufficient.

Use this option only if the refactors are not possible. If the number is set too high, the page rendering may slow down due to insufficient memory resources and because JS is single-threaded.

> [!WARNING]
> This feature is stable and is not considered experimental. However, this feature is only intended to address difficult performance issues, and breaking changes may occur in a [minor release](https://docs.astro.build/en/upgrade-astro/#semantic-versioning) to keep this option as performant as possible.
```js
// astro.config.mjs
import { defineConfig } from 'astro';

export default defineConfig({
build: {
concurrency: 2,
},
});
```
27 changes: 27 additions & 0 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,33 @@ export interface AstroUserConfig {
* ```
*/
inlineStylesheets?: 'always' | 'auto' | 'never';
/**
* @docs
* @name build.concurrency
* @type { number }
* @default `1`
* @version 4.16.0
* @description
* The number of pages to build in parallel.
*
* **In most cases, you should not change the default value of `1`.**
*
* Use this option only when other attempts to reduce the overall rendering time (e.g. batch or cache long running tasks like fetch calls or data access) are not possible or are insufficient.
* If the number is set too high, page rendering may slow down due to insufficient memory resources and because JS is single-threaded.
*
* ```js
* {
* build: {
* concurrency: 2
* }
* }
* ```
*
* :::caution[Breaking changes possible]
* This feature is stable and is not considered experimental. However, this feature is only intended to address difficult performance issues, and breaking changes may occur in a [minor release](https://docs.astro.build/en/upgrade-astro/#semantic-versioning) to keep this option as performant as possible. Please check the [Astro CHANGELOG](https://github.com/withastro/astro/blob/refs/heads/next/packages/astro/CHANGELOG.md) for every minor release if you are using this feature.
* :::
*/
concurrency?: number;
};

/**
Expand Down
69 changes: 50 additions & 19 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'node:fs';
import os from 'node:os';
import { bgGreen, black, blue, bold, dim, green, magenta, red } from 'kleur/colors';
import PLimit from 'p-limit';
import PQueue from 'p-queue';
import type {
AstroConfig,
Expand Down Expand Up @@ -198,35 +199,65 @@ async function generatePage(
styles,
mod: pageModule,
};

async function generatePathWithLogs(
path: string,
route: RouteData,
index: number,
paths: string[],
isConcurrent: boolean,
) {
const timeStart = performance.now();
pipeline.logger.debug('build', `Generating: ${path}`);

const filePath = getOutputFilename(config, path, pageData.route.type);
const lineIcon =
(index === paths.length - 1 && !isConcurrent) || paths.length === 1 ? '└─' : '├─';

// Log the rendering path first if not concurrent. We'll later append the time taken to render.
// We skip if it's concurrent as the logs may overlap
if (!isConcurrent) {
logger.info(null, ` ${blue(lineIcon)} ${dim(filePath)}`, false);
}

await generatePath(path, pipeline, generationOptions, route);

const timeEnd = performance.now();
const isSlow = timeEnd - timeStart > THRESHOLD_SLOW_RENDER_TIME_MS;
const timeIncrease = (isSlow ? red : dim)(`(+${getTimeStat(timeStart, timeEnd)})`);

if (isConcurrent) {
logger.info(null, ` ${blue(lineIcon)} ${dim(filePath)} ${timeIncrease}`);
} else {
logger.info('SKIP_FORMAT', ` ${timeIncrease}`);
}
}

// Now we explode the routes. A route render itself, and it can render its fallbacks (i18n routing)
for (const route of eachRouteInRouteData(pageData)) {
const icon =
route.type === 'page' || route.type === 'redirect' || route.type === 'fallback'
? green('▶')
: magenta('λ');
logger.info(null, `${icon} ${getPrettyRouteName(route)}`);

// Get paths for the route, calling getStaticPaths if needed.
const paths = await getPathsForRoute(route, pageModule, pipeline, builtPaths);
let timeStart = performance.now();
let prevTimeEnd = timeStart;
for (let i = 0; i < paths.length; i++) {
const path = paths[i];
pipeline.logger.debug('build', `Generating: ${path}`);
const filePath = getOutputFilename(config, path, pageData.route.type);
const lineIcon = i === paths.length - 1 ? '└─' : '├─';
logger.info(null, ` ${blue(lineIcon)} ${dim(filePath)}`, false);
await generatePath(path, pipeline, generationOptions, route);
const timeEnd = performance.now();
const timeChange = getTimeStat(prevTimeEnd, timeEnd);
const timeIncrease = `(+${timeChange})`;
let timeIncreaseLabel;
if (timeEnd - prevTimeEnd > THRESHOLD_SLOW_RENDER_TIME_MS) {
timeIncreaseLabel = red(timeIncrease);
} else {
timeIncreaseLabel = dim(timeIncrease);

// Generate each paths
if (config.build.concurrency > 1) {
const limit = PLimit(config.build.concurrency);
const promises: Promise<void>[] = [];
for (let i = 0; i < paths.length; i++) {
const path = paths[i];
promises.push(limit(() => generatePathWithLogs(path, route, i, paths, true)));
}
await Promise.allSettled(promises);
} else {
for (let i = 0; i < paths.length; i++) {
const path = paths[i];
await generatePathWithLogs(path, route, i, paths, false);
}
logger.info('SKIP_FORMAT', ` ${timeIncreaseLabel}`);
prevTimeEnd = timeEnd;
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
serverEntry: 'entry.mjs',
redirects: true,
inlineStylesheets: 'auto',
concurrency: 1,
},
image: {
service: { entrypoint: 'astro/assets/services/sharp', config: {} },
Expand Down Expand Up @@ -186,6 +187,7 @@ export const AstroConfigSchema = z.object({
.enum(['always', 'auto', 'never'])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
concurrency: z.number().min(1).optional().default(ASTRO_CONFIG_DEFAULTS.build.concurrency),
})
.default({}),
server: z.preprocess(
Expand Down Expand Up @@ -619,6 +621,7 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: string) {
.enum(['always', 'auto', 'never'])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
concurrency: z.number().min(1).optional().default(ASTRO_CONFIG_DEFAULTS.build.concurrency),
})
.optional()
.default({}),
Expand Down
Loading

0 comments on commit 3ac2263

Please sign in to comment.