Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output assets to _astro directory #5772

Merged
merged 16 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .changeset/lovely-worms-invite.md
Original file line number Diff line number Diff line change
@@ -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.
19 changes: 19 additions & 0 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,25 @@ export interface AstroUserConfig {
* ```
*/
server?: string;
/**
* @docs
* @name build.assets
* @type {string}
* @default `'_astro'`
natemoo-re marked this conversation as resolved.
Show resolved Hide resolved
* @see outDir
* @version 2.0.0
* @description
natemoo-re marked this conversation as resolved.
Show resolved Hide resolved
* 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
Expand Down
77 changes: 23 additions & 54 deletions packages/astro/src/core/build/static-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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]`,
natemoo-re marked this conversation as resolved.
Show resolved Hide resolved
...viteConfig.build?.rollupOptions?.output,
entryFileNames: opts.buildConfig.serverEntry,
},
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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
Expand Down Expand Up @@ -374,22 +342,23 @@ 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) {
// Make the directory
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);
}
}
3 changes: 3 additions & 0 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
format: 'directory',
client: './dist/client/',
server: './dist/server/',
assets: '_astro',
serverEntry: 'entry.mjs',
},
server: {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
19 changes: 19 additions & 0 deletions packages/astro/src/core/fs/index.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/test/0-css.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', '');
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions packages/astro/test/astro-css-bundling.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/'], // don’t match hashes, which change based on content
'/one/index.html': ['/assets/'],
'/two/index.html': ['/assets/'],
'/index.html': ['/_astro/'], // don’t match hashes, which change based on content
'/one/index.html': ['/_astro/'],
'/two/index.html': ['/_astro/'],
};
const UNEXPECTED_CSS = [
'/src/components/nav.css',
Expand Down Expand Up @@ -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/);
});
});
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/test/astro-dynamic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
});
});
4 changes: 2 additions & 2 deletions packages/astro/test/astro-envs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/test/astro-scripts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand Down
Loading