summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/silly-peas-battle.md5
-rw-r--r--packages/astro/package.json4
-rw-r--r--packages/astro/src/@types/astro-core.ts8
-rw-r--r--packages/astro/src/@types/astro-runtime.ts1
-rw-r--r--packages/astro/src/core/build/index.ts116
-rw-r--r--packages/astro/src/core/build/types.d.ts10
-rw-r--r--packages/astro/src/core/ssr/index.ts287
-rw-r--r--packages/astro/src/core/util.ts4
-rw-r--r--packages/astro/src/runtime/server/index.ts11
-rw-r--r--packages/astro/src/runtime/server/metadata.ts26
-rw-r--r--packages/astro/src/vite-plugin-build-css/index.ts146
-rw-r--r--packages/astro/src/vite-plugin-build-css/resolve.ts27
-rw-r--r--packages/astro/src/vite-plugin-build-html/add-rollup-input.ts49
-rw-r--r--packages/astro/src/vite-plugin-build-html/extract-assets.ts204
-rw-r--r--packages/astro/src/vite-plugin-build-html/index.ts391
-rw-r--r--packages/astro/test/astro-assets.test.js48
-rw-r--r--packages/astro/test/astro-css-bundling.test.js63
-rw-r--r--packages/astro/test/astro-dynamic.test.js4
-rw-r--r--packages/astro/test/astro-global.test.js6
-rw-r--r--packages/astro/test/astro-styles-ssr.test.js33
-rw-r--r--packages/astro/test/fixtures/astro-assets/src/pages/index.astro2
-rw-r--r--packages/astro/test/fixtures/astro-css-bundling/src/pages/index.astro6
-rw-r--r--packages/astro/test/fixtures/astro-css-bundling/src/pages/one.astro4
-rw-r--r--packages/astro/test/fixtures/astro-css-bundling/src/pages/preload-merge.astro8
-rw-r--r--packages/astro/test/fixtures/astro-css-bundling/src/pages/preload.astro4
-rw-r--r--packages/astro/test/fixtures/astro-css-bundling/src/pages/two.astro6
-rw-r--r--packages/astro/test/fixtures/astro-global/src/components/nested/images/penguin.pngbin0 -> 77977 bytes
-rw-r--r--packages/astro/test/fixtures/astro-global/src/images/penguin.pngbin0 -> 9799063 bytes
-rw-r--r--yarn.lock125
29 files changed, 1203 insertions, 395 deletions
diff --git a/.changeset/silly-peas-battle.md b/.changeset/silly-peas-battle.md
new file mode 100644
index 000000000..f25be96fa
--- /dev/null
+++ b/.changeset/silly-peas-battle.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Update the build to build/bundle assets
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 19f473c9d..7c9c69049 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -66,7 +66,7 @@
"@babel/traverse": "^7.15.4",
"@proload/core": "^0.2.1",
"@proload/plugin-tsm": "^0.1.0",
- "@web/rollup-plugin-html": "^1.10.1",
+ "@web/parse5-utils": "^1.3.0",
"astring": "^1.7.5",
"ci-info": "^3.2.0",
"connect": "^3.7.0",
@@ -80,6 +80,7 @@
"mime": "^2.5.2",
"morphdom": "^2.6.1",
"node-fetch": "^2.6.5",
+ "parse5": "^6.0.1",
"path-to-regexp": "^6.2.0",
"prismjs": "^1.25.0",
"remark-slug": "^7.0.0",
@@ -91,6 +92,7 @@
"shorthash": "^0.0.2",
"slash": "^4.0.0",
"sourcemap-codec": "^1.4.8",
+ "srcset-parse": "^1.1.0",
"string-width": "^5.0.0",
"strip-ansi": "^7.0.1",
"strip-indent": "^4.0.0",
diff --git a/packages/astro/src/@types/astro-core.ts b/packages/astro/src/@types/astro-core.ts
index 29eb7bba4..204c94a30 100644
--- a/packages/astro/src/@types/astro-core.ts
+++ b/packages/astro/src/@types/astro-core.ts
@@ -1,7 +1,7 @@
import type babel from '@babel/core';
import type { z } from 'zod';
import type { AstroConfigSchema } from '../core/config';
-import type { AstroComponentFactory } from '../runtime/server';
+import type { AstroComponentFactory, Metadata } from '../runtime/server';
import type vite from '../../vendor/vite';
export interface AstroComponentMetadata {
@@ -139,11 +139,9 @@ export interface CollectionRSS {
/** Generic interface for a component (Astro, Svelte, React, etc.) */
export interface ComponentInstance {
- $$metadata: {
- modules: { module: Record<string, unknown>; specifier: string }[];
- fileURL: URL;
- };
+ $$metadata: Metadata;
default: AstroComponentFactory;
+ css?: string[];
getStaticPaths?: (options: GetStaticPathsOptions) => GetStaticPathsResult;
}
diff --git a/packages/astro/src/@types/astro-runtime.ts b/packages/astro/src/@types/astro-runtime.ts
index fa312f840..f1984c27c 100644
--- a/packages/astro/src/@types/astro-runtime.ts
+++ b/packages/astro/src/@types/astro-runtime.ts
@@ -48,6 +48,7 @@ export interface TopLevelAstro {
export interface SSRMetadata {
renderers: Renderer[];
+ pathname: string;
}
export interface SSRResult {
diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts
index d00c8c8bf..b4b8831a2 100644
--- a/packages/astro/src/core/build/index.ts
+++ b/packages/astro/src/core/build/index.ts
@@ -1,17 +1,19 @@
-import type { InputHTMLOptions } from '@web/rollup-plugin-html';
import type { AstroConfig, ComponentInstance, GetStaticPathsResult, ManifestData, RouteCache, RouteData, RSSResult } from '../../@types/astro-core';
import type { LogOptions } from '../logger';
+import type { AllPagesData } from './types';
+import type { RenderedChunk } from 'rollup';
-import { rollupPluginHTML } from '@web/rollup-plugin-html';
+import { rollupPluginAstroBuildHTML } from '../../vite-plugin-build-html/index.js';
+import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js';
import fs from 'fs';
-import { bold, cyan, green, dim } from 'kleur/colors';
+import { bold, cyan, green } from 'kleur/colors';
import { performance } from 'perf_hooks';
import vite, { ViteDevServer } from '../vite.js';
import { fileURLToPath } from 'url';
import { createVite } from '../create-vite.js';
import { pad } from '../dev/util.js';
import { debug, defaultLogOptions, levels, timerMessage, warn } from '../logger.js';
-import { ssr } from '../ssr/index.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';
@@ -71,9 +73,9 @@ class AstroBuilder {
this.viteServer = viteServer;
debug(logging, 'build', timerMessage('Vite started', timer.viteStart));
- timer.renderStart = performance.now();
+ timer.loadStart = performance.now();
const assets: Record<string, string> = {};
- const allPages: Record<string, RouteData & { paths: string[] }> = {};
+ 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
@@ -82,7 +84,21 @@ class AstroBuilder {
this.manifest.routes.map(async (route) => {
// static route:
if (route.pathname) {
- allPages[route.component] = { ...route, paths: [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,
+ })
+ };
return;
}
// dynamic route:
@@ -94,38 +110,37 @@ class AstroBuilder {
}
assets[fileURLToPath(rssFile)] = result.rss.xml;
}
- allPages[route.component] = { ...route, paths: result.paths };
+ 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,
+ })
+ };
})
);
+ debug(logging, 'build', timerMessage('All pages loaded', timer.loadStart));
- // After all routes have been collected, start building them.
- // TODO: test parallel vs. serial performance. Promise.all() may be
- // making debugging harder without any perf gain. If parallel is best,
- // then we should set a max number of parallel builds.
- const input: InputHTMLOptions[] = [];
- await Promise.all(
- Object.entries(allPages).map(([component, route]) =>
- Promise.all(
- route.paths.map(async (pathname) => {
- input.push({
- html: await ssr({
- astroConfig: this.config,
- filePath: new URL(`./${component}`, this.config.projectRoot),
- logging,
- mode: 'production',
- origin,
- pathname,
- route,
- routeCache: this.routeCache,
- viteServer,
- }),
- name: pathname.replace(/\/?$/, '/index.html').replace(/^\//, ''),
- });
- })
- )
- )
- );
- debug(logging, 'build', timerMessage('All pages rendered', timer.renderStart));
+ // 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<RenderedChunk>();
+ const chunkToReferenceIdMap = new Map<string, string>();
+
+ // This is a mapping of pathname to the string source of all collected
+ // inline <style> for a page.
+ const astroStyleMap = new Map<string, string>();
+ // This is a virtual JS module that imports all dependent styles for a page.
+ const astroPageStyleMap = new Map<string, string>();
+
+ const pageNames: string[] = [];
// Bundle the assets in your final build: This currently takes the HTML output
// of every page (stored in memory) and bundles the assets pointed to on those pages.
@@ -138,17 +153,32 @@ class AstroBuilder {
minify: 'esbuild', // significantly faster than "terser" but may produce slightly-bigger bundles
outDir: fileURLToPath(this.config.dist),
rollupOptions: {
+ // The `input` will be populated in the build rollup plugin.
input: [],
output: { format: 'esm' },
},
target: 'es2020', // must match an esbuild target
},
plugins: [
- rollupPluginHTML({
- rootDir: viteConfig.root,
- input,
- extractAssets: false,
- }) as any, // "any" needed for CI; also we don’t need typedefs for this anyway
+ rollupPluginAstroBuildHTML({
+ astroConfig: this.config,
+ astroPageStyleMap,
+ astroStyleMap,
+ chunkToReferenceIdMap,
+ pureCSSChunks,
+ logging,
+ origin,
+ allPages,
+ pageNames,
+ routeCache: this.routeCache,
+ viteServer
+ }),
+ rollupPluginAstroBuildCSS({
+ astroPageStyleMap,
+ astroStyleMap,
+ chunkToReferenceIdMap,
+ pureCSSChunks
+ }),
...(viteConfig.plugins || []),
],
publicDir: viteConfig.publicDir,
@@ -172,7 +202,7 @@ class AstroBuilder {
timer.sitemapStart = performance.now();
if (this.config.buildOptions.sitemap && this.config.buildOptions.site) {
const sitemapStart = performance.now();
- const sitemap = generateSitemap(input.map(({ name }) => new URL(`/${name}`, this.config.buildOptions.site).href));
+ const sitemap = generateSitemap(pageNames.map(pageName => new URL(`/${pageName}`, this.config.buildOptions.site).href));
const sitemapPath = new URL('./sitemap.xml', this.config.dist);
await fs.promises.mkdir(new URL('./', sitemapPath), { recursive: true });
await fs.promises.writeFile(sitemapPath, sitemap, 'utf8');
@@ -182,7 +212,7 @@ class AstroBuilder {
// You're done! Time to clean up.
await viteServer.close();
if (logging.level && levels[logging.level] <= levels['info']) {
- await this.printStats({ cwd: this.config.dist, pageCount: input.length });
+ await this.printStats({ cwd: this.config.dist, pageCount: pageNames.length });
}
}
diff --git a/packages/astro/src/core/build/types.d.ts b/packages/astro/src/core/build/types.d.ts
new file mode 100644
index 000000000..c165f9ce3
--- /dev/null
+++ b/packages/astro/src/core/build/types.d.ts
@@ -0,0 +1,10 @@
+import type { ComponentPreload } from '../ssr/index';
+import type { RouteData } from '../../@types/astro-core';
+
+export interface PageBuildData {
+ paths: string[];
+ preload: ComponentPreload;
+ route: RouteData;
+}
+export type AllPagesData = Record<string, PageBuildData>;
+
diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts
index 8823c483e..1a472b808 100644
--- a/packages/astro/src/core/ssr/index.ts
+++ b/packages/astro/src/core/ssr/index.ts
@@ -7,7 +7,7 @@ import type { LogOptions } from '../logger';
import fs from 'fs';
import path from 'path';
import { renderPage, renderSlot } from '../../runtime/server/index.js';
-import { canonicalURL as getCanonicalURL, codeFrame, resolveDependency } from '../util.js';
+import { canonicalURL as getCanonicalURL, codeFrame, resolveDependency, viteifyPath } from '../util.js';
import { getStylesForID } from './css.js';
import { injectTags } from './html.js';
import { generatePaginateFunction } from './paginate.js';
@@ -72,145 +72,174 @@ async function resolveRenderers(viteServer: vite.ViteDevServer, astroConfig: Ast
return renderers;
}
+async function errorHandler(e: unknown, viteServer: vite.ViteDevServer, filePath: URL) {
+ if(e instanceof Error) {
+ viteServer.ssrFixStacktrace(e);
+ }
+
+ // Astro error (thrown by esbuild so it needs to be formatted for Vite)
+ const anyError = e as any;
+ if (anyError.errors) {
+ const { location, pluginName, text } = (e as BuildResult).errors[0];
+ const err = new Error(text) as SSRError;
+ if (location) err.loc = { file: location.file, line: location.line, column: location.column };
+ const frame = codeFrame(await fs.promises.readFile(filePath, 'utf8'), err.loc);
+ err.frame = frame;
+ err.id = location?.file;
+ err.message = `${location?.file}: ${text}
+${frame}
+`;
+ err.stack = anyError.stack;
+ if (pluginName) err.plugin = pluginName;
+ throw err;
+ }
+
+ // Generic error (probably from Vite, and already formatted)
+ throw e;
+}
+
+export type ComponentPreload = [Renderer[], ComponentInstance];
+
+export async function preload({ astroConfig, filePath, viteServer }: SSROptions): Promise<ComponentPreload> {
+ // Important: This needs to happen first, in case a renderer provides polyfills.
+ const renderers = await resolveRenderers(viteServer, astroConfig);
+ // Load the module from the Vite SSR Runtime.
+ const viteFriendlyURL = viteifyPath(filePath.pathname);
+ const mod = (await viteServer.ssrLoadModule(viteFriendlyURL)) as ComponentInstance;
+
+ return [renderers, mod];
+}
+
/** use Vite to SSR */
-export async function ssr({ astroConfig, filePath, logging, mode, origin, pathname, route, routeCache, viteServer }: SSROptions): Promise<string> {
- try {
- // Important: This needs to happen first, in case a renderer provides polyfills.
- const renderers = await resolveRenderers(viteServer, astroConfig);
- // Load the module from the Vite SSR Runtime.
- const viteFriendlyURL = `/@fs${filePath.pathname}`;
- const mod = (await viteServer.ssrLoadModule(viteFriendlyURL)) as ComponentInstance;
- // Handle dynamic routes
- let params: Params = {};
- let pageProps: Props = {};
- if (route && !route.pathname) {
- if (route.params.length) {
- const paramsMatch = route.pattern.exec(pathname);
- if (paramsMatch) {
- params = getParams(route.params)(paramsMatch);
- }
- }
- validateGetStaticPathsModule(mod);
- if (!routeCache[route.component]) {
- routeCache[route.component] = await (
- await mod.getStaticPaths!({
- paginate: generatePaginateFunction(route),
- rss: () => {
- /* noop */
- },
- })
- ).flat();
+export async function render(renderers: Renderer[], mod: ComponentInstance, ssrOpts: SSROptions): Promise<string> {
+ const { astroConfig, filePath, logging, mode, origin, pathname, route, routeCache, viteServer } = ssrOpts;
+
+ // Handle dynamic routes
+ let params: Params = {};
+ let pageProps: Props = {};
+ if (route && !route.pathname) {
+ if (route.params.length) {
+ const paramsMatch = route.pattern.exec(pathname);
+ if (paramsMatch) {
+ params = getParams(route.params)(paramsMatch);
}
- validateGetStaticPathsResult(routeCache[route.component], logging);
- const routePathParams: GetStaticPathsResult = routeCache[route.component];
- const matchedStaticPath = routePathParams.find(({ params: _params }) => JSON.stringify(_params) === JSON.stringify(params));
- if (!matchedStaticPath) {
- throw new Error(`[getStaticPaths] route pattern matched, but no matching static path found. (${pathname})`);
- }
- pageProps = { ...matchedStaticPath.props } || {};
}
-
- // Validate the page component before rendering the page
- const Component = await mod.default;
- if (!Component) throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
- if (!Component.isAstroComponentFactory) throw new Error(`Unable to SSR non-Astro component (${route?.component})`);
-
- // Create the result object that will be passed into the render function.
- // This object starts here as an empty shell (not yet the result) but then
- // calling the render() function will populate the object with scripts, styles, etc.
- const result: SSRResult = {
- styles: new Set<SSRElement>(),
- scripts: new Set<SSRElement>(),
- /** This function returns the `Astro` faux-global */
- createAstro(astroGlobal: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null) {
- const site = new URL(origin);
- const url = new URL('.' + pathname, site);
- const canonicalURL = getCanonicalURL('.' + pathname, astroConfig.buildOptions.site || origin);
- return {
- __proto__: astroGlobal,
- props,
- request: {
- canonicalURL,
- params,
- url,
+ validateGetStaticPathsModule(mod);
+ if (!routeCache[route.component]) {
+ routeCache[route.component] = await (
+ await mod.getStaticPaths!({
+ paginate: generatePaginateFunction(route),
+ rss: () => {
+ /* noop */
},
- slots: Object.fromEntries(Object.entries(slots || {}).map(([slotName]) => [slotName, true])),
- // This is used for <Markdown> but shouldn't be used publicly
- privateRenderSlotDoNotUse(slotName: string) {
- return renderSlot(result, slots ? slots[slotName] : null);
- },
- // <Markdown> also needs the same `astroConfig.markdownOptions.render` as `.md` pages
- async privateRenderMarkdownDoNotUse(content: string, opts: any) {
- let render = astroConfig.markdownOptions.render;
- let renderOpts = {};
- if (Array.isArray(render)) {
- renderOpts = render[1];
- render = render[0];
- }
- if (typeof render === 'string') {
- ({ default: render } = await import(render));
- }
- const { code } = await render(content, { ...renderOpts, ...(opts ?? {}) });
- return code;
- },
- } as unknown as AstroGlobal;
- },
- _metadata: { renderers },
- };
-
- let html = await renderPage(result, Component, pageProps, null);
-
- // inject tags
- const tags: vite.HtmlTagDescriptor[] = [];
-
- // dev only: inject Astro HMR client
- if (mode === 'development') {
- tags.push({
- tag: 'script',
- attrs: { type: 'module' },
- children: `import 'astro/runtime/client/hmr.js';`,
- injectTo: 'head',
- });
+ })
+ ).flat();
}
+ validateGetStaticPathsResult(routeCache[route.component], logging);
+ const routePathParams: GetStaticPathsResult = routeCache[route.component];
+ const matchedStaticPath = routePathParams.find(({ params: _params }) => JSON.stringify(_params) === JSON.stringify(params));
+ if (!matchedStaticPath) {
+ throw new Error(`[getStaticPaths] route pattern matched, but no matching static path found. (${pathname})`);
+ }
+ pageProps = { ...matchedStaticPath.props } || {};
+ }
- // inject CSS
- [...getStylesForID(filePath.pathname, viteServer)].forEach((href) => {
- tags.push({
- tag: 'link',
- attrs: { type: 'text/css', rel: 'stylesheet', href },
- injectTo: 'head',
- });
+ // Validate the page component before rendering the page
+ const Component = await mod.default;
+ if (!Component) throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
+ if (!Component.isAstroComponentFactory) throw new Error(`Unable to SSR non-Astro component (${route?.component})`);
+
+ // Create the result object that will be passed into the render function.
+ // This object starts here as an empty shell (not yet the result) but then
+ // calling the render() function will populate the object with scripts, styles, etc.
+ const result: SSRResult = {
+ styles: new Set<SSRElement>(),
+ scripts: new Set<SSRElement>(),
+ /** This function returns the `Astro` faux-global */
+ createAstro(astroGlobal: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null) {
+ const site = new URL(origin);
+ const url = new URL('.' + pathname, site);
+ const canonicalURL = getCanonicalURL('.' + pathname, astroConfig.buildOptions.site || origin);
+ return {
+ __proto__: astroGlobal,
+ props,
+ request: {
+ canonicalURL,
+ params,
+ url,
+ },
+ slots: Object.fromEntries(Object.entries(slots || {}).map(([slotName]) => [slotName, true])),
+ // This is used for <Markdown> but shouldn't be used publicly
+ privateRenderSlotDoNotUse(slotName: string) {
+ return renderSlot(result, slots ? slots[slotName] : null);
+ },
+ // <Markdown> also needs the same `astroConfig.markdownOptions.render` as `.md` pages
+ async privateRenderMarkdownDoNotUse(content: string, opts: any) {
+ let mdRender = astroConfig.markdownOptions.render;
+ let renderOpts = {};
+ if (Array.isArray(mdRender)) {
+ renderOpts = mdRender[1];
+ mdRender = mdRender[0];
+ }
+ if (typeof mdRender === 'string') {
+ ({ default: mdRender } = await import(mdRender));
+ }
+ const { code } = await mdRender(content, { ...renderOpts, ...(opts ?? {}) });
+ return code;
+ },
+ } as unknown as AstroGlobal;
+ },
+ _metadata: {
+ renderers,
+ pathname
+ },
+ };
+
+ let html = await renderPage(result, Component, pageProps, null);
+
+ // inject tags
+ const tags: vite.HtmlTagDescriptor[] = [];
+
+ // dev only: inject Astro HMR client
+ if (mode === 'development') {
+ tags.push({
+ tag: 'script',
+ attrs: { type: 'module' },
+ children: `import 'astro/runtime/client/hmr.js';`,
+ injectTo: 'head',
});
+ }
- // add injected tags
- html = injectTags(html, tags);
+ // inject CSS
+ [...getStylesForID(filePath.pathname, viteServer)].forEach((href) => {
+ tags.push({
+ tag: 'link',
+ attrs: {
+ rel: 'stylesheet',
+ href,
+ 'data-astro-injected': true
+ },
+ injectTo: 'head',
+ });
+ });
- // run transformIndexHtml() in dev to run Vite dev transformations
- if (mode === 'development') {
- html = await viteServer.transformIndexHtml(filePath.pathname, html, pathname);
- }
+ // add injected tags
+ html = injectTags(html, tags);
- return html;
- } catch (e: any) {
- viteServer.ssrFixStacktrace(e);
- // Astro error (thrown by esbuild so it needs to be formatted for Vite)
- if (e.errors) {
- const { location, pluginName, text } = (e as BuildResult).errors[0];
- const err = new Error(text) as SSRError;
- if (location) err.loc = { file: location.file, line: location.line, column: location.column };
- const frame = codeFrame(await fs.promises.readFile(filePath, 'utf8'), err.loc);
- err.frame = frame;
- err.id = location?.file;
- err.message = `${location?.file}: ${text}
+ // run transformIndexHtml() in dev to run Vite dev transformations
+ if (mode === 'development') {
+ html = await viteServer.transformIndexHtml(filePath.pathname, html, pathname);
+ }
-${frame}
-`;
- err.stack = e.stack;
- if (pluginName) err.plugin = pluginName;
- throw err;
- }
+ return html;
+}
- // Generic error (probably from Vite, and already formatted)
+export async function ssr(ssrOpts: SSROptions): Promise<string> {
+ try {
+ const [renderers, mod] = await preload(ssrOpts);
+ return render(renderers, mod, ssrOpts);
+ } catch (e: unknown) {
+ await errorHandler(e, ssrOpts.viteServer, ssrOpts.filePath);
throw e;
}
-}
+} \ No newline at end of file
diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts
index 4fb32634c..b43a04700 100644
--- a/packages/astro/src/core/util.ts
+++ b/packages/astro/src/core/util.ts
@@ -71,3 +71,7 @@ export function resolveDependency(dep: string, astroConfig: AstroConfig) {
// For Windows compat, we need a fully resolved `file://` URL string
return pathToFileURL(resolved).toString();
}
+
+export function viteifyPath(pathname: string): string {
+ return `/@fs${pathname}`;
+} \ No newline at end of file
diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts
index f748e73ff..dd9043097 100644
--- a/packages/astro/src/runtime/server/index.ts
+++ b/packages/astro/src/runtime/server/index.ts
@@ -6,6 +6,7 @@ import shorthash from 'shorthash';
import { extractDirectives, generateHydrateScript } from './hydration.js';
import { serializeListValue } from './util.js';
export { createMetadata } from './metadata.js';
+export type { Metadata } from './metadata';
// INVESTIGATE:
// 2. Less anys when possible and make it well known when they are needed.
@@ -271,10 +272,16 @@ export async function renderPage(result: SSRResult, Component: AstroComponentFac
const template = await renderToString(result, Component, props, children);
const styles = Array.from(result.styles)
.filter(uniqueElements)
- .map((style) => renderElement('style', style));
+ .map((style) => renderElement('style', {
+ ...style,
+ props: { ...style.props, 'astro-style': true }
+ }));
const scripts = Array.from(result.scripts)
.filter(uniqueElements)
- .map((script) => renderElement('script', script));
+ .map((script, i) => renderElement('script', {
+ ...script,
+ props: { ...script.props, 'astro-script': result._metadata.pathname + '/script-' + i }
+ }));
return template.replace('</head>', styles.join('\n') + scripts.join('\n') + '</head>');
}
diff --git a/packages/astro/src/runtime/server/metadata.ts b/packages/astro/src/runtime/server/metadata.ts
index 2b318ad80..4dcfc73c9 100644
--- a/packages/astro/src/runtime/server/metadata.ts
+++ b/packages/astro/src/runtime/server/metadata.ts
@@ -8,10 +8,10 @@ interface ComponentMetadata {
componentUrl: string;
}
-class Metadata {
+export class Metadata {
public fileURL: URL;
private metadataCache: Map<any, ComponentMetadata | null>;
- constructor(fileURL: string, public modules: ModuleInfo[], components: any[]) {
+ constructor(fileURL: string, public modules: ModuleInfo[], public hydratedComponents: any[], public hoisted: any[]) {
this.fileURL = new URL(fileURL);
this.metadataCache = new Map<any, ComponentMetadata | null>();
}
@@ -26,6 +26,26 @@ class Metadata {
return metadata?.componentExport || null;
}
+ // Recursively collect all of the hydrated components' paths.
+ getAllHydratedComponentPaths(): Set<string> {
+ const paths = new Set<string>();
+ for(const component of this.hydratedComponents) {
+ const path = this.getPath(component);
+ if(path) {
+ paths.add(path);
+ }
+ }
+
+ for(const {module: mod} of this.modules) {
+ if(typeof mod.$$metadata !== 'undefined') {
+ for(const path of mod.$$metadata.getAllHydratedComponentPaths()) {
+ paths.add(path);
+ }
+ }
+ }
+ return paths;
+ }
+
private getComponentMetadata(Component: any): ComponentMetadata | null {
if (this.metadataCache.has(Component)) {
return this.metadataCache.get(Component)!;
@@ -66,5 +86,5 @@ interface CreateMetadataOptions {
}
export function createMetadata(fileURL: string, options: CreateMetadataOptions) {
- return new Metadata(fileURL, options.modules, options.hydratedComponents);
+ return new Metadata(fileURL, options.modules, options.hydratedComponents, options.hoisted);
}
diff --git a/packages/astro/src/vite-plugin-build-css/index.ts b/packages/astro/src/vite-plugin-build-css/index.ts
new file mode 100644
index 000000000..e54c04bed
--- /dev/null
+++ b/packages/astro/src/vite-plugin-build-css/index.ts
@@ -0,0 +1,146 @@
+
+import type { ResolveIdHook, LoadHook, RenderedChunk } from 'rollup';
+import type { Plugin as VitePlugin } from 'vite';
+
+import { STYLE_EXTENSIONS } from '../core/ssr/css.js';
+import { getViteResolve, getViteLoad } from './resolve.js';
+import { getViteTransform, TransformHook } from '../vite-plugin-astro/styles.js';
+import * as path from 'path';
+
+
+const PLUGIN_NAME = '@astrojs/rollup-plugin-build-css';
+
+// This is a virtual module that represents the .astro <style> usage on a page
+const ASTRO_STYLE_PREFIX = '@astro-inline-style';
+
+const ASTRO_PAGE_STYLE_PREFIX = '@astro-page-all-styles';
+
+const isCSSRequest = (request: string) => STYLE_EXTENSIONS.has(path.extname(request));
+
+export function getAstroPageStyleId(pathname: string) {
+ let styleId = ASTRO_PAGE_STYLE_PREFIX + pathname;
+ if(styleId.endsWith('/')) {
+ styleId += 'index';
+ }
+ styleId += '.js';
+ return styleId;
+}
+
+export function getAstroStyleId(pathname: string) {
+ let styleId = ASTRO_STYLE_PREFIX + pathname;
+ if(styleId.endsWith('/')) {
+ styleId += 'index';
+ }
+ styleId += '.css';
+ return styleId;
+}
+
+export function getAstroStylePathFromId(id: string) {
+ return id.substr(ASTRO_STYLE_PREFIX.length + 1);
+}
+
+function isStyleVirtualModule(id: string) {
+ return id.startsWith(ASTRO_STYLE_PREFIX);
+}
+
+function isPageStyleVirtualModule(id: string) {
+ return id.startsWith(ASTRO_PAGE_STYLE_PREFIX);
+}
+
+interface PluginOptions {
+ astroStyleMap: Map<string, string>;
+ astroPageStyleMap: Map<string, string>;
+ chunkToReferenceIdMap: Map<string, string>;
+ pureCSSChunks: Set<RenderedChunk>;
+}
+
+export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
+ const { astroPageStyleMap, astroStyleMap, chunkToReferenceIdMap, pureCSSChunks } = options;
+ const styleSourceMap = new Map<string, string>();
+
+ let viteTransform: TransformHook;
+
+ return {
+ name: PLUGIN_NAME,
+
+ enforce: 'pre',
+
+ configResolved(resolvedConfig) {
+ viteTransform = getViteTransform(resolvedConfig);
+ },
+
+ async resolveId(id) {
+ if(isPageStyleVirtualModule(id)) {
+ return id;
+ }
+ if(isStyleVirtualModule(id)) {
+ return id;
+ }
+ return undefined;
+ },
+
+ async load(id) {
+ if(isPageStyleVirtualModule(id)) {
+ const source = astroPageStyleMap.get(id)!;
+ return source;
+ }
+ if(isStyleVirtualModule(id)) {
+ return astroStyleMap.get(id)!;
+ }
+ return null;
+ },
+
+ async transform(value, id) {
+ if(isStyleVirtualModule(id)) {
+ styleSourceMap.set(id, value);
+ return null;
+ }
+ if(isCSSRequest(id)) {
+ let result = await viteTransform(value, id);
+ if(result) {
+ styleSourceMap.set(id, result.code);
+ } else {
+ styleSourceMap.set(id, value);
+ }
+
+ return result;
+ }
+
+ return null;
+ },
+
+ renderChunk(_code, chunk) {
+ let chunkCSS = '';
+ let isPureCSS = true;
+ for(const [id] of Object.entries(chunk.modules)) {
+ if(!isCSSRequest(id) && !isPageStyleVirtualModule(id)) {
+ isPureCSS = false;
+ }
+ if(styleSourceMap.has(id)) {
+ chunkCSS += styleSourceMap.get(id)!;
+ }
+ }
+
+ if(isPureCSS) {
+ const referenceId = this.emitFile({
+ name: chunk.name + '.css',
+ type: 'asset',
+ source: chunkCSS
+ });
+ pureCSSChunks.add(chunk);
+ chunkToReferenceIdMap.set(chunk.fileName, referenceId);
+ }
+
+ return null;
+ },
+
+ // Delete CSS chunks so JS is not produced for them.
+ generateBundle(_options, bundle) {
+ for(const [chunkId, chunk] of Object.entries(bundle)) {
+ if(chunk.type === 'chunk' && pureCSSChunks.has(chunk)) {
+ delete bundle[chunkId];
+ }
+ }
+ }
+ }
+}
diff --git a/packages/astro/src/vite-plugin-build-css/resolve.ts b/packages/astro/src/vite-plugin-build-css/resolve.ts
new file mode 100644
index 000000000..7c55107d3
--- /dev/null
+++ b/packages/astro/src/vite-plugin-build-css/resolve.ts
@@ -0,0 +1,27 @@
+import type { ResolveIdHook, LoadHook } from 'rollup';
+import type { ResolvedConfig, Plugin as VitePlugin } from 'vite';
+
+export function getVitePluginByName(viteConfig: ResolvedConfig, pluginName: string): VitePlugin {
+ const plugin = viteConfig.plugins.find(({ name }) => name === pluginName);
+ if (!plugin) throw new Error(`${pluginName} plugin couldn’t be found`);
+ return plugin;}
+
+export function getViteResolvePlugin(viteConfig: ResolvedConfig): VitePlugin {
+ return getVitePluginByName(viteConfig, 'vite:resolve');
+}
+
+export function getViteLoadFallbackPlugin(viteConfig: ResolvedConfig): VitePlugin {
+ return getVitePluginByName(viteConfig, 'vite:load-fallback');
+}
+
+export function getViteResolve(viteConfig: ResolvedConfig): ResolveIdHook {
+ const plugin = getViteResolvePlugin(viteConfig);
+ if (!plugin.resolveId) throw new Error(`vite:resolve has no resolveId() hook`);
+ return plugin.resolveId.bind(null as any) as any;
+}
+
+export function getViteLoad(viteConfig: ResolvedConfig): LoadHook {
+ const plugin = getViteLoadFallbackPlugin(viteConfig);
+ if (!plugin.load) throw new Error(`vite:load-fallback has no load() hook`);
+ return plugin.load.bind(null as any) as any;
+}
diff --git a/packages/astro/src/vite-plugin-build-html/add-rollup-input.ts b/packages/astro/src/vite-plugin-build-html/add-rollup-input.ts
new file mode 100644
index 000000000..0da25ecf9
--- /dev/null
+++ b/packages/astro/src/vite-plugin-build-html/add-rollup-input.ts
@@ -0,0 +1,49 @@
+import { InputOptions } from 'rollup';
+
+function fromEntries<V>(entries: [string, V][]) {
+ const obj: Record<string, V> = {};
+ for (const [k, v] of entries) {
+ obj[k] = v;
+ }
+ return obj;
+}
+
+export function addRollupInput(
+ inputOptions: InputOptions,
+ newInputs: string[]
+): InputOptions {
+ // Add input module ids to existing input option, whether it's a string, array or object
+ // this way you can use multiple html plugins all adding their own inputs
+ if (!inputOptions.input) {
+ return { ...inputOptions, input: newInputs };
+ }
+
+ if (typeof inputOptions.input === 'string') {
+ return {
+ ...inputOptions,
+ input: [inputOptions.input, ...newInputs],
+ };
+ }
+
+ if (Array.isArray(inputOptions.input)) {
+ return {
+ ...inputOptions,
+ input: [...inputOptions.input, ...newInputs],
+ };
+ }
+
+ if (typeof inputOptions.input === 'object') {
+ return {
+ ...inputOptions,
+ input: {
+ ...inputOptions.input,
+ ...fromEntries(
+ newInputs
+ .map(i => [i.split('/').slice(-1)[0].split('.')[0], i]),
+ ),
+ },
+ };
+ }
+
+ throw new Error(`Unknown rollup input type. Supported inputs are string, array and object.`);
+}
diff --git a/packages/astro/src/vite-plugin-build-html/extract-assets.ts b/packages/astro/src/vite-plugin-build-html/extract-assets.ts
new file mode 100644
index 000000000..3f38828a4
--- /dev/null
+++ b/packages/astro/src/vite-plugin-build-html/extract-assets.ts
@@ -0,0 +1,204 @@
+import { Document, Element, Node } from 'parse5';
+import npath from 'path';
+import { findElements, getTagName, getAttribute, findNodes } from '@web/parse5-utils';
+import adapter from 'parse5/lib/tree-adapters/default.js';
+
+const hashedLinkRels = ['stylesheet', 'preload'];
+const linkRels = [...hashedLinkRels, 'icon', 'manifest', 'apple-touch-icon', 'mask-icon'];
+
+function getSrcSetUrls(srcset: string) {
+ if (!srcset) {
+ return [];
+ }
+ const srcsetParts = srcset.includes(',') ? srcset.split(',') : [srcset];
+ const urls = srcsetParts
+ .map(url => url.trim())
+ .map(url => (url.includes(' ') ? url.split(' ')[0] : url));
+ return urls;
+}
+
+function extractFirstUrlOfSrcSet(node: Element) {
+ const srcset = getAttribute(node, 'srcset');
+ if (!srcset) {
+ return '';
+ }
+ const urls = getSrcSetUrls(srcset);
+ return urls[0];
+}
+
+function isAsset(node: Element) {
+ let path = '';
+ switch (getTagName(node)) {
+ case 'img':
+ path = getAttribute(node, 'src') ?? '';
+ break;
+ case 'source':
+ path = extractFirstUrlOfSrcSet(node) ?? '';
+ break;
+ case 'link':
+ if (linkRels.includes(getAttribute(node, 'rel') ?? '')) {
+ path = getAttribute(node, 'href') ?? '';
+ }
+ break;
+ case 'meta':
+ if (getAttribute(node, 'property') === 'og:image' && getAttribute(node, 'content')) {
+ path = getAttribute(node, 'content') ?? '';
+ }
+ break;
+ case 'script':
+ if (getAttribute(node, 'type') !== 'module' && getAttribute(node, 'src')) {
+ path = getAttribute(node, 'src') ?? '';
+ }
+ break;
+ default:
+ return false;
+ }
+ if (!path) {
+ return false;
+ }
+ try {
+ new URL(path);
+ return false;
+ } catch (e) {
+ return true;
+ }
+}
+
+function isInlineScript(node: Element): boolean {
+ switch (getTagName(node)) {
+ case 'script':
+ if (getAttribute(node, 'type') === 'module' && !getAttribute(node, 'src')) {
+ return true;
+ }
+ return false;
+ default:
+ return false;
+ }
+}
+
+function isInlineStyle(node: Element): boolean {
+ return getTagName(node) === 'style';
+}
+
+export function isStylesheetLink(node: Element): boolean {
+ return getTagName(node) === 'link' && getAttribute(node, 'rel') === 'stylesheet';
+}
+
+export function isHashedAsset(node: Element) {
+ switch (getTagName(node)) {
+ case 'img':
+ return true;
+ case 'source':
+ return true;
+ case 'script':
+ return true;
+ case 'link':
+ return hashedLinkRels.includes(getAttribute(node, 'rel')!);
+ case 'meta':
+ return true;
+ default:
+ return false;
+ }
+}
+
+export function resolveAssetFilePath(
+ browserPath: string,
+ htmlDir: string,
+ projectRootDir: string,
+ absolutePathPrefix?: string,
+) {
+ const _browserPath =
+ absolutePathPrefix && browserPath[0] === '/'
+ ? '/' + npath.posix.relative(absolutePathPrefix, browserPath)
+ : browserPath;
+ return npath.join(
+ _browserPath.startsWith('/') ? projectRootDir : htmlDir,
+ _browserPath.split('/').join(npath.sep),
+ );
+}
+
+export function getSourceAttribute(node: Element) {
+ switch (getTagName(node)) {
+ case 'img': {
+ return 'src';
+ }
+ case 'source': {
+ return 'srcset';
+ }
+ case 'link': {
+ return 'href';
+ }
+ case 'script': {
+ return 'src';
+ }
+ case 'meta': {
+ return 'content';
+ }
+ default:
+ throw new Error(`Unknown node with tagname ${getTagName(node)}`);
+ }
+}
+
+export interface Location {
+ start: number;
+ end: number;
+}
+
+export function getSourcePaths(node: Element) {
+ const key = getSourceAttribute(node);
+
+ let location: Location = { start: 0, end: 0 };
+ const src = getAttribute(node, key);
+ if(node.sourceCodeLocation) {
+ let loc = node.sourceCodeLocation.attrs[key];
+ if(loc) {
+ location.start = loc.startOffset;
+ location.end = loc.endOffset;
+ }
+ }
+ if (typeof key !== 'string' || src === '') {
+ throw new Error(`Missing attribute ${key} in element ${node.nodeName}`);
+ }
+
+ let paths: {path: string, location: Location}[] = [];
+ if(src && key === 'srcset') {
+ paths = getSrcSetUrls(src).map(path => ({
+ path,
+ location
+ }));
+ } else if(src) {
+ paths.push({
+ path: src,
+ location
+ });
+ }
+
+ return paths;
+}
+
+export function getTextContent(node: Node): string {
+ if (adapter.isCommentNode(node)) {
+ return node.data || '';
+ }
+ if (adapter.isTextNode(node)) {
+ return node.value || '';
+ }
+ const subtree = findNodes(node, n => adapter.isTextNode(n));
+ return subtree.map(getTextContent).join('');
+}
+
+export function findAssets(document: Document) {
+ return findElements(document, isAsset);
+}
+
+export function findInlineScripts(document: Document) {
+ return findElements(document, isInlineScript);
+}
+
+export function findInlineStyles(document: Document) {
+ return findElements(document, isInlineStyle);
+}
+
+export function findStyleLinks(document: Document) {
+ return findElements(document, isStylesheetLink);
+} \ No newline at end of file
diff --git a/packages/astro/src/vite-plugin-build-html/index.ts b/packages/astro/src/vite-plugin-build-html/index.ts
new file mode 100644
index 000000000..16085e707
--- /dev/null
+++ b/packages/astro/src/vite-plugin-build-html/index.ts
@@ -0,0 +1,391 @@
+
+import type { AstroConfig, RouteCache } from '../@types/astro-core';
+import type { LogOptions } from '../core/logger';
+import type { ViteDevServer, Plugin as VitePlugin } from 'vite';
+import type { OutputChunk, PreRenderedChunk, RenderedChunk } from 'rollup';
+import type { AllPagesData } from '../core/build/types';
+import parse5 from 'parse5';
+import srcsetParse from 'srcset-parse';
+import * as npath from 'path';
+import { promises as fs } from 'fs';
+import { getAttribute, getTagName, insertBefore, remove, createScript, createElement, setAttribute } from '@web/parse5-utils';
+import { addRollupInput } from './add-rollup-input.js';
+import { findAssets, findInlineScripts, findInlineStyles, getTextContent, isStylesheetLink } from './extract-assets.js';
+import { render as ssrRender } from '../core/ssr/index.js';
+import { getAstroStyleId, getAstroPageStyleId } from '../vite-plugin-build-css/index.js';
+import { viteifyPath } from '../core/util.js';
+
+// This package isn't real ESM, so have to coerce it
+const matchSrcset: typeof srcsetParse = (srcsetParse as any).default;
+
+const PLUGIN_NAME = '@astro/rollup-plugin-build';
+const ASTRO_PAGE_PREFIX = '@astro-page';
+const ASTRO_SCRIPT_PREFIX = '@astro-script';
+
+const ASTRO_EMPTY = '@astro-empty';
+
+const tagsWithSrcSet = new Set(['img', 'source']);
+
+const isAstroInjectedLink = (node: parse5.Element) => isStylesheetLink(node) && getAttribute(node, 'data-astro-injected') === '';
+const isBuildableLink = (node: parse5.Element, srcRoot: string) => isAstroInjectedLink(node) || getAttribute(node, 'href')?.startsWith(srcRoot);
+const isBuildableImage = (node: parse5.Element, srcRoot: string) => getTagName(node) === 'img' && getAttribute(node, 'src')?.startsWith(srcRoot);
+const hasSrcSet = (node: parse5.Element) => tagsWithSrcSet.has(getTagName(node)) && !!getAttribute(node, 'srcset');
+
+interface PluginOptions {
+ astroConfig: AstroConfig;
+ astroStyleMap: Map<string, string>;
+ astroPageStyleMap: Map<string, string>;
+ chunkToReferenceIdMap: Map<string, string>;
+ logging: LogOptions;
+ allPages: AllPagesData;
+ pageNames: string[];
+ pureCSSChunks: Set<RenderedChunk>;
+ origin: string;
+ routeCache: RouteCache;
+ viteServer: ViteDevServer;
+}
+
+export function rollupPluginAstroBuildHTML(options: PluginOptions): VitePlugin {
+ const { astroConfig, astroStyleMap, astroPageStyleMap, chunkToReferenceIdMap, pureCSSChunks, logging, origin, allPages, routeCache, viteServer, pageNames } = options;
+
+ const srcRoot = astroConfig.src.pathname;
+
+ // A map of pages to rendered HTML
+ const renderedPageMap = new Map<string, string>();
+
+ //
+ const astroScriptMap = new Map<string, string>();
+ const astroPageMap = new Map<string, string>();
+ const astroAssetMap = new Map<string, Promise<Buffer>>();
+
+ const cssChunkMap = new Map<string, string[]>();
+
+ return {
+ name: PLUGIN_NAME,
+
+ enforce: 'pre',
+
+ async options(inputOptions) {
+ const htmlInput: Set<string> = new Set();
+ const assetInput: Set<string> = new Set(); // TODO remove?
+ const jsInput: Set<string> = new Set();
+
+ for(const [component, pageData] of Object.entries(allPages)) {
+ const [renderers, mod] = pageData.preload;
+
+ for(const path of mod.$$metadata.getAllHydratedComponentPaths()) {
+ jsInput.add(path);
+ }
+
+ for(const pathname of pageData.paths) {
+ pageNames.push(pathname.replace(/\/?$/, '/index.html').replace(/^\//, ''));
+ const id = ASTRO_PAGE_PREFIX + pathname;
+ const html = await ssrRender(renderers, mod, {
+ astroConfig,
+ filePath: new URL(`./${component}`, astroConfig.projectRoot),
+ logging,
+ mode: 'production',
+ origin,
+ pathname,
+ route: pageData.route,
+ routeCache,
+ viteServer,
+ });
+ renderedPageMap.set(id, html);
+
+ const document = parse5.parse(html, {
+ sourceCodeLocationInfo: true
+ });
+
+ const frontEndImports = [];
+ for(const script of findInlineScripts(document)) {
+ const astroScript = getAttribute(script, 'astro-script');
+ if(astroScript) {
+ const js = getTextContent(script);
+ const scriptId = ASTRO_SCRIPT_PREFIX + astroScript;
+ frontEndImports.push(scriptId);
+ astroScriptMap.set(scriptId, js);
+ }
+ }
+
+ let styles = '';
+ for(const node of findInlineStyles(document)) {
+ if(getAttribute(node, 'astro-style')) {
+ styles += getTextContent(node);
+ }
+ }
+
+ const assetImports = [];
+ for(let node of findAssets(document)) {
+ if(isBuildableLink(node, srcRoot)) {
+ const href = getAttribute(node, 'href')!;
+ const linkId = viteifyPath(href);
+ assetImports.push(linkId);
+ }
+
+ if(isBuildableImage(node, srcRoot)) {
+ const src = getAttribute(node, 'src');
+ if(src?.startsWith(srcRoot) && !astroAssetMap.has(src)) {
+ astroAssetMap.set(src, fs.readFile(new URL(`file://${src}`)));
+ }
+ }
+
+ if(hasSrcSet(node)) {
+ const candidates = matchSrcset(getAttribute(node, 'srcset')!);
+ for(const {url} of candidates) {
+ if(url.startsWith(srcRoot) && !astroAssetMap.has(url)) {
+ astroAssetMap.set(url, fs.readFile(new URL(`file://${url}`)));
+ }
+ }
+ }
+ }
+
+ if(styles) {
+ const styleId = getAstroStyleId(pathname);
+ astroStyleMap.set(styleId, styles);
+ // Put this at the front of imports
+ assetImports.unshift(styleId);
+ }
+
+ if(frontEndImports.length) {
+ htmlInput.add(id);
+ const jsSource = frontEndImports.map(sid => `import '${sid}';`).join('\n');
+ astroPageMap.set(id, jsSource);
+ }
+
+ if(assetImports.length) {
+ const pageStyleId = getAstroPageStyleId(pathname);
+ const jsSource = assetImports.map(sid => `import '${sid}';`).join('\n');
+ astroPageStyleMap.set(pageStyleId, jsSource);
+ assetInput.add(pageStyleId);
+ }
+ }
+ }
+
+ const allInputs = new Set([...jsInput, ...htmlInput, ...assetInput]);
+ // You always need at least 1 input, so add an placeholder just so we can build HTML/CSS
+ if(!allInputs.size) {
+ allInputs.add(ASTRO_EMPTY);
+ }
+ const outOptions = addRollupInput(inputOptions, Array.from(allInputs));
+ return outOptions;
+ },
+
+
+ async resolveId(id) {
+ switch(true) {
+ case astroScriptMap.has(id):
+ case astroPageMap.has(id):
+ case id === ASTRO_EMPTY: {
+ return id;
+ }
+ }
+
+ return undefined;
+ },
+
+ async load(id) {
+ // Load pages
+ if(astroPageMap.has(id)) {
+ return astroPageMap.get(id)!;
+ }
+ // Load scripts
+ if(astroScriptMap.has(id)) {
+ return astroScriptMap.get(id)!;
+ }
+ // Give this module actual code so it doesnt warn about an empty chunk
+ if(id === ASTRO_EMPTY) {
+ return 'console.log("empty");';
+ }
+
+ return null;
+ },
+
+ outputOptions(outputOptions) {
+ Object.assign(outputOptions, {
+ entryFileNames(chunk: PreRenderedChunk) {
+ // Removes the `@astro-page` prefix from JS chunk names.
+ if(chunk.name.startsWith(ASTRO_PAGE_PREFIX)) {
+ let pageName = chunk.name.substr(ASTRO_PAGE_PREFIX.length + 1);
+ if(!pageName) {
+ pageName = 'index';
+ }
+ return `assets/${pageName}.[hash].js`;
+ }
+ return 'assets/[name].[hash].js';
+ }
+ });
+ return outputOptions;
+ },
+
+ async generateBundle(_options, bundle) {
+ const facadeIdMap = new Map<string, string>();
+ for(const [chunkId, output] of Object.entries(bundle)) {
+ if(output.type === 'chunk') {
+ const chunk = output as OutputChunk;
+ const id = chunk.facadeModuleId;
+ if(id === ASTRO_EMPTY) {
+ delete bundle[chunkId];
+ } else if(id) {
+ facadeIdMap.set(id, chunk.fileName);
+ }
+ }
+ }
+
+ // Emit assets (images, etc)
+ const assetIdMap = new Map<string, string>();
+ for(const [assetPath, dataPromise] of astroAssetMap) {
+ const referenceId = this.emitFile({
+ type: 'asset',
+ name: npath.basename(assetPath),
+ source: await dataPromise
+ });
+ assetIdMap.set(assetPath, referenceId);
+ }
+
+ // Create a mapping of chunks to dependent chunks, used to add the proper
+ // link tags for CSS.
+ for(const chunk of pureCSSChunks) {
+ const referenceId = chunkToReferenceIdMap.get(chunk.fileName)!;
+ const chunkReferenceIds = [referenceId];
+ for(const imp of chunk.imports) {
+ if(chunkToReferenceIdMap.has(imp)) {
+ chunkReferenceIds.push(chunkToReferenceIdMap.get(imp)!);
+ }
+ }
+ for(const [id] of Object.entries(chunk.modules)) {
+ cssChunkMap.set(id, chunkReferenceIds);
+ }
+ }
+
+ // Keep track of links added so we don't do so twice.
+ const linkChunksAdded = new Set<string>();
+ const appendStyleChunksBefore = (ref: parse5.Element, pathname: string, referenceIds: string[] | undefined, attrs: Record<string, any> = {}) => {
+ let added = false;
+ if(referenceIds) {
+ const lastNode = ref;
+ for(const referenceId of referenceIds) {
+ const chunkFileName = this.getFileName(referenceId);
+ const relPath = npath.posix.relative(pathname, '/' + chunkFileName);
+
+ // This prevents added links more than once per type.
+ const key = pathname + relPath + attrs.rel || 'stylesheet';
+ if(!linkChunksAdded.has(key)) {
+ linkChunksAdded.add(key);
+ insertBefore(lastNode.parentNode, createElement('link', {
+ rel: 'stylesheet',
+ ...attrs,
+ href: relPath
+ }), lastNode);
+ added = true;
+ }
+
+ }
+ }
+ return added;
+ }
+
+ for(const [id, html] of renderedPageMap) {
+ const pathname = id.substr(ASTRO_PAGE_PREFIX.length);
+ const document = parse5.parse(html, {
+ sourceCodeLocationInfo: true
+ });
+
+ if(facadeIdMap.has(id)) {
+ const bundleId = facadeIdMap.get(id)!;
+ const bundlePath = '/' + bundleId;
+
+ // Update scripts
+ let i = 0;
+ for(let script of findInlineScripts(document)) {
+ if(getAttribute(script, 'astro-script')) {
+ if(i === 0) {
+ const relPath = npath.posix.relative(pathname, bundlePath);
+ insertBefore(script.parentNode, createScript({
+ type: 'module',
+ src: relPath
+ }), script);
+ }
+ remove(script);
+ }
+ i++;
+ }
+ }
+
+ const styleId = getAstroPageStyleId(pathname);
+ let pageCSSAdded = false;
+ for(const node of findAssets(document)) {
+ if(isBuildableLink(node, srcRoot)) {
+ const rel = getAttribute(node, 'rel');
+ switch(rel) {
+ case 'stylesheet': {
+ if(!pageCSSAdded) {
+ const attrs = Object.fromEntries(node.attrs.map(attr => [attr.name, attr.value]));
+ delete attrs['data-astro-injected'];
+ pageCSSAdded = appendStyleChunksBefore(node, pathname, cssChunkMap.get(styleId), attrs);
+ }
+ remove(node);
+ break;
+ }
+ case 'preload': {
+ if(getAttribute(node, 'as') === 'style') {
+ const attrs = Object.fromEntries(node.attrs.map(attr => [attr.name, attr.value]));
+ appendStyleChunksBefore(node, pathname, cssChunkMap.get(styleId), attrs);
+ remove(node);
+ }
+ }
+ }
+ }
+
+ if(isBuildableImage(node, srcRoot)) {
+ const src = getAttribute(node, 'src')!;
+ const referenceId = assetIdMap.get(src);
+ if(referenceId) {
+ const fileName = this.getFileName(referenceId);
+ const relPath = npath.posix.relative(pathname, '/' + fileName);
+ setAttribute(node, 'src', relPath);
+ }
+ }
+
+ // Could be a `source` or an `img`.
+ if(hasSrcSet(node)) {
+ const srcset = getAttribute(node, 'srcset')!;
+ let changedSrcset = srcset;
+ const urls = matchSrcset(srcset).map(c => c.url);
+ for(const url of urls) {
+ if(assetIdMap.has(url)) {
+ const referenceId = assetIdMap.get(url)!;
+ const fileName = this.getFileName(referenceId);
+ const relPath = npath.posix.relative(pathname, '/' + fileName);
+ changedSrcset = changedSrcset.replace(url, relPath);
+ }
+ }
+ // If anything changed, update it
+ if(changedSrcset !== srcset) {
+ setAttribute(node, 'srcset', changedSrcset);
+ }
+ }
+ }
+
+ // Page styles for <style> usage, if not already appended via links.
+ for(const style of findInlineStyles(document)) {
+ if(getAttribute(style, 'astro-style')) {
+ if(!pageCSSAdded) {
+ pageCSSAdded = appendStyleChunksBefore(style, pathname, cssChunkMap.get(styleId));
+ }
+
+ remove(style);
+ }
+ }
+
+ const outHTML = parse5.serialize(document);
+ const outPath = npath.posix.join(pathname.substr(1), 'index.html');
+ this.emitFile({
+ fileName: outPath,
+ source: outHTML,
+ type: 'asset'
+ });
+ }
+ }
+ }
+}
diff --git a/packages/astro/test/astro-assets.test.js b/packages/astro/test/astro-assets.test.js
index 2e75f3920..df279f140 100644
--- a/packages/astro/test/astro-assets.test.js
+++ b/packages/astro/test/astro-assets.test.js
@@ -1,29 +1,45 @@
-/**
- * UNCOMMENT: add support for automatic <img> and srcset in build
import { expect } from 'chai';
+import cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
+import srcsetParse from 'srcset-parse';
-let fixture;
+// This package isn't real ESM, so have to coerce it
+const matchSrcset = (srcsetParse).default;
-before(async () => {
- fixture = await loadFixture({ projectRoot: './fixtures/astro-assets/' });
- await fixture.build();
-});
-
-// TODO: add automatic asset bundling
+// Asset bundling
describe('Assets', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({ projectRoot: './fixtures/astro-assets/' });
+ await fixture.build();
+ });
+
it('built the base image', async () => {
- await fixture.readFile('/images/twitter.png');
+ const html = await fixture.readFile('/index.html');
+ const $ = cheerio.load(html);
+ const imgPath = $('img').attr('src');
+ const data = await fixture.readFile('/' + imgPath);
+ expect(!!data).to.equal(true);
});
it('built the 2x image', async () => {
- await fixture.readFile('/images/twitter@2x.png');
+ const html = await fixture.readFile('/index.html');
+ const $ = cheerio.load(html);
+ const srcset = $('img').attr('srcset');
+ const candidates = matchSrcset(srcset);
+ const match = candidates.find(a => a.density === 2);
+ const data = await fixture.readFile('/' + match.url);
+ expect(!!data).to.equal(true);
});
it('built the 3x image', async () => {
- await fixture.readFile('/images/twitter@3x.png');
+ const html = await fixture.readFile('/index.html');
+ const $ = cheerio.load(html);
+ const srcset = $('img').attr('srcset');
+ const candidates = matchSrcset(srcset);
+ const match = candidates.find(a => a.density === 3);
+ const data = await fixture.readFile('/' + match.url);
+ expect(!!data).to.equal(true);
});
-});
-*/
-
-it.skip('is skipped', () => {});
+}); \ No newline at end of file
diff --git a/packages/astro/test/astro-css-bundling.test.js b/packages/astro/test/astro-css-bundling.test.js
index fddb258e6..0ff7cadbd 100644
--- a/packages/astro/test/astro-css-bundling.test.js
+++ b/packages/astro/test/astro-css-bundling.test.js
@@ -1,28 +1,26 @@
-/**
- * UNCOMMENT: implement CSS bundling
import { expect } from 'chai';
import cheerio from 'cheerio';
-import { loadFixture } from './test-utils';
+import { loadFixture } from './test-utils.js';
// note: the hashes should be deterministic, but updating the file contents will change hashes
// be careful not to test that the HTML simply contains CSS, because it always will! filename and quanity matter here (bundling).
const EXPECTED_CSS = {
- '/index.html': ['/_astro/common-', '/_astro/index-'], // don’t match hashes, which change based on content
- '/one/index.html': ['/_astro/common-', '/_astro/one/index-'],
- '/two/index.html': ['/_astro/common-', '/_astro/two/index-'],
- '/preload/index.html': ['/_astro/common-', '/_astro/preload/index-'],
- '/preload-merge/index.html': ['/_astro/preload-merge/index-'],
+ '/index.html': ['assets/index', 'assets/typography'], // don’t match hashes, which change based on content
+ '/one/index.html': ['../assets/one'],
+ '/two/index.html': ['../assets/two', '../assets/typography'],
+ '/preload/index.html': ['../assets/preload'],
+ '/preload-merge/index.html': ['../assets/preload-merge'],
};
-const UNEXPECTED_CSS = ['/_astro/components/nav.css', '../css/typography.css', '../css/colors.css', '../css/page-index.css', '../css/page-one.css', '../css/page-two.css'];
+const UNEXPECTED_CSS = ['/src/components/nav.css', '../css/typography.css', '../css/colors.css', '../css/page-index.css', '../css/page-one.css', '../css/page-two.css'];
-let fixture;
+describe('CSS Bundling', function() {
+ let fixture;
-before(async () => {
- fixture = await loadFixture({ projectRoot: './fixtures/astro-css-bundling/' });
- await fixture.build({ mode: 'production' });
-});
+ before(async () => {
+ fixture = await loadFixture({ projectRoot: './fixtures/astro-css-bundling/' });
+ await fixture.build({ mode: 'production' });
+ });
-describe('CSS Bundling', () => {
it('Bundles CSS', async () => {
const builtCSS = new Set();
@@ -35,7 +33,8 @@ describe('CSS Bundling', () => {
for (const href of css) {
const link = $(`link[rel="stylesheet"][href^="${href}"]`);
expect(link).to.have.lengthOf(1);
- builtCSS.add(link.attr('href'));
+ const outHref = link.attr('href');
+ builtCSS.add(outHref.startsWith('../') ? outHref.substr(2) : outHref);
}
// test 2: assert old CSS was removed
@@ -46,8 +45,8 @@ describe('CSS Bundling', () => {
// test 3: preload tags was not removed and attributes was preserved
if (filepath === '/preload/index.html') {
- const stylesheet = $('link[rel="stylesheet"][href^="/_astro/preload/index-"]');
- const preload = $('link[rel="preload"][href^="/_astro/preload/index-"]');
+ const stylesheet = $('link[rel="stylesheet"][href^="../assets/preload"]');
+ const preload = $('link[rel="preload"][href^="../assets/preload"]');
expect(stylesheet[0].attribs.media).to.equal('print');
expect(preload).to.have.lengthOf(1); // Preload tag was removed
}
@@ -60,33 +59,9 @@ describe('CSS Bundling', () => {
// test 5: assert all bundled CSS was built and contains CSS
for (const url of builtCSS.keys()) {
- const css = await context.readFile(url);
+ const css = await fixture.readFile(url);
expect(css).to.be.ok;
}
-
- // test 6: assert ordering is preserved (typography.css before colors.css)
- const bundledLoc = [...builtCSS].find((k) => k.startsWith('/_astro/common-'));
- const bundledContents = await context.readFile(bundledLoc);
- const typographyIndex = bundledContents.indexOf('body{');
- const colorsIndex = bundledContents.indexOf(':root{');
- expect(typographyIndex).toBeLessThan(colorsIndex);
-
- // test 7: assert multiple style blocks were bundled (Nav.astro includes 2 scoped style blocks)
- const scopedNavStyles = [...bundledContents.matchAll('.nav.astro-')];
- expect(scopedNavStyles).to.have.lengthOf(2);
-
- // test 8: assert <style global> was not scoped (in Nav.astro)
- const globalStyles = [...bundledContents.matchAll('html{')];
- expect(globalStyles).to.have.lengthOf(1);
-
- // test 9: assert keyframes are only scoped for non-global styles (from Nav.astro)
- const scopedKeyframes = [...bundledContents.matchAll('nav-scoped-fade-astro')];
- const globalKeyframes = [...bundledContents.matchAll('nav-global-fade{')];
- expect(scopedKeyframes.length).toBeGreaterThan(0);
- expect(globalKeyframes.length).toBeGreaterThan(0);
}
});
-});
-*/
-
-it.skip('is skipped', () => {});
+}); \ No newline at end of file
diff --git a/packages/astro/test/astro-dynamic.test.js b/packages/astro/test/astro-dynamic.test.js
index 82fdc3818..0e68db2db 100644
--- a/packages/astro/test/astro-dynamic.test.js
+++ b/packages/astro/test/astro-dynamic.test.js
@@ -14,7 +14,7 @@ describe('Dynamic components', () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
- expect($('script').length).to.eq(2);
+ expect($('script').length).to.eq(1);
});
it('Loads pages using client:media hydrator', async () => {
@@ -23,12 +23,10 @@ describe('Dynamic components', () => {
const $ = cheerio.load(html);
// test 1: static value rendered
-
let js = await fixture.readFile(new URL($('script').attr('src'), root).pathname);
expect(js).to.include(`value:"(max-width: 700px)"`);
// test 2: dynamic value rendered
- js = await fixture.readFile(new URL($('script').eq(1).attr('src'), root).pathname);
expect(js).to.include(`value:"(max-width: 600px)"`);
});
diff --git a/packages/astro/test/astro-global.test.js b/packages/astro/test/astro-global.test.js
index ad002b985..75d44e743 100644
--- a/packages/astro/test/astro-global.test.js
+++ b/packages/astro/test/astro-global.test.js
@@ -48,10 +48,10 @@ describe('Astro.*', () => {
expect($('#site').attr('href')).to.equal('https://mysite.dev/blog/');
});
- it('Astro.resolve in development', async () => {
+ it('Astro.resolve built', async () => {
const html = await fixture.readFile('/resolve/index.html');
const $ = cheerio.load(html);
- expect($('img').attr('src')).to.include('/src/images/penguin.png');
- expect($('#inner-child img').attr('src')).to.include('/src/components/nested/images/penguin.png');
+ expect($('img').attr('src')).to.include('assets/penguin.ccd44411.png'); // Main src/images
+ expect($('#inner-child img').attr('src')).to.include('assets/penguin.b9ab122a.png');
});
});
diff --git a/packages/astro/test/astro-styles-ssr.test.js b/packages/astro/test/astro-styles-ssr.test.js
index 31f643d17..6b223ebc9 100644
--- a/packages/astro/test/astro-styles-ssr.test.js
+++ b/packages/astro/test/astro-styles-ssr.test.js
@@ -2,22 +2,17 @@ import { expect } from 'chai';
import cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
-let fixture;
+describe('Styles SSR', () => {
+ let fixture;
-before(async () => {
- fixture = await loadFixture({ projectRoot: './fixtures/astro-styles-ssr/' });
- await fixture.build();
-});
+ before(async () => {
+ fixture = await loadFixture({ projectRoot: './fixtures/astro-styles-ssr/' });
+ await fixture.build();
+ });
-describe('Styles SSR', () => {
it('Has <link> tags', async () => {
const MUST_HAVE_LINK_TAGS = [
- '/src/components/ReactCSS.css',
- '/src/components/ReactModules.module.css',
- '/src/components/SvelteScoped.svelte',
- '/src/components/VueCSS.vue',
- '/src/components/VueModules.vue',
- '/src/components/VueScoped.vue',
+ 'assets/index'
];
const html = await fixture.readFile('/index.html');
@@ -70,17 +65,19 @@ describe('Styles SSR', () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
+ const href = '/' + $('link').attr('href');
+ const raw = await fixture.readFile(href);
+
let scopedClass;
// test 1: <style> tag in <head> is transformed
- const css = $('style')
- .html()
+ const css = raw
.replace(/\.astro-[A-Za-z0-9-]+/, (match) => {
scopedClass = match; // get class hash from result
return match;
});
- expect(css).to.equal(`.wrapper${scopedClass}{margin-left:auto;margin-right:auto;max-width:1200px;}.outer${scopedClass}{color:red;}`);
+ expect(css).to.include(`.wrapper${scopedClass}{margin-left:auto;margin-right:auto;max-width:1200px;}.outer${scopedClass}{color:red;}`);
// test 2: element received .astro-XXXXXX class (this selector will succeed if transformed correctly)
const wrapper = $(`.wrapper${scopedClass}`);
@@ -110,10 +107,8 @@ describe('Styles SSR', () => {
expect(el1.attr('class')).to.equal(`blue ${scopedClass}`);
expect(el2.attr('class')).to.equal(`visible ${scopedClass}`);
- let css = '';
- $('style').each((_, el) => {
- css += $(el).html();
- });
+ const href = '/' + $('link').attr('href');
+ const css = await fixture.readFile(href);
// test 4: CSS generates as expected
expect(css).to.include(`.blue.${scopedClass}{color:powderblue;}.color\\:blue.${scopedClass}{color:powderblue;}.visible.${scopedClass}{display:block;}`);
diff --git a/packages/astro/test/fixtures/astro-assets/src/pages/index.astro b/packages/astro/test/fixtures/astro-assets/src/pages/index.astro
index 18760ab3b..0932c66d4 100644
--- a/packages/astro/test/fixtures/astro-assets/src/pages/index.astro
+++ b/packages/astro/test/fixtures/astro-assets/src/pages/index.astro
@@ -5,7 +5,7 @@
</style>
<body>
<h1>Icons</h1>
- <img src="../images/twitter.png" srcset="../images/twitter.png 1x, ../images/twitter@2x.png 2x, ../images/twitter@3x.png 3x" />
+ <img src={Astro.resolve('../images/twitter.png')} srcset={`${Astro.resolve('../images/twitter.png')} 1x, ${Astro.resolve('../images/twitter@2x.png')} 2x, ${Astro.resolve('../images/twitter@3x.png')} 3x`} />
<img srcset="https://ik.imagekit.io/demo/tr:w-300,h-300/medium_cafe_B1iTdD0C.jpg, https://ik.imagekit.io/demo/tr:w-450,h-450/medium_cafe_B1iTdD0C.jpg 600w, https://ik.imagekit.io/demo/tr:w-600,h-600/medium_cafe_B1iTdD0C.jpg 800w">
<img srcset="https://ik.imagekit.io/demo/tr:w-300,h-300/medium_cafe_B1iTdD0C.jpg, https://ik.imagekit.io/demo/tr:w-450,h-450/medium_cafe_B1iTdD0C.jpg 1.5x, https://ik.imagekit.io/demo/tr:w-600,h-600/medium_cafe_B1iTdD0C.jpg 2x">
<!--
diff --git a/packages/astro/test/fixtures/astro-css-bundling/src/pages/index.astro b/packages/astro/test/fixtures/astro-css-bundling/src/pages/index.astro
index 9479981c8..f776de2e8 100644
--- a/packages/astro/test/fixtures/astro-css-bundling/src/pages/index.astro
+++ b/packages/astro/test/fixtures/astro-css-bundling/src/pages/index.astro
@@ -4,9 +4,9 @@ import Nav from '../components/Nav.astro';
<html>
<head>
- <link rel="stylesheet" href="../css/typography.css" />
- <link rel="stylesheet" href="../css/colors.css" />
- <link rel="stylesheet" href="../css/page-index.css" />
+ <link rel="stylesheet" href={Astro.resolve('../css/typography.css')}>
+ <link rel="stylesheet" href={Astro.resolve('../css/colors.css')}>
+ <link rel="stylesheet" href={Astro.resolve('../css/page-index.css')}>
</head>
<body>
<Nav />
diff --git a/packages/astro/test/fixtures/astro-css-bundling/src/pages/one.astro b/packages/astro/test/fixtures/astro-css-bundling/src/pages/one.astro
index cd47f0b27..935db26c4 100644
--- a/packages/astro/test/fixtures/astro-css-bundling/src/pages/one.astro
+++ b/packages/astro/test/fixtures/astro-css-bundling/src/pages/one.astro
@@ -4,8 +4,8 @@ import Nav from '../components/Nav.astro';
<html>
<head>
- <link rel="stylesheet" href="../css/typography.css" />
- <link rel="stylesheet" href="../css/page-one.css" />
+ <link rel="stylesheet" href={Astro.resolve('../css/typography.css')} />
+ <link rel="stylesheet" href={Astro.resolve('../css/page-one.css')} />
</head>
<body>
<Nav />
diff --git a/packages/astro/test/fixtures/astro-css-bundling/src/pages/preload-merge.astro b/packages/astro/test/fixtures/astro-css-bundling/src/pages/preload-merge.astro
index 0f256c166..34b080966 100644
--- a/packages/astro/test/fixtures/astro-css-bundling/src/pages/preload-merge.astro
+++ b/packages/astro/test/fixtures/astro-css-bundling/src/pages/preload-merge.astro
@@ -4,11 +4,11 @@ import Nav from '../components/Nav.astro';
<html>
<head>
- <link rel="preload" as="style" href="../css/page-preload-merge.css" />
- <link rel="preload" as="style" href="../css/page-preload-merge-2.css" />
+ <link rel="preload" as="style" href={Astro.resolve('../css/page-preload-merge.css')} />
+ <link rel="preload" as="style" href={Astro.resolve('../css/page-preload-merge-2.css')} />
- <link rel="stylesheet" href="../css/page-preload-merge.css" />
- <link rel="stylesheet" href="../css/page-preload-merge-2.css" />
+ <link rel="stylesheet" href={Astro.resolve('../css/page-preload-merge.css')} />
+ <link rel="stylesheet" href={Astro.resolve('../css/page-preload-merge-2.css')} />
</head>
<body>
<Nav />
diff --git a/packages/astro/test/fixtures/astro-css-bundling/src/pages/preload.astro b/packages/astro/test/fixtures/astro-css-bundling/src/pages/preload.astro
index 6d421acbe..535b5f3e3 100644
--- a/packages/astro/test/fixtures/astro-css-bundling/src/pages/preload.astro
+++ b/packages/astro/test/fixtures/astro-css-bundling/src/pages/preload.astro
@@ -4,8 +4,8 @@ import Nav from '../components/Nav.astro';
<html>
<head>
- <link rel="preload" href="../css/page-preload.css" />
- <link rel="stylesheet" href="../css/page-preload.css" media="print" onload="this.media='all'" />
+ <link rel="preload" as="style" href={Astro.resolve('../css/page-preload.css')} />
+ <link rel="stylesheet" href={Astro.resolve('../css/page-preload.css')} media="print" onload="this.media='all'" />
</head>
<body>
<Nav />
diff --git a/packages/astro/test/fixtures/astro-css-bundling/src/pages/two.astro b/packages/astro/test/fixtures/astro-css-bundling/src/pages/two.astro
index cc730f0d1..2c733d7f0 100644
--- a/packages/astro/test/fixtures/astro-css-bundling/src/pages/two.astro
+++ b/packages/astro/test/fixtures/astro-css-bundling/src/pages/two.astro
@@ -4,9 +4,9 @@ import Nav from '../components/Nav.astro';
<html>
<head>
- <link rel="stylesheet" href="../css/typography.css" />
- <link rel="stylesheet" href="../css/colors.css" />
- <link rel="stylesheet" href="../css/page-two.css" />
+ <link rel="stylesheet" href={Astro.resolve('../css/typography.css')} />
+ <link rel="stylesheet" href={Astro.resolve('../css/colors.css')} />
+ <link rel="stylesheet" href={Astro.resolve('../css/page-two.css')} />
</head>
<body>
<Nav />
diff --git a/packages/astro/test/fixtures/astro-global/src/components/nested/images/penguin.png b/packages/astro/test/fixtures/astro-global/src/components/nested/images/penguin.png
new file mode 100644
index 000000000..bc9523bd4
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-global/src/components/nested/images/penguin.png
Binary files differ
diff --git a/packages/astro/test/fixtures/astro-global/src/images/penguin.png b/packages/astro/test/fixtures/astro-global/src/images/penguin.png
new file mode 100644
index 000000000..74cb5e8f3
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-global/src/images/penguin.png
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index e0d2acfd9..e33f0f691 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2214,16 +2214,6 @@
"@types/parse5" "^6.0.1"
parse5 "^6.0.1"
-"@web/rollup-plugin-html@^1.10.1":
- version "1.10.1"
- resolved "https://registry.yarnpkg.com/@web/rollup-plugin-html/-/rollup-plugin-html-1.10.1.tgz#7995d3aff436f6b5c1a365830a9ff525388b40d8"
- integrity sha512-XYJxHtdllwA5l4X8wh8CailrOykOl3YY+BRqO8+wS/I1Kq0JFISg3EUHdWAyVcw0TRDnHNLbOBJTm2ptAM+eog==
- dependencies:
- "@web/parse5-utils" "^1.3.0"
- glob "^7.1.6"
- html-minifier-terser "^6.0.0"
- parse5 "^6.0.1"
-
"@webcomponents/template-shadowroot@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@webcomponents/template-shadowroot/-/template-shadowroot-0.1.0.tgz#adb3438d0d9a18e8fced08abc253f56b7eadab00"
@@ -2901,14 +2891,6 @@ calmcard@~0.1.1:
resolved "https://registry.yarnpkg.com/calmcard/-/calmcard-0.1.1.tgz#35ac2b66492b0ed39ad06a893a0ff6e61124e449"
integrity sha1-NawrZkkrDtOa0GqJOg/25hEk5Ek=
-camel-case@^4.1.2:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
- integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
- dependencies:
- pascal-case "^3.1.2"
- tslib "^2.0.3"
-
camelcase-css@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
@@ -3151,13 +3133,6 @@ ci-info@^3.2.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6"
integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==
-clean-css@^5.1.5:
- version "5.1.5"
- resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.1.5.tgz#3b0af240dcfc9a3779a08c2332df3ebd4474f232"
- integrity sha512-9dr/cU/LjMpU57PXlSvDkVRh0rPxJBXiBtD0+SgYt8ahTCsXtfKjCkNYgIoTC6mBg8CFr5EKhW3DKCaGMUbUfQ==
- dependencies:
- source-map "~0.6.0"
-
clean-stack@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
@@ -3330,20 +3305,15 @@ comma-separated-tokens@^2.0.0:
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98"
integrity sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==
-commander@^2.20.0, commander@~2.20.3:
- version "2.20.3"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
- integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
-
commander@^6.0.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
-commander@^8.1.0:
- version "8.2.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-8.2.0.tgz#37fe2bde301d87d47a53adeff8b5915db1381ca8"
- integrity sha512-LLKxDvHeL91/8MIyTAD5BFMNtoIwztGPMiM/7Bl8rIPmHCZXRxmSWr91h57dpOpnQ6jIUqEWdXE/uBYMfiVZDA==
+commander@~2.20.3:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@~3.0.2:
version "3.0.2"
@@ -3922,14 +3892,6 @@ domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0, domutils@^2.8.0:
domelementtype "^2.2.0"
domhandler "^4.2.0"
-dot-case@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
- integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
- dependencies:
- no-case "^3.0.4"
- tslib "^2.0.3"
-
dot-prop@^5.1.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
@@ -5655,7 +5617,7 @@ hastscript@^7.0.0:
property-information "^6.0.0"
space-separated-tokens "^2.0.0"
-he@1.2.0, he@^1.2.0:
+he@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@@ -5697,19 +5659,6 @@ html-entities@2.3.2, html-entities@^2.3.2:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488"
integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==
-html-minifier-terser@^6.0.0:
- version "6.0.2"
- resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.0.2.tgz#14059ad64b69bf9f8b8a33f25b53411d8321e75d"
- integrity sha512-AgYO3UGhMYQx2S/FBJT3EM0ZYcKmH6m9XL9c1v77BeK/tYJxGPxT1/AtsdUi4FcP8kZGmqqnItCcjFPcX9hk6A==
- dependencies:
- camel-case "^4.1.2"
- clean-css "^5.1.5"
- commander "^8.1.0"
- he "^1.2.0"
- param-case "^3.0.4"
- relateurl "^0.2.7"
- terser "^5.7.2"
-
html-tags@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
@@ -6861,13 +6810,6 @@ loose-envify@^1.1.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
-lower-case@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
- integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
- dependencies:
- tslib "^2.0.3"
-
lru-cache@4.1.x, lru-cache@^4.0.1:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
@@ -7797,14 +7739,6 @@ nlcst-to-string@^2.0.0:
resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-2.0.4.tgz#9315dfab80882bbfd86ddf1b706f53622dc400cc"
integrity sha512-3x3jwTd6UPG7vi5k4GEzvxJ5rDA7hVUIRNHPblKuMVP9Z3xmlsd9cgLcpAMkc5uPOBna82EeshROFhsPkbnTZg==
-no-case@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
- integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
- dependencies:
- lower-case "^2.0.2"
- tslib "^2.0.3"
-
node-emoji@^1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c"
@@ -8491,14 +8425,6 @@ pacote@^11.2.6:
ssri "^8.0.1"
tar "^6.1.0"
-param-case@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
- integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
- dependencies:
- dot-case "^3.0.4"
- tslib "^2.0.3"
-
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@@ -8611,14 +8537,6 @@ parseurl@^1.3.2, parseurl@~1.3.3:
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
-pascal-case@^3.1.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
- integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
- dependencies:
- no-case "^3.0.4"
- tslib "^2.0.3"
-
path-browserify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
@@ -9348,11 +9266,6 @@ rehype-toc@^3.0.2:
dependencies:
"@jsdevtools/rehype-toc" "3.0.2"
-relateurl@^0.2.7:
- version "0.2.7"
- resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
- integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
-
remark-code-titles@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/remark-code-titles/-/remark-code-titles-0.1.2.tgz#ae41b47c517eae4084c761a59a60df5f0bd54aa8"
@@ -9935,25 +9848,17 @@ source-map-js@^0.6.2:
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
-source-map-support@~0.5.20:
- version "0.5.20"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
- integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==
- dependencies:
- buffer-from "^1.0.0"
- source-map "^0.6.0"
-
source-map@^0.5.0:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
-source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
+source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
-source-map@^0.7.3, source-map@~0.7.2:
+source-map@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
@@ -10043,6 +9948,11 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
+srcset-parse@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/srcset-parse/-/srcset-parse-1.1.0.tgz#73f787f38b73ede2c5af775e0a3465579488122b"
+ integrity sha512-JWp4cG2eybkvKA1QUHGoNK6JDEYcOnSuhzNGjZuYUPqXreDl/VkkvP2sZW7Rmh+icuCttrR9ccb2WPIazyM/Cw==
+
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
@@ -10448,15 +10358,6 @@ term-size@^2.1.0:
resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54"
integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==
-terser@^5.7.2:
- version "5.8.0"
- resolved "https://registry.yarnpkg.com/terser/-/terser-5.8.0.tgz#c6d352f91aed85cc6171ccb5e84655b77521d947"
- integrity sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==
- dependencies:
- commander "^2.20.0"
- source-map "~0.7.2"
- source-map-support "~0.5.20"
-
text-extensions@^1.0.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26"
@@ -10630,7 +10531,7 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.1, tslib@^2.0.3, tslib@^2.2.0:
+tslib@^2.0.1, tslib@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==