Skip to content

Commit

Permalink
removes eslint flag, adds lint cli command, includes eslint in create…
Browse files Browse the repository at this point in the history
… next app templates, and documentation
  • Loading branch information
housseindjirdeh committed May 24, 2021
1 parent f29dfe2 commit ee29a91
Show file tree
Hide file tree
Showing 31 changed files with 467 additions and 191 deletions.
12 changes: 11 additions & 1 deletion docs/api-reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Usage
$ next <command>

Available commands
build, start, export, dev, telemetry
build, start, export, dev, lint, telemetry

Options
--version, -v Version number
Expand Down Expand Up @@ -84,6 +84,16 @@ The application will start at `http://localhost:3000` by default. The default po
npx next start -p 4000
```

## Lint

`next lint` runs ESLint for all files in the `pages` directory and provides a guided setup to install any required dependencies if ESLint is not already configured in your application.

You can also run ESLint on other directories with the `--dir` flag:

```bash
next lint --dir components
```

## Telemetry

Next.js collects **completely anonymous** telemetry data about general usage.
Expand Down
113 changes: 113 additions & 0 deletions docs/basic-features/eslint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
description: Next.js supports ESLint by default. You can get started with ESLint in Next.js here.
---

# ESLint

Next.js provides an integrated [ESLint](https://eslint.org/) experience out of the box. To get started, run `next lint`:

```bash
next lint
```

If you don't already have ESLint configured in your application, you will be guided through the installation of any required packages.

```bash
next lint

# You'll see instructions like these:
#
# Please install eslint and eslint-config-next by running:
#
# yarn add --dev eslint eslint-config-next
#
# ...
```

If no ESLint configuration is present, Next.js will create an `.eslintrc` file in the root of your project and automatically configure it with the base configuration:

```
{
"extends": "next"
}
```

Now you can run `next lint` every time you want to run ESLint to catch

> The default base configuration (`"extends": "next"`) can be updated at any time and will only be included if no ESLint configuration is present.
We recommend using an appropriate [integration](https://eslint.org/docs/user-guide/integrations#editors) to view warnings and errors directly in your code editor during development.

## Linting During Builds

Once ESLint has been set up, it will automatically run during every build (`next build`). Errors will fail the build while warnings will not.

If you do not want ESLint to run as a build step, it can be disabled using the `--no-lint` flag:

```bash
next build --no-lint
```

This is not recommended unless you have configured ESLint to run in a separate part of your workflow (for example, in CI or a pre-commit hook).

## Linting Custom Directories

By default, Next.js will only run ESLint for all files in the `pages/` directory. However, you can specify other custom directories to run by using the `--dir` flag in `next lint`:

```bash
next lint --dir components --dir lib
```

## ESLint Plugin

Next.js provides an ESLint plugin, [`eslint-plugin-next`](https://www.npmjs.com/package/@next/eslint-plugin-next), that makes it easier to catch common issues and problems in a Next.js application. The full set of rules can be found in the [package repository](https://github.com/vercel/next.js/tree/master/packages/eslint-plugin-next/lib/rules).

## Base Configuration

The Next.js base ESLint configuration is automatically generated when `next lint` is run for the first time:

```
{
"extends": "next"
}
```

This configuration extends recommended rule sets from [`eslint-plugin-react`](https://www.npmjs.com/package/eslint-plugin-react), [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks), and [`eslint-plugin-next`](https://www.npmjs.com/package/@next/eslint-plugin-next). You can see the full details of the shareable configuration in the [`eslint-config-next`](https://www.npmjs.com/package/eslint-config-next) package.

If you would like to modify any rules provided by the supported plugins (`react`, `react-hooks`, `next`), you can directly modify them using the `rules` property:

```
{
"extends": "next",
"rules": {
"react/no-unescaped-entities": "off",
"@next/next/no-page-custom-font": "error",
}
}
```

> **Note**: If you need to also include a separate, custom ESLint configuration, it is highly recommended that `eslint-config-next` is extended last after other configurations. For example:
>
> ```
> {
> "extends": ["eslint:recommended", "next"]
> }
> ```
>
> The `next` configuration already handles setting default values for the `parser`, `plugins` and `settings` properties.
> There is no need to manually re-declare any of these properties unless you need a different configuration for your use case.
> If you include any other shareable configurations, you will need to make sure that these properties are not overwritten or modified.
### Core Web Vitals

A stricter `next/core-web-vitals` entrypoint can also be specified in `.eslintrc`:

```
{
"extends": ["next", "next/core-web-vitals"]
}
```

This does not include any additional rules not already provided by the base `eslint-config-next` configuration, but will error on a number of important rules in `eslint-plugin-next` instead of provide a warning if they are likely to affect [Core Web Vitals](https://web.dev/vitals/).

> Both `next` and `next/core-web-vitals` entry points are automatically included for new applications built with [Create Next App](/docs/api-reference/create-next-app.md).
4 changes: 3 additions & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ Open `package.json` and add the following `scripts`:
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
"start": "next start",
"lint": "next lint"
}
```

Expand All @@ -64,6 +65,7 @@ These scripts refer to the different stages of developing an application:
- `dev` - Runs [`next dev`](/docs/api-reference/cli.md#development) which starts Next.js in development mode
- `build` - Runs [`next build`](/docs/api-reference/cli.md#build) which builds the application for production usage
- `start` - Runs [`next start`](/docs/api-reference/cli.md#production) which starts a Next.js production server
- `lint` - Runs [`next lint`](/docs/api-reference/cli.md#lint) which sets up Next.js' built-in ESLint configuration

Next.js is built around the concept of [pages](/docs/basic-features/pages.md). A page is a [React Component](https://reactjs.org/docs/components-and-props.html) exported from a `.js`, `.jsx`, `.ts`, or `.tsx` file in the `pages` directory.

Expand Down
4 changes: 4 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
"title": "Fast Refresh",
"path": "/docs/basic-features/fast-refresh.md"
},
{
"title": "ESLint",
"path": "/docs/basic-features/eslint.md"
},
{
"title": "TypeScript",
"path": "/docs/basic-features/typescript.md"
Expand Down
6 changes: 4 additions & 2 deletions packages/create-next-app/create-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export async function createApp({
dev: 'next dev',
build: 'next build',
start: 'next start',
lint: 'next lint',
},
}
/**
Expand All @@ -207,7 +208,7 @@ export async function createApp({
/**
* Default devDependencies.
*/
const devDependencies = []
const devDependencies = ['eslint', 'eslint-config-next']
/**
* TypeScript projects will have type definitions and other devDependencies.
*/
Expand Down Expand Up @@ -250,7 +251,8 @@ export async function createApp({
cwd: path.join(__dirname, 'templates', template),
rename: (name) => {
switch (name) {
case 'gitignore': {
case 'gitignore':
case 'eslintrc': {
return '.'.concat(name)
}
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
Expand Down
3 changes: 3 additions & 0 deletions packages/create-next-app/templates/default/eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next", "next/core-web-vitals"]
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction

export default (req, res) => {
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
3 changes: 3 additions & 0 deletions packages/create-next-app/templates/typescript/eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next", "next/core-web-vitals"]
}
3 changes: 3 additions & 0 deletions packages/create-next-app/templates/typescript/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
eslint: true,
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ type Data = {
name: string
}

export default (req: NextApiRequest, res: NextApiResponse<Data>) => {
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
res.status(200).json({ name: 'John Doe' })
}
8 changes: 8 additions & 0 deletions packages/eslint-config-next/core-web-vitals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
extends: ['.'].map(require.resolve),
rules: {
'@next/next/no-sync-scripts': 2,
'@next/next/no-html-link-for-pages': 2,
'@next/next/no-img-element': 2,
},
}
1 change: 1 addition & 0 deletions packages/eslint-config-next/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = {
rules: {
'import/no-anonymous-default-export': 'warn',
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
},
parser: './parser.js',
parserOptions: {
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-next/lib/rules/no-sync-scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = function (context) {
context.report({
node,
message:
'Synchronous scripts are forbidden. See: https://nextjs.org/docs/messages/no-sync-scripts.',
'External synchronous scripts are forbidden. See: https://nextjs.org/docs/messages/no-sync-scripts.',
})
}
},
Expand Down
1 change: 1 addition & 0 deletions packages/next/bin/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const commands: { [command: string]: () => Promise<cliCommand> } = {
start: () => import('../cli/next-start').then((i) => i.nextStart),
export: () => import('../cli/next-export').then((i) => i.nextExport),
dev: () => import('../cli/next-dev').then((i) => i.nextDev),
lint: () => import('../cli/next-lint').then((i) => i.nextLint),
telemetry: () => import('../cli/next-telemetry').then((i) => i.nextTelemetry),
}

Expand Down
6 changes: 3 additions & 3 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ export default async function build(
dir: string,
conf = null,
reactProductionProfiling = false,
debugOutput = false
debugOutput = false,
runLint = true
): Promise<void> {
const nextBuildSpan = trace('next-build')

Expand Down Expand Up @@ -212,13 +213,12 @@ export default async function build(
typeCheckingSpinner.stopAndPersist()
}

if (config.experimental.eslint) {
if (runLint) {
await nextBuildSpan
.traceChild('verify-and-lint')
.traceAsyncFn(async () => {
await verifyAndLint(
dir,
pagesDir,
config.experimental.cpus,
config.experimental.workerThreads
)
Expand Down
9 changes: 8 additions & 1 deletion packages/next/cli/next-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const nextBuild: cliCommand = (argv) => {
'--help': Boolean,
'--profile': Boolean,
'--debug': Boolean,
'--no-lint': Boolean,
// Aliases
'-h': '--help',
'-d': '--debug',
Expand Down Expand Up @@ -41,13 +42,17 @@ const nextBuild: cliCommand = (argv) => {
Options
--profile Can be used to enable React Production Profiling
--no-lint Disable linting
`,
0
)
}
if (args['--profile']) {
Log.warn('Profiling is enabled. Note: This may affect performance')
}
if (args['--no-lint']) {
Log.warn('Linting is disabled')
}
const dir = resolve(args._[0] || '.')

// Check if the provided directory exists
Expand Down Expand Up @@ -93,7 +98,9 @@ const nextBuild: cliCommand = (argv) => {
}

return preflight()
.then(() => build(dir, null, args['--profile'], args['--debug']))
.then(() =>
build(dir, null, args['--profile'], args['--debug'], !args['--no-lint'])
)
.catch((err) => {
console.error('')
console.error('> Build error occurred')
Expand Down
76 changes: 76 additions & 0 deletions packages/next/cli/next-lint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env node
import { existsSync } from 'fs'
import arg from 'next/dist/compiled/arg/index.js'
import { resolve, join } from 'path'
import { cliCommand } from '../bin/next'
import { runLintCheck } from '../lib/eslint/runLintCheck'
import { printAndExit } from '../server/lib/utils'

const nextLint: cliCommand = (argv) => {
const validArgs: arg.Spec = {
// Types
'--help': Boolean,
'--dir': [String],

// Aliases
'-h': '--help',
'-d': '--dir',
}

let args: arg.Result<arg.Spec>
try {
args = arg(validArgs, { argv })
} catch (error) {
if (error.code === 'ARG_UNKNOWN_OPTION') {
return printAndExit(error.message, 1)
}
throw error
}
if (args['--help']) {
printAndExit(
`
Description
Run ESLint on every file in specified directories.
If not configured, ESLint will be set up for the first time.
Usage
$ next lint <baseDir> [options]
<baseDir> represents the directory of the Next.js application.
If no directory is provided, the current directory will be used.
Options
-h - list this help
-d - set directory, or directories, to run ESLint (defaults to only 'pages')
`,
0
)
}

const baseDir = resolve(args._[0] || '.')

// Check if the provided directory exists
if (!existsSync(baseDir)) {
printAndExit(`> No such directory exists as the project root: ${baseDir}`)
}

const dirs: string[] = args['--dir']
const lintDirs = dirs
? dirs.reduce((res: string[], d: string) => {
const currDir = join(baseDir, d)
if (!existsSync(currDir)) return res
res.push(currDir)
return res
}, [])
: null

runLintCheck(baseDir, lintDirs)
.then((results) => {
if (results) console.log(results)
})
.catch((err) => {
printAndExit(err.message)
})
}

export { nextLint }
Loading

0 comments on commit ee29a91

Please sign in to comment.