What Is ModuleScopePlugin in Create React App and How Does It Restrict Imports?
ModuleScopePlugin is a custom Webpack resolver plugin in Create React App (CRA) that enforces a strict project boundary by blocking any import that resolves outside the src/ directory unless explicitly whitelisted.
Create React App uses ModuleScopePlugin (located in packages/react-dev-utils/ModuleScopePlugin.js) to guarantee that application code remains isolated within the src/ folder. This prevents accidental dependencies on files scattered across the repository root or other arbitrary locations, ensuring consistent builds and predictable module resolution.
What Is ModuleScopePlugin?
ModuleScopePlugin is a Webpack plugin that intercepts module resolution at the compiler level. It was introduced in the facebook/create-react-app monorepo to solve a specific problem: developers accidentally importing configuration files, build scripts, or shared utilities located outside the src/ directory, which breaks the encapsulation of the React application.
The plugin operates as a resolver hook in Webpack’s enhanced-resolve pipeline. When Webpack attempts to resolve an import statement, ModuleScopePlugin examines both the issuer (the file containing the import) and the requested file path before allowing the resolution to proceed.
How ModuleScopePlugin Restricts Imports
The restriction logic follows a two-step validation process defined in packages/react-dev-utils/ModuleScopePlugin.js.
The Issuer Check
First, the plugin verifies whether the file performing the import (the issuer) resides within the configured source directories (typically paths.appSrc which maps to src/). If the issuer is outside these directories, the plugin immediately returns without enforcing any restrictions, allowing build scripts and configuration files at the project root to import freely.
The Request Validation
If the issuer is inside src/, the plugin then checks the requested file path:
- If the request resolves to a location inside any configured
appSrcdirectory, resolution proceeds normally. - If the request resolves outside all
appSrcdirectories and is not present in theallowedFilesorallowedPathssets, the plugin throws a blocking error.
The error message explicitly states:
You attempted to import ../../config which falls outside of the project src/ directory.
Relative imports outside of src/ are not supported.
You can either move it inside src/, or add a symlink to it from project's node_modules/.
This error originates from lines 66-92 in ModuleScopePlugin.js, where the plugin constructs the error using the relative path information and resolution context.
ModuleScopePlugin Configuration in CRA
The plugin is instantiated in packages/react-scripts/config/webpack.config.js at line 339:
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson])
This configuration establishes two critical parameters:
paths.appSrc: Defines the primary source directory (usually./src)allowedFilesarray: Containspaths.appPackageJson(the project'spackage.json), permitting imports likeimport packageJson from '../package.json'despitepackage.jsonresiding at the repository root
The plugin applies to both development and production builds, ensuring consistent enforcement across all Webpack configurations generated by CRA.
What Imports Are Blocked vs. Allowed
Understanding the boundary conditions helps developers structure their projects correctly.
Allowed Imports
Any import where both the issuer and target reside within src/:
// src/components/App.js
import Helper from '../utils/helper'; // ✅ Allowed: both files under src/
import logo from './logo.svg'; // ✅ Allowed: relative import within src/
Imports from node_modules are also permitted because Webpack resolves these through its standard node_modules resolution algorithm before ModuleScopePlugin validates the result.
Blocked Imports
Any attempt to import files located outside src/ from within src/:
// src/components/App.js
import config from '../../config'; // ❌ Blocked: resolves to repository root
import rootUtil from '../..'; // ❌ Blocked: outside src/ boundary
Attempting to build this code triggers the descriptive error from ModuleScopePlugin.js, halting the compilation process.
How to Whitelist Files Outside src/
While CRA strongly recommends moving all application code into src/, the plugin provides escape hatches for edge cases.
Adding Files to allowedFiles
You can permit specific files by modifying the plugin instantiation in webpack.config.js (requires ejecting or using a tool like react-app-rewired):
const path = require('path');
new ModuleScopePlugin(paths.appSrc, [
paths.appPackageJson,
path.resolve(__dirname, '../../shared/constants.js') // Whitelisted file
])
This allows import constants from '../../shared/constants' from within src/ despite the file residing outside the source directory.
Adding Directories to allowedPaths
The plugin also accepts an allowedPaths option (distinct from allowedFiles) that permits entire directories. This is handled internally in the plugin's resolution logic and can be passed as a third argument or configured through the plugin's options object depending on the CRA version.
Summary
- ModuleScopePlugin is a Webpack resolver plugin in
packages/react-dev-utils/ModuleScopePlugin.jsthat enforces thesrc/directory boundary. - It blocks any import that resolves outside
src/unless the file is explicitly whitelisted inallowedFilesorallowedPaths. - The plugin is configured in
packages/react-scripts/config/webpack.config.jswithnew ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]). - Attempting to import from outside
src/produces a descriptive error suggesting moving the file intosrc/or creating a symlink innode_modules/.
Frequently Asked Questions
Can I disable ModuleScopePlugin in CRA without ejecting?
Disabling ModuleScopePlugin without ejecting requires using a customization tool like react-app-rewired or craco. You would modify the Webpack configuration to remove the plugin from the resolve.plugins array. However, disabling it removes the safety guard that keeps your application code isolated, potentially leading to brittle dependencies on files outside your source tree.
Why does CRA restrict imports outside the src/ directory?
The restriction ensures that all application logic, assets, and components reside in a single, predictable location. This encapsulation prevents accidental dependencies on build configuration files, documentation, or server-side code that may exist at the repository root. It also simplifies future migrations, refactoring, and code splitting since the bundler can assume all source code lives under src/.
How do I import from a shared directory outside src/?
The recommended approach is to move the shared code into src/ or publish it as an npm package. If neither is feasible, you can whitelist specific files by adding them to the allowedFiles array in the ModuleScopePlugin constructor within webpack.config.js (requires ejecting or using a rewiring tool). Alternatively, create a symlink from node_modules/ pointing to the external directory, which bypasses the plugin's checks since node_modules imports are handled separately.
Does ModuleScopePlugin affect imports from node_modules?
No, ModuleScopePlugin does not block imports from node_modules. The plugin specifically checks if the requested file falls outside the configured appSrc directories, but it runs after Webpack's standard resolution for node_modules. Since packages in node_modules are resolved through a different mechanism and are considered external dependencies, they bypass the src/ boundary check entirely.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →