The CommonJS module system translates this sentence into English.
The CommonJS module system is one of the core mechanisms in Node.js for organizing and managing code. It implements module importing and exporting through require
and module.exports
, addressing the lack of native modular support in early JavaScript.
Basic Concepts of the CommonJS Module System
The CommonJS specification was initially designed for server-side JavaScript environments and was later adopted by Node.js as the foundation of its module system. In Node.js, each file is treated as an independent module, where variables, functions, and classes within the module are private by default and can only be accessed by other modules through explicit exports.
Key features of the module system include:
- Synchronous module loading
- Module caching mechanism
- Clear dependency relationships
- Simple import/export syntax
Module Export Mechanism
CommonJS provides two primary ways to export module content:
Using module.exports
This is the most basic export method, allowing the export of any type of value:
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
module.exports = {
add,
subtract
};
It can also directly export a single function or class:
// logger.js
module.exports = function(message) {
console.log(`[LOG] ${new Date().toISOString()}: ${message}`);
};
Using the exports
Shortcut
Node.js provides exports
as a shortcut for module.exports
:
// utils.js
exports.capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
exports.trim = str => str.trim();
Note that exports
is merely a reference to module.exports
and cannot be directly reassigned:
// Incorrect usage
exports = { foo: 'bar' }; // This won't work
// Correct usage
module.exports = { foo: 'bar' };
Module Import Mechanism
Use the require
function to import modules:
// app.js
const math = require('./math');
const logger = require('./logger');
const { capitalize } = require('./utils');
console.log(math.add(2, 3)); // 5
logger('Application started');
console.log(capitalize('hello')); // 'Hello'
require
Lookup Rules
Node.js follows this order when searching for modules:
- Core modules (e.g., fs, path, etc.)
- File modules (starting with ./ or ../)
- Directory modules (looking for package.json or index.js)
- Modules in node_modules
// Import core module
const fs = require('fs');
// Import file module
const config = require('./config.json');
// Import directory module (automatically looks for index.js)
const models = require('./models');
// Import module from node_modules
const express = require('express');
Module Caching Mechanism
Node.js caches loaded modules to improve performance:
// moduleA.js
console.log('Module A loaded');
module.exports = { value: Math.random() };
// main.js
const a1 = require('./moduleA');
const a2 = require('./moduleA');
console.log(a1 === a2); // true
console.log(a1.value === a2.value); // true
Handling Circular Dependencies
CommonJS can handle circular dependencies between modules, but the timing of exports must be considered:
// a.js
console.log('a starting');
exports.done = false;
const b = require('./b');
console.log('in a, b.done =', b.done);
exports.done = true;
console.log('a done');
// b.js
console.log('b starting');
exports.done = false;
const a = require('./a');
console.log('in b, a.done =', a.done);
exports.done = true;
console.log('b done');
// main.js
console.log('main starting');
const a = require('./a');
const b = require('./b');
console.log('in main, a.done=', a.done, 'b.done=', b.done);
The execution result will show the order of module loading and state changes.
Module Scope
Each module has its own independent scope and does not pollute the global scope:
// module1.js
var count = 0;
function increment() {
count++;
}
// module2.js
var count = 100; // Does not affect the count in module1
Dynamic Loading
Although CommonJS primarily loads modules synchronously, dynamic loading can also be implemented:
// Dynamic loading example
const moduleName = process.env.NODE_ENV === 'production'
? './prodModule'
: './devModule';
const dynamicModule = require(moduleName);
Differences from ES Modules
Although Node.js now supports ES modules, CommonJS remains the primary module system:
- CommonJS loads synchronously, while ES modules load asynchronously.
- CommonJS uses
require
/module.exports
, while ES modules useimport
/export
. - CommonJS modules are loaded at runtime, while ES modules are statically resolved at compile time.
// CommonJS
module.exports = { foo: 'bar' };
const lib = require('./lib');
// ES Modules
export const foo = 'bar';
import { foo } from './lib.mjs';
Practical Application Examples
Configuration File Management
// config.js
const env = process.env.NODE_ENV || 'development';
const configs = {
development: {
apiUrl: 'http://localhost:3000',
debug: true
},
production: {
apiUrl: 'https://api.example.com',
debug: false
}
};
module.exports = configs[env];
// app.js
const config = require('./config');
console.log(`API URL: ${config.apiUrl}`);
Middleware Pattern
// middleware.js
module.exports = function(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
};
// server.js
const express = require('express');
const logger = require('./middleware');
const app = express();
app.use(logger);
Module Hot Replacement
Although CommonJS does not natively support hot replacement, it can be achieved with some tricks:
// hotModule.js
module.exports = {
data: 'initial'
};
// watcher.js
const fs = require('fs');
const path = require('path');
function watchModule(modulePath, callback) {
const fullPath = require.resolve(modulePath);
fs.watch(fullPath, () => {
// Clear cache
delete require.cache[fullPath];
const newModule = require(modulePath);
callback(newModule);
});
}
// Usage example
watchModule('./hotModule', (module) => {
console.log('Module updated:', module.data);
});
Performance Optimization Tips
- Organize module structure reasonably to avoid deep dependency chains.
- For frequently used core modules, cache them in advance:
const _require = require;
const path = _require('path');
const fs = _require('fs');
- Avoid dynamic
require
in hot paths.
Debugging the Module System
You can view module information through the module
global object:
console.log(module);
// View module cache
console.log(require.cache);
Module Pattern Practices
CommonJS supports various module organization patterns:
Singleton Pattern
// db.js
let instance = null;
class Database {
constructor() {
if (!instance) {
instance = this;
this.connection = null;
}
return instance;
}
connect() {
this.connection = 'Connected';
}
}
module.exports = new Database();
// app.js
const db1 = require('./db');
const db2 = require('./db');
console.log(db1 === db2); // true
Factory Pattern
// logger.js
module.exports = (prefix) => {
return {
log: message => console.log(`[${prefix}] ${message}`),
error: message => console.error(`[${prefix}] ERROR: ${message}`)
};
};
// app.js
const createLogger = require('./logger');
const dbLogger = createLogger('DB');
const apiLogger = createLogger('API');
dbLogger.log('Connection established');
apiLogger.error('Request failed');
Module Version Management
In large projects, managing different module versions may be necessary:
// v1/module.js
module.exports = function() {
console.log('This is version 1');
};
// v2/module.js
module.exports = function() {
console.log('This is version 2');
};
// app.js
const v1 = require('./v1/module');
const v2 = require('./v2/module');
v1(); // This is version 1
v2(); // This is version 2
Module Testing Techniques
When testing CommonJS modules, you can leverage the caching mechanism:
// counter.js
let count = 0;
module.exports = {
increment: () => ++count,
getCount: () => count
};
// test.js
const counter = require('./counter');
// Reset module state before each test case
beforeEach(() => {
delete require.cache[require.resolve('./counter')];
});
test('increment should increase count', () => {
const counter = require('./counter');
counter.increment();
expect(counter.getCount()).toBe(1);
});
Module Metadata
You can access module metadata through the module
object:
console.log('Module ID:', module.id);
console.log('Module filename:', module.filename);
console.log('Module loaded:', module.loaded);
console.log('Parent module:', module.parent);
console.log('Children modules:', module.children);
Module Path Resolution
Node.js provides utility methods for module path resolution:
const resolvedPath = require.resolve('./someModule');
console.log('Resolved path:', resolvedPath);
Module Wrapper
Node.js wraps module code in a function before execution:
(function(exports, require, module, __filename, __dirname) {
// Module code is executed here
});
This explains why these variables are available in modules:
console.log('File:', __filename);
console.log('Directory:', __dirname);
Module Loading Process
Detailed steps of Node.js module loading:
- Resolve module path
- Check cache
- Create new module instance
- Store module instance in cache
- Load module content
- Wrap module code
- Execute module code
- Return
module.exports
Relationship Between Modules and Packages
In the Node.js ecosystem:
- A module is a single file
- A package is a directory containing package.json
- A package can contain multiple modules
// Import main module from package
const pkg = require('some-package');
// Import specific module from package
const util = require('some-package/utils');
Module System Extensions
Although CommonJS is Node.js's default module system, it can be extended through loaders:
// Custom JSON loader
require.extensions['.json'] = function(module, filename) {
const content = fs.readFileSync(filename, 'utf8');
module.exports = JSON.parse(content);
};
// Now JSON files can be directly required
const data = require('./data.json');
Note: Modifying require.extensions
has been deprecated by the official documentation and is not recommended for production environments.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:单线程与事件循环
下一篇:Webpack核心知识点