Replies: 5 comments
|
You can achieve this in two main ways depending on whether you want to catch it during linting (which is the standard, fastest approach) or directly during the Vite build/dev process. Option 1: The Linting Approach // eslint.config.js (Flat Config) export default [ Option 2: The Vite Plugin Approach import { defineConfig } from 'vite'; function restrictDependenciesPlugin() { return { }; export default defineConfig({ |
|
To enforce that only production dependencies (listed in 1. ESLint (Recommended & Industry Standard)The standard way to restrict imports is using ESLint with You can configure it in your // eslint.config.js
import importPlugin from 'eslint-plugin-import';
export default [
{
plugins: {
import: importPlugin
},
rules: {
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'**/*.test.ts',
'**/*.spec.ts',
'vite.config.ts',
'vitest.config.ts'
],
optionalDependencies: false,
peerDependencies: false
}
]
}
}
];2. Custom Vite Build-Time PluginIf you want the Vite build itself to fail immediately upon encountering a forbidden import, you can write a simple custom Vite resolver plugin: // vite.config.ts
import { defineConfig } from 'vite';
import { readFileSync } from 'fs';
import { resolve } from 'path';
function restrictImportsPlugin() {
const pkg = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf-8'));
const allowedImports = new Set([
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
]);
return {
name: 'vite-plugin-restrict-imports',
resolveId(source, importer) {
// Ignore relative, absolute, and virtual module paths
if (source.startsWith('.') || source.startsWith('/') || source.startsWith('\\') || source.includes('\x00')) {
return null;
}
// Extract root package name (e.g., "lodash/map" -> "lodash")
const match = source.match(/^([^@/]+|@[^/]+/[^/]+)/);
const pkgName = match ? match[1] : source;
// Allow node built-ins
if (pkgName.startsWith('node:') || ['fs', 'path', 'os', 'crypto'].includes(pkgName)) {
return null;
}
if (!allowedImports.has(pkgName)) {
throw new Error(
`Forbidden import: "${source}" is imported in ${importer} but is not listed in package.json dependencies.`
);
}
return null;
}
};
}
export default defineConfig({
plugins: [restrictImportsPlugin()]
}); |
|
Vite itself doesn't distinguish between For example, This keeps the check explicit and catches violations early without affecting Vite's module resolution. |
|
There's no built-in Vite option for this, but it's a few lines as a small plugin. A // vite.config.js
import { readFileSync } from 'node:fs'
import { builtinModules } from 'node:module'
const pkg = JSON.parse(readFileSync('./package.json', 'utf-8'))
const allowed = new Set(Object.keys(pkg.dependencies || {}))
const builtins = new Set([
...builtinModules,
...builtinModules.map((m) => 'node:' + m),
])
const isBare = (id) => !id.startsWith('.') && !id.startsWith('/') && !id.startsWith('\0')
const pkgNameOf = (id) => {
const p = id.split('/')
return id.startsWith('@') ? p.slice(0, 2).join('/') : p[0]
}
function allowlistDeps() {
return {
name: 'allowlist-deps',
enforce: 'pre',
resolveId(source) {
// skip relative/absolute, virtual ids and `pkg:`-style ids
if (!isBare(source) || source.includes(':')) return null
const name = pkgNameOf(source)
// allow node builtins and vite's own internal imports
if (builtins.has(name) || name === 'vite') return null
if (!allowed.has(name)) {
throw new Error(
`[allowlist-deps] Import of "${source}" is not allowed. ` +
`"${name}" is not listed in "dependencies" of package.json.`
)
}
return null // let Vite resolve it normally
},
}
}
export default { plugins: [allowlistDeps()] }This gives you exactly the "only allow modules in A couple of notes from testing this on Vite 8:
|
|
Vite doesn't have a built in allow or deny list based on // vite.config.ts
import { readFileSync } from 'node:fs'
import { builtinModules } from 'node:module'
import type { Plugin } from 'vite'
function allowOnlyDependencies(): Plugin {
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'))
const allowed = new Set([
...Object.keys(pkg.dependencies ?? {}),
...builtinModules, // include if you want to allow node builtins
])
return {
name: 'allow-only-dependencies',
enforce: 'pre',
resolveId(source, importer) {
const isBare = !source.startsWith('.') && !source.startsWith('/')
if (!isBare) return null // ignore relative / absolute paths
const name = source.startsWith('@')
? source.split('/').slice(0, 2).join('/') // @scope/pkg
: source.split('/')[0] // pkg (drop the subpath)
if (!allowed.has(name)) {
throw new Error(
`[allow-only-dependencies] "${name}" is not in dependencies` +
(importer ? ` (imported by ${importer})` : '')
)
}
return null // let Vite resolve it normally
},
}
}
export default { plugins: [allowOnlyDependencies()] }
Couple of related built ins to know about: If this does what you're after, would be grateful if you could mark it as the answer. |
Uh oh!
There was an error while loading. Please reload this page.
To prevent importing project tools from node_modules, I would like to only allow modules defined in
dependencies, or disallow any modules defined indevDependencies. How should I achieve that?All reactions