Skip to content

Commit

Permalink
Add build.assetsPrefix option for CDN support (#6714)
Browse files Browse the repository at this point in the history
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
  • Loading branch information
bluwy and sarah11918 authored Apr 5, 2023
1 parent 26daba8 commit ff04307
Show file tree
Hide file tree
Showing 35 changed files with 481 additions and 62 deletions.
8 changes: 8 additions & 0 deletions .changeset/two-beans-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'astro': minor
'@astrojs/image': patch
---

Add `build.assetsPrefix` option for CDN support. If set, all Astro-generated asset links will be prefixed with it. For example, setting it to `https://cdn.example.com` would generate `https://cdn.example.com/_astro/penguin.123456.png` links.

Also adds `import.meta.env.ASSETS_PREFIX` environment variable that can be used to manually create asset links not handled by Astro.
23 changes: 23 additions & 0 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,29 @@ export interface AstroUserConfig {
* ```
*/
assets?: string;
/**
* @docs
* @name build.assetsPrefix
* @type {string}
* @default `undefined`
* @version 2.2.0
* @description
* Specifies the prefix for Astro-generated asset links. This can be used if assets are served from a different domain than the current site.
*
* For example, if this is set to `https://cdn.example.com`, assets will be fetched from `https://cdn.example.com/_astro/...` (regardless of the `base` option).
* You would need to upload the files in `./dist/_astro/` to `https://cdn.example.com/_astro/` to serve the assets.
* The process varies depending on how the third-party domain is hosted.
* To rename the `_astro` path, specify a new directory in `build.assets`.
*
* ```js
* {
* build: {
* assetsPrefix: 'https://cdn.example.com'
* }
* }
* ```
*/
assetsPrefix?: string;
/**
* @docs
* @name build.serverEntry
Expand Down
17 changes: 14 additions & 3 deletions packages/astro/src/assets/vite-plugin-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import type * as vite from 'vite';
import { normalizePath } from 'vite';
import type { AstroPluginOptions, ImageTransform } from '../@types/astro';
import { error } from '../core/logger/core.js';
import { joinPaths, prependForwardSlash } from '../core/path.js';
import {
appendForwardSlash,
joinPaths,
prependForwardSlash,
} from '../core/path.js';
import { VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js';
import { isESMImportedImage } from './internal.js';
import { isLocalService } from './services/service.js';
Expand Down Expand Up @@ -174,7 +178,11 @@ export default function assets({
globalThis.astroAsset.staticImages.set(hash, { path: filePath, options: options });
}

return prependForwardSlash(joinPaths(settings.config.base, filePath));
if (settings.config.build.assetsPrefix) {
return joinPaths(settings.config.build.assetsPrefix, filePath);
} else {
return prependForwardSlash(joinPaths(settings.config.base, filePath));
}
};
},
async buildEnd() {
Expand Down Expand Up @@ -202,7 +210,10 @@ export default function assets({
const [full, hash, postfix = ''] = match;

const file = this.getFileName(hash);
const outputFilepath = normalizePath(resolvedConfig.base + file + postfix);
const prefix = settings.config.build.assetsPrefix
? appendForwardSlash(settings.config.build.assetsPrefix)
: resolvedConfig.base;
const outputFilepath = prefix + normalizePath(file + postfix);

s.overwrite(match.index, match.index + full.length, outputFilepath);
}
Expand Down
17 changes: 13 additions & 4 deletions packages/astro/src/content/vite-plugin-content-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { AstroBuildPlugin } from '../core/build/plugin.js';
import type { StaticBuildOptions } from '../core/build/types';
import type { ModuleLoader } from '../core/module-loader/loader.js';
import { createViteLoader } from '../core/module-loader/vite.js';
import { prependForwardSlash } from '../core/path.js';
import { joinPaths, prependForwardSlash } from '../core/path.js';
import { getStylesForURL } from '../core/render/dev/css.js';
import { getScriptsForURL } from '../core/render/dev/scripts.js';
import {
Expand Down Expand Up @@ -71,7 +71,11 @@ export function astroContentAssetPropagationPlugin({
'development'
);

const hoistedScripts = await getScriptsForURL(pathToFileURL(basePath), devModuleLoader);
const hoistedScripts = await getScriptsForURL(
pathToFileURL(basePath),
settings.config.root,
devModuleLoader
);

return {
code: code
Expand Down Expand Up @@ -106,8 +110,13 @@ export function astroConfigBuildPlugin(
},
'build:post': ({ ssrOutputs, clientOutputs, mutate }) => {
const outputs = ssrOutputs.flatMap((o) => o.output);
const prependBase = (src: string) =>
prependForwardSlash(npath.posix.join(options.settings.config.base, src));
const prependBase = (src: string) => {
if (options.settings.config.build.assetsPrefix) {
return joinPaths(options.settings.config.build.assetsPrefix, src);
} else {
return prependForwardSlash(joinPaths(options.settings.config.base, src));
}
};
for (const chunk of outputs) {
if (
chunk.type === 'chunk' &&
Expand Down
27 changes: 22 additions & 5 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ import { AstroError } from '../errors/index.js';
import { debug, info } from '../logger/core.js';
import { createEnvironment, createRenderContext, renderPage } from '../render/index.js';
import { callGetStaticPaths } from '../render/route-cache.js';
import { createLinkStylesheetElementSet, createModuleScriptsSet } from '../render/ssr-element.js';
import {
createAssetLink,
createLinkStylesheetElementSet,
createModuleScriptsSet,
} from '../render/ssr-element.js';
import { createRequest } from '../request.js';
import { matchRoute } from '../routing/match.js';
import { getOutputFilename } from '../util.js';
Expand Down Expand Up @@ -351,18 +355,27 @@ async function generatePath(

debug('build', `Generating: ${pathname}`);

const links = createLinkStylesheetElementSet(linkIds, settings.config.base);
const links = createLinkStylesheetElementSet(
linkIds,
settings.config.base,
settings.config.build.assetsPrefix
);
const scripts = createModuleScriptsSet(
hoistedScripts ? [hoistedScripts] : [],
settings.config.base
settings.config.base,
settings.config.build.assetsPrefix
);

if (settings.scripts.some((script) => script.stage === 'page')) {
const hashedFilePath = internals.entrySpecifierToBundleMap.get(PAGE_SCRIPT_ID);
if (typeof hashedFilePath !== 'string') {
throw new Error(`Cannot find the built path for ${PAGE_SCRIPT_ID}`);
}
const src = prependForwardSlash(npath.posix.join(settings.config.base, hashedFilePath));
const src = createAssetLink(
hashedFilePath,
settings.config.base,
settings.config.build.assetsPrefix
);
scripts.add({
props: { type: 'module', src },
children: '',
Expand Down Expand Up @@ -403,7 +416,11 @@ async function generatePath(
}
throw new Error(`Cannot find the built path for ${specifier}`);
}
return prependForwardSlash(npath.posix.join(settings.config.base, hashedFilePath));
return createAssetLink(
hashedFilePath,
settings.config.base,
settings.config.build.assetsPrefix
);
},
routeCache,
site: settings.config.site
Expand Down
19 changes: 12 additions & 7 deletions packages/astro/src/core/build/plugins/plugin-ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { fileURLToPath } from 'url';
import { runHookBuildSsr } from '../../../integrations/index.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
import { pagesVirtualModuleId } from '../../app/index.js';
import { removeLeadingForwardSlash, removeTrailingForwardSlash } from '../../path.js';
import { joinPaths, prependForwardSlash } from '../../path.js';
import { serializeRouteData } from '../../routing/index.js';
import { addRollupInput } from '../add-rollup-input.js';
import { getOutFile, getOutFolder } from '../common.js';
Expand Down Expand Up @@ -134,8 +134,13 @@ function buildManifest(
staticFiles.push(entryModules[PAGE_SCRIPT_ID]);
}

const bareBase = removeTrailingForwardSlash(removeLeadingForwardSlash(settings.config.base));
const joinBase = (pth: string) => (bareBase ? bareBase + '/' + pth : pth);
const prefixAssetPath = (pth: string) => {
if (settings.config.build.assetsPrefix) {
return joinPaths(settings.config.build.assetsPrefix, pth);
} else {
return prependForwardSlash(joinPaths(settings.config.base, pth));
}
};

for (const pageData of eachPrerenderedPageData(internals)) {
if (!pageData.route.pathname) continue;
Expand Down Expand Up @@ -165,7 +170,7 @@ function buildManifest(
const scripts: SerializedRouteInfo['scripts'] = [];
if (pageData.hoistedScript) {
const hoistedValue = pageData.hoistedScript.value;
const value = hoistedValue.endsWith('.js') ? joinBase(hoistedValue) : hoistedValue;
const value = hoistedValue.endsWith('.js') ? prefixAssetPath(hoistedValue) : hoistedValue;
scripts.unshift(
Object.assign({}, pageData.hoistedScript, {
value,
Expand All @@ -177,11 +182,11 @@ function buildManifest(

scripts.push({
type: 'external',
value: joinBase(src),
value: prefixAssetPath(src),
});
}

const links = sortedCSS(pageData).map((pth) => joinBase(pth));
const links = sortedCSS(pageData).map((pth) => prefixAssetPath(pth));

routes.push({
file: '',
Expand Down Expand Up @@ -212,7 +217,7 @@ function buildManifest(
componentMetadata: Array.from(internals.componentMetadata),
renderers: [],
entryModules,
assets: staticFiles.map((s) => settings.config.base + s),
assets: staticFiles.map(prefixAssetPath),
};

return ssrManifest;
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export const AstroConfigSchema = z.object({
.default(ASTRO_CONFIG_DEFAULTS.build.server)
.transform((val) => new URL(val)),
assets: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.build.assets),
assetsPrefix: z.string().optional(),
serverEntry: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.build.serverEntry),
})
.optional()
Expand Down Expand Up @@ -222,6 +223,7 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) {
.default(ASTRO_CONFIG_DEFAULTS.build.server)
.transform((val) => new URL(val, fileProtocolRoot)),
assets: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.build.assets),
assetsPrefix: z.string().optional(),
serverEntry: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.build.serverEntry),
})
.optional()
Expand Down
15 changes: 15 additions & 0 deletions packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import astroScannerPlugin from '../vite-plugin-scanner/index.js';
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
import { vitePluginSSRManifest } from '../vite-plugin-ssr-manifest/index.js';
import { joinPaths } from './path.js';

interface CreateViteOptions {
settings: AstroSettings;
Expand Down Expand Up @@ -174,6 +175,20 @@ export async function createVite(
},
};

// If the user provides a custom assets prefix, make sure assets handled by Vite
// are prefixed with it too. This uses one of it's experimental features, but it
// has been stable for a long time now.
const assetsPrefix = settings.config.build.assetsPrefix;
if (assetsPrefix) {
commonConfig.experimental = {
renderBuiltUrl(filename, { type }) {
if (type === 'asset') {
return joinPaths(assetsPrefix, filename);
}
},
};
}

// Merge configs: we merge vite configuration objects together in the following order,
// where future values will override previous values.
// 1. common vite config
Expand Down
5 changes: 0 additions & 5 deletions packages/astro/src/core/dev/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,6 @@ export async function createContainer(params: CreateContainerParams = {}): Promi
optimizeDeps: {
include: rendererClientEntries,
},
define: {
'import.meta.env.BASE_URL': settings.config.base
? JSON.stringify(settings.config.base)
: 'undefined',
},
},
{ settings, logging, mode: 'dev', command: 'dev', fs }
);
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/render/dev/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ interface GetScriptsAndStylesParams {

async function getScriptsAndStyles({ env, filePath }: GetScriptsAndStylesParams) {
// Add hoisted script tags
const scripts = await getScriptsForURL(filePath, env.loader);
const scripts = await getScriptsForURL(filePath, env.settings.config.root, env.loader);

// Inject HMR scripts
if (isPage(filePath, env.settings) && env.mode === 'development') {
Expand Down
12 changes: 7 additions & 5 deletions packages/astro/src/core/render/dev/scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,40 @@ import type { SSRElement } from '../../../@types/astro';
import type { PluginMetadata as AstroPluginMetadata } from '../../../vite-plugin-astro/types';
import type { ModuleInfo, ModuleLoader } from '../../module-loader/index';

import { viteID } from '../../util.js';
import { rootRelativePath, viteID } from '../../util.js';
import { createModuleScriptElementWithSrc } from '../ssr-element.js';
import { crawlGraph } from './vite.js';

export async function getScriptsForURL(
filePath: URL,
root: URL,
loader: ModuleLoader
): Promise<Set<SSRElement>> {
const elements = new Set<SSRElement>();
const rootID = viteID(filePath);
const modInfo = loader.getModuleInfo(rootID);
addHoistedScripts(elements, modInfo);
addHoistedScripts(elements, modInfo, root);
for await (const moduleNode of crawlGraph(loader, rootID, true)) {
const id = moduleNode.id;
if (id) {
const info = loader.getModuleInfo(id);
addHoistedScripts(elements, info);
addHoistedScripts(elements, info, root);
}
}

return elements;
}

function addHoistedScripts(set: Set<SSRElement>, info: ModuleInfo | null) {
function addHoistedScripts(set: Set<SSRElement>, info: ModuleInfo | null, root: URL) {
if (!info?.meta?.astro) {
return;
}

let id = info.id;
const astro = info?.meta?.astro as AstroPluginMetadata['astro'];
for (let i = 0; i < astro.scripts.length; i++) {
const scriptId = `${id}?astro&type=script&index=${i}&lang.ts`;
let scriptId = `${id}?astro&type=script&index=${i}&lang.ts`;
scriptId = rootRelativePath(root, scriptId);
const element = createModuleScriptElementWithSrc(scriptId);
set.add(element);
}
Expand Down
Loading

0 comments on commit ff04307

Please sign in to comment.