@vue/create-eslint-config
Version:
Utility to setup ESLint in Vue.js projects.
182 lines (159 loc) • 4.98 kB
JavaScript
// @ts-check
import renderEjsFile from './renderEjsFile.js'
import thisPackage from './package.json' with { type: 'json' }
const versionMap = thisPackage.devDependencies
// This is also used in `create-vue`
export default function createConfig({
styleGuide = 'default', // default ~~| airbnb | standard~~ only the default is supported for now
hasTypeScript = false,
needsPrettier = false,
needsOxlint = false,
additionalConfigs = [],
}) {
// This is the pkg object to extend
const pickDependencies = (/** @type {string[]} */ keys) =>
pickKeysFromObject(versionMap, keys)
const pkg = {
devDependencies: pickDependencies(['eslint', 'eslint-plugin-vue']),
scripts: {},
}
const fileExtensions = ['vue']
if (hasTypeScript) {
// TODO: allowJs option
fileExtensions.unshift('ts', 'mts', 'tsx')
additionalConfigs.unshift({
devDependencies: pickDependencies(['@vue/eslint-config-typescript', 'jiti']),
})
} else {
fileExtensions.unshift('js', 'mjs', 'jsx')
additionalConfigs.unshift({
devDependencies: pickDependencies(['@eslint/js']),
})
}
if (needsOxlint) {
additionalConfigs.push({
devDependencies: pickDependencies([
'oxlint',
'eslint-plugin-oxlint',
'npm-run-all2',
]),
afterVuePlugin: [
{
importer: "import oxlint from 'eslint-plugin-oxlint'",
content: "oxlint.configs['flat/recommended'],",
},
],
})
pkg.scripts['lint:oxlint'] = 'oxlint . --fix -D correctness --ignore-path .gitignore'
pkg.scripts['lint:eslint'] = 'eslint . --fix'
pkg.scripts.lint = 'run-s lint:*'
} else {
pkg.scripts.lint = 'eslint . --fix'
}
if (needsPrettier) {
additionalConfigs.push({
devDependencies: pickDependencies([
'prettier',
'@vue/eslint-config-prettier',
]),
afterVuePlugin: [
{
importer:
"import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'",
content: 'skipFormatting,',
},
],
})
// Default to only format the `src/` directory to avoid too much noise, and
// the need for a `.prettierignore` file.
// Users can still append any paths they'd like to format to the command,
// e.g. `npm run format cypress/`.
pkg.scripts.format = 'prettier --write src/'
}
const configsBeforeVuePlugin = [],
configsAfterVuePlugin = []
for (const config of additionalConfigs) {
deepMerge(pkg.devDependencies, config.devDependencies ?? {})
configsBeforeVuePlugin.push(...(config.beforeVuePlugin ?? []))
configsAfterVuePlugin.push(...(config.afterVuePlugin ?? []))
}
const templateData = {
styleGuide,
needsPrettier,
fileExtensions,
configsBeforeVuePlugin,
configsAfterVuePlugin,
}
const files = {
'.editorconfig': renderEjsFile(
'./templates/_editorconfig.ejs',
templateData,
),
}
if (hasTypeScript) {
files['eslint.config.ts'] = renderEjsFile(
'./templates/eslint.config.ts.ejs',
templateData,
)
} else {
files['eslint.config.js'] = renderEjsFile(
'./templates/eslint.config.js.ejs',
templateData,
)
}
// .editorconfig & .prettierrc.json
if (needsPrettier) {
// Prettier recommends an explicit configuration file to let the editor know that it's used.
files['.prettierrc.json'] = renderEjsFile(
'./templates/_prettierrc.json.ejs',
templateData,
)
}
if (styleGuide !== 'standard' || needsPrettier) {
// TODO:
// I used renderEjsFile instead of readFileSync for simplicity and easier integration
// with create-vue. But it's ugly.
// Should refactor later, or move this project into create-vue.
files['.gitattributes'] = renderEjsFile('./templates/_gitattributes', {})
}
return {
pkg,
files,
}
}
/**
* Picks specified keys from an object.
*
* @param {Object} obj - The source object.
* @param {string[]} keys - The keys to pick from the object.
* @returns {Object} - A new object with the picked keys.
*/
function pickKeysFromObject(obj, keys) {
return keys.reduce((acc, key) => {
if (key in obj) {
acc[key] = obj[key]
}
return acc
}, {})
}
const isObject = val => val && typeof val === 'object'
const mergeArrayWithDedupe = (a, b) => Array.from(new Set([...a, ...b]))
/**
* Recursively merge the content of the new object to the existing one
* @param {object} target the existing object
* @param {object} obj the new object
*/
export function deepMerge(target, obj) {
for (const key of Object.keys(obj)) {
const oldVal = target[key]
const newVal = obj[key]
if (Array.isArray(oldVal) && Array.isArray(newVal)) {
target[key] = mergeArrayWithDedupe(oldVal, newVal)
} else if (isObject(oldVal) && isObject(newVal)) {
target[key] = deepMerge(oldVal, newVal)
} else {
target[key] = newVal
}
}
return target
}