Skip to content

Commit

Permalink
Only serve _app/immutable with immutable cache headers (#5051)
Browse files Browse the repository at this point in the history
* put build inside _app/build directory

* use free variable for native fetch

* update test

* only serve _app/build with immutable cache header, not _app/version.json - fixes #4837

* Update .changeset/wise-berries-flash.md

* fix is_static_file

* rename /build/ to /immutable/

* Update packages/kit/src/core/preview/index.js

* Update packages/kit/src/core/build/build_client.js

* Update packages/adapter-netlify/src/edge.js

* Update .changeset/wise-berries-flash.md

* update test

* fix cloudflare adapters
  • Loading branch information
Rich-Harris authored May 24, 2022
1 parent c936eb8 commit f0fb175
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 30 deletions.
10 changes: 10 additions & 0 deletions .changeset/wise-berries-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@sveltejs/adapter-cloudflare': patch
'@sveltejs/adapter-cloudflare-workers': patch
'@sveltejs/adapter-netlify': patch
'@sveltejs/adapter-node': patch
'@sveltejs/adapter-vercel': patch
'@sveltejs/kit': patch
---

only serve `_app/immutable` with immutable cache header, not `_app/version.json`
8 changes: 6 additions & 2 deletions packages/adapter-cloudflare-workers/files/entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ export default {
const res = await get_asset_from_kv(req, env, context);
if (is_error(res.status)) return res;

const cache_control = url.pathname.startsWith(prefix + 'immutable/')
? 'public, immutable, max-age=31536000'
: 'no-cache';

return new Response(res.body, {
headers: {
// include original cache headers, minus cache-control which
// include original headers, minus cache-control which
// is overridden, and etag which is no longer useful
'cache-control': 'public, immutable, max-age=31536000',
'cache-control': cache_control,
'content-type': res.headers.get('content-type'),
'x-robots-tag': 'noindex'
}
Expand Down
8 changes: 6 additions & 2 deletions packages/adapter-cloudflare/src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ const worker = {
if (pathname.startsWith(prefix)) {
res = await env.ASSETS.fetch(req);

const cache_control = pathname.startsWith(prefix + 'immutable/')
? 'public, immutable, max-age=31536000'
: 'no-cache';

res = new Response(res.body, {
headers: {
// include original cache headers, minus cache-control which
// include original headers, minus cache-control which
// is overridden, and etag which is no longer useful
'cache-control': 'public, immutable, max-age=31536000',
'cache-control': cache_control,
'content-type': res.headers.get('content-type'),
'x-robots-tag': 'noindex'
}
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-netlify/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
builder.copy('_headers', headers_file);
appendFileSync(
headers_file,
`\n\n/${builder.config.kit.appDir}/*\n cache-control: public\n cache-control: immutable\n cache-control: max-age=31536000\n`
`\n\n/${builder.config.kit.appDir}/immutable/*\n cache-control: public\n cache-control: immutable\n cache-control: max-age=31536000\n`
);

// for esbuild, use ESM
Expand Down
2 changes: 2 additions & 0 deletions packages/adapter-netlify/src/edge.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const prefix = `/${manifest.appDir}/`;
export default function handler(request, context) {
if (is_static_file(request)) {
// Static files can skip the handler
// TODO can we serve _app/immutable files with an immutable cache header?
return;
}

Expand All @@ -33,6 +34,7 @@ function is_static_file(request) {
if (url.pathname.startsWith(prefix)) {
return true;
}

// prerendered pages and index.html files
const pathname = url.pathname.replace(/\/$/, '');
let file = pathname.substring(1);
Expand Down
23 changes: 14 additions & 9 deletions packages/adapter-node/src/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,23 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));

/**
* @param {string} path
* @param {number} max_age
* @param {boolean} immutable
* @param {boolean} client
*/
function serve(path, max_age, immutable = false) {
function serve(path, client = false) {
return (
fs.existsSync(path) &&
sirv(path, {
etag: true,
maxAge: max_age,
immutable,
gzip: true,
brotli: true
brotli: true,
setHeaders:
client &&
((res, pathname) => {
// only apply to build directory, not e.g. version.json
if (pathname.startsWith(`/${manifest.appDir}/immutable/`)) {
res.setHeader('cache-control', 'public,max-age=31536000,immutable');
}
})
})
);
}
Expand Down Expand Up @@ -126,9 +131,9 @@ function get_origin(headers) {

export const handler = sequence(
[
serve(path.join(__dirname, '/client'), 31536000, true),
serve(path.join(__dirname, '/static'), 0),
serve(path.join(__dirname, '/prerendered'), 0),
serve(path.join(__dirname, '/client'), true),
serve(path.join(__dirname, '/static')),
serve(path.join(__dirname, '/prerendered')),
ssr
].filter(Boolean)
);
2 changes: 1 addition & 1 deletion packages/adapter-vercel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ async function v1(builder, external) {
...prerendered_pages,
...prerendered_redirects,
{
src: `/${builder.config.kit.appDir}/.+`,
src: `/${builder.config.kit.appDir}/immutable/.+`,
headers: {
'cache-control': 'public, immutable, max-age=31536000'
}
Expand Down
6 changes: 4 additions & 2 deletions packages/kit/src/core/build/build_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export async function build_client({
build: {
cssCodeSplit: true,
manifest: true,
outDir: client_out_dir,
outDir: `${client_out_dir}/immutable`,
polyfillDynamicImport: false,
rollupOptions: {
input,
Expand Down Expand Up @@ -95,7 +95,9 @@ export async function build_client({
const { chunks, assets } = await create_build(merged_config);

/** @type {import('vite').Manifest} */
const vite_manifest = JSON.parse(fs.readFileSync(`${client_out_dir}/manifest.json`, 'utf-8'));
const vite_manifest = JSON.parse(
fs.readFileSync(`${client_out_dir}/immutable/manifest.json`, 'utf-8')
);

const entry = posixify(client_entry_file);
const entry_js = new Set();
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/core/build/build_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class Server {
manifest,
method_override: ${s(config.kit.methodOverride)},
paths: { base, assets },
prefix: assets + '/${config.kit.appDir}/',
prefix: assets + '/${config.kit.appDir}/immutable/',
prerender: {
default: ${config.kit.prerender.default},
enabled: ${config.kit.prerender.enabled}
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/core/build/build_service_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export async function build_service_worker(
export const build = [
${Array.from(build)
.map((file) => `${s(`${config.kit.paths.base}/${config.kit.appDir}/${file}`)}`)
.map((file) => `${s(`${config.kit.paths.base}/${config.kit.appDir}/immutable/${file}`)}`)
.join(',\n\t\t\t\t')}
];
Expand Down
17 changes: 10 additions & 7 deletions packages/kit/src/core/build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ export async function build(config, { log }) {

const { manifest_data } = sync.all(config);

// TODO this is so that Vite's preloading works. Unfortunately, it fails
// during `svelte-kit preview`, because we use a local asset path. If Vite
// used relative paths, I _think_ this could get fixed. Issue here:
// https://github.com/vitejs/vite/issues/2009
const { base, assets } = config.kit.paths;
const assets_base = `${assets || base}/${config.kit.appDir}/immutable/`;

const options = {
cwd,
config,
build_dir,
// TODO this is so that Vite's preloading works. Unfortunately, it fails
// during `svelte-kit preview`, because we use a local asset path. If Vite
// used relative paths, I _think_ this could get fixed. Issue here:
// https://github.com/vitejs/vite/issues/2009
assets_base: `${config.kit.paths.assets || config.kit.paths.base}/${config.kit.appDir}/`,
assets_base,
manifest_data,
output_dir,
client_entry_file: path.relative(cwd, `${get_runtime_path(config)}/client/start.js`),
Expand Down Expand Up @@ -65,8 +68,8 @@ export async function build(config, { log }) {

const files = new Set([
...static_files,
...client.chunks.map((chunk) => `${config.kit.appDir}/${chunk.fileName}`),
...client.assets.map((chunk) => `${config.kit.appDir}/${chunk.fileName}`)
...client.chunks.map((chunk) => `${config.kit.appDir}/immutable/${chunk.fileName}`),
...client.assets.map((chunk) => `${config.kit.appDir}/immutable/${chunk.fileName}`)
]);

// TODO is this right?
Expand Down
8 changes: 6 additions & 2 deletions packages/kit/src/core/preview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ export async function preview({ port, host, https: use_https = false }) {
scoped(
assets,
sirv(join(config.kit.outDir, 'output/client'), {
maxAge: 31536000,
immutable: true
setHeaders: (res, pathname) => {
// only apply to build directory, not e.g. version.json
if (pathname.startsWith(`/${config.kit.appDir}/immutable`)) {
res.setHeader('cache-control', 'public,max-age=31536000,immutable');
}
}
})
),

Expand Down
12 changes: 11 additions & 1 deletion packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ test.describe.parallel('Imports', () => {
]);
} else {
expect(sources[0].startsWith('data:image/png;base64,')).toBeTruthy();
expect(sources[1]).toBe(`${baseURL}/_app/assets/large-3183867c.jpg`);
expect(sources[1]).toBe(`${baseURL}/_app/immutable/assets/large-3183867c.jpg`);
}
});
});
Expand Down Expand Up @@ -2649,3 +2649,13 @@ test.describe.parallel('XSS', () => {
);
});
});

test.describe.parallel('Version', () => {
test('does not serve version.json with an immutable cache header', async ({ request }) => {
// this isn't actually a great test, because caching behaviour is down to adapters.
// but it's better than nothing
const response = await request.get('/_app/version.json');
const headers = response.headers();
expect(headers['cache-control'] || '').not.toContain('immutable');
});
});
2 changes: 1 addition & 1 deletion packages/kit/test/apps/options-2/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ test.describe.parallel('Service worker', () => {
const response = await request.get('/basepath/service-worker.js');
const content = await response.text();

expect(content).toMatch(/\/_app\/start-[a-z0-9]+\.js/);
expect(content).toMatch(/\/_app\/immutable\/start-[a-z0-9]+\.js/);
});

test('does not register /basepath/service-worker.js', async ({ page }) => {
Expand Down

0 comments on commit f0fb175

Please sign in to comment.