0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-10 23:01:26 -05:00

add support parallel builds (#11984)

* 支持并发生成

* 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 <sarah@rainsberger.ca>

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* 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 <sarah@rainsberger.ca>

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

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: bluwy <bjornlu.dev@gmail.com>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
chaegumi 2024-10-09 17:51:03 +08:00 committed by GitHub
parent 116c533596
commit 3ac2263ff6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 878 additions and 658 deletions

View file

@ -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,
},
});
```

View file

@ -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;
};
/**

View file

@ -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,
@ -198,6 +199,40 @@ 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 =
@ -205,28 +240,24 @@ async function generatePage(
? 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;
}
}
}

View file

@ -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: {} },
@ -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(
@ -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({}),

1412
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff