Output assets to _astro directory (#5772)

* WIP: emit assets to _astro

* chore: better _astro handling

* refactor: emit server assets to `chunks/`

* chore: update /asset tests

* test: add explicit build output tests

* fix: update image to emit to configured asset path

* chore: update changeset

* chore: update image tests

* chore: update image tests

* test: update css test

* test: update bundling test

* test: update tests

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

Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>

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

Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>

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

Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>

* chore: add clarifying comment

Co-authored-by: Nate Moore <nate@astro.build>
Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
This commit is contained in:
Nate Moore 2023-01-09 11:01:33 -05:00 committed by GitHub
parent 7572f74022
commit f8f4d49aba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 367 additions and 135 deletions

View file

@ -1,8 +1,10 @@
---
'@astrojs/deno': major
'@astrojs/netlify': major
'@astrojs/image': minor
'@astrojs/image': major
'astro': major
---
Builds chunks into the `assets` folder. This simplifies configuring immutable caching with your adapter provider as all files are now in the same `assets` folder.
**Breaking Change**: client assets are built to an `_astro` directory rather than the previous `assets` directory. This setting can now be controlled by the new `build` configuration option named `assets`.
This should simplify configuring immutable caching with your adapter provider as all files are now in the same `_astro` directory.

View file

@ -586,6 +586,25 @@ export interface AstroUserConfig {
* ```
*/
server?: string;
/**
* @docs
* @name build.assets
* @type {string}
* @default `'_astro'`
* @see outDir
* @version 2.0.0
* @description
* Specifies the directory in the build output where Astro-generated assets (bundled JS and CSS for example) should live.
*
* ```js
* {
* build: {
* assets: '_custom'
* }
* }
* ```
*/
assets?: string;
/**
* @docs
* @name build.serverEntry

View file

@ -10,9 +10,10 @@ import {
BuildInternals,
createBuildInternals,
eachPrerenderedPageData,
isHoistedScript,
} from '../../core/build/internal.js';
import { emptyDir, removeDir } from '../../core/fs/index.js';
import { prependForwardSlash } from '../../core/path.js';
import { emptyDir, removeDir, removeEmptyDirs } from '../../core/fs/index.js';
import { appendForwardSlash, prependForwardSlash } from '../../core/path.js';
import { isModeServerWithNoAdapter } from '../../core/util.js';
import { runHookBuildSetup } from '../../integrations/index.js';
import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
@ -133,8 +134,10 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
input: [],
output: {
format: 'esm',
chunkFileNames: 'chunks/[name].[hash].mjs',
assetFileNames: 'assets/[name].[hash][extname]',
// Server chunks can't go in the assets (_astro) folder
// We need to keep these separate
chunkFileNames: `chunks/[name].[hash].mjs`,
assetFileNames: `${settings.config.build.assets}/[name].[hash][extname]`,
...viteConfig.build?.rollupOptions?.output,
entryFileNames: opts.buildConfig.serverEntry,
},
@ -212,9 +215,9 @@ async function clientBuild(
input: Array.from(input),
output: {
format: 'esm',
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/chunks/[name].[hash].js',
assetFileNames: 'assets/[name].[hash][extname]',
entryFileNames: `${settings.config.build.assets}/[name].[hash].js`,
chunkFileNames: `${settings.config.build.assets}/[name].[hash].js`,
assetFileNames: `${settings.config.build.assets}/[name].[hash][extname]`,
...viteConfig.build?.rollupOptions?.output,
},
preserveEntrySignatures: 'exports-only',
@ -285,25 +288,8 @@ async function cleanStaticOutput(opts: StaticBuildOptions, internals: BuildInter
await fs.promises.writeFile(url, value, { encoding: 'utf8' });
})
);
// Map directories heads from the .mjs files
const directories: Set<string> = new Set();
files.forEach((i) => {
const splitFilePath = i.split(path.sep);
// If the path is more than just a .mjs filename itself
if (splitFilePath.length > 1) {
directories.add(splitFilePath[0]);
}
});
// Attempt to remove only those folders which are empty
await Promise.all(
Array.from(directories).map(async (filename) => {
const url = new URL(filename, out);
const folder = await fs.promises.readdir(url);
if (!folder.length) {
await fs.promises.rm(url, { recursive: true, force: true });
}
})
);
removeEmptyDirs(out);
}
}
@ -321,28 +307,10 @@ async function cleanServerOutput(opts: StaticBuildOptions) {
await fs.promises.rm(url);
})
);
// Map directories heads from the .mjs files
const directories: Set<string> = new Set();
files.forEach((i) => {
const splitFilePath = i.split(path.sep);
// If the path is more than just a .mjs filename itself
if (splitFilePath.length > 1) {
directories.add(splitFilePath[0]);
}
});
// Attempt to remove only those folders which are empty
await Promise.all(
Array.from(directories).map(async (filename) => {
const url = new URL(filename, out);
const dir = await glob(fileURLToPath(url));
// Do not delete chunks/ directory!
if (filename === 'chunks') return;
if (!dir.length) {
await fs.promises.rm(url, { recursive: true, force: true });
}
})
);
removeEmptyDirs(out);
}
// Clean out directly if the outDir is outside of root
if (out.toString() !== opts.settings.config.outDir.toString()) {
// Copy assets before cleaning directory if outside root
@ -374,10 +342,11 @@ async function ssrMoveAssets(opts: StaticBuildOptions) {
const serverRoot =
opts.settings.config.output === 'static' ? opts.buildConfig.client : opts.buildConfig.server;
const clientRoot = opts.buildConfig.client;
const serverAssets = new URL('./assets/', serverRoot);
const clientAssets = new URL('./assets/', clientRoot);
const files = await glob('assets/**/*', {
cwd: fileURLToPath(serverRoot),
const assets = opts.settings.config.build.assets;
const serverAssets = new URL(`./${assets}/`, appendForwardSlash(serverRoot.toString()));
const clientAssets = new URL(`./${assets}/`, appendForwardSlash(clientRoot.toString()));
const files = await glob(`**/*`, {
cwd: fileURLToPath(serverAssets),
});
if (files.length > 0) {
@ -385,11 +354,11 @@ async function ssrMoveAssets(opts: StaticBuildOptions) {
await fs.promises.mkdir(clientAssets, { recursive: true });
await Promise.all(
files.map(async (filename) => {
const currentUrl = new URL(filename, serverRoot);
const clientUrl = new URL(filename, clientRoot);
const currentUrl = new URL(filename, appendForwardSlash(serverAssets.toString()));
const clientUrl = new URL(filename, appendForwardSlash(clientAssets.toString()));
return fs.promises.rename(currentUrl, clientUrl);
})
);
removeDir(serverAssets);
removeEmptyDirs(serverAssets);
}
}

View file

@ -23,6 +23,7 @@ const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
format: 'directory',
client: './dist/client/',
server: './dist/server/',
assets: '_astro',
serverEntry: 'entry.mjs',
},
server: {
@ -102,6 +103,7 @@ export const AstroConfigSchema = z.object({
.optional()
.default(ASTRO_CONFIG_DEFAULTS.build.server)
.transform((val) => new URL(val)),
assets: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.build.assets),
serverEntry: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.build.serverEntry),
})
.optional()
@ -246,6 +248,7 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) {
.optional()
.default(ASTRO_CONFIG_DEFAULTS.build.server)
.transform((val) => new URL(val, fileProtocolRoot)),
assets: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.build.assets),
serverEntry: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.build.serverEntry),
})
.optional()

View file

@ -1,6 +1,7 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { appendForwardSlash } from '../path.js';
const isWindows = process.platform === 'win32';
@ -10,6 +11,24 @@ export function removeDir(_dir: URL): void {
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 3 });
}
export function removeEmptyDirs(root: URL): void {
const dir = fileURLToPath(root);
if (!fs.statSync(dir).isDirectory()) return;
let files = fs.readdirSync(dir);
if (files.length > 0) {
files.map(file => {
const url = new URL(`./${file}`, appendForwardSlash(root.toString()));
removeEmptyDirs(url);
})
files = fs.readdirSync(dir);
}
if (files.length === 0) {
fs.rmdirSync(dir);
}
}
export function emptyDir(_dir: URL, skip?: Set<string>): void {
const dir = fileURLToPath(_dir);
if (!fs.existsSync(dir)) return undefined;

View file

@ -29,7 +29,7 @@ describe('CSS', function () {
// get bundled CSS (will be hashed, hence DOM query)
html = await fixture.readFile('/index.html');
$ = cheerio.load(html);
const bundledCSSHREF = $('link[rel=stylesheet][href^=/assets/]').attr('href');
const bundledCSSHREF = $('link[rel=stylesheet][href^=/_astro/]').attr('href');
bundledCSS = (await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')))
.replace(/\s/g, '')
.replace('/n', '');
@ -364,7 +364,7 @@ describe('CSS', function () {
});
it('remove unused styles from client:load components', async () => {
const bundledAssets = await fixture.readdir('./assets');
const bundledAssets = await fixture.readdir('./_astro');
// SvelteDynamic styles is already included in the main page css asset
const unusedCssAsset = bundledAssets.find((asset) => /SvelteDynamic\..*\.css/.test(asset));
expect(unusedCssAsset, 'Found unused style ' + unusedCssAsset).to.be.undefined;

View file

@ -5,9 +5,9 @@ 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': ['/assets/'], // dont match hashes, which change based on content
'/one/index.html': ['/assets/'],
'/two/index.html': ['/assets/'],
'/index.html': ['/_astro/'], // dont match hashes, which change based on content
'/one/index.html': ['/_astro/'],
'/two/index.html': ['/_astro/'],
};
const UNEXPECTED_CSS = [
'/src/components/nav.css',
@ -61,12 +61,12 @@ describe('CSS Bundling', function () {
});
it('there are 4 css files', async () => {
const dir = await fixture.readdir('/assets');
const dir = await fixture.readdir('/_astro');
expect(dir).to.have.a.lengthOf(4);
});
it('CSS includes hashes', async () => {
const [firstFound] = await fixture.readdir('/assets');
const [firstFound] = await fixture.readdir('/_astro');
expect(firstFound).to.match(/[a-z]+\.[0-9a-z]{8}\.css/);
});
});

View file

@ -75,6 +75,6 @@ describe('Dynamic components subpath', () => {
expect($('astro-island').html()).to.equal('');
// test 2: has component url
const attr = $('astro-island').attr('component-url');
expect(attr).to.include(`blog/assets/PersistentCounter`);
expect(attr).to.include(`blog/_astro/PersistentCounter`);
});
});

View file

@ -52,7 +52,7 @@ describe('Environment Variables', () => {
});
it('includes public env in client-side JS', async () => {
let dirs = await fixture.readdir('/assets');
let dirs = await fixture.readdir('/_astro');
console.log(dirs);
let found = false;
@ -62,7 +62,7 @@ describe('Environment Variables', () => {
await Promise.all(
dirs.map(async (path) => {
if (path.endsWith('.js')) {
let js = await fixture.readFile(`/assets/${path}`);
let js = await fixture.readFile(`/_astro/${path}`);
if (js.includes('BLUE_BAYOU')) {
found = true;
}

View file

@ -73,7 +73,7 @@ describe('Scripts (hoisted and not)', () => {
// test 2: inside assets
let entryURL = $('script').attr('src');
expect(entryURL.includes('assets/')).to.equal(true);
expect(entryURL.includes('_astro/')).to.equal(true);
});
it('External page using non-hoist scripts that are not modules are built standalone', async () => {
@ -85,7 +85,7 @@ describe('Scripts (hoisted and not)', () => {
// test 2: inside assets
let entryURL = $('script').attr('src');
expect(entryURL.includes('assets/')).to.equal(true);
expect(entryURL.includes('_astro/')).to.equal(true);
});
it('Scripts added via Astro.glob are hoisted', async () => {

View file

@ -0,0 +1,171 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
import { preact } from './fixtures/before-hydration/deps.mjs';
import testAdapter from './test-adapter.js';
describe('build assets (static)', () => {
describe('with default configuration', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/build-assets/',
integrations: [preact()],
});
await fixture.build();
});
it('Populates /_astro directory', async () => {
let files = await fixture.readdir('/_astro');
expect(files.length).to.be.greaterThan(0);
});
it('Defaults to flat /_astro output', async () => {
let files = await fixture.readdir('/_astro');
for (const file of files) {
expect(file.slice(1)).to.not.contain('/');
}
});
it('emits CSS assets to /_astro', async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
expect($('link[href$=".css"]').attr('href')).to.match(/^\/_astro\//);
});
it('emits JS assets to /_astro', async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
const island = $('astro-island');
expect(island.length).to.eq(1);
expect(island.attr('component-url')).to.match(/^\/_astro\//);
expect(island.attr('renderer-url')).to.match(/^\/_astro\//);
});
});
describe('with custom configuration', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/build-assets/',
integrations: [preact()],
build: {
assets: 'custom-assets',
},
});
await fixture.build();
});
it('Populates /custom-assets directory', async () => {
let files = await fixture.readdir('/custom-assets');
expect(files.length).to.be.greaterThan(0);
});
it('emits CSS assets to /custom-assets', async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
expect($('link[href$=".css"]').attr('href')).to.match(/^\/custom-assets\//);
});
it('emits JS assets to /custom-assets', async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
const island = $('astro-island');
expect(island.length).to.eq(1);
expect(island.attr('component-url')).to.match(/^\/custom-assets\//);
expect(island.attr('renderer-url')).to.match(/^\/custom-assets\//);
});
});
});
describe('build assets (server)', () => {
describe('with default configuration', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/build-assets/',
integrations: [preact()],
adapter: testAdapter(),
});
await fixture.build();
});
it('Populates /_astro directory', async () => {
let files = await fixture.readdir('/_astro');
expect(files.length).to.be.greaterThan(0);
});
it('Defaults to flat /_astro output', async () => {
let files = await fixture.readdir('/_astro');
for (const file of files) {
expect(file.slice(1)).to.not.contain('/');
}
});
it('emits CSS assets to /_astro', async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
expect($('link[href$=".css"]').attr('href')).to.match(/^\/_astro\//);
});
it('emits JS assets to /_astro', async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
const island = $('astro-island');
expect(island.length).to.eq(1);
expect(island.attr('component-url')).to.match(/^\/_astro\//);
expect(island.attr('renderer-url')).to.match(/^\/_astro\//);
});
});
describe('with custom configuration', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/build-assets/',
integrations: [preact()],
build: {
assets: 'custom-assets',
},
adapter: testAdapter(),
});
await fixture.build();
});
it('Populates /custom-assets directory', async () => {
let files = await fixture.readdir('/custom-assets');
expect(files.length).to.be.greaterThan(0);
});
it('emits CSS assets to /custom-assets', async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
expect($('link[href$=".css"]').attr('href')).to.match(/^\/custom-assets\//);
});
it('emits JS assets to /custom-assets', async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
const island = $('astro-island');
expect(island.length).to.eq(1);
expect(island.attr('component-url')).to.match(/^\/custom-assets\//);
expect(island.attr('renderer-url')).to.match(/^\/custom-assets\//);
});
});
});

View file

@ -24,7 +24,7 @@ describe('CSS', function () {
// get bundled CSS (will be hashed, hence DOM query)
html = await fixture.readFile('/index.html');
$ = cheerio.load(html);
const bundledCSSHREF = $('link[rel=stylesheet][href^=/assets/]').attr('href');
const bundledCSSHREF = $('link[rel=stylesheet][href^=/_astro/]').attr('href');
bundledCSS = (await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')))
.replace(/\s/g, '')
.replace('/n', '');

View file

@ -14,7 +14,7 @@ describe('vite.build.cssCodeSplit: false', () => {
it('loads styles correctly', async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
const cssHref = $('link[rel=stylesheet][href^=/assets/]').attr('href');
expect(cssHref).to.match(/\/assets\/style\..*?\.css/);
const cssHref = $('link[rel=stylesheet][href^=/_astro/]').attr('href');
expect(cssHref).to.match(/\/_astro\/style\..*?\.css/);
});
});

View file

@ -0,0 +1,8 @@
{
"name": "@test/build-assets",
"dependencies": {
"@astrojs/preact": "workspace:*",
"astro": "workspace:*",
"preact": "^10.11.0"
}
}

View file

@ -0,0 +1,6 @@
export default function() {
return (
<div>Testing</div>
);
}

View file

@ -0,0 +1,22 @@
---
import SomeComponent from '../components/SomeComponent';
---
<html>
<head>
<title>Testing</title>
</head>
<body>
<h1>Hello world!</h1>
<SomeComponent client:idle />
<style>
h1 {
color: red;
}
</style>
<script>
console.log("Hello world!")
</script>
</body>
</html>

View file

@ -18,7 +18,7 @@ describe('PostCSS', function () {
// get bundled CSS (will be hashed, hence DOM query)
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
const bundledCSSHREF = $('link[rel=stylesheet][href^=/assets/]').attr('href');
const bundledCSSHREF = $('link[rel=stylesheet][href^=/_astro/]').attr('href');
bundledCSS = (await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')))
.replace(/\s/g, '')
.replace('/n', '');

View file

@ -10,7 +10,7 @@ describe('Sourcemap', async () => {
});
it('Builds sourcemap', async () => {
const dir = await fixture.readdir('./assets');
const dir = await fixture.readdir('./_astro');
const counterMap = dir.find((file) => file.match(/^Counter\.\w+\.js\.map$/));
expect(counterMap).to.be.ok;
});

View file

@ -22,7 +22,7 @@ describe('Tailwind', () => {
// get bundled CSS (will be hashed, hence DOM query)
const html = await fixture.readFile('/index.html');
$ = cheerio.load(html);
const bundledCSSHREF = $('link[rel=stylesheet][href^=/assets/]').attr('href');
const bundledCSSHREF = $('link[rel=stylesheet][href^=/_astro/]').attr('href');
bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
});
@ -61,7 +61,7 @@ describe('Tailwind', () => {
it('handles Markdown pages', async () => {
const html = await fixture.readFile('/markdown-page/index.html');
const $md = cheerio.load(html);
const bundledCSSHREF = $md('link[rel=stylesheet][href^=/assets/]').attr('href');
const bundledCSSHREF = $md('link[rel=stylesheet][href^=/_astro/]').attr('href');
const mdBundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
expect(mdBundledCSS, 'includes used component classes').to.match(/\.bg-purple-600{/);
});
@ -69,7 +69,7 @@ describe('Tailwind', () => {
it('handles MDX pages (with integration)', async () => {
const html = await fixture.readFile('/mdx-page/index.html');
const $md = cheerio.load(html);
const bundledCSSHREF = $md('link[rel=stylesheet][href^=/assets/]').attr('href');
const bundledCSSHREF = $md('link[rel=stylesheet][href^=/_astro/]').attr('href');
const mdBundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
expect(mdBundledCSS, 'includes used component classes').to.match(/\.bg-purple-600{/);
});

View file

@ -7,6 +7,7 @@ import { fileURLToPath } from 'url';
interface BuildConfig {
server: URL;
serverEntry: string;
assets: string;
}
interface Options {
@ -86,7 +87,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
// Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash.
try {
const chunkFileNames =
_vite?.build?.rollupOptions?.output?.chunkFileNames ?? 'assets/chunks/chunk.[hash].mjs';
_vite?.build?.rollupOptions?.output?.chunkFileNames ?? `chunks/chunk.[hash].mjs`;
const chunkPath = npath.dirname(chunkFileNames);
const chunksDirUrl = new URL(chunkPath + '/', _buildConfig.server);
await fs.promises.rm(chunksDirUrl, { recursive: true, force: true });

View file

@ -172,10 +172,10 @@ export async function ssgBuild({
let outputFileURL: URL;
if (isRemoteImage(src)) {
outputFileURL = new URL(path.join('./assets', path.basename(filename)), outDir);
outputFileURL = new URL(path.join(`./${config.build.assets}`, path.basename(filename)), outDir);
outputFile = fileURLToPath(outputFileURL);
} else {
outputFileURL = new URL(path.join('./assets', filename), outDir);
outputFileURL = new URL(path.join(`./${config.build.assets}`, filename), outDir);
outputFile = fileURLToPath(outputFileURL);
}

View file

@ -21,6 +21,7 @@ const UNSUPPORTED_ADAPTERS = new Set([
interface BuildConfig {
client: URL;
server: URL;
assets: string;
}
interface ImageIntegration {
@ -130,7 +131,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte
// Doing this here makes sure that base is ignored when building
// staticImages to /dist, but the rendered HTML will include the
// base prefix for `src`.
return prependForwardSlash(joinPaths(_config.base, 'assets', filename));
return prependForwardSlash(joinPaths(_config.base, _buildConfig.assets, filename));
}
// Helpers for building static images should only be available for SSG
@ -146,7 +147,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte
// For the Squoosh service, copy all wasm files to dist/chunks.
// Because the default loader is dynamically imported (above),
// Vite will bundle squoosh to dist/chunks and expect to find the wasm files there
await copyWasmFiles(new URL('./assets/chunks', dir));
await copyWasmFiles(new URL('./chunks', dir));
}
if (loader && 'transform' in loader && staticImages.size > 0) {
@ -166,7 +167,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte
},
'astro:build:ssr': async () => {
if (resolvedOptions.serviceEntryPoint === '@astrojs/image/squoosh') {
await copyWasmFiles(new URL('./assets/chunks/', _buildConfig.server));
await copyWasmFiles(new URL('./chunks/', _buildConfig.server));
}
},
},

View file

@ -23,7 +23,7 @@ describe('SSR image with background', function () {
f: 'jpeg',
w: '256',
h: '256',
href: /^\/assets\/file-icon.\w{8}.png/,
href: /^\/_astro\/file-icon.\w{8}.png/,
bg: 'dimgray',
},
},
@ -34,7 +34,7 @@ describe('SSR image with background', function () {
f: 'jpeg',
w: '256',
h: '256',
href: /^\/assets\/file-icon.\w{8}.png/,
href: /^\/_astro\/file-icon.\w{8}.png/,
bg: '#696969',
},
},
@ -45,7 +45,7 @@ describe('SSR image with background', function () {
f: 'jpeg',
w: '256',
h: '256',
href: /^\/assets\/file-icon.\w{8}.png/,
href: /^\/_astro\/file-icon.\w{8}.png/,
bg: '#666',
},
},
@ -56,7 +56,7 @@ describe('SSR image with background', function () {
f: 'jpeg',
w: '256',
h: '256',
href: /^\/assets\/file-icon.\w{8}.png/,
href: /^\/_astro\/file-icon.\w{8}.png/,
bg: 'rgb(105,105,105)',
},
},
@ -67,7 +67,7 @@ describe('SSR image with background', function () {
f: 'jpeg',
w: '256',
h: '256',
href: /^\/assets\/file-icon.\w{8}.png/,
href: /^\/_astro\/file-icon.\w{8}.png/,
bg: 'rgb(105, 105, 105)',
},
},
@ -78,7 +78,7 @@ describe('SSR image with background', function () {
f: 'jpeg',
w: '256',
h: '256',
href: /^\/assets\/file-icon.\w{8}.png/,
href: /^\/_astro\/file-icon.\w{8}.png/,
bg: 'rgb(105,105,105,0.5)',
},
},
@ -89,7 +89,7 @@ describe('SSR image with background', function () {
f: 'jpeg',
w: '256',
h: '256',
href: /^\/assets\/file-icon.\w{8}.png/,
href: /^\/_astro\/file-icon.\w{8}.png/,
bg: 'rgb(105, 105, 105, 0.5)',
},
},

View file

@ -11,7 +11,7 @@ describe('getImage', function () {
});
it('Remote images works', async () => {
const assets = await fixture.readdir('/assets');
const assets = await fixture.readdir('/_astro');
expect(assets).to.have.a.lengthOf(1);
});
});

View file

@ -220,43 +220,43 @@ describe('SSG images - build', function () {
{
title: 'Local images',
id: '#social-jpg',
regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/,
regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/,
size: { width: 506, height: 253, type: 'jpg' },
},
{
title: 'Filename with spaces',
id: '#spaces',
regex: /^\/assets\/introducing astro.\w{8}_\w{4,10}.webp/,
regex: /^\/_astro\/introducing astro.\w{8}_\w{4,10}.webp/,
size: { width: 768, height: 414, type: 'webp' },
},
{
title: 'Inline imports',
id: '#inline',
regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/,
regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/,
size: { width: 506, height: 253, type: 'jpg' },
},
{
title: 'Remote images',
id: '#google',
regex: /^\/assets\/googlelogo_color_272x92dp_\w{4,10}.webp/,
regex: /^\/_astro\/googlelogo_color_272x92dp_\w{4,10}.webp/,
size: { width: 544, height: 184, type: 'webp' },
},
{
title: 'Remote without file extension',
id: '#ipsum',
regex: /^\/assets\/200x300_\w{4,10}/,
regex: /^\/_astro\/200x300_\w{4,10}/,
size: { width: 200, height: 300, type: 'jpg' },
},
{
title: 'Public images',
id: '#hero',
regex: /^\/assets\/hero_\w{4,10}.webp/,
regex: /^\/_astro\/hero_\w{4,10}.webp/,
size: { width: 768, height: 414, type: 'webp' },
},
{
title: 'Remote images',
id: '#bg-color',
regex: /^\/assets\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
regex: /^\/_astro\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
size: { width: 544, height: 184, type: 'jpg' },
},
].forEach(({ title, id, regex, size }) => {
@ -302,43 +302,43 @@ describe('SSG images with subpath - build', function () {
{
title: 'Local images',
id: '#social-jpg',
regex: /^\/docs\/assets\/social.\w{8}_\w{4,10}.jpg/,
regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.jpg/,
size: { width: 506, height: 253, type: 'jpg' },
},
{
title: 'Filename with spaces',
id: '#spaces',
regex: /^\/docs\/assets\/introducing astro.\w{8}_\w{4,10}.webp/,
regex: /^\/docs\/_astro\/introducing astro.\w{8}_\w{4,10}.webp/,
size: { width: 768, height: 414, type: 'webp' },
},
{
title: 'Inline imports',
id: '#inline',
regex: /^\/docs\/assets\/social.\w{8}_\w{4,10}.jpg/,
regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.jpg/,
size: { width: 506, height: 253, type: 'jpg' },
},
{
title: 'Remote images',
id: '#google',
regex: /^\/docs\/assets\/googlelogo_color_272x92dp_\w{4,10}.webp/,
regex: /^\/docs\/_astro\/googlelogo_color_272x92dp_\w{4,10}.webp/,
size: { width: 544, height: 184, type: 'webp' },
},
{
title: 'Remote without file extension',
id: '#ipsum',
regex: /^\/docs\/assets\/200x300_\w{4,10}/,
regex: /^\/docs\/_astro\/200x300_\w{4,10}/,
size: { width: 200, height: 300, type: 'jpg' },
},
{
title: 'Public images',
id: '#hero',
regex: /^\/docs\/assets\/hero_\w{4,10}.webp/,
regex: /^\/docs\/_astro\/hero_\w{4,10}.webp/,
size: { width: 768, height: 414, type: 'webp' },
},
{
title: 'Remote images',
id: '#bg-color',
regex: /^\/docs\/assets\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
regex: /^\/docs\/_astro\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
size: { width: 544, height: 184, type: 'jpg' },
},
].forEach(({ title, id, regex, size }) => {

View file

@ -20,19 +20,19 @@ describe('SSR images - build', async function () {
title: 'Local images',
id: '#social-jpg',
url: '/_image',
query: { f: 'jpg', w: '506', h: '253', href: /^\/assets\/social.\w{8}.jpg/ },
query: { f: 'jpg', w: '506', h: '253', href: /^\/_astro\/social.\w{8}.jpg/ },
},
{
title: 'Filename with spaces',
id: '#spaces',
url: '/_image',
query: { f: 'webp', w: '768', h: '414', href: /^\/assets\/introducing astro.\w{8}.jpg/ },
query: { f: 'webp', w: '768', h: '414', href: /^\/_astro\/introducing astro.\w{8}.jpg/ },
},
{
title: 'Inline imports',
id: '#inline',
url: '/_image',
query: { f: 'jpg', w: '506', h: '253', href: /^\/assets\/social.\w{8}.jpg/ },
query: { f: 'jpg', w: '506', h: '253', href: /^\/_astro\/social.\w{8}.jpg/ },
},
{
title: 'Remote images',
@ -131,7 +131,7 @@ describe('SSR images with subpath - build', function () {
title: 'Local images',
id: '#social-jpg',
url: '/_image',
query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/assets\/social.\w{8}.jpg/ },
query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/_astro\/social.\w{8}.jpg/ },
},
{
title: 'Filename with spaces',
@ -141,14 +141,14 @@ describe('SSR images with subpath - build', function () {
f: 'webp',
w: '768',
h: '414',
href: /^\/docs\/assets\/introducing astro.\w{8}.jpg/,
href: /^\/docs\/_astro\/introducing astro.\w{8}.jpg/,
},
},
{
title: 'Inline imports',
id: '#inline',
url: '/_image',
query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/assets\/social.\w{8}.jpg/ },
query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/_astro\/social.\w{8}.jpg/ },
},
{
title: 'Remote images',

View file

@ -211,42 +211,42 @@ describe('SSG pictures - build', function () {
{
title: 'Local images',
id: '#social-jpg',
regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/,
regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/,
size: { width: 506, height: 253, type: 'jpg' },
alt: 'Social image',
},
{
title: 'Filename with spaces',
id: '#spaces',
regex: /^\/assets\/introducing astro.\w{8}_\w{4,10}.jpg/,
regex: /^\/_astro\/introducing astro.\w{8}_\w{4,10}.jpg/,
size: { width: 768, height: 414, type: 'jpg' },
alt: 'spaces',
},
{
title: 'Inline images',
id: '#inline',
regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/,
regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/,
size: { width: 506, height: 253, type: 'jpg' },
alt: 'Inline social image',
},
{
title: 'Remote images',
id: '#google',
regex: /^\/assets\/googlelogo_color_272x92dp_\w{4,10}.png/,
regex: /^\/_astro\/googlelogo_color_272x92dp_\w{4,10}.png/,
size: { width: 544, height: 184, type: 'png' },
alt: 'Google logo',
},
{
title: 'Remote without file extension',
id: '#ipsum',
regex: /^\/assets\/200x300_\w{4,10}/,
regex: /^\/_astro\/200x300_\w{4,10}/,
size: { width: 200, height: 300, type: 'jpg' },
alt: 'ipsum',
},
{
title: 'Public images',
id: '#hero',
regex: /^\/assets\/hero_\w{4,10}.jpg/,
regex: /^\/_astro\/hero_\w{4,10}.jpg/,
size: { width: 768, height: 414, type: 'jpg' },
alt: 'Hero image',
},
@ -318,35 +318,35 @@ describe('SSG pictures with subpath - build', function () {
{
title: 'Local images',
id: '#social-jpg',
regex: /^\/docs\/assets\/social.\w{8}_\w{4,10}.jpg/,
regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.jpg/,
size: { width: 506, height: 253, type: 'jpg' },
alt: 'Social image',
},
{
title: 'Inline images',
id: '#inline',
regex: /^\/docs\/assets\/social.\w{8}_\w{4,10}.jpg/,
regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.jpg/,
size: { width: 506, height: 253, type: 'jpg' },
alt: 'Inline social image',
},
{
title: 'Remote images',
id: '#google',
regex: /^\/docs\/assets\/googlelogo_color_272x92dp_\w{4,10}.png/,
regex: /^\/docs\/_astro\/googlelogo_color_272x92dp_\w{4,10}.png/,
size: { width: 544, height: 184, type: 'png' },
alt: 'Google logo',
},
{
title: 'Remote without file extension',
id: '#ipsum',
regex: /^\/docs\/assets\/200x300_\w{4,10}/,
regex: /^\/docs\/_astro\/200x300_\w{4,10}/,
size: { width: 200, height: 300, type: 'jpg' },
alt: 'ipsum',
},
{
title: 'Public images',
id: '#hero',
regex: /^\/docs\/assets\/hero_\w{4,10}.jpg/,
regex: /^\/docs\/_astro\/hero_\w{4,10}.jpg/,
size: { width: 768, height: 414, type: 'jpg' },
alt: 'Hero image',
},

View file

@ -20,21 +20,21 @@ describe('SSR pictures - build', function () {
title: 'Local images',
id: '#social-jpg',
url: '/_image',
query: { f: 'jpg', w: '506', h: '253', href: /^\/assets\/social.\w{8}.jpg/ },
query: { f: 'jpg', w: '506', h: '253', href: /^\/_astro\/social.\w{8}.jpg/ },
alt: 'Social image',
},
{
title: 'Filename with spaces',
id: '#spaces',
url: '/_image',
query: { w: '768', h: '414', f: 'jpg', href: /^\/assets\/introducing astro.\w{8}.jpg/ },
query: { w: '768', h: '414', f: 'jpg', href: /^\/_astro\/introducing astro.\w{8}.jpg/ },
alt: 'spaces',
},
{
title: 'Inline imports',
id: '#inline',
url: '/_image',
query: { f: 'jpg', w: '506', h: '253', href: /^\/assets\/social.\w{8}.jpg/ },
query: { f: 'jpg', w: '506', h: '253', href: /^\/_astro\/social.\w{8}.jpg/ },
alt: 'Inline social image',
},
{
@ -131,21 +131,21 @@ describe('SSR pictures with subpath - build', function () {
title: 'Local images',
id: '#social-jpg',
url: '/_image',
query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/assets\/social.\w{8}.jpg/ },
query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/_astro\/social.\w{8}.jpg/ },
alt: 'Social image',
},
{
title: 'Filename with spaces',
id: '#spaces',
url: '/_image',
query: { w: '768', h: '414', f: 'jpg', href: /^\/docs\/assets\/introducing astro.\w{8}.jpg/ },
query: { w: '768', h: '414', f: 'jpg', href: /^\/docs\/_astro\/introducing astro.\w{8}.jpg/ },
alt: 'spaces',
},
{
title: 'Inline imports',
id: '#inline',
url: '/_image',
query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/assets\/social.\w{8}.jpg/ },
query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/_astro\/social.\w{8}.jpg/ },
alt: 'Inline social image',
},
{

View file

@ -32,7 +32,7 @@ describe('Image rotation', function () {
it('Landscape images', () => {
for (let i = 0; i < 9; i++) {
const image = $(`#landscape-${i}`);
const regex = new RegExp(`\^/assets\/Landscape_${i}.\\w{8}_\\w{4,10}.jpg`);
const regex = new RegExp(`\^/_astro\/Landscape_${i}.\\w{8}_\\w{4,10}.jpg`);
expect(image.attr('src')).to.match(regex);
expect(image.attr('width')).to.equal('1800');
@ -49,7 +49,7 @@ describe('Image rotation', function () {
it('Portait images', () => {
for (let i = 0; i < 9; i++) {
const image = $(`#portrait-${i}`);
const regex = new RegExp(`\^/assets\/Portrait_${i}.\\w{8}_\\w{4,10}.jpg`);
const regex = new RegExp(`\^/_astro\/Portrait_${i}.\\w{8}_\\w{4,10}.jpg`);
expect(image.attr('src')).to.match(regex);
expect(image.attr('width')).to.equal('1200');

View file

@ -28,31 +28,31 @@ describe('Images in MDX - build', function () {
{
title: 'Local images',
id: '#social-jpg',
regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/,
regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/,
size: { width: 506, height: 253, type: 'jpg' },
},
{
title: 'Inline imports',
id: '#inline',
regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/,
regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/,
size: { width: 506, height: 253, type: 'jpg' },
},
{
title: 'Remote images',
id: '#google',
regex: /^\/assets\/googlelogo_color_272x92dp_\w{4,10}.webp/,
regex: /^\/_astro\/googlelogo_color_272x92dp_\w{4,10}.webp/,
size: { width: 544, height: 184, type: 'webp' },
},
{
title: 'Public images',
id: '#hero',
regex: /^\/assets\/hero_\w{4,10}.webp/,
regex: /^\/_astro\/hero_\w{4,10}.webp/,
size: { width: 768, height: 414, type: 'webp' },
},
{
title: 'Background color',
id: '#bg-color',
regex: /^\/assets\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
regex: /^\/_astro\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
size: { width: 544, height: 184, type: 'jpg' },
},
].forEach(({ title, id, regex, size }) => {

View file

@ -10,6 +10,7 @@ interface BuildConfig {
server: URL;
client: URL;
serverEntry: string;
assets: string;
}
const SHIM = `globalThis.process = {
@ -100,7 +101,7 @@ async function bundleServerEntry({ serverEntry, server }: BuildConfig, vite: any
// Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash.
try {
const chunkFileNames =
vite?.build?.rollupOptions?.output?.chunkFileNames ?? 'assets/chunks/chunk.[hash].mjs';
vite?.build?.rollupOptions?.output?.chunkFileNames ?? `chunks/chunk.[hash].mjs`;
const chunkPath = npath.dirname(chunkFileNames);
const chunksDirUrl = new URL(chunkPath + '/', server);
await fs.promises.rm(chunksDirUrl, { recursive: true, force: true });

View file

@ -1582,6 +1582,16 @@ importers:
astro: link:../../..
preact: 10.11.3
packages/astro/test/fixtures/build-assets:
specifiers:
'@astrojs/preact': workspace:*
astro: workspace:*
preact: ^10.11.0
dependencies:
'@astrojs/preact': link:../../../../integrations/preact
astro: link:../../..
preact: 10.11.3
packages/astro/test/fixtures/client-address:
specifiers:
astro: workspace:*