Skip to content

Commit 84b3a63

Browse files
authored
fix: support basepath for static assets (#141)
* test: add unit test cases for basePath * test: add integration tests for placing static assets in publish dir (for no basePath and for basePath) * fix: support basepath for static assets
1 parent 445320b commit 84b3a63

File tree

13 files changed

+769
-66
lines changed

13 files changed

+769
-66
lines changed

src/build/content/static.test.ts

+126-14
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,28 @@ import { copyStaticAssets, copyStaticContent } from './static.js'
1313

1414
type Context = FixtureTestContext & {
1515
pluginContext: PluginContext
16+
publishDir: string
17+
}
18+
const createFsFixtureWithBasePath = (
19+
fixture: Record<string, string>,
20+
ctx: Omit<Context, 'pluginContext'>,
21+
basePath = '',
22+
) => {
23+
return createFsFixture(
24+
{
25+
...fixture,
26+
[join(ctx.publishDir, 'routes-manifest.json')]: JSON.stringify({ basePath }),
27+
},
28+
ctx,
29+
)
1630
}
1731

1832
describe('Regular Repository layout', () => {
1933
beforeEach<Context>((ctx) => {
34+
ctx.publishDir = '.next'
2035
ctx.pluginContext = new PluginContext({
2136
constants: {
22-
PUBLISH_DIR: '.next',
37+
PUBLISH_DIR: ctx.publishDir,
2338
},
2439
utils: { build: { failBuild: vi.fn() } as unknown },
2540
} as NetlifyPluginOptions)
@@ -35,11 +50,11 @@ describe('Regular Repository layout', () => {
3550
)
3651
})
3752

38-
test<Context>('should link static content from the publish directory to the static directory', async ({
53+
test<Context>('should link static content from the publish directory to the static directory (no basePath)', async ({
3954
pluginContext,
4055
...ctx
4156
}) => {
42-
const { cwd } = await createFsFixture(
57+
const { cwd } = await createFsFixtureWithBasePath(
4358
{
4459
'.next/static/test.js': '',
4560
'.next/static/sub-dir/test2.js': '',
@@ -58,11 +73,35 @@ describe('Regular Repository layout', () => {
5873
)
5974
})
6075

61-
test<Context>('should link static content from the public directory to the static directory', async ({
76+
test<Context>('should link static content from the publish directory to the static directory (with basePath)', async ({
77+
pluginContext,
78+
...ctx
79+
}) => {
80+
const { cwd } = await createFsFixtureWithBasePath(
81+
{
82+
'.next/static/test.js': '',
83+
'.next/static/sub-dir/test2.js': '',
84+
},
85+
ctx,
86+
'/base/path',
87+
)
88+
89+
await copyStaticAssets(pluginContext)
90+
expect(await glob('**/*', { cwd, dot: true, absolute: true })).toEqual(
91+
expect.arrayContaining([
92+
join(cwd, '.next/static/test.js'),
93+
join(cwd, '.next/static/sub-dir/test2.js'),
94+
join(pluginContext.staticDir, 'base/path/_next/static/test.js'),
95+
join(pluginContext.staticDir, 'base/path/_next/static/sub-dir/test2.js'),
96+
]),
97+
)
98+
})
99+
100+
test<Context>('should link static content from the public directory to the static directory (no basePath)', async ({
62101
pluginContext,
63102
...ctx
64103
}) => {
65-
const { cwd } = await createFsFixture(
104+
const { cwd } = await createFsFixtureWithBasePath(
66105
{
67106
'public/fake-image.svg': '',
68107
'public/another-asset.json': '',
@@ -81,11 +120,35 @@ describe('Regular Repository layout', () => {
81120
)
82121
})
83122

123+
test<Context>('should link static content from the public directory to the static directory (with basePath)', async ({
124+
pluginContext,
125+
...ctx
126+
}) => {
127+
const { cwd } = await createFsFixtureWithBasePath(
128+
{
129+
'public/fake-image.svg': '',
130+
'public/another-asset.json': '',
131+
},
132+
ctx,
133+
'/base/path',
134+
)
135+
136+
await copyStaticAssets(pluginContext)
137+
expect(await glob('**/*', { cwd, dot: true, absolute: true })).toEqual(
138+
expect.arrayContaining([
139+
join(cwd, 'public/another-asset.json'),
140+
join(cwd, 'public/fake-image.svg'),
141+
join(pluginContext.staticDir, '/base/path/another-asset.json'),
142+
join(pluginContext.staticDir, '/base/path/fake-image.svg'),
143+
]),
144+
)
145+
})
146+
84147
test<Context>('should copy the static pages to the publish directory if there are no corresponding JSON files', async ({
85148
pluginContext,
86149
...ctx
87150
}) => {
88-
await createFsFixture(
151+
await createFsFixtureWithBasePath(
89152
{
90153
'.next/server/pages/test.html': '',
91154
'.next/server/pages/test2.html': '',
@@ -107,7 +170,7 @@ describe('Regular Repository layout', () => {
107170
pluginContext,
108171
...ctx
109172
}) => {
110-
await createFsFixture(
173+
await createFsFixtureWithBasePath(
111174
{
112175
'.next/server/pages/test.html': '',
113176
'.next/server/pages/test.json': '',
@@ -124,20 +187,21 @@ describe('Regular Repository layout', () => {
124187

125188
describe('Mono Repository', () => {
126189
beforeEach<Context>((ctx) => {
190+
ctx.publishDir = 'apps/app-1/.next'
127191
ctx.pluginContext = new PluginContext({
128192
constants: {
129-
PUBLISH_DIR: 'apps/app-1/.next',
193+
PUBLISH_DIR: ctx.publishDir,
130194
PACKAGE_PATH: 'apps/app-1',
131195
},
132196
utils: { build: { failBuild: vi.fn() } as unknown },
133197
} as NetlifyPluginOptions)
134198
})
135199

136-
test<Context>('should link static content from the publish directory to the static directory', async ({
200+
test<Context>('should link static content from the publish directory to the static directory (no basePath)', async ({
137201
pluginContext,
138202
...ctx
139203
}) => {
140-
const { cwd } = await createFsFixture(
204+
const { cwd } = await createFsFixtureWithBasePath(
141205
{
142206
'apps/app-1/.next/static/test.js': '',
143207
'apps/app-1/.next/static/sub-dir/test2.js': '',
@@ -156,11 +220,35 @@ describe('Mono Repository', () => {
156220
)
157221
})
158222

159-
test<Context>('should link static content from the public directory to the static directory', async ({
223+
test<Context>('should link static content from the publish directory to the static directory (with basePath)', async ({
160224
pluginContext,
161225
...ctx
162226
}) => {
163-
const { cwd } = await createFsFixture(
227+
const { cwd } = await createFsFixtureWithBasePath(
228+
{
229+
'apps/app-1/.next/static/test.js': '',
230+
'apps/app-1/.next/static/sub-dir/test2.js': '',
231+
},
232+
ctx,
233+
'/base/path',
234+
)
235+
236+
await copyStaticAssets(pluginContext)
237+
expect(await glob('**/*', { cwd, dot: true, absolute: true })).toEqual(
238+
expect.arrayContaining([
239+
join(cwd, 'apps/app-1/.next/static/test.js'),
240+
join(cwd, 'apps/app-1/.next/static/sub-dir/test2.js'),
241+
join(pluginContext.staticDir, '/base/path/_next/static/test.js'),
242+
join(pluginContext.staticDir, '/base/path/_next/static/sub-dir/test2.js'),
243+
]),
244+
)
245+
})
246+
247+
test<Context>('should link static content from the public directory to the static directory (no basePath)', async ({
248+
pluginContext,
249+
...ctx
250+
}) => {
251+
const { cwd } = await createFsFixtureWithBasePath(
164252
{
165253
'apps/app-1/public/fake-image.svg': '',
166254
'apps/app-1/public/another-asset.json': '',
@@ -179,11 +267,35 @@ describe('Mono Repository', () => {
179267
)
180268
})
181269

270+
test<Context>('should link static content from the public directory to the static directory (with basePath)', async ({
271+
pluginContext,
272+
...ctx
273+
}) => {
274+
const { cwd } = await createFsFixtureWithBasePath(
275+
{
276+
'apps/app-1/public/fake-image.svg': '',
277+
'apps/app-1/public/another-asset.json': '',
278+
},
279+
ctx,
280+
'/base/path',
281+
)
282+
283+
await copyStaticAssets(pluginContext)
284+
expect(await glob('**/*', { cwd, dot: true, absolute: true })).toEqual(
285+
expect.arrayContaining([
286+
join(cwd, 'apps/app-1/public/another-asset.json'),
287+
join(cwd, 'apps/app-1/public/fake-image.svg'),
288+
join(pluginContext.staticDir, '/base/path/another-asset.json'),
289+
join(pluginContext.staticDir, '/base/path/fake-image.svg'),
290+
]),
291+
)
292+
})
293+
182294
test<Context>('should copy the static pages to the publish directory if there are no corresponding JSON files', async ({
183295
pluginContext,
184296
...ctx
185297
}) => {
186-
await createFsFixture(
298+
await createFsFixtureWithBasePath(
187299
{
188300
'apps/app-1/.next/server/pages/test.html': '',
189301
'apps/app-1/.next/server/pages/test2.html': '',
@@ -205,7 +317,7 @@ describe('Mono Repository', () => {
205317
pluginContext,
206318
...ctx
207319
}) => {
208-
await createFsFixture(
320+
await createFsFixtureWithBasePath(
209321
{
210322
'apps/app-1/.next/server/pages/test.html': '',
211323
'apps/app-1/.next/server/pages/test.json': '',

src/build/content/static.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ export const copyStaticContent = async (ctx: PluginContext): Promise<void> => {
3939
export const copyStaticAssets = async (ctx: PluginContext): Promise<void> => {
4040
try {
4141
await rm(ctx.staticDir, { recursive: true, force: true })
42+
const { basePath } = await ctx.getRoutesManifest()
4243
if (existsSync(ctx.resolve('public'))) {
43-
await cp(ctx.resolve('public'), ctx.staticDir, { recursive: true })
44+
await cp(ctx.resolve('public'), join(ctx.staticDir, basePath), { recursive: true })
4445
}
4546
if (existsSync(join(ctx.publishDir, 'static'))) {
46-
await cp(join(ctx.publishDir, 'static'), join(ctx.staticDir, '_next/static'), {
47+
await cp(join(ctx.publishDir, 'static'), join(ctx.staticDir, basePath, '_next/static'), {
4748
recursive: true,
4849
})
4950
}

src/build/plugin-context.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { dirname, join, resolve } from 'node:path'
77
import { fileURLToPath } from 'node:url'
88

99
import { NetlifyPluginConstants, NetlifyPluginOptions, NetlifyPluginUtils } from '@netlify/build'
10-
import { PrerenderManifest } from 'next/dist/build/index.js'
10+
import { PrerenderManifest, RoutesManifest } from 'next/dist/build/index.js'
1111
import { MiddlewareManifest } from 'next/dist/build/webpack/plugins/middleware-plugin.js'
1212

1313
const MODULE_DIR = fileURLToPath(new URL('.', import.meta.url))
@@ -135,6 +135,13 @@ export class PluginContext {
135135
)
136136
}
137137

138+
/**
139+
* Get Next.js routes manifest from the build output
140+
*/
141+
async getRoutesManifest(): Promise<RoutesManifest> {
142+
return JSON.parse(await readFile(join(this.publishDir, 'routes-manifest.json'), 'utf-8'))
143+
}
144+
138145
/**
139146
* Write a cache entry to the blob upload directory using
140147
* base64 keys to avoid collisions with directories
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const metadata = {
2+
title: 'Simple Next App',
3+
description: 'Description for Simple Next App',
4+
}
5+
6+
export default function RootLayout({ children }) {
7+
return (
8+
<html lang="en">
9+
<body>{children}</body>
10+
</html>
11+
)
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Home() {
2+
return (
3+
<main>
4+
<h1>Other</h1>
5+
</main>
6+
)
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function Home() {
2+
return (
3+
<main>
4+
<h1>Home</h1>
5+
<img src="/base/path/squirrel.jpg" alt="a cute squirrel" width="300px" />
6+
</main>
7+
)
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
output: 'standalone',
4+
basePath: '/base/path',
5+
eslint: {
6+
ignoreDuringBuilds: true,
7+
},
8+
}
9+
10+
module.exports = nextConfig

0 commit comments

Comments
 (0)