summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/integrations/node/src/serve-static.ts6
-rw-r--r--packages/integrations/node/test/fixtures/trailing-slash/public/one.css1
-rw-r--r--packages/integrations/node/test/trailing-slash.test.js (renamed from packages/integrations/node/test/trailing-slash.js)123
3 files changed, 76 insertions, 54 deletions
diff --git a/packages/integrations/node/src/serve-static.ts b/packages/integrations/node/src/serve-static.ts
index a65e52e96..811f748a4 100644
--- a/packages/integrations/node/src/serve-static.ts
+++ b/packages/integrations/node/src/serve-static.ts
@@ -6,6 +6,9 @@ import type { NodeApp } from 'astro/app/node';
import send from 'send';
import type { Options } from './types.js';
+// check for a dot followed by a extension made up of lowercase characters
+const isSubresourceRegex = /.+\.[a-z]+$/i
+
/**
* Creates a Node.js http listener for static files and prerendered pages.
* In standalone mode, the static handler is queried first for the static files.
@@ -48,7 +51,8 @@ export function createStaticHandler(app: NodeApp, options: Options) {
}
break;
case 'always':
- if (!hasSlash) {
+ // trailing slash is not added to "subresources"
+ if (!hasSlash && !urlPath.match(isSubresourceRegex)) {
pathname = urlPath + '/' + (urlQuery ? '?' + urlQuery : '');
res.statusCode = 301;
res.setHeader('Location', pathname);
diff --git a/packages/integrations/node/test/fixtures/trailing-slash/public/one.css b/packages/integrations/node/test/fixtures/trailing-slash/public/one.css
new file mode 100644
index 000000000..5ce768ca5
--- /dev/null
+++ b/packages/integrations/node/test/fixtures/trailing-slash/public/one.css
@@ -0,0 +1 @@
+h1 { color: red; }
diff --git a/packages/integrations/node/test/trailing-slash.js b/packages/integrations/node/test/trailing-slash.test.js
index 28bfe0f5d..64fff1964 100644
--- a/packages/integrations/node/test/trailing-slash.js
+++ b/packages/integrations/node/test/trailing-slash.test.js
@@ -1,4 +1,5 @@
-import { expect } from 'chai';
+import { after, before, describe, it } from 'node:test';
+import * as assert from 'node:assert/strict';
import * as cheerio from 'cheerio';
import nodejs from '../dist/index.js';
import { loadFixture } from './test-utils.js';
@@ -48,24 +49,24 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('Index');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'Index');
});
it('Can render prerendered route with redirect', async () => {
const res = await fetch(`http://${server.host}:${server.port}/some-base/one`, {
redirect: 'manual',
});
- expect(res.status).to.equal(301);
- expect(res.headers.get('location')).to.equal('/some-base/one/');
+ assert.equal(res.status, 301);
+ assert.equal(res.headers.get('location'), '/some-base/one/');
});
it('Can render prerendered route with redirect and query params', async () => {
const res = await fetch(`http://${server.host}:${server.port}/some-base/one?foo=bar`, {
redirect: 'manual',
});
- expect(res.status).to.equal(301);
- expect(res.headers.get('location')).to.equal('/some-base/one/?foo=bar');
+ assert.equal(res.status, 301);
+ assert.equal(res.headers.get('location'), '/some-base/one/?foo=bar');
});
it('Can render prerendered route with query params', async () => {
@@ -73,9 +74,17 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
+
+ it('Does not add trailing slash to subresource urls', async () => {
+ const res = await fetch(`http://${server.host}:${server.port}/some-base/one.css`);
+ const css = await res.text();
+
+ assert.equal(res.status, 200);
+ assert.equal(css, 'h1 { color: red; }\n');
+ })
});
describe('Without base', async () => {
before(async () => {
@@ -105,24 +114,24 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('Index');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'Index');
});
it('Can render prerendered route with redirect', async () => {
const res = await fetch(`http://${server.host}:${server.port}/one`, {
redirect: 'manual',
});
- expect(res.status).to.equal(301);
- expect(res.headers.get('location')).to.equal('/one/');
+ assert.equal(res.status, 301);
+ assert.equal(res.headers.get('location'), '/one/');
});
it('Can render prerendered route with redirect and query params', async () => {
const res = await fetch(`http://${server.host}:${server.port}/one?foo=bar`, {
redirect: 'manual',
});
- expect(res.status).to.equal(301);
- expect(res.headers.get('location')).to.equal('/one/?foo=bar');
+ assert.equal(res.status, 301);
+ assert.equal(res.headers.get('location'), '/one/?foo=bar');
});
it('Can render prerendered route with query params', async () => {
@@ -130,9 +139,17 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
+
+ it('Does not add trailing slash to subresource urls', async () => {
+ const res = await fetch(`http://${server.host}:${server.port}/one.css`);
+ const css = await res.text();
+
+ assert.equal(res.status, 200);
+ assert.equal(css, 'h1 { color: red; }\n');
+ })
});
});
describe('Never', async () => {
@@ -165,16 +182,16 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('Index');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'Index');
});
it('Can render prerendered route with redirect', async () => {
const res = await fetch(`http://${server.host}:${server.port}/some-base/one/`, {
redirect: 'manual',
});
- expect(res.status).to.equal(301);
- expect(res.headers.get('location')).to.equal('/some-base/one');
+ assert.equal(res.status, 301);
+ assert.equal(res.headers.get('location'), '/some-base/one');
});
it('Can render prerendered route with redirect and query params', async () => {
@@ -182,8 +199,8 @@ describe('Trailing slash', () => {
redirect: 'manual',
});
- expect(res.status).to.equal(301);
- expect(res.headers.get('location')).to.equal('/some-base/one?foo=bar');
+ assert.equal(res.status, 301);
+ assert.equal(res.headers.get('location'), '/some-base/one?foo=bar');
});
it('Can render prerendered route with query params', async () => {
@@ -191,8 +208,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
});
describe('Without base', async () => {
@@ -223,16 +240,16 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('Index');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'Index');
});
it('Can render prerendered route with redirect', async () => {
const res = await fetch(`http://${server.host}:${server.port}/one/`, {
redirect: 'manual',
});
- expect(res.status).to.equal(301);
- expect(res.headers.get('location')).to.equal('/one');
+ assert.equal(res.status, 301);
+ assert.equal(res.headers.get('location'), '/one');
});
it('Can render prerendered route with redirect and query params', async () => {
@@ -240,8 +257,8 @@ describe('Trailing slash', () => {
redirect: 'manual',
});
- expect(res.status).to.equal(301);
- expect(res.headers.get('location')).to.equal('/one?foo=bar');
+ assert.equal(res.status, 301);
+ assert.equal(res.headers.get('location'), '/one?foo=bar');
});
it('Can render prerendered route and query params', async () => {
@@ -249,8 +266,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
});
});
@@ -284,8 +301,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('Index');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'Index');
});
it('Can render prerendered route with slash', async () => {
@@ -295,8 +312,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
it('Can render prerendered route without slash', async () => {
@@ -306,8 +323,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
it('Can render prerendered route with slash and query params', async () => {
@@ -317,8 +334,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
it('Can render prerendered route without slash and with query params', async () => {
@@ -328,8 +345,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
});
describe('Without base', async () => {
@@ -360,8 +377,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('Index');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'Index');
});
it('Can render prerendered route with slash', async () => {
@@ -369,8 +386,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
it('Can render prerendered route without slash', async () => {
@@ -378,8 +395,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
it('Can render prerendered route with slash and query params', async () => {
@@ -389,8 +406,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
it('Can render prerendered route without slash and with query params', async () => {
@@ -398,8 +415,8 @@ describe('Trailing slash', () => {
const html = await res.text();
const $ = cheerio.load(html);
- expect(res.status).to.equal(200);
- expect($('h1').text()).to.equal('One');
+ assert.equal(res.status, 200);
+ assert.equal($('h1').text(), 'One');
});
});
});