diff --git a/examples/fast-build/astro.config.mjs b/examples/fast-build/astro.config.mjs
new file mode 100644
index 000000000..32eca8696
--- /dev/null
+++ b/examples/fast-build/astro.config.mjs
@@ -0,0 +1,11 @@
+import { imagetools } from 'vite-imagetools';
+
+// @ts-check
+export default /** @type {import('astro').AstroUserConfig} */ ({
+ renderers: [
+ "@astrojs/renderer-vue"
+ ],
+ vite: {
+ plugins: [imagetools()]
+ }
+});
diff --git a/examples/fast-build/package.json b/examples/fast-build/package.json
new file mode 100644
index 000000000..4abd5fc13
--- /dev/null
+++ b/examples/fast-build/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@example/fast-build",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "dev": "astro dev --experimental-static-build",
+ "start": "astro dev",
+ "build": "astro build --experimental-static-build",
+ "preview": "astro preview"
+ },
+ "devDependencies": {
+ "astro": "^0.21.6",
+ "unocss": "^0.15.5",
+ "vite-imagetools": "^4.0.1"
+ }
+}
diff --git a/examples/fast-build/src/components/Greeting.vue b/examples/fast-build/src/components/Greeting.vue
new file mode 100644
index 000000000..69fa4fbca
--- /dev/null
+++ b/examples/fast-build/src/components/Greeting.vue
@@ -0,0 +1,20 @@
+
+
+
+ {{ greeting }}
+
+
+
diff --git a/examples/fast-build/src/images/penguin.jpg b/examples/fast-build/src/images/penguin.jpg
new file mode 100644
index 000000000..6c5dcd37a
Binary files /dev/null and b/examples/fast-build/src/images/penguin.jpg differ
diff --git a/examples/fast-build/src/images/random.jpg b/examples/fast-build/src/images/random.jpg
new file mode 100644
index 000000000..291883837
Binary files /dev/null and b/examples/fast-build/src/images/random.jpg differ
diff --git a/examples/fast-build/src/pages/index.astro b/examples/fast-build/src/pages/index.astro
new file mode 100644
index 000000000..b5b9785da
--- /dev/null
+++ b/examples/fast-build/src/pages/index.astro
@@ -0,0 +1,32 @@
+---
+import imgUrl from '../images/penguin.jpg';
+import grayscaleUrl from '../images/random.jpg?grayscale=true';
+import Greeting from '../components/Greeting.vue';
+---
+
+
+
+ Demo app
+
+
+
+
+ Images
+
+ Imported in JS
+
+
+
+
+
+
+ ImageTools
+
+
+
+
\ No newline at end of file
diff --git a/examples/fast-build/src/styles/global.css b/examples/fast-build/src/styles/global.css
new file mode 100644
index 000000000..9f52e094e
--- /dev/null
+++ b/examples/fast-build/src/styles/global.css
@@ -0,0 +1,3 @@
+body {
+ background: lightcoral;
+}
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 8435c3c20..9a0db9f66 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -56,7 +56,7 @@
"test": "mocha --parallel --timeout 15000"
},
"dependencies": {
- "@astrojs/compiler": "^0.5.4",
+ "@astrojs/compiler": "^0.6.0",
"@astrojs/language-server": "^0.8.2",
"@astrojs/markdown-remark": "^0.5.0",
"@astrojs/prism": "0.3.0",
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index bce09f050..cdb2771d5 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -363,11 +363,13 @@ export interface SSRElement {
export interface SSRMetadata {
renderers: Renderer[];
pathname: string;
+ experimentalStaticBuild: boolean;
}
export interface SSRResult {
styles: Set;
scripts: Set;
+ links: Set;
createAstro(Astro: AstroGlobalPartial, props: Record, slots: Record | null): AstroGlobal;
_metadata: SSRMetadata;
}
diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts
index 621678e50..03c84a711 100644
--- a/packages/astro/src/cli/index.ts
+++ b/packages/astro/src/cli/index.ts
@@ -25,6 +25,7 @@ interface CLIState {
hostname?: string;
port?: number;
config?: string;
+ experimentalStaticBuild?: boolean;
};
}
@@ -37,6 +38,7 @@ function resolveArgs(flags: Arguments): CLIState {
port: typeof flags.port === 'number' ? flags.port : undefined,
config: typeof flags.config === 'string' ? flags.config : undefined,
hostname: typeof flags.hostname === 'string' ? flags.hostname : undefined,
+ experimentalStaticBuild: typeof flags.experimentalStaticBuild === 'boolean' ? flags.experimentalStaticBuild : false,
};
if (flags.version) {
@@ -73,6 +75,7 @@ function printHelp() {
--config Specify the path to the Astro config file.
--project-root Specify the path to the project root folder.
--no-sitemap Disable sitemap generation (build only).
+ --experimental-static-build A more performant build that expects assets to be define statically.
--verbose Enable verbose logging
--silent Disable logging
--version Show the version number and exit.
@@ -92,6 +95,7 @@ function mergeCLIFlags(astroConfig: AstroConfig, flags: CLIState['options']) {
if (typeof flags.site === 'string') astroConfig.buildOptions.site = flags.site;
if (typeof flags.port === 'number') astroConfig.devOptions.port = flags.port;
if (typeof flags.hostname === 'string') astroConfig.devOptions.hostname = flags.hostname;
+ if (typeof flags.experimentalStaticBuild === 'boolean') astroConfig.buildOptions.experimentalStaticBuild = flags.experimentalStaticBuild;
}
/** The primary CLI action */
diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts
index be1496b61..3789f9789 100644
--- a/packages/astro/src/core/build/index.ts
+++ b/packages/astro/src/core/build/index.ts
@@ -1,23 +1,18 @@
-import type { AstroConfig, ComponentInstance, GetStaticPathsResult, ManifestData, RouteCache, RouteData, RSSResult } from '../../@types/astro';
+import type { AstroConfig, ManifestData, RouteCache } from '../../@types/astro';
import type { LogOptions } from '../logger';
-import type { AllPagesData } from './types';
-import type { RenderedChunk } from 'rollup';
-import { rollupPluginAstroBuildHTML } from '../../vite-plugin-build-html/index.js';
-import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js';
import fs from 'fs';
import * as colors from 'kleur/colors';
import { polyfill } from '@astropub/webapi';
import { performance } from 'perf_hooks';
import vite, { ViteDevServer } from '../vite.js';
-import { fileURLToPath } from 'url';
import { createVite, ViteConfigWithSSR } from '../create-vite.js';
import { debug, defaultLogOptions, info, levels, timerMessage, warn } from '../logger.js';
-import { preload as ssrPreload } from '../ssr/index.js';
-import { generatePaginateFunction } from '../ssr/paginate.js';
-import { createRouteManifest, validateGetStaticPathsModule, validateGetStaticPathsResult } from '../ssr/routing.js';
-import { generateRssFunction } from '../ssr/rss.js';
+import { createRouteManifest } from '../ssr/routing.js';
import { generateSitemap } from '../ssr/sitemap.js';
+import { collectPagesData } from './page-data.js';
+import { build as scanBasedBuild } from './scan-based-build.js';
+import { staticBuild } from './static-build.js';
export interface BuildOptions {
mode?: string;
@@ -82,137 +77,45 @@ class AstroBuilder {
debug(logging, 'build', timerMessage('Vite started', timer.viteStart));
timer.loadStart = performance.now();
- const assets: Record = {};
- const allPages: AllPagesData = {};
- // Collect all routes ahead-of-time, before we start the build.
- // NOTE: This enforces that `getStaticPaths()` is only called once per route,
- // and is then cached across all future SSR builds. In the past, we've had trouble
- // with parallelized builds without guaranteeing that this is called first.
- await Promise.all(
- this.manifest.routes.map(async (route) => {
- // static route:
- if (route.pathname) {
- allPages[route.component] = {
- route,
- paths: [route.pathname],
- preload: await ssrPreload({
- astroConfig: this.config,
- filePath: new URL(`./${route.component}`, this.config.projectRoot),
- logging,
- mode: 'production',
- origin,
- pathname: route.pathname,
- route,
- routeCache: this.routeCache,
- viteServer,
- })
- .then((routes) => {
- const html = `${route.pathname}`.replace(/\/?$/, '/index.html');
- debug(logging, 'build', `├── ${colors.bold(colors.green('✔'))} ${route.component} → ${colors.yellow(html)}`);
- return routes;
- })
- .catch((err) => {
- debug(logging, 'build', `├── ${colors.bold(colors.red('✘'))} ${route.component}`);
- throw err;
- }),
- };
- return;
- }
- // dynamic route:
- const result = await this.getStaticPathsForRoute(route)
- .then((routes) => {
- const label = routes.paths.length === 1 ? 'page' : 'pages';
- debug(logging, 'build', `├── ${colors.bold(colors.green('✔'))} ${route.component} → ${colors.magenta(`[${routes.paths.length} ${label}]`)}`);
- return routes;
- })
- .catch((err) => {
- debug(logging, 'build', `├── ${colors.bold(colors.red('✗'))} ${route.component}`);
- throw err;
- });
- if (result.rss?.xml) {
- const rssFile = new URL(result.rss.url.replace(/^\/?/, './'), this.config.dist);
- if (assets[fileURLToPath(rssFile)]) {
- throw new Error(`[getStaticPaths] RSS feed ${result.rss.url} already exists.\nUse \`rss(data, {url: '...'})\` to choose a unique, custom URL. (${route.component})`);
- }
- assets[fileURLToPath(rssFile)] = result.rss.xml;
- }
- allPages[route.component] = {
- route,
- paths: result.paths,
- preload: await ssrPreload({
- astroConfig: this.config,
- filePath: new URL(`./${route.component}`, this.config.projectRoot),
- logging,
- mode: 'production',
- origin,
- pathname: result.paths[0],
- route,
- routeCache: this.routeCache,
- viteServer,
- }),
- };
- })
- );
+ const { assets, allPages } = await collectPagesData({
+ astroConfig: this.config,
+ logging: this.logging,
+ manifest: this.manifest,
+ origin,
+ routeCache: this.routeCache,
+ viteServer: this.viteServer,
+ });
debug(logging, 'build', timerMessage('All pages loaded', timer.loadStart));
- // Pure CSS chunks are chunks that only contain CSS.
- // This is all of them, and chunkToReferenceIdMap maps them to a hash id used to find the final file.
- const pureCSSChunks = new Set();
- const chunkToReferenceIdMap = new Map();
-
- // This is a mapping of pathname to the string source of all collected
- // inline