Skip to content
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

feat: cache .next/cache between builds #185

Merged
merged 4 commits into from
Apr 21, 2021
Merged
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}`)
2 changes: 1 addition & 1 deletion helpers/isStaticExportProject.js
Original file line number Diff line number Diff line change
@@ -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.',
)
}

6 changes: 6 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -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)

@@ -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
}
@@ -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 })
},
}
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
@@ -18,6 +18,10 @@ const utils = {
throw new Error(message)
},
},
cache: {
save() {},
restore() {},
},
}

// Temporary switch cwd
@@ -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()', () => {
@@ -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'))
})
})