Skip to content

Commit a153af8

Browse files
authored
feat: replace eslint-loader by eslint-webpack-plugin (#6094)
closes #5926, using eslint-webpack-plugin can fix the problem of eslint-loader caused by cache-loader closes #3065 closes #5399 closes #4425
1 parent 17339d7 commit a153af8

File tree

7 files changed

+165
-545
lines changed

7 files changed

+165
-545
lines changed

packages/@vue/cli-plugin-e2e-nightwatch/__tests__/nightwatchPlugin.spec.js

+20-16
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,27 @@ describe('nightwatch e2e plugin', () => {
4242
})
4343

4444
test('should accept the --url cli option', async () => {
45-
await project.run(`vue-cli-service build`)
46-
const server = createServer({ root: path.join(project.dir, 'dist') })
47-
await new Promise((resolve, reject) => {
48-
server.listen(8080, err => {
49-
if (err) return reject(err)
50-
resolve()
45+
let server
46+
try {
47+
await project.run(`vue-cli-service build`)
48+
server = createServer({ root: path.join(project.dir, 'dist') })
49+
await new Promise((resolve, reject) => {
50+
server.listen(8080, err => {
51+
if (err) return reject(err)
52+
resolve()
53+
})
5154
})
52-
})
53-
await project.run(`vue-cli-service test:e2e --headless --url http://127.0.0.1:8080/`)
54-
server.close()
55-
56-
let results = await project.read('test_results.json')
57-
results = JSON.parse(results)
58-
expect(Object.keys(results.modules)).toEqual([
59-
'test-with-pageobjects',
60-
'test'
61-
])
55+
await project.run(`vue-cli-service test:e2e --headless --url http://127.0.0.1:8080/`)
56+
57+
let results = await project.read('test_results.json')
58+
results = JSON.parse(results)
59+
expect(Object.keys(results.modules)).toEqual([
60+
'test-with-pageobjects',
61+
'test'
62+
])
63+
} finally {
64+
server && server.close()
65+
}
6266
})
6367

6468
test('should run single test with custom nightwatch.json', async () => {

packages/@vue/cli-plugin-eslint/__tests__/eslintPlugin.spec.js

+30
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,33 @@ test('should save report results to file with --output-file option', async () =>
195195
// results file should show "Missing semicolon" errors
196196
expect(resultsFileContents).toEqual(expect.stringContaining('Missing semicolon'))
197197
})
198+
199+
test('should persist cache', async () => {
200+
const project = await create('eslint-cache', {
201+
plugins: {
202+
'@vue/cli-plugin-eslint': {
203+
config: 'airbnb',
204+
lintOn: 'save'
205+
}
206+
}
207+
})
208+
209+
let done
210+
const donePromise = new Promise(resolve => {
211+
done = resolve
212+
})
213+
const { has, run } = project
214+
const server = run('vue-cli-service serve')
215+
216+
server.stdout.on('data', data => {
217+
data = data.toString()
218+
if (data.match(/Compiled successfully/)) {
219+
server.stdin.write('close')
220+
done()
221+
}
222+
})
223+
224+
await donePromise
225+
226+
expect(has('node_modules/.cache/eslint/cache.json')).toBe(true)
227+
})

packages/@vue/cli-plugin-eslint/index.js

+35-38
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,60 @@
11
const path = require('path')
2+
const eslintWebpackPlugin = require('eslint-webpack-plugin')
23

4+
/** @type {import('@vue/cli-service').ServicePlugin} */
35
module.exports = (api, options) => {
46
if (options.lintOnSave) {
57
const extensions = require('./eslintOptions').extensions(api)
68
// Use loadModule to allow users to customize their ESLint dependency version.
79
const { resolveModule, loadModule } = require('@vue/cli-shared-utils')
810
const cwd = api.getCwd()
11+
912
const eslintPkg =
1013
loadModule('eslint/package.json', cwd, true) ||
1114
loadModule('eslint/package.json', __dirname, true)
1215

13-
// eslint-loader doesn't bust cache when eslint config changes
14-
// so we have to manually generate a cache identifier that takes the config
15-
// into account.
16-
const { cacheIdentifier } = api.genCacheConfig(
17-
'eslint-loader',
16+
// ESLint doesn't clear the cache when you upgrade ESLint plugins (ESlint do consider config changes)
17+
// so we have to manually generate a cache identifier that takes lock file into account.
18+
const { cacheIdentifier, cacheDirectory } = api.genCacheConfig(
19+
'eslint',
1820
{
19-
'eslint-loader': require('eslint-loader/package.json').version,
2021
eslint: eslintPkg.version
2122
},
22-
[
23-
'.eslintrc.js',
24-
'.eslintrc.yaml',
25-
'.eslintrc.yml',
26-
'.eslintrc.json',
27-
'.eslintrc',
28-
'.eslintignore',
29-
'package.json'
30-
]
23+
['package.json']
3124
)
3225

3326
api.chainWebpack(webpackConfig => {
3427
const { lintOnSave } = options
3528
const allWarnings = lintOnSave === true || lintOnSave === 'warning'
3629
const allErrors = lintOnSave === 'error'
3730

38-
webpackConfig.module
39-
.rule('eslint')
40-
.pre()
41-
.exclude
42-
.add(/node_modules/)
43-
.add(path.dirname(require.resolve('@vue/cli-service')))
44-
.end()
45-
.test(/\.(vue|(j|t)sx?)$/)
46-
.use('eslint-loader')
47-
.loader(require.resolve('eslint-loader'))
48-
.options({
49-
extensions,
50-
cache: true,
51-
cacheIdentifier,
52-
emitWarning: allWarnings,
53-
// only emit errors in production mode.
54-
emitError: allErrors,
55-
eslintPath: path.dirname(
56-
resolveModule('eslint/package.json', cwd) ||
57-
resolveModule('eslint/package.json', __dirname)
58-
),
59-
formatter: loadModule('eslint/lib/formatters/codeframe', cwd, true)
60-
})
31+
/** @type {import('eslint-webpack-plugin').Options & import('eslint').ESLint.Options} */
32+
const eslintWebpackPluginOptions = {
33+
// common to both plugin and ESlint
34+
extensions,
35+
// ESlint options
36+
cwd,
37+
cache: true,
38+
cacheLocation: path.format({
39+
dir: cacheDirectory,
40+
name: process.env.VUE_CLI_TEST
41+
? 'cache'
42+
: cacheIdentifier,
43+
ext: '.json'
44+
}),
45+
// plugin options
46+
context: cwd,
47+
// https://github.com/webpack-contrib/eslint-webpack-plugin/issues/56
48+
threads: false,
49+
emitWarning: allWarnings,
50+
emitError: allErrors,
51+
eslintPath: path.dirname(
52+
resolveModule('eslint/package.json', cwd) ||
53+
resolveModule('eslint/package.json', __dirname)
54+
),
55+
formatter: loadModule('eslint/lib/formatters/codeframe', cwd, true)
56+
}
57+
webpackConfig.plugin('eslint').use(eslintWebpackPlugin, [eslintWebpackPluginOptions])
6158
})
6259
}
6360

packages/@vue/cli-plugin-eslint/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
},
2525
"dependencies": {
2626
"@vue/cli-shared-utils": "^4.5.8",
27-
"eslint-loader": "^4.0.2",
27+
"eslint-webpack-plugin": "^2.4.1",
2828
"globby": "^9.2.0",
2929
"inquirer": "^7.1.0",
3030
"webpack": "^5.0.0",

packages/@vue/cli-service-global/__tests__/globalService.spec.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')
1111
const cwd = path.resolve(__dirname, 'temp')
1212
const binPath = require.resolve('@vue/cli/bin/vue')
1313
const write = (file, content) => fs.writeFile(path.join(cwd, file), content)
14+
const remove = (file) => fs.remove(path.join(cwd, file))
1415

1516
const entryVue = fs.readFileSync(path.resolve(__dirname, 'entry.vue'), 'utf-8')
1617

@@ -26,6 +27,7 @@ beforeEach(async () => {
2627
await write('App.vue', entryVue)
2728
await write('Other.vue', entryVue)
2829
await write('foo.js', entryJs)
30+
await remove('node_modules/.cache')
2931
})
3032

3133
test('global serve', async () => {
@@ -55,9 +57,12 @@ test('global serve', async () => {
5557

5658
test('global serve with eslint', async () => {
5759
try {
60+
const cachePath = path.join(cwd, 'node_modules/.cache/eslint/cache.json')
61+
expect(fs.existsSync(cachePath)).toBe(false)
5862
await serve(
5963
() => execa(binPath, ['serve', 'foo.js'], { cwd }),
60-
async ({ page, nextUpdate, helpers }) => {
64+
async ({ nextUpdate, helpers }) => {
65+
expect(fs.existsSync(cachePath)).toBe(true)
6166
expect(await helpers.getText('h1')).toMatch('hi')
6267

6368
write('foo.js', entryJs.replace(`$mount('#app')`, `$mount('#app');`))
@@ -72,7 +77,7 @@ test('global serve with eslint', async () => {
7277
// Failed because of no-extra-semi
7378
expect(err).toMatch('Failed to compile with 1 errors')
7479
}
75-
expect.assertions(3)
80+
expect.assertions(5)
7681
})
7782

7883
let server, browser, page

packages/@vue/cli-service-global/lib/globalConfigPlugin.js

+31-26
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { loadPartialConfigSync } = require('@babel/core')
66
module.exports = function createConfigPlugin (context, entry, asLib) {
77
return {
88
id: '@vue/cli-service-global-config',
9+
/** @type {import('@vue/cli-service').ServicePlugin} */
910
apply: (api, options) => {
1011
const _entry = path.resolve(context, entry)
1112
api.chainWebpack(config => {
@@ -85,6 +86,7 @@ module.exports = function createConfigPlugin (context, entry, asLib) {
8586
// messed up when the project is inside another project.
8687
const ESLintConfigFile = findExisting(context, [
8788
'.eslintrc.js',
89+
'.eslintrc.cjs',
8890
'.eslintrc.yaml',
8991
'.eslintrc.yml',
9092
'.eslintrc.json',
@@ -99,33 +101,36 @@ module.exports = function createConfigPlugin (context, entry, asLib) {
99101
const hasBabelConfig = !!babelConfig && babelConfig.hasFilesystemConfig()
100102

101103
// set inline eslint options
102-
config.module
103-
.rule('eslint')
104-
.include
105-
.clear()
106-
.end()
107-
.exclude
108-
.add(/node_modules/)
109-
.end()
110-
.use('eslint-loader')
111-
.tap(loaderOptions => Object.assign({}, loaderOptions, {
112-
useEslintrc: hasESLintConfig,
113-
baseConfig: {
114-
extends: [
115-
'plugin:vue/essential',
116-
'eslint:recommended'
117-
],
118-
parserOptions: {
119-
parser: '@babel/eslint-parser',
120-
requireConfigFile: hasBabelConfig,
121-
babelOptions
122-
},
123-
rules: {
124-
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
125-
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
126-
}
104+
config
105+
.plugin('eslint')
106+
.tap(args => {
107+
/** @type {import('eslint-webpack-plugin').Options & import('eslint').ESLint.Options} */
108+
const eslintWebpackPluginOptions = {
109+
// eslint@7 load config and plugin related to baseConfig.extends from cwd,
110+
// By default, cwd is the current working directory of `vue serve`,
111+
// should load baseConfig.extends config(dependencies of @vue/cli-service-global) from `__dirname`
112+
cwd: __dirname,
113+
useEslintrc: hasESLintConfig,
114+
baseConfig: {
115+
extends: [
116+
'plugin:vue/essential',
117+
'eslint:recommended'
118+
],
119+
parserOptions: {
120+
parser: '@babel/eslint-parser',
121+
requireConfigFile: hasBabelConfig,
122+
babelOptions
123+
},
124+
rules: {
125+
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
126+
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
127127
}
128-
}))
128+
}
129+
}
130+
Object.assign(args[0], eslintWebpackPluginOptions)
131+
132+
return args
133+
})
129134

130135
if (!asLib) {
131136
// set html plugin template

0 commit comments

Comments
 (0)