Skip to content

feat: replace eslint-loader by eslint-webpack-plugin #6094

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 10 commits into from
Dec 7, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,27 @@ describe('nightwatch e2e plugin', () => {
})

test('should accept the --url cli option', async () => {
await project.run(`vue-cli-service build`)
const server = createServer({ root: path.join(project.dir, 'dist') })
await new Promise((resolve, reject) => {
server.listen(8080, err => {
if (err) return reject(err)
resolve()
let server
try {
await project.run(`vue-cli-service build`)
server = createServer({ root: path.join(project.dir, 'dist') })
await new Promise((resolve, reject) => {
server.listen(8080, err => {
if (err) return reject(err)
resolve()
})
})
})
await project.run(`vue-cli-service test:e2e --headless --url http://127.0.0.1:8080/`)
server.close()

let results = await project.read('test_results.json')
results = JSON.parse(results)
expect(Object.keys(results.modules)).toEqual([
'test-with-pageobjects',
'test'
])
await project.run(`vue-cli-service test:e2e --headless --url http://127.0.0.1:8080/`)

let results = await project.read('test_results.json')
results = JSON.parse(results)
expect(Object.keys(results.modules)).toEqual([
'test-with-pageobjects',
'test'
])
} finally {
server && server.close()
}
})

test('should run single test with custom nightwatch.json', async () => {
Expand Down
30 changes: 30 additions & 0 deletions packages/@vue/cli-plugin-eslint/__tests__/eslintPlugin.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,33 @@ test('should save report results to file with --output-file option', async () =>
// results file should show "Missing semicolon" errors
expect(resultsFileContents).toEqual(expect.stringContaining('Missing semicolon'))
})

test('should persist cache', async () => {
const project = await create('eslint-cache', {
plugins: {
'@vue/cli-plugin-eslint': {
config: 'airbnb',
lintOn: 'save'
}
}
})

let done
const donePromise = new Promise(resolve => {
done = resolve
})
const { has, run } = project
const server = run('vue-cli-service serve')

server.stdout.on('data', data => {
data = data.toString()
if (data.match(/Compiled successfully/)) {
server.stdin.write('close')
done()
}
})

await donePromise

expect(has('node_modules/.cache/eslint/cache.json')).toBe(true)
})
73 changes: 35 additions & 38 deletions packages/@vue/cli-plugin-eslint/index.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,60 @@
const path = require('path')
const eslintWebpackPlugin = require('eslint-webpack-plugin')

/** @type {import('@vue/cli-service').ServicePlugin} */
module.exports = (api, options) => {
if (options.lintOnSave) {
const extensions = require('./eslintOptions').extensions(api)
// Use loadModule to allow users to customize their ESLint dependency version.
const { resolveModule, loadModule } = require('@vue/cli-shared-utils')
const cwd = api.getCwd()

const eslintPkg =
loadModule('eslint/package.json', cwd, true) ||
loadModule('eslint/package.json', __dirname, true)

// eslint-loader doesn't bust cache when eslint config changes
// so we have to manually generate a cache identifier that takes the config
// into account.
const { cacheIdentifier } = api.genCacheConfig(
'eslint-loader',
// ESLint doesn't clear the cache when you upgrade ESLint plugins (ESlint do consider config changes)
// so we have to manually generate a cache identifier that takes lock file into account.
const { cacheIdentifier, cacheDirectory } = api.genCacheConfig(
'eslint',
{
'eslint-loader': require('eslint-loader/package.json').version,
eslint: eslintPkg.version
},
[
'.eslintrc.js',
'.eslintrc.yaml',
'.eslintrc.yml',
'.eslintrc.json',
'.eslintrc',
'.eslintignore',
'package.json'
]
['package.json']
)

api.chainWebpack(webpackConfig => {
const { lintOnSave } = options
const allWarnings = lintOnSave === true || lintOnSave === 'warning'
const allErrors = lintOnSave === 'error'

webpackConfig.module
.rule('eslint')
.pre()
.exclude
.add(/node_modules/)
.add(path.dirname(require.resolve('@vue/cli-service')))
.end()
.test(/\.(vue|(j|t)sx?)$/)
.use('eslint-loader')
.loader(require.resolve('eslint-loader'))
.options({
extensions,
cache: true,
cacheIdentifier,
emitWarning: allWarnings,
// only emit errors in production mode.
emitError: allErrors,
eslintPath: path.dirname(
resolveModule('eslint/package.json', cwd) ||
resolveModule('eslint/package.json', __dirname)
),
formatter: loadModule('eslint/lib/formatters/codeframe', cwd, true)
})
/** @type {import('eslint-webpack-plugin').Options & import('eslint').ESLint.Options} */
const eslintWebpackPluginOptions = {
// common to both plugin and ESlint
extensions,
// ESlint options
cwd,
cache: true,
cacheLocation: path.format({
dir: cacheDirectory,
name: process.env.VUE_CLI_TEST
? 'cache'
: cacheIdentifier,
ext: '.json'
}),
// plugin options
context: cwd,
// https://github.com/webpack-contrib/eslint-webpack-plugin/issues/56
threads: false,
emitWarning: allWarnings,
emitError: allErrors,
eslintPath: path.dirname(
resolveModule('eslint/package.json', cwd) ||
resolveModule('eslint/package.json', __dirname)
),
formatter: loadModule('eslint/lib/formatters/codeframe', cwd, true)
}
webpackConfig.plugin('eslint').use(eslintWebpackPlugin, [eslintWebpackPluginOptions])
})
}

Expand Down
2 changes: 1 addition & 1 deletion packages/@vue/cli-plugin-eslint/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
},
"dependencies": {
"@vue/cli-shared-utils": "^4.5.8",
"eslint-loader": "^4.0.2",
"eslint-webpack-plugin": "^2.4.1",
"globby": "^9.2.0",
"inquirer": "^7.1.0",
"webpack": "^5.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')
const cwd = path.resolve(__dirname, 'temp')
const binPath = require.resolve('@vue/cli/bin/vue')
const write = (file, content) => fs.writeFile(path.join(cwd, file), content)
const remove = (file) => fs.remove(path.join(cwd, file))

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

Expand All @@ -26,6 +27,7 @@ beforeEach(async () => {
await write('App.vue', entryVue)
await write('Other.vue', entryVue)
await write('foo.js', entryJs)
await remove('node_modules/.cache')
})

test('global serve', async () => {
Expand Down Expand Up @@ -55,9 +57,12 @@ test('global serve', async () => {

test('global serve with eslint', async () => {
try {
const cachePath = path.join(cwd, 'node_modules/.cache/eslint/cache.json')
expect(fs.existsSync(cachePath)).toBe(false)
await serve(
() => execa(binPath, ['serve', 'foo.js'], { cwd }),
async ({ page, nextUpdate, helpers }) => {
async ({ nextUpdate, helpers }) => {
expect(fs.existsSync(cachePath)).toBe(true)
expect(await helpers.getText('h1')).toMatch('hi')

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

let server, browser, page
Expand Down
57 changes: 31 additions & 26 deletions packages/@vue/cli-service-global/lib/globalConfigPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { loadPartialConfigSync } = require('@babel/core')
module.exports = function createConfigPlugin (context, entry, asLib) {
return {
id: '@vue/cli-service-global-config',
/** @type {import('@vue/cli-service').ServicePlugin} */
apply: (api, options) => {
const _entry = path.resolve(context, entry)
api.chainWebpack(config => {
Expand Down Expand Up @@ -85,6 +86,7 @@ module.exports = function createConfigPlugin (context, entry, asLib) {
// messed up when the project is inside another project.
const ESLintConfigFile = findExisting(context, [
'.eslintrc.js',
'.eslintrc.cjs',
'.eslintrc.yaml',
'.eslintrc.yml',
'.eslintrc.json',
Expand All @@ -99,33 +101,36 @@ module.exports = function createConfigPlugin (context, entry, asLib) {
const hasBabelConfig = !!babelConfig && babelConfig.hasFilesystemConfig()

// set inline eslint options
config.module
.rule('eslint')
.include
.clear()
.end()
.exclude
.add(/node_modules/)
.end()
.use('eslint-loader')
.tap(loaderOptions => Object.assign({}, loaderOptions, {
useEslintrc: hasESLintConfig,
baseConfig: {
extends: [
'plugin:vue/essential',
'eslint:recommended'
],
parserOptions: {
parser: '@babel/eslint-parser',
requireConfigFile: hasBabelConfig,
babelOptions
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
config
.plugin('eslint')
.tap(args => {
/** @type {import('eslint-webpack-plugin').Options & import('eslint').ESLint.Options} */
const eslintWebpackPluginOptions = {
// eslint@7 load config and plugin related to baseConfig.extends from cwd,
// By default, cwd is the current working directory of `vue serve`,
// should load baseConfig.extends config(dependencies of @vue/cli-service-global) from `__dirname`
cwd: __dirname,
useEslintrc: hasESLintConfig,
baseConfig: {
extends: [
'plugin:vue/essential',
'eslint:recommended'
],
parserOptions: {
parser: '@babel/eslint-parser',
requireConfigFile: hasBabelConfig,
babelOptions
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}))
}
}
Object.assign(args[0], eslintWebpackPluginOptions)

return args
})

if (!asLib) {
// set html plugin template
Expand Down
Loading