Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/shy-crabs-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/router': patch
---

fix: preserve no-trailing-slash static routes
5 changes: 4 additions & 1 deletion packages/qwik-router/src/adapters/shared/vite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ export function viteAdapter(opts: ViteAdapterPluginOptions) {
const basePathname = qwikRouterPlugin.api.getBasePathname();
const clientOutDir = qwikVitePlugin.api.getClientOutDir()!;
const clientPublicOutDir = qwikVitePlugin.api.getClientPublicOutDir()!;
const noTrailingSlash =
builder.config.define?.['globalThis.__NO_TRAILING_SLASH__'] === 'true';
const ssgEnv = builder.environments.ssg;
const ssgOutDir = ssgOutDirFor(builder.config.root);

Expand Down Expand Up @@ -303,7 +305,8 @@ export function viteAdapter(opts: ViteAdapterPluginOptions) {
serverOutDir,
basePathname,
staticPaths,
!!opts.cleanStaticGenerated
!!opts.cleanStaticGenerated,
noTrailingSlash
);

if (typeof opts.generate === 'function') {
Expand Down
20 changes: 15 additions & 5 deletions packages/qwik-router/src/adapters/shared/vite/post-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export async function postBuild(
serverOutDir: string,
pathName: string,
userStaticPaths: string[],
cleanStatic: boolean
cleanStatic: boolean,
noTrailingSlash: boolean
) {
if (pathName && !pathName.endsWith('/')) {
pathName = ensureSlash(pathName);
Expand All @@ -20,7 +21,15 @@ export async function postBuild(
pathNameBase + (globalThis.__QWIK_ASSETS_DIR__ || 'assets') + '/',
]);

const staticPaths = new Set(userStaticPaths.map(ensureSlash));
const getStaticPathname = (pathname: string) => {
pathname = ensureSlash(pathname);
if (noTrailingSlash && pathname !== pathNameBase) {
return pathname.slice(0, -1);
}
return pathname;
};

const staticPaths = new Set(userStaticPaths.map(getStaticPathname));

const loadItem = async (fsDir: string, fsName: string, pathname: string) => {
pathname = ensureSlash(pathname);
Expand All @@ -32,16 +41,17 @@ export async function postBuild(

if (fsName === 'index.html') {
// The route pathname already represents this page; clean it if that route is no longer static.
if (!staticPaths.has(pathname) && cleanStatic) {
if (!staticPaths.has(getStaticPathname(pathname)) && cleanStatic) {
await fs.promises.unlink(fsPath);
}
return;
}

if (LOADER_REGEX.test('/' + fsName)) {
// List the exact sidecar SSG wrote so isStaticPath only claims loaders with data on disk.
if (staticPaths.has(pathname)) {
staticPaths.add(pathname + fsName);
const staticPathname = getStaticPathname(pathname);
if (staticPaths.has(staticPathname)) {
staticPaths.add(ensureSlash(staticPathname) + fsName);
} else if (cleanStatic) {
await fs.promises.unlink(fsPath);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ async function tmp() {
}

/** Run postBuild against a temp client/server tree and return the injected STATIC_PATHS array. */
async function run(files: Record<string, string>, userStaticPaths: string[], cleanStatic = false) {
async function run(
files: Record<string, string>,
userStaticPaths: string[],
cleanStatic = false,
noTrailingSlash = false,
pathName = '/'
) {
const clientOutDir = await tmp();
const serverOutDir = await tmp();
for (const [rel, content] of Object.entries(files)) {
Expand All @@ -28,7 +34,14 @@ async function run(files: Record<string, string>, userStaticPaths: string[], cle
join(serverOutDir, 'server.js'),
`export const staticPaths = new Set(['__QWIK_ROUTER_STATIC_PATHS_ARRAY__']);`
);
await postBuild(clientOutDir, serverOutDir, '/', userStaticPaths, cleanStatic);
await postBuild(
clientOutDir,
serverOutDir,
pathName,
userStaticPaths,
cleanStatic,
noTrailingSlash
);
const code = await readFile(join(serverOutDir, 'server.js'), 'utf-8');
return JSON.parse(code.match(/new Set\((\[[^\]]*\])\)/)![1]) as string[];
}
Expand All @@ -46,6 +59,26 @@ test('lists a written loader sidecar of a static route, but not its index.html',
expect(paths).not.toContain('/blog/index.html');
});

test('uses no-trailing-slash static paths for static pages', async () => {
const paths = await run(
{
'blog/index.html': '<html></html>',
'blog/q-loader-WaXl02RHfZE.abc.json': '{"d":{}}',
},
['/blog'],
false,
true
);
expect(paths).toContain('/blog');
expect(paths).toContain('/blog/q-loader-WaXl02RHfZE.abc.json');
expect(paths).not.toContain('/blog/');
});

test('keeps the base pathname trailing slash for static pages', async () => {
const paths = await run({ 'index.html': '<html></html>' }, ['/app/'], false, true, '/app/');
expect(paths).toEqual(['/app/']);
});

test('does not list a loader sidecar whose route is not static', async () => {
const paths = await run({ 'other/q-loader-X.abc.json': '{"d":{}}' }, []);
expect(paths).not.toContain('/other/q-loader-X.abc.json');
Expand Down