Skip to content

feat: cache .next/cache between builds #185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 21, 2021
Merged
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
32 changes: 32 additions & 0 deletions helpers/cacheBuild.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const path = require('path')

const DEFAULT_DIST_DIR = '.next'

// Account for possible custom distDir
const getPath = (distDir, source) => {
return path.join(distDir || DEFAULT_DIST_DIR, source)
}

const restoreCache = async ({ cache, distDir }) => {
const cacheDir = getPath(distDir, 'cache')
if (await cache.restore(cacheDir)) {
console.log('Next.js cache restored.')
} else {
console.log('No Next.js cache to restore.')
}
}

const saveCache = async ({ cache, distDir }) => {
const cacheDir = getPath(distDir, 'cache')
const buildManifest = getPath(distDir, 'build-manifest.json')
if (await cache.save(cacheDir, { digests: [buildManifest] })) {
console.log('Next.js cache saved.')
} else {
console.log('No Next.js cache to save.')
}
}

module.exports = {
restoreCache,
saveCache,
}
16 changes: 12 additions & 4 deletions helpers/getNextConfig.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
'use strict'

const { cwd: getCwd } = require('process')
const { resolve } = require('path')

const moize = require('moize')

// Load next.config.js
const getNextConfig = async function (failBuild = defaultFailBuild) {
// We used to cache nextConfig for any cwd. Now we pass process.cwd() to cache
// (or memoize) nextConfig per cwd.
const getNextConfig = async function (failBuild = defaultFailBuild, cwd = getCwd()) {
// We cannot load `next` at the top-level because we validate whether the
// site is using `next` inside `onPreBuild`.
const { PHASE_PRODUCTION_BUILD } = require('next/constants')
const loadConfig = require('next/dist/next-server/server/config').default

try {
return await loadConfig(PHASE_PRODUCTION_BUILD, resolve('.'))
return await loadConfig(PHASE_PRODUCTION_BUILD, cwd)
} catch (error) {
return failBuild('Error loading your next.config.js.', { error })
}
}

const moizedGetNextConfig = moize(getNextConfig, { maxSize: 1e3, isPromise: true })
const moizedGetNextConfig = moize(getNextConfig, {
maxSize: 1e3,
isPromise: true,
// Memoization cache key. We need to use `transformArgs` so `process.cwd()`
// default value is assigned
transformArgs: ([, cwd = getCwd()]) => [cwd],
})

const defaultFailBuild = function (message, { error }) {
throw new Error(`${message}\n${error.stack}`)
Expand Down
2 changes: 1 addition & 1 deletion helpers/isStaticExportProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const isStaticExportProject = ({ build, scripts }) => {

if (isStaticExport) {
console.log(
`Static HTML export Next.js projects do not require this plugin. Check your project's build command for 'next export'.`,
'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.',
)
}

Expand Down
6 changes: 6 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const validateNextUsage = require('./helpers/validateNextUsage')
const doesNotNeedPlugin = require('./helpers/doesNotNeedPlugin')
const getNextConfig = require('./helpers/getNextConfig')
const copyUnstableIncludedDirs = require('./helpers/copyUnstableIncludedDirs')
const { restoreCache, saveCache } = require('./helpers/cacheBuild')

const pWriteFile = util.promisify(fs.writeFile)

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

const nextConfig = await getNextConfig(utils.failBuild)
await restoreCache({ cache: utils.cache, distDir: nextConfig.distDir })

if (await doesNotNeedPlugin({ netlifyConfig, packageJson, failBuild })) {
return
}
Expand Down Expand Up @@ -60,12 +64,14 @@ module.exports = {

await nextOnNetlify({ functionsDir: FUNCTIONS_SRC, publishDir: PUBLISH_DIR })
},

async onPostBuild({ netlifyConfig, packageJson, constants: { FUNCTIONS_DIST }, utils }) {
if (await doesNotNeedPlugin({ netlifyConfig, packageJson, utils })) {
return
}

const nextConfig = await getNextConfig(utils.failBuild)
await saveCache({ cache: utils.cache, distDir: nextConfig.distDir })
copyUnstableIncludedDirs({ nextConfig, functionsDist: FUNCTIONS_DIST })
},
}
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/dist_dir_next_config/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
target: 'serverless',
distDir: 'build',
}
57 changes: 57 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const utils = {
throw new Error(message)
},
},
cache: {
save() {},
restore() {},
},
}

// Temporary switch cwd
Expand Down Expand Up @@ -155,6 +159,29 @@ describe('preBuild()', () => {
}),
).rejects.toThrow(`Error loading your next.config.js.`)
})

test('restores cache with right paths', async () => {
await useFixture('dist_dir_next_config')

let distPath
const utils_ = {
...utils,
cache: {
restore: (x) => (distPath = x),
},
}
const spy = jest.spyOn(utils_.cache, 'restore')

await plugin.onPreBuild({
netlifyConfig,
packageJson: DUMMY_PACKAGE_JSON,
utils: utils_,
constants: { FUNCTIONS_SRC: 'out_functions' },
})

expect(spy).toHaveBeenCalled()
expect(path.normalize(distPath)).toBe(path.normalize('build/cache'))
})
})

describe('onBuild()', () => {
Expand Down Expand Up @@ -229,3 +256,33 @@ describe('onBuild()', () => {
expect(await pathExists(`${resolvedFunctions}/next_api_test/next_api_test.js`)).toBeTruthy()
})
})

describe('onPostBuild', () => {
test('saves cache with right paths', async () => {
await useFixture('dist_dir_next_config')

let distPath
let manifestPath
const utils_ = {
...utils,
cache: {
save: (x, y) => {
distPath = x
manifestPath = y
},
},
}
const spy = jest.spyOn(utils_.cache, 'save')

await plugin.onPostBuild({
netlifyConfig,
packageJson: DUMMY_PACKAGE_JSON,
utils: utils_,
constants: { FUNCTIONS_SRC: 'out_functions' },
})

expect(spy).toHaveBeenCalled()
expect(path.normalize(distPath)).toBe(path.normalize('build/cache'))
expect(path.normalize(manifestPath.digests[0])).toBe(path.normalize('build/build-manifest.json'))
})
})