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 @@ + + + + + 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

+ +
+ +
+

Component CSS

+ +
+ +
+

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