Skip to content

Commit 8cb478c

Browse files
committed
feat: cache .next/cache between builds
1 parent 2953faa commit 8cb478c

File tree

10 files changed

+109
-8
lines changed

10 files changed

+109
-8
lines changed

helpers/cacheBuild.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const path = require('path')
2+
3+
const DEFAULT_DIST_DIR = '.next'
4+
5+
// Account for possible custom distDir
6+
const getPath = (distDir, source) => {
7+
return path.join(distDir || DEFAULT_DIST_DIR, source)
8+
}
9+
10+
const restoreCache = async ({ cache, distDir }) => {
11+
const cacheDir = getPath(distDir, 'cache')
12+
if (await cache.restore(cacheDir)) {
13+
console.log('Next.js cache restored.')
14+
} else {
15+
console.log('No Next.js cache to restore.')
16+
}
17+
}
18+
19+
const saveCache = async ({ cache, distDir }) => {
20+
const cacheDir = getPath(distDir, 'cache')
21+
const buildManifest = getPath(distDir, 'build-manifest.json')
22+
if (await cache.save(cacheDir, { digests: [buildManifest] })) {
23+
console.log('Next.js cache saved.')
24+
} else {
25+
console.log('No Next.js cache to save.')
26+
}
27+
}
28+
29+
module.exports = {
30+
restoreCache,
31+
saveCache,
32+
}

helpers/getNextConfig.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ const { resolve } = require('path')
44

55
const moize = require('moize')
66

7-
// Load next.config.js
8-
const getNextConfig = async function (failBuild = defaultFailBuild) {
7+
// We used to cache nextConfig for any cwd. Now we pass process.cwd() to cache
8+
// (or memoize) nextConfig per cwd.
9+
const getNextConfig = async function (cwd, failBuild = defaultFailBuild) {
910
// We cannot load `next` at the top-level because we validate whether the
1011
// site is using `next` inside `onPreBuild`.
1112
const { PHASE_PRODUCTION_BUILD } = require('next/constants')
@@ -24,4 +25,5 @@ const defaultFailBuild = function (message, { error }) {
2425
throw new Error(`${message}\n${error.stack}`)
2526
}
2627

28+
// module.exports = process.env.NODE_ENV === 'test' ? getNextConfig : moizedGetNextConfig
2729
module.exports = moizedGetNextConfig

helpers/hasCorrectNextConfig.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const hasCorrectNextConfig = async ({ nextConfigPath, failBuild }) => {
55
// In the plugin's case, no config is valid because we'll make it ourselves
66
if (nextConfigPath === undefined) return true
77

8-
const { target } = await getNextConfig(failBuild)
8+
const { target } = await getNextConfig(process.cwd(), failBuild)
99

1010
// If the next config exists, log warning if target isnt in acceptableTargets
1111
const acceptableTargets = ['serverless', 'experimental-serverless-trace']

helpers/isStaticExportProject.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const isStaticExportProject = ({ build, scripts }) => {
1717

1818
if (isStaticExport) {
1919
console.log(
20-
`Static HTML export Next.js projects do not require this plugin. Check your project's build command for 'next export'.`,
20+
'NOTE: Static HTML export Next.js projects (projects that use `next export`) do not require most of this plugin. For these sites, this plugin *only* caches builds.',
2121
)
2222
}
2323

index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const validateNextUsage = require('./helpers/validateNextUsage')
99
const doesNotNeedPlugin = require('./helpers/doesNotNeedPlugin')
1010
const getNextConfig = require('./helpers/getNextConfig')
1111
const copyUnstableIncludedDirs = require('./helpers/copyUnstableIncludedDirs')
12+
const { restoreCache, saveCache } = require('./helpers/cacheBuild')
1213

1314
const pWriteFile = util.promisify(fs.writeFile)
1415

@@ -27,6 +28,9 @@ module.exports = {
2728
return failBuild('Could not find a package.json for this project')
2829
}
2930

31+
const nextConfig = await getNextConfig(process.cwd(), utils.failBuild)
32+
await restoreCache({ cache: utils.cache, distDir: nextConfig.distDir })
33+
3034
if (await doesNotNeedPlugin({ netlifyConfig, packageJson, failBuild })) {
3135
return
3236
}
@@ -60,12 +64,14 @@ module.exports = {
6064

6165
await nextOnNetlify({ functionsDir: FUNCTIONS_SRC, publishDir: PUBLISH_DIR })
6266
},
67+
6368
async onPostBuild({ netlifyConfig, packageJson, constants: { FUNCTIONS_DIST }, utils }) {
6469
if (await doesNotNeedPlugin({ netlifyConfig, packageJson, utils })) {
6570
return
6671
}
6772

68-
const nextConfig = await getNextConfig(utils.failBuild)
73+
const nextConfig = await getNextConfig(process.cwd(), utils.failBuild)
74+
await saveCache({ cache: utils.cache, distDir: nextConfig.distDir })
6975
copyUnstableIncludedDirs({ nextConfig, functionsDist: FUNCTIONS_DIST })
7076
},
7177
}

src/lib/helpers/getI18n.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const getNextConfig = require('../../../helpers/getNextConfig')
33

44
const getI18n = async () => {
5-
const nextConfig = await getNextConfig()
5+
const nextConfig = await getNextConfig(process.cwd())
66

77
return nextConfig.i18n || { locales: [] }
88
}

src/lib/helpers/getNextDistDir.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const { join } = require('path')
33
const getNextConfig = require('../../../helpers/getNextConfig')
44

55
const getNextDistDir = async () => {
6-
const nextConfig = await getNextConfig()
6+
const nextConfig = await getNextConfig(process.cwd())
77

88
return join('.', nextConfig.distDir)
99
}

src/lib/helpers/getPrerenderManifest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const transformManifestForI18n = async (manifest) => {
2727
}
2828

2929
const getPrerenderManifest = async () => {
30-
const nextConfig = await getNextConfig()
30+
const nextConfig = await getNextConfig(process.cwd())
3131
const nextDistDir = await getNextDistDir()
3232
const manifest = readJSONSync(join(nextDistDir, 'prerender-manifest.json'))
3333
if (nextConfig.i18n) return await transformManifestForI18n(manifest)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
target: 'serverless',
3+
distDir: 'build',
4+
}

test/index.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ const utils = {
1818
throw new Error(message)
1919
},
2020
},
21+
cache: {
22+
save() {},
23+
restore() {},
24+
},
2125
}
2226

2327
// Temporary switch cwd
@@ -155,6 +159,29 @@ describe('preBuild()', () => {
155159
}),
156160
).rejects.toThrow(`Error loading your next.config.js.`)
157161
})
162+
163+
test('restores cache with right paths', async () => {
164+
await useFixture('dist_dir_next_config')
165+
166+
let distPath
167+
const utils_ = {
168+
...utils,
169+
cache: {
170+
restore: (x) => (distPath = x),
171+
},
172+
}
173+
const spy = jest.spyOn(utils_.cache, 'restore')
174+
175+
await plugin.onPreBuild({
176+
netlifyConfig,
177+
packageJson: DUMMY_PACKAGE_JSON,
178+
utils: utils_,
179+
constants: { FUNCTIONS_SRC: 'out_functions' },
180+
})
181+
182+
expect(spy).toHaveBeenCalled()
183+
expect(distPath).toBe('build/cache')
184+
})
158185
})
159186

160187
describe('onBuild()', () => {
@@ -229,3 +256,33 @@ describe('onBuild()', () => {
229256
expect(await pathExists(`${resolvedFunctions}/next_api_test/next_api_test.js`)).toBeTruthy()
230257
})
231258
})
259+
260+
describe('onPostBuild', () => {
261+
test('saves cache with right paths', async () => {
262+
await useFixture('dist_dir_next_config')
263+
264+
let distPath
265+
let manifestPath
266+
const utils_ = {
267+
...utils,
268+
cache: {
269+
save: (x, y) => {
270+
distPath = x
271+
manifestPath = y
272+
},
273+
},
274+
}
275+
const spy = jest.spyOn(utils_.cache, 'save')
276+
277+
await plugin.onPostBuild({
278+
netlifyConfig,
279+
packageJson: DUMMY_PACKAGE_JSON,
280+
utils: utils_,
281+
constants: { FUNCTIONS_SRC: 'out_functions' },
282+
})
283+
284+
expect(spy).toHaveBeenCalled()
285+
expect(distPath).toBe('build/cache')
286+
expect(manifestPath.digests[0]).toBe('build/build-manifest.json')
287+
})
288+
})

0 commit comments

Comments
 (0)