Skip to content

Commit

Permalink
feat: replace eslint-loader by eslint-webpack-plugin (#6094)
Browse files Browse the repository at this point in the history
closes #5926, using eslint-webpack-plugin can fix the problem of eslint-loader caused by cache-loader

closes #3065 
closes #5399 
closes #4425
  • Loading branch information
fangbinwei authored Dec 7, 2020
1 parent 17339d7 commit a153af8
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 545 deletions.
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

0 comments on commit a153af8

Please sign in to comment.