Skip to content

Commit

Permalink
enables eslint caching by default (#28349)
Browse files Browse the repository at this point in the history
Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
housseindjirdeh and ijjk committed Aug 23, 2021
1 parent 8afa24f commit 80921b6
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 12 deletions.
8 changes: 8 additions & 0 deletions docs/basic-features/eslint.md
Expand Up @@ -139,6 +139,14 @@ Similarly, the `--dir` flag can be used for `next lint`:
next lint --dir pages --dir utils
```

## Caching

To improve performance, information of files processed by ESLint are cached by default. This is stored in `.next/cache` or in your defined [build directory](/docs/api-reference/next.config.js/setting-a-custom-build-directory). If you include any ESLint rules that depend on more than the contents of a single source file and need to disable the cache, use the `--no-cache` flag with `next lint`.

```bash
next lint --no-cache
```

## Disabling Rules

If you would like to modify or disable any rules provided by the supported plugins (`react`, `react-hooks`, `next`), you can directly change them using the `rules` property in your `.eslintrc`:
Expand Down
5 changes: 3 additions & 2 deletions packages/next/build/index.ts
Expand Up @@ -217,14 +217,15 @@ export default async function build(
}

const ignoreESLint = Boolean(config.eslint?.ignoreDuringBuilds)
const lintDirs = config.eslint?.dirs
const eslintCacheDir = path.join(cacheDir, 'eslint/')
if (!ignoreESLint && runLint) {
await nextBuildSpan
.traceChild('verify-and-lint')
.traceAsyncFn(async () => {
await verifyAndLint(
dir,
lintDirs,
eslintCacheDir,
config.eslint?.dirs,
config.experimental.cpus,
config.experimental.workerThreads,
telemetry
Expand Down
22 changes: 13 additions & 9 deletions packages/next/cli/next-lint.ts
Expand Up @@ -14,7 +14,7 @@ import { PHASE_PRODUCTION_BUILD } from '../shared/lib/constants'
import { eventLintCheckCompleted } from '../telemetry/events'
import { CompileError } from '../lib/compile-error'

const eslintOptions = (args: arg.Spec) => ({
const eslintOptions = (args: arg.Spec, defaultCacheLocation: string) => ({
overrideConfigFile: args['--config'] || null,
extensions: args['--ext'] ?? ['.js', '.jsx', '.ts', '.tsx'],
resolvePluginsRelativeTo: args['--resolve-plugins-relative-to'] || null,
Expand All @@ -26,8 +26,8 @@ const eslintOptions = (args: arg.Spec) => ({
allowInlineConfig: !Boolean(args['--no-inline-config']),
reportUnusedDisableDirectives:
args['--report-unused-disable-directives'] || null,
cache: args['--cache'] ?? false,
cacheLocation: args['--cache-location'] || '.eslintcache',
cache: !Boolean(args['--no-cache']),
cacheLocation: args['--cache-location'] || defaultCacheLocation,
errorOnUnmatchedPattern: args['--error-on-unmatched-pattern']
? Boolean(args['--error-on-unmatched-pattern'])
: false,
Expand Down Expand Up @@ -61,7 +61,8 @@ const nextLint: cliCommand = async (argv) => {
'--max-warnings': Number,
'--no-inline-config': Boolean,
'--report-unused-disable-directives': String,
'--cache': Boolean,
'--cache': Boolean, // Although cache is enabled by default, this dummy flag still exists to not cause any breaking changes
'--no-cache': Boolean,
'--cache-location': String,
'--error-on-unmatched-pattern': Boolean,
'--format': String,
Expand Down Expand Up @@ -127,7 +128,7 @@ const nextLint: cliCommand = async (argv) => {
--report-unused-disable-directives Adds reported errors for unused eslint-disable directives ("error" | "warn" | "off")
Caching:
--cache Only check changed files - default: false
--no-cache Disable caching
--cache-location path::String Path to the cache file or directory - default: .eslintcache
Miscellaneous:
Expand All @@ -144,9 +145,9 @@ const nextLint: cliCommand = async (argv) => {
printAndExit(`> No such directory exists as the project root: ${baseDir}`)
}

const conf = await loadConfig(PHASE_PRODUCTION_BUILD, baseDir)
const nextConfig = await loadConfig(PHASE_PRODUCTION_BUILD, baseDir)

const dirs: string[] = args['--dir'] ?? conf.eslint?.dirs
const dirs: string[] = args['--dir'] ?? nextConfig.eslint?.dirs
const lintDirs = (dirs ?? ESLINT_DEFAULT_DIRS).reduce(
(res: string[], d: string) => {
const currDir = join(baseDir, d)
Expand All @@ -162,11 +163,14 @@ const nextLint: cliCommand = async (argv) => {
const formatter = args['--format'] || null
const strict = Boolean(args['--strict'])

const distDir = join(baseDir, nextConfig.distDir)
const defaultCacheLocation = join(distDir, 'cache', 'eslint/')

runLintCheck(
baseDir,
lintDirs,
false,
eslintOptions(args),
eslintOptions(args, defaultCacheLocation),
reportErrorsOnly,
maxWarnings,
formatter,
Expand All @@ -178,7 +182,7 @@ const nextLint: cliCommand = async (argv) => {

if (typeof lintResults !== 'string' && lintResults?.eventInfo) {
const telemetry = new Telemetry({
distDir: join(baseDir, conf.distDir),
distDir,
})
telemetry.record(
eventLintCheckCompleted({
Expand Down
1 change: 1 addition & 0 deletions packages/next/lib/eslint/runLintCheck.ts
Expand Up @@ -116,6 +116,7 @@ async function lint(
baseConfig: {},
errorOnUnmatchedPattern: false,
extensions: ['.js', '.jsx', '.ts', '.tsx'],
cache: true,
...eslintOptions,
}

Expand Down
5 changes: 4 additions & 1 deletion packages/next/lib/verifyAndLint.ts
Expand Up @@ -9,6 +9,7 @@ import { CompileError } from './compile-error'

export async function verifyAndLint(
dir: string,
cacheLocation: string,
configLintDirs: string[] | undefined,
numWorkers: number | undefined,
enableWorkerThreads: boolean | undefined,
Expand All @@ -35,7 +36,9 @@ export async function verifyAndLint(
[]
)

const lintResults = await lintWorkers.runLintCheck(dir, lintDirs, true)
const lintResults = await lintWorkers.runLintCheck(dir, lintDirs, true, {
cacheLocation,
})
const lintOutput =
typeof lintResults === 'string' ? lintResults : lintResults?.output

Expand Down
4 changes: 4 additions & 0 deletions test/integration/eslint/eslint-cache-custom-dir/.eslintrc
@@ -0,0 +1,4 @@
{
"extends": "next",
"root": true
}
@@ -0,0 +1,3 @@
module.exports = {
distDir: 'build',
}
@@ -0,0 +1,7 @@
const Home = () => (
<div>
<p>Home</p>
</div>
)

export default Home
4 changes: 4 additions & 0 deletions test/integration/eslint/eslint-cache/.eslintrc
@@ -0,0 +1,4 @@
{
"extends": "next",
"root": true
}
7 changes: 7 additions & 0 deletions test/integration/eslint/eslint-cache/pages/index.js
@@ -0,0 +1,7 @@
const Home = () => (
<div>
<p>Home</p>
</div>
)

export default Home
78 changes: 78 additions & 0 deletions test/integration/eslint/test/index.test.js
Expand Up @@ -29,6 +29,8 @@ const dirEmptyDirectory = join(__dirname, '../empty-directory')
const dirEslintIgnore = join(__dirname, '../eslint-ignore')
const dirNoEslintPlugin = join(__dirname, '../no-eslint-plugin')
const dirNoConfig = join(__dirname, '../no-config')
const dirEslintCache = join(__dirname, '../eslint-cache')
const dirEslintCacheCustomDir = join(__dirname, '../eslint-cache-custom-dir')

describe('ESLint', () => {
describe('Next Build', () => {
Expand Down Expand Up @@ -144,6 +146,35 @@ describe('ESLint', () => {
'The Next.js plugin was not detected in your ESLint configuration'
)
})

test('eslint caching is enabled', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')

await fs.remove(cacheDir)
await nextBuild(dirEslintCache, [])

const files = await fs.readdir(join(cacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))

expect(cacheExists).toBe(true)
})

test('eslint cache lives in the user defined build directory', async () => {
const oldCacheDir = join(dirEslintCacheCustomDir, '.next', 'cache')
const newCacheDir = join(dirEslintCacheCustomDir, 'build', 'cache')

await fs.remove(oldCacheDir)
await fs.remove(newCacheDir)

await nextBuild(dirEslintCacheCustomDir, [])

expect(fs.existsSync(oldCacheDir)).toBe(false)

const files = await fs.readdir(join(newCacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))

expect(cacheExists).toBe(true)
})
})

describe('Next Lint', () => {
Expand Down Expand Up @@ -429,5 +460,52 @@ describe('ESLint', () => {
expect(stdout).toContain('<script src="https://example.com" />')
expect(stdout).toContain('2 warnings found')
})

test('eslint caching is enabled by default', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')

await fs.remove(cacheDir)
await nextLint(dirEslintCache, [])

const files = await fs.readdir(join(cacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))

expect(cacheExists).toBe(true)
})

test('eslint caching is disabled with the --no-cache flag', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')

await fs.remove(cacheDir)
await nextLint(dirEslintCache, ['--no-cache'])

expect(fs.existsSync(join(cacheDir, 'eslint/'))).toBe(false)
})

test('the default eslint cache lives in the user defined build directory', async () => {
const oldCacheDir = join(dirEslintCacheCustomDir, '.next', 'cache')
const newCacheDir = join(dirEslintCacheCustomDir, 'build', 'cache')

await fs.remove(oldCacheDir)
await fs.remove(newCacheDir)

await nextLint(dirEslintCacheCustomDir, [])

expect(fs.existsSync(oldCacheDir)).toBe(false)

const files = await fs.readdir(join(newCacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))

expect(cacheExists).toBe(true)
})

test('the --cache-location flag allows the user to define a separate cache location', async () => {
const cacheFile = join(dirEslintCache, '.eslintcache')

await fs.remove(cacheFile)
await nextLint(dirEslintCache, ['--cache-location', cacheFile])

expect(fs.existsSync(cacheFile)).toBe(true)
})
})
})

0 comments on commit 80921b6

Please sign in to comment.