diff --git a/packages/airbnb-base/index.js b/packages/airbnb-base/index.js index 17bd00c8e..c8420655d 100644 --- a/packages/airbnb-base/index.js +++ b/packages/airbnb-base/index.js @@ -8,6 +8,9 @@ const { module.exports = ({ eslint = {}, ...opts } = {}) => (neutrino) => { const baseConfig = eslint.baseConfig || {}; + const usedPlugins = ['import']; + const usedResolvers = { node: {} }; + neutrino.use( lint({ ...opts, @@ -41,4 +44,19 @@ module.exports = ({ eslint = {}, ...opts } = {}) => (neutrino) => { }, }), ); + + lint.aliasPlugins( + { + plugins: usedPlugins, + }, + __filename, + ); + lint.aliasImportResolvers( + { + settings: { + 'import/resolver': usedResolvers, + }, + }, + __filename, + ); }; diff --git a/packages/airbnb-base/package.json b/packages/airbnb-base/package.json index 7a610a46f..e41eaee93 100644 --- a/packages/airbnb-base/package.json +++ b/packages/airbnb-base/package.json @@ -29,7 +29,8 @@ "dependencies": { "@neutrinojs/eslint": "9.5.0", "eslint-config-airbnb-base": "^14.2.1", - "eslint-plugin-import": "^2.22.1" + "eslint-plugin-import": "^2.22.1", + "eslint-import-resolver-node": "0.3.4" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0", diff --git a/packages/airbnb/index.js b/packages/airbnb/index.js index e26281df2..afb63df5d 100644 --- a/packages/airbnb/index.js +++ b/packages/airbnb/index.js @@ -8,6 +8,9 @@ const { module.exports = ({ eslint = {}, ...opts } = {}) => (neutrino) => { const baseConfig = eslint.baseConfig || {}; + const usedPlugins = ['react', 'react-hooks', 'jsx-a11y', 'import']; + const usedResolvers = { node: {} }; + neutrino.use( lint({ ...opts, @@ -45,4 +48,19 @@ module.exports = ({ eslint = {}, ...opts } = {}) => (neutrino) => { }, }), ); + + lint.aliasPlugins( + { + plugins: usedPlugins, + }, + __filename, + ); + lint.aliasImportResolvers( + { + settings: { + 'import/resolver': usedResolvers, + }, + }, + __filename, + ); }; diff --git a/packages/airbnb/package.json b/packages/airbnb/package.json index a4b23403f..470f7799e 100644 --- a/packages/airbnb/package.json +++ b/packages/airbnb/package.json @@ -33,7 +33,8 @@ "eslint-plugin-import": "^2.22.1", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.21.5", - "eslint-plugin-react-hooks": "^4.2.0" + "eslint-plugin-react-hooks": "^4.2.0", + "eslint-import-resolver-node": "0.3.4" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0", diff --git a/packages/eslint/README.md b/packages/eslint/README.md index 3d85bdbfa..3153da880 100644 --- a/packages/eslint/README.md +++ b/packages/eslint/README.md @@ -186,6 +186,165 @@ The following is a list of rules and their identifiers which can be overridden: | ------ | ------------------------------------------------------------------------------------------------------------------------------- | -------- | | `lint` | By default, lints JS and JSX files from the `src` and `test` directories using ESLint. Contains a single loader named `eslint`. | all | +## Utility functions + +### Plugins aliasing + +When developing your custom ESLint preset, you may face a problem with sharable +ESLint configs and plugins. This is due to ESLint plugins resolving system which +searches for plugins packages in the project root. This may fail in some +environments, especially in Plug'n'Play. This module provides `aliasPlugins()` +function to resolve this issue in your package providing aliases from package +names to absolute paths. You can import it in 2 ways: +`require('@neutrinojs/eslint/alias-plugins')` or +`require('@neutrinojs/eslint').aliasPlugins`. Example: + +```js +const eslint = require('@neutrinojs/eslint'); +const eslintBaseConfig = { plugins: ['node'] }; + +neutrino.use( + eslint({ + eslint: { + baseConfig: eslintBaseConfig, + }, + }), +); + +lint.aliasPlugins( + // ESLint config that contains used plugins + eslintBaseConfig, + // Path to the current module file, so aliases can be correctly resolved from your package + // In most cases it is always `__filename` + __filename, +); +``` + +If you use 3rd party configs, plugins will not be present in the configuration. +So you have to list them manually just for aliasing. For example: + +```js +const eslint = require('@neutrinojs/eslint'); +const usedPlugins = ['react', 'react-hooks', 'jsx-a11y', 'import']; +const eslintBaseConfig = { + extends: [ + require.resolve('eslint-config-airbnb'), + require.resolve('eslint-config-airbnb/hooks'), + ], +}; + +neutrino.use( + eslint({ + eslint: { + baseConfig: eslintBaseConfig, + }, + }), +); + +lint.aliasPlugins( + // ESLint config that contains only used plugins + { plugins: usedPlugins }, + // Path to the current module file, so aliases can be correctly resolved from your package + // In most cases it is always `__filename` + __filename, +); +``` + +**Important! Make sure all aliased plugins are present in your dependencies in +package.json** + +```json +{ + "dependencies": { + "eslint-plugin-import": "latest", + "eslint-plugin-jsx-a11y": "latest", + "eslint-plugin-react": "latest", + "eslint-plugin-react-hooks": "latest" + } +} +``` + +### Import resolvers aliasing + +Also you may have problems with allocation of import resolvers when +`eslint-plugin-import` is used. This can happen in some environments, especially +in Plug'n'Play. This module provides `aliasImportResolvers()` function to +resolve this issue in your package providing aliases from package names to +absolute paths. You can import it in 2 ways: +`require('@neutrinojs/eslint/alias-import-resolvers')` or +`require('@neutrinojs/eslint').aliasImportResolvers`. Example: + +```js +const eslint = require('@neutrinojs/eslint'); +const eslintBaseConfig = { + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.json'], + }, + }, + }, +}; + +neutrino.use( + eslint({ + eslint: { + baseConfig: eslintBaseConfig, + }, + }), +); + +lint.aliasImportResolvers( + // ESLint config that contains settings + eslintBaseConfig, + // Path to the current module file, so aliases can be correctly resolved from your package + // In most cases it is always `__filename` + __filename, +); +``` + +If you use 3rd party configs, settings will not be present in the configuration. +So you have to imitate them manually just for aliasing. For example: + +```js +const eslint = require('@neutrinojs/eslint'); +const usedResolvers = { node: {} }; +const eslintBaseConfig = { + extends: [require.resolve('eslint-config-standard')], +}; + +neutrino.use( + eslint({ + eslint: { + baseConfig: eslintBaseConfig, + }, + }), +); + +lint.aliasPlugins( + // ESLint config that contains only used resolvers + { + settings: { + 'import/resolver': usedResolvers, + }, + }, + // Path to the current module file, so aliases can be correctly resolved from your package + // In most cases it is always `__filename` + __filename, +); +``` + +**Important! Make sure all aliased import resolvers are present in your +dependencies in package.json** + +```json +{ + "dependencies": { + "eslint-import-resolver-node": "latest" + } +} +``` + ## Contributing This middleware is part of the diff --git a/packages/eslint/alias-import-resolvers.js b/packages/eslint/alias-import-resolvers.js new file mode 100644 index 000000000..632fbe30d --- /dev/null +++ b/packages/eslint/alias-import-resolvers.js @@ -0,0 +1,36 @@ +const moduleAlias = require('module-alias'); + +function toFullName(pluginName) { + const ESLINT_PREFIX = 'eslint-import-resolver-'; + const ORGANIZATION_EXPRESSION = /^(@[\d.A-z-]+)\/(.+)$/; + const nameIsFull = pluginName.indexOf(ESLINT_PREFIX) === 0; + const nameIsOrganization = ORGANIZATION_EXPRESSION.test(pluginName); + + if (nameIsOrganization) { + const [, organizationName, name] = pluginName.match( + ORGANIZATION_EXPRESSION, + ); + + return `${organizationName}/${toFullName(name)}`; + } + + return nameIsFull ? pluginName : `${ESLINT_PREFIX}${pluginName}`; +} + +function aliasModuleFrom(baseFilename = __filename) { + return function aliasImportResolver(importResolverName) { + const resolvedImportResolverPath = require.resolve(importResolverName, { + paths: [baseFilename], + }); + + moduleAlias.addAlias(importResolverName, resolvedImportResolverPath); + }; +} + +module.exports = function aliasImportResolvers(eslintConfig, baseFilename) { + const { settings = {} } = eslintConfig; + const resolver = settings['import/resolver'] || {}; + const resolversNames = Object.keys(resolver); + + resolversNames.map(toFullName).forEach(aliasModuleFrom(baseFilename)); +}; diff --git a/packages/eslint/alias-plugins.js b/packages/eslint/alias-plugins.js new file mode 100644 index 000000000..a5b37ee3a --- /dev/null +++ b/packages/eslint/alias-plugins.js @@ -0,0 +1,34 @@ +const moduleAlias = require('module-alias'); + +function toFullName(pluginName) { + const ESLINT_PREFIX = 'eslint-plugin-'; + const ORGANIZATION_EXPRESSION = /^(@[\d.A-z-]+)\/(.+)$/; + const nameIsFull = pluginName.indexOf(ESLINT_PREFIX) === 0; + const nameIsOrganization = ORGANIZATION_EXPRESSION.test(pluginName); + + if (nameIsOrganization) { + const [, organizationName, name] = pluginName.match( + ORGANIZATION_EXPRESSION, + ); + + return `${organizationName}/${toFullName(name)}`; + } + + return nameIsFull ? pluginName : `${ESLINT_PREFIX}${pluginName}`; +} + +function aliasModuleFrom(baseFilename = __filename) { + return function aliasPlugin(pluginName) { + const resolvedPluginPath = require.resolve(pluginName, { + paths: [baseFilename], + }); + + moduleAlias.addAlias(pluginName, resolvedPluginPath); + }; +} + +module.exports = function aliasPlugins(eslintConfig, baseFilename) { + const { plugins = [] } = eslintConfig; + + plugins.map(toFullName).forEach(aliasModuleFrom(baseFilename)); +}; diff --git a/packages/eslint/index.js b/packages/eslint/index.js index 26296b798..f0fcc695a 100644 --- a/packages/eslint/index.js +++ b/packages/eslint/index.js @@ -1,4 +1,6 @@ const { ConfigurationError, DuplicateRuleError } = require('neutrino/errors'); +const aliasPlugins = require('./alias-plugins'); +const aliasImportResolvers = require('./alias-import-resolvers'); const arrayToObject = (array) => array.reduce((obj, item) => Object.assign(obj, { [item]: true }), {}); @@ -125,7 +127,7 @@ module.exports = ({ test, include, exclude, eslint = {} } = {}) => { } const baseConfig = eslint.baseConfig || {}; - + const usedPlugins = ['babel']; const loaderOptions = { // For supported options, see: // https://github.com/webpack-contrib/eslint-loader#options @@ -172,10 +174,12 @@ module.exports = ({ test, include, exclude, eslint = {} } = {}) => { // Unfortunately we can't `require.resolve('eslint-plugin-babel')` due to: // https://github.com/eslint/eslint/issues/6237 // ...so we have no choice but to rely on it being hoisted. - plugins: ['babel', ...(baseConfig.plugins || [])], + plugins: [...usedPlugins, ...(baseConfig.plugins || [])], }, }; + aliasPlugins({ plugins: usedPlugins }); + neutrino.config.module .rule('lint') .test(test || neutrino.regexFromExtensions()) @@ -192,3 +196,6 @@ module.exports = ({ test, include, exclude, eslint = {} } = {}) => { neutrino.register('eslintrc', eslintrc); }; }; + +module.exports.aliasPlugins = aliasPlugins; +module.exports.aliasImportResolvers = aliasImportResolvers; diff --git a/packages/eslint/package.json b/packages/eslint/package.json index 8a63abf7b..363c88a28 100644 --- a/packages/eslint/package.json +++ b/packages/eslint/package.json @@ -29,11 +29,17 @@ "dependencies": { "babel-eslint": "^10.1.0", "eslint-loader": "^4.0.2", - "eslint-plugin-babel": "^5.3.1" + "eslint-plugin-babel": "^5.3.1", + "module-alias": "^2.2.2" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0", "neutrino": "^9.0.0", "webpack": "^4.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } } } diff --git a/packages/jest/package.json b/packages/jest/package.json index b2895d83a..3198e29df 100644 --- a/packages/jest/package.json +++ b/packages/jest/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "@babel/core": "^7.12.10", + "@neutrinojs/eslint": "9.4.0", "babel-core": "^7.0.0-bridge.0", "babel-jest": "^26.6.3", "deepmerge": "^1.5.2", diff --git a/packages/jest/src/index.js b/packages/jest/src/index.js index 61c03f12f..8a23337e1 100644 --- a/packages/jest/src/index.js +++ b/packages/jest/src/index.js @@ -2,10 +2,12 @@ const merge = require('deepmerge'); const omit = require('lodash.omit'); const { basename, isAbsolute, join, relative } = require('path'); const { media, style } = require('neutrino/extensions'); +const { aliasPlugins } = require('@neutrinojs/eslint'); module.exports = (options = {}) => (neutrino) => { const lintRule = neutrino.config.module.rules.get('lint'); if (lintRule) { + aliasPlugins({ plugins: ['jest'] }, __filename); lintRule.use('eslint').tap( // Don't adjust the lint configuration for projects using their own .eslintrc. (lintOptions) => @@ -39,9 +41,7 @@ module.exports = (options = {}) => (neutrino) => { return path; } - return path.startsWith('.') - ? join('', path) - : join('', 'node_modules', path); + return path.startsWith('.') ? join('', path) : path; }; const extensionsToNames = (extensions) => `\\.(${extensions.join('|')})$`; const { extensions, source, tests, root, debug } = neutrino.options; diff --git a/packages/jest/test/jest_test.js b/packages/jest/test/jest_test.js index 4e3307d0d..ee5e36ef8 100644 --- a/packages/jest/test/jest_test.js +++ b/packages/jest/test/jest_test.js @@ -160,7 +160,7 @@ test('exposes babel config without babel-loader specific options', (t) => { t.false('customize' in babelOptions); }); -test('configures webpack aliases in moduleNameMapper correctly', (t) => { +test('configures absolute webpack aliases in moduleNameMapper correctly', (t) => { const api = new Neutrino(); const reactPath = path.resolve(path.join('node_modules', 'react')); api.use(reactPreset()); @@ -175,6 +175,33 @@ test('configures webpack aliases in moduleNameMapper correctly', (t) => { ); }); +test('configures package webpack aliases in moduleNameMapper correctly', (t) => { + const api = new Neutrino(); + api.use(mw()); + api.config.resolve.alias.set('_', 'lodash'); + const config = api.outputHandlers.get('jest')(api); + + t.true( + Object.entries(config.moduleNameMapper).some(([key, alias]) => { + return key === '^_$' && alias === 'lodash'; + }), + ); +}); + +test('configures relative webpack aliases in moduleNameMapper correctly', (t) => { + const api = new Neutrino(); + api.use(mw()); + api.config.resolve.alias.set('images', './src/images'); + const config = api.outputHandlers.get('jest')(api); + + t.true( + Object.entries(config.moduleNameMapper).some(([key, alias]) => { + const urlAlias = alias.replace(/\\/g, '/'); // consider OS difference + return key === '^images$' && urlAlias === '/src/images'; + }), + ); +}); + test('gives custom moduleNameMapper entries priority over default entries', (t) => { const api = new Neutrino(); api.use(mw({ moduleNameMapper: { foo: 'bar' } })); diff --git a/packages/library/index.js b/packages/library/index.js index 866f7411e..761fe1a9c 100644 --- a/packages/library/index.js +++ b/packages/library/index.js @@ -1,6 +1,7 @@ const banner = require('@neutrinojs/banner'); const compileLoader = require('@neutrinojs/compile-loader'); const clean = require('@neutrinojs/clean'); +const pnp = require('@neutrinojs/pnp'); const babelMerge = require('babel-merge'); const merge = require('deepmerge'); const nodeExternals = require('webpack-node-externals'); @@ -63,6 +64,7 @@ module.exports = (opts = {}) => { (pkg.dependencies && 'source-map-support' in pkg.dependencies) || (pkg.devDependencies && 'source-map-support' in pkg.devDependencies); + neutrino.use(pnp()); neutrino.use( compileLoader({ include: [neutrino.options.source, neutrino.options.tests], diff --git a/packages/library/package.json b/packages/library/package.json index 0833cd03d..b21b19fb3 100644 --- a/packages/library/package.json +++ b/packages/library/package.json @@ -40,6 +40,7 @@ "@neutrinojs/banner": "9.5.0", "@neutrinojs/clean": "9.5.0", "@neutrinojs/compile-loader": "9.5.0", + "@neutrinojs/pnp": "9.4.0", "babel-merge": "^3.0.0", "deepmerge": "^1.5.2", "webpack-node-externals": "^1.7.2" diff --git a/packages/node/index.js b/packages/node/index.js index 4652f24c4..6ac10b2b9 100644 --- a/packages/node/index.js +++ b/packages/node/index.js @@ -2,6 +2,7 @@ const banner = require('@neutrinojs/banner'); const compileLoader = require('@neutrinojs/compile-loader'); const clean = require('@neutrinojs/clean'); const startServer = require('@neutrinojs/start-server'); +const pnp = require('@neutrinojs/pnp'); const babelMerge = require('babel-merge'); const nodeExternals = require('webpack-node-externals'); const { basename, parse, format } = require('path'); @@ -39,6 +40,7 @@ module.exports = (opts = {}) => { ); const coreJsVersion = neutrino.getDependencyVersion('core-js'); + neutrino.use(pnp()); neutrino.use( compileLoader({ include: [neutrino.options.source, neutrino.options.tests], diff --git a/packages/node/package.json b/packages/node/package.json index 46bc35298..ff273d19f 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -33,6 +33,7 @@ "@neutrinojs/banner": "9.5.0", "@neutrinojs/clean": "9.5.0", "@neutrinojs/compile-loader": "9.5.0", + "@neutrinojs/pnp": "9.4.0", "@neutrinojs/start-server": "9.5.0", "babel-merge": "^3.0.0", "deepmerge": "^1.5.2", diff --git a/packages/pnp/.npmignore b/packages/pnp/.npmignore new file mode 100644 index 000000000..193378602 --- /dev/null +++ b/packages/pnp/.npmignore @@ -0,0 +1 @@ +/test/ diff --git a/packages/pnp/README.md b/packages/pnp/README.md new file mode 100644 index 000000000..f4cb1e3b6 --- /dev/null +++ b/packages/pnp/README.md @@ -0,0 +1,87 @@ +# Neutrino Plug'n'Play Middleware + +`@neutrinojs/pnp` is Neutrino middleware for Plug'n'Play support. + +[![NPM version][npm-image]][npm-url] [![NPM downloads][npm-downloads]][npm-url] + +## Requirements + +- Node.js 10+ +- Yarn v1.2.1+, or npm v5.4+ +- Neutrino 9 +- Webpack 4 + +## Installation + +`@neutrinojs/pnp` can be installed via the Yarn or npm clients. + +#### Yarn + +```bash +❯ yarn add --dev @neutrinojs/pnp +``` + +#### npm + +```bash +❯ npm install --save-dev @neutrinojs/pnp +``` + +## Usage + +`@neutrinojs/pnp` can be consumed from the Neutrino API, middleware, or presets. +Require this package and plug it into Neutrino: + +```js +const pnp = require('@neutrinojs/pnp'); + +// Use with default options +neutrino.use(pnp()); + +// Usage shows the default values of this middleware: +neutrino.use(pnp({ pluginId: 'pnp' })); +``` + +```js +// Using in .neutrinorc.js +const pnp = require('@neutrinojs/pnp'); + +// Use with default options +module.exports = { + use: [pnp()], +}; + +// Usage shows the default values of this middleware: +module.exports = { + use: [pnp({ pluginId: 'pnp' })], +}; +``` + +- `pluginId`: The plugin identifier. Override this to add an additional copy + plugin instance. + +## Customization + +`@neutrinojs/pnp` creates some conventions to make overriding the configuration +easier once you are ready to make changes. + +### Plugins + +The following is a list of plugins and their identifiers which can be +overridden: + +| Name | Description | NODE_ENV | +| ----- | --------------------------------------------------------- | -------- | +| `pnp` | Resolve modules considering Plug and Play feature of Yarn | all | + +## Contributing + +This middleware is part of the +[neutrino](https://github.com/neutrinojs/neutrino) repository, a monorepo +containing all resources for developing Neutrino and its core presets and +middleware. Follow the +[contributing guide](https://neutrinojs.org/contributing/) for details. + +[npm-image]: https://img.shields.io/npm/v/@neutrinojs/pnp.svg +[npm-downloads]: https://img.shields.io/npm/dt/@neutrinojs/pnp.svg +[npm-url]: https://www.npmjs.com/package/@neutrinojs/pnp diff --git a/packages/pnp/index.js b/packages/pnp/index.js new file mode 100644 index 000000000..807b9be9c --- /dev/null +++ b/packages/pnp/index.js @@ -0,0 +1,39 @@ +const moduleAlias = require('module-alias'); + +module.exports = function pnp(settings = {}) { + return function pnpMiddleware(neutrino) { + const { pluginId = 'pnp' } = settings; + const projectPath = process.cwd(); + const environmentIsPnP = Boolean(process.versions.pnp); + + if (environmentIsPnP) { + // solve the issue with the linked middleware outside of the project root + // https://github.com/yarnpkg/berry/issues/693 + moduleAlias.addAlias( + 'pnpapi', + require.resolve('pnpapi', { paths: [projectPath] }), + ); + } + + // eslint-disable-next-line global-require -- Have to import this after `require` alias is applied + const PnpWebpackPlugin = require(`pnp-webpack-plugin`); + + function PnpPlugin() { + // create new instance of Plugin config + return { ...PnpWebpackPlugin }; + } + function PnpLoaderPlugin() { + return PnpWebpackPlugin.moduleLoader(module); + } + + neutrino.config.resolve + .plugin(pluginId) + .use(PnpPlugin) + .end() + .end() + .resolveLoader.plugin(pluginId) + .use(PnpLoaderPlugin) + .end() + .end(); + }; +}; diff --git a/packages/pnp/package.json b/packages/pnp/package.json new file mode 100644 index 000000000..e8aac10e3 --- /dev/null +++ b/packages/pnp/package.json @@ -0,0 +1,38 @@ +{ + "name": "@neutrinojs/pnp", + "version": "9.4.0", + "description": "Neutrino middleware for Plug'n'Play support", + "main": "index.js", + "keywords": [ + "neutrino", + "neutrino-middleware", + "pnp", + "plug and play", + "yarn2" + ], + "author": "Constantine Genchevsky ", + "license": "MPL-2.0", + "repository": { + "type": "git", + "url": "https://github.com/neutrinojs/neutrino.git", + "directory": "packages/pnp" + }, + "homepage": "https://neutrinojs.org", + "bugs": "https://github.com/neutrinojs/neutrino/issues", + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=10", + "npm": ">=5.4.0", + "yarn": ">=1.2.1" + }, + "dependencies": { + "pnp-webpack-plugin": "^1.6.4", + "module-alias": "2.2.2" + }, + "peerDependencies": { + "neutrino": "^9.0.0", + "webpack": "^4.0.0" + } +} diff --git a/packages/pnp/test/pnp_test.js b/packages/pnp/test/pnp_test.js new file mode 100644 index 000000000..108b8513d --- /dev/null +++ b/packages/pnp/test/pnp_test.js @@ -0,0 +1,33 @@ +import test from 'ava'; +import Neutrino from '../../neutrino/Neutrino'; + +const mw = (...args) => require('..')(...args); +const options = { pluginId: 'test-id' }; + +test('loads middleware', (t) => { + t.notThrows(() => require('..')); +}); + +test('uses middleware', (t) => { + t.notThrows(() => new Neutrino().use(mw())); +}); + +test('uses with options', (t) => { + t.notThrows(() => new Neutrino().use(mw(options))); +}); + +test('instantiates', (t) => { + const api = new Neutrino(); + + api.use(mw()); + + t.notThrows(() => api.config.toConfig()); +}); + +test('instantiates with options', (t) => { + const api = new Neutrino(); + + api.use(mw(options)); + + t.notThrows(() => api.config.toConfig()); +}); diff --git a/packages/preact/index.js b/packages/preact/index.js index 4db267231..5bfab62a2 100644 --- a/packages/preact/index.js +++ b/packages/preact/index.js @@ -1,6 +1,7 @@ const babelMerge = require('babel-merge'); const web = require('@neutrinojs/web'); const merge = require('deepmerge'); +const { aliasPlugins } = require('@neutrinojs/eslint'); module.exports = (opts = {}) => (neutrino) => { const options = { @@ -29,6 +30,7 @@ module.exports = (opts = {}) => (neutrino) => { const lintRule = neutrino.config.module.rules.get('lint'); if (lintRule) { + aliasPlugins({ plugins: ['react'] }, __filename); lintRule.use('eslint').tap( // Don't adjust the lint configuration for projects using their own .eslintrc. (lintOptions) => diff --git a/packages/preact/package.json b/packages/preact/package.json index d9dd935ce..eacf57c58 100644 --- a/packages/preact/package.json +++ b/packages/preact/package.json @@ -31,6 +31,7 @@ "@babel/core": "^7.12.10", "@babel/plugin-transform-react-jsx": "^7.12.12", "@neutrinojs/web": "9.5.0", + "@neutrinojs/eslint": "9.4.0", "babel-merge": "^3.0.0", "deepmerge": "^1.5.2", "eslint-plugin-react": "^7.21.5" diff --git a/packages/react/index.js b/packages/react/index.js index 8084eb15d..ac7a32b32 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -1,6 +1,7 @@ const web = require('@neutrinojs/web'); const babelMerge = require('babel-merge'); const merge = require('deepmerge'); +const { aliasPlugins } = require('@neutrinojs/eslint'); module.exports = (opts = {}) => (neutrino) => { const options = merge( @@ -53,6 +54,7 @@ module.exports = (opts = {}) => (neutrino) => { const lintRule = neutrino.config.module.rules.get('lint'); if (lintRule) { + aliasPlugins({ plugins: ['react', 'react-hooks'] }, __filename); lintRule.use('eslint').tap( // Don't adjust the lint configuration for projects using their own .eslintrc. (lintOptions) => diff --git a/packages/react/package.json b/packages/react/package.json index 27d2573b8..bb0f2188d 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -30,6 +30,7 @@ "dependencies": { "@babel/core": "^7.12.10", "@babel/preset-react": "^7.12.10", + "@neutrinojs/eslint": "9.4.0", "@neutrinojs/web": "9.5.0", "babel-merge": "^3.0.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", diff --git a/packages/standardjs/index.js b/packages/standardjs/index.js index f49b3c174..b1a611e93 100644 --- a/packages/standardjs/index.js +++ b/packages/standardjs/index.js @@ -3,6 +3,9 @@ const { rules: standardRules } = require('eslint-config-standard'); module.exports = ({ eslint = {}, ...opts } = {}) => (neutrino) => { const baseConfig = eslint.baseConfig || {}; + const usedPlugins = ['react', 'import', 'node', 'promise', 'standard']; + const usedResolvers = { node: {} }; + neutrino.use( lint({ ...opts, @@ -39,4 +42,19 @@ module.exports = ({ eslint = {}, ...opts } = {}) => (neutrino) => { }, }), ); + + lint.aliasPlugins( + { + plugins: usedPlugins, + }, + __filename, + ); + lint.aliasImportResolvers( + { + settings: { + 'import/resolver': usedResolvers, + }, + }, + __filename, + ); }; diff --git a/packages/standardjs/package.json b/packages/standardjs/package.json index 28dc84061..94688ddbe 100644 --- a/packages/standardjs/package.json +++ b/packages/standardjs/package.json @@ -35,7 +35,8 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react": "^7.21.5", - "eslint-plugin-standard": "^4.1.0" + "eslint-plugin-standard": "^4.1.0", + "eslint-import-resolver-node": "0.3.4" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0", diff --git a/packages/vue/index.js b/packages/vue/index.js index 23bb895c3..58dab2f33 100644 --- a/packages/vue/index.js +++ b/packages/vue/index.js @@ -1,5 +1,6 @@ const web = require('@neutrinojs/web'); const merge = require('deepmerge'); +const { aliasPlugins } = require('@neutrinojs/eslint'); const applyUse = (from) => (to) => { from.uses.values().forEach((use) => { @@ -89,6 +90,7 @@ module.exports = (opts = {}) => (neutrino) => { const lintRule = neutrino.config.module.rules.get('lint'); if (lintRule) { + aliasPlugins({ plugins: ['vue'] }, __filename); // We need to re-set the extension list used by the eslint settings // since when it was generated it didn't include the vue extension. lintRule.test(neutrino.regexFromExtensions()); @@ -101,9 +103,9 @@ module.exports = (opts = {}) => (neutrino) => { : merge(lintOptions, { baseConfig: { extends: ['plugin:vue/base'], - parser: 'vue-eslint-parser', + parser: require.resolve('vue-eslint-parser'), parserOptions: { - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }, plugins: ['vue'], }, diff --git a/packages/vue/package.json b/packages/vue/package.json index d505919d6..8949e978d 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -21,11 +21,14 @@ "yarn": ">=1.2.1" }, "dependencies": { + "@neutrinojs/eslint": "9.4.0", "@neutrinojs/web": "9.5.0", + "babel-eslint": "^10.1.0", "css-loader": "^3.6.0", "deepmerge": "^1.5.2", "eslint-plugin-react": "^7.21.5", "eslint-plugin-vue": "^6.2.2", + "vue-eslint-parser": "^7.0.0", "vue-loader": "^15.9.6", "vue-style-loader": "^4.1.2", "vue-template-compiler": "^2.6.12" diff --git a/packages/vue/test/vue_test.js b/packages/vue/test/vue_test.js index 9d24e2233..199567150 100644 --- a/packages/vue/test/vue_test.js +++ b/packages/vue/test/vue_test.js @@ -50,26 +50,13 @@ test('updates lint config by default', (t) => { api.use(mw()); const lintRule = api.config.module.rule('lint'); + const eslintConfig = lintRule.use('eslint').get('options').baseConfig; + t.deepEqual(lintRule.get('test'), /\.(mjs|jsx|vue|js)$/); - t.deepEqual(lintRule.use('eslint').get('options').baseConfig, { - env: { - browser: true, - commonjs: true, - es6: true, - }, - extends: ['plugin:vue/base'], - globals: { - process: true, - }, - parser: 'vue-eslint-parser', - parserOptions: { - ecmaVersion: 2018, - parser: 'babel-eslint', - sourceType: 'module', - }, - plugins: ['babel', 'vue'], - root: true, - }); + t.assert(eslintConfig.extends.includes('plugin:vue/base')); + t.assert(eslintConfig.parser.includes('vue-eslint-parser')); + t.assert(eslintConfig.parserOptions.parser.includes('babel-eslint')); + t.assert(eslintConfig.plugins.includes('vue')); }); test('does not update lint config if useEslintrc true', (t) => { diff --git a/packages/web/index.js b/packages/web/index.js index f3257dac5..725a4bddf 100644 --- a/packages/web/index.js +++ b/packages/web/index.js @@ -6,6 +6,7 @@ const compileLoader = require('@neutrinojs/compile-loader'); const htmlTemplate = require('@neutrinojs/html-template'); const clean = require('@neutrinojs/clean'); const devServer = require('@neutrinojs/dev-server'); +const pnp = require('@neutrinojs/pnp'); const babelMerge = require('babel-merge'); const merge = require('deepmerge'); const { ConfigurationError } = require('neutrino/errors'); @@ -162,6 +163,7 @@ module.exports = (opts = {}) => (neutrino) => { neutrino.config.devtool(devtool); } + neutrino.use(pnp()); neutrino.use(htmlLoader()); neutrino.use( compileLoader({ diff --git a/packages/web/package.json b/packages/web/package.json index ad2ded0ec..ed18a99dc 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -37,6 +37,7 @@ "@neutrinojs/html-loader": "9.5.0", "@neutrinojs/html-template": "9.5.0", "@neutrinojs/image-loader": "9.5.0", + "@neutrinojs/pnp": "9.4.0", "@neutrinojs/style-loader": "9.5.0", "babel-merge": "^3.0.0", "deepmerge": "^1.5.2" diff --git a/yarn.lock b/yarn.lock index 81c31f644..364b73332 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6035,14 +6035,15 @@ eslint-plugin-standard@^4.1.0: resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz#0c3bf3a67e853f8bbbc580fb4945fbf16f41b7c5" integrity sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ== -eslint-plugin-vue@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-6.2.2.tgz#27fecd9a3a24789b0f111ecdd540a9e56198e0fe" - integrity sha512-Nhc+oVAHm0uz/PkJAWscwIT4ijTrK5fqNqz9QB1D35SbbuMG1uB6Yr5AJpvPSWg+WOw7nYNswerYh0kOk64gqQ== +eslint-plugin-vue@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-7.1.0.tgz#832d83e4e1e480c7285b2bc3ff1076cd0dca7a5b" + integrity sha512-9dW7kj8/d2IkDdgNpvIhJdJ3XzU3x4PThXYMzWt49taktYnGyrTY6/bXCYZ/VtQKU9kXPntPrZ41+8Pw0Nxblg== dependencies: + eslint-utils "^2.1.0" natural-compare "^1.4.0" - semver "^5.6.0" - vue-eslint-parser "^7.0.0" + semver "^7.3.2" + vue-eslint-parser "^7.1.1" eslint-rule-composer@^0.3.0: version "0.3.0" @@ -10218,6 +10219,11 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== +module-alias@2.2.2, module-alias@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0" + integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q== + moment@^2.15.1, moment@^2.19.3, moment@^2.24.0: version "2.29.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" @@ -11436,6 +11442,13 @@ pn@^1.1.0: resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== +pnp-webpack-plugin@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" + integrity sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg== + dependencies: + ts-pnp "^1.1.6" + portfinder@^1.0.26: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" @@ -14115,6 +14128,11 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +ts-pnp@^1.1.6: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" + integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== + tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" @@ -14693,6 +14711,18 @@ vue-eslint-parser@^7.0.0: esquery "^1.0.1" lodash "^4.17.15" +vue-eslint-parser@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz#c43c1c715ff50778b9a7e9a4e16921185f3425d3" + integrity sha512-8FdXi0gieEwh1IprIBafpiJWcApwrU+l2FEj8c1HtHFdNXMd0+2jUSjBVmcQYohf/E72irwAXEXLga6TQcB3FA== + dependencies: + debug "^4.1.1" + eslint-scope "^5.0.0" + eslint-visitor-keys "^1.1.0" + espree "^6.2.1" + esquery "^1.0.1" + lodash "^4.17.15" + vue-hot-reload-api@^2.3.0: version "2.3.4" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"