I have a lerna monorepo and I like to install development only dependencies at the workspace root rather than in individual packages. This is because it is easier to maintain one set of versions for development tools rather than many, and it is not important that the development environment of each package is self contained - the monorepo IS the development environment.
After https://github.com/benmosher/eslint-plugin-import/pull/1085, this is possible.
However, I still would like the published versions of my packages to have proper entries in dependencies for any other packages that it requires in order to work. Users of the package don't check out the monorepo, they install the package.
To illustrate, imagine the following project structure:
package.json
yarn.lock
packages/
apple/
package.json
devScripts.js
productionCode.js
// package.json
{
"devDependencies": {
"development-tool-orange": "1.0.0"
}
}
// packages/apple/package.json
{
"dependencies": {
"production-dependency-pineapple": "1.0.0"
}
}
// packages/apple/devScripts.js
import 'development-tool-orange'; // This is fine, this file is only used in development inside the monorepo
// packages/apple/productionCode.js
import 'production-dependency-pineapple'; // This is fine, there is a dependency in this package
import 'development-tool-orange'; // This is bad! the published version will not work!
Allow both devDependencies and packageDir to be used only for specific files which are not used in production:
{
rules: {
'import/no-extraneous-dependencies': [
2,
{
groups: [
{
dirs: ['/devScripts.js'],
devDependencies: true,
packageDir: [
path.join(__dirname, 'package.json'),
path.join(__dirname, '../../package.json'),
],
},
],
},
],
},
}
Initially this made sense to me, but after thinking about it - it doesn't.
This is how we approach it:
dependencies - this is imperativedevDependencies common to all packages.// These are allowed to require packages from devDependecies
const devScripts = [
'scripts/*.js',
'**/webpack.*.js',
'**/cosmos.proxies.js',
'**/*.fixture.jsx',
'**/next.config.js'
];
module.exports = {
rules: {
'import/no-extraneous-dependencies': [
'error',
{
// scripts that are allowed to import from devDependencies
devDependencies: devScripts
}
],
},
};
In each package folder (packages/packageName/):
dependenciesdevDependencies for that specific packageconst { join } = require('path');
module.exports = {
rules: {
'import/no-extraneous-dependencies': [
'error',
// Use package.json from both this package folder and root.
{ packageDir: [__dirname, join(__dirname, '../../')] }
]
}
};
See more on this in https://github.com/benmosher/eslint-plugin-import/issues/1302#issuecomment-572546519
I have ended up using an alternative using eslint overrides with the following features:
devDependenciesdependencies of the package.json in that packageconst path = require('path');
const devConfig = {
rules: {
'import/no-extraneous-dependencies': [
2,
{
// Dependencies must be specified in `devDependencies` in the monorepo root
devDependencies: true,
packageDir: __dirname,
},
],
},
};
const runtimeConfig = {
rules: {
// Dependencies must be specified in the projects package.json
'import/no-extraneous-dependencies': [
2,
{
devDependencies: false,
// default `packageDir` is the closest `package.json` to the file being linted
},
],
},
};
module.exports = {
...,
overrides: [
{
files: ['./packages/*/**/*'],
...runtimeConfig,
},
{
files: ['./packages/*/scripts/**/*', './packages/*/*.config.js'],
...devConfig,
},
]
};
Most helpful comment
I have ended up using an alternative using eslint overrides with the following features:
devDependenciesdependenciesof the package.json in that package