Modules
In webpack, modules are discrete chunks of functionality that encapsulate implementation details and expose a public API. Webpack supports various module formats and processes them through its module system.
What is a Webpack Module?
Unlike Node.js modules which only support CommonJS, webpack modules can express dependencies in multiple ways:
- An ES2015
import statement
- A CommonJS
require() statement
- An AMD
define and require statement
- An
@import statement inside CSS/Sass/Less files
- An image URL in a stylesheet (
url(...)) or HTML (<img src=...>)
Supported Module Types
Webpack natively supports the following module types:
ECMAScript Modules (ESM)
import { foo } from './module.js';
export const bar = 'bar';
export default function() { }
CommonJS
const module = require('./module');
module.exports = { foo: 'bar' };
exports.baz = 'qux';
AMD (Asynchronous Module Definition)
define(['./module'], function(module) {
return { foo: module.bar };
});
require(['./module'], function(module) {
// Use module
});
WebAssembly Modules
import { add } from './math.wasm';
add(1, 2);
Asset Modules
import imageUrl from './image.png';
import styles from './styles.css';
import data from './data.json';
Module Resolution
Webpack uses enhanced-resolve to find module files:
Absolute Paths
import '/home/user/file';
import 'C:\\Users\\user\\file';
Relative Paths
import '../src/file';
import './file';
Module Paths
import 'module';
import 'module/lib/file';
Webpack searches in directories specified in resolve.modules (defaults to node_modules).
Module Configuration
Module Rules
Define how different module types should be processed:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|jpg|gif)$/,
type: 'asset/resource'
}
]
}
};
No Parse
Exclude modules from parsing:
module.exports = {
module: {
noParse: /jquery|lodash/,
}
};
Use noParse for large libraries that don’t use module syntax to improve build performance.
NormalModule
Webpack represents most modules as NormalModule instances:
// From NormalModule.js (simplified)
const { runLoaders } = require('loader-runner');
class NormalModule extends Module {
constructor({
type,
request,
userRequest,
rawRequest,
loaders,
resource,
parser,
generator,
resolveOptions
}) {
super(type, request);
this.request = request;
this.userRequest = userRequest;
this.rawRequest = rawRequest;
this.loaders = loaders;
this.resource = resource;
this.parser = parser;
this.generator = generator;
}
build(options, compilation, resolver, fs, callback) {
// Run loaders
runLoaders({
resource: this.resource,
loaders: this.loaders,
context: loaderContext,
readResource: fs.readFile.bind(fs)
}, (err, result) => {
if (err) return callback(err);
// Parse the result
this.parser.parse(result.result, {
current: this,
module: this,
compilation: compilation
});
callback();
});
}
}
Module Types
JavaScript Module Types
module.exports = {
module: {
rules: [
{
test: /\.js$/,
type: 'javascript/auto', // Default, supports all module systems
},
{
test: /\.mjs$/,
type: 'javascript/esm', // ESM only
},
{
test: /\.cjs$/,
type: 'javascript/dynamic', // CommonJS
}
]
}
};
Asset Module Types
module.exports = {
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource', // Emits file, returns URL
},
{
test: /\.svg$/,
type: 'asset/inline', // Inlines as data URI
},
{
test: /\.txt$/,
type: 'asset/source', // Exports source as string
},
{
test: /\.jpg$/,
type: 'asset', // Auto chooses between resource and inline
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8kb
}
}
}
]
}
};
Module Graph
Webpack builds a ModuleGraph to track dependencies:
// From ModuleGraph.js (simplified)
class ModuleGraph {
constructor() {
this._dependencyMap = new WeakMap();
this._moduleMap = new Map();
}
getModule(dependency) {
const connection = this._dependencyMap.get(dependency);
return connection ? connection.module : null;
}
getIssuer(module) {
const mgm = this._getModuleGraphModule(module);
return mgm.issuer;
}
getOutgoingConnections(module) {
const mgm = this._getModuleGraphModule(module);
return mgm.outgoingConnections;
}
getIncomingConnections(module) {
const mgm = this._getModuleGraphModule(module);
return mgm.incomingConnections;
}
}
Module Context
Each module is executed with context:
// Available in modules
__filename // Current file name
__dirname // Current directory
module // Module object
exports // Module exports
require // Require function
Module Variables
// These are available in webpack modules
console.log(__filename); // Path to the module
console.log(__dirname); // Directory of the module
console.log(module.id); // Module identifier
Dynamic Imports
Create code split points with dynamic imports:
// Dynamically import a module
import('./module').then(module => {
module.doSomething();
});
// With async/await
const module = await import('./module');
module.doSomething();
Control dynamic import behavior:
import(
/* webpackChunkName: "my-chunk" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
/* webpackPreload: true */
'./module'
);
Chunk Name
Prefetch
Preload
import(/* webpackChunkName: "lodash" */ 'lodash')
.then(_ => {
// Use lodash
});
import(/* webpackPrefetch: true */ './module')
.then(module => {
// Module prefetched during idle time
});
import(/* webpackPreload: true */ './module')
.then(module => {
// Module loaded in parallel with parent
});
Module Federation
Share modules between separate builds:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button'
},
shared: ['react', 'react-dom']
})
]
};
Context Modules
Require with expressions creates context modules:
// Creates a context for all .js files in the directory
require('./pages/' + name + '.js');
// Equivalent to:
const context = require.context('./pages', false, /\.js$/);
const module = context('./' + name + '.js');
require.context
const context = require.context(
'./components', // Directory
true, // Use subdirectories
/\.vue$/ // Match .vue files
);
context.keys().forEach(key => {
const component = context(key);
// Register component
});
External Modules
Exclude dependencies from the bundle:
module.exports = {
externals: {
jquery: 'jQuery',
react: 'React',
'react-dom': 'ReactDOM'
}
};
External Types
module.exports = {
externalsType: 'var', // or 'module', 'commonjs', 'amd', etc.
externals: {
lodash: '_'
}
};
Module Concatenation
Scope hoisting merges modules into a single scope:
module.exports = {
optimization: {
concatenateModules: true // Enabled by default in production
}
};
Module concatenation reduces function call overhead and bundle size.
Module Identifiers
Modules are identified by:
Module IDs
module.exports = {
optimization: {
moduleIds: 'deterministic' // or 'named', 'natural', 'size'
}
};
Module Path
Each module has a unique request path:
module.exports = {
output: {
pathinfo: true // Include path info in bundles (development)
}
};
Best Practices
- Use ESM syntax - Enables tree shaking and static analysis
- Avoid mixing module types - Stick to one module system per project
- Use dynamic imports - For code splitting and lazy loading
- Configure module rules - Process different file types appropriately
- Exclude node_modules - Use
exclude in loader rules for performance
- Use module concatenation - Enable in production for smaller bundles
Common Patterns
Conditional Imports
let module;
if (condition) {
module = await import('./moduleA');
} else {
module = await import('./moduleB');
}
Parallel Imports
const [moduleA, moduleB] = await Promise.all([
import('./moduleA'),
import('./moduleB')
]);
Named Exports Aggregation
export * from './moduleA';
export * from './moduleB';
export { default as ModuleC } from './moduleC';