Modules and namespaces
Basic Concepts of ECMAScript 6 Modules
ECMAScript 6 (ES6) introduced a module system, which is a natively supported modularization solution in the JavaScript language. Modules allow developers to split code into multiple files, where each file can export specific functionalities that other files can import. This mechanism addresses issues such as global variable pollution and dependency management chaos in traditional JavaScript.
The core of modules lies in the export
and import
keywords. A module can export multiple values, which can be variables, functions, or classes. Exports come in two forms: named exports and default exports.
// math.js - Named export example
export const PI = 3.14159;
export function square(x) {
return x * x;
}
export class Circle {
constructor(radius) {
this.radius = radius;
}
area() {
return PI * this.radius ** 2;
}
}
Module Import and Export Methods
ES6 modules offer various import and export methods to suit different use cases. Named exports allow a module to export multiple values, while default exports let a module specify a primary export value.
Named Import and Export
Named exports use the export
keyword and can be done individually for each declaration or collectively at the end of the module:
// Method 1: Individual exports
export const name = 'module';
export function hello() { /*...*/ }
// Method 2: Unified export
const name = 'module';
function hello() { /*...*/ }
export { name, hello };
When importing named exports, the same names must be used, or the as
keyword can be used to rename them:
import { name, hello as greet } from './module.js';
console.log(name); // 'module'
greet();
Default Import and Export
Each module can have one default export, using the export default
syntax:
// module.js
export default function() {
console.log('Default export');
}
When importing a default export, any name can be specified:
import myFunction from './module.js';
myFunction(); // 'Default export'
Mixed Import and Export
A module can use both named and default exports simultaneously:
// module.js
export const version = '1.0';
export default function() { /*...*/ }
// When importing
import mainFunc, { version } from './module.js';
Module Loading Mechanism
ES6 module loading is static, meaning module dependencies are determined before the code is executed. This static nature allows tools to perform static analysis and implement optimizations like tree-shaking.
Module loading is asynchronous. Browsers download module files in parallel but execute them in dependency order. A module is loaded and executed only once, no matter how many times it is imported.
// Circular dependency example
// a.js
import { b } from './b.js';
export const a = 'a';
// b.js
import { a } from './a.js';
export const b = 'b';
Although ES6 supports circular dependencies, such designs should be avoided as they can lead to hard-to-understand code and potential issues.
Relationship Between Namespaces and Modules
In TypeScript, namespaces are a logical grouping mechanism used to organize code and avoid naming conflicts. While ES6 modules inherently provide scope isolation, namespaces still have value in certain scenarios.
TypeScript Namespaces
TypeScript namespaces can span multiple files and are defined using the namespace
keyword:
// shapes.ts
namespace Shapes {
export class Circle { /*...*/ }
export class Square { /*...*/ }
}
// Usage
let circle = new Shapes.Circle();
Comparison Between Namespaces and Modules
- Scope Isolation: Modules naturally isolate scopes, while namespaces require explicit definition.
- Dependency Management: Modules explicitly declare dependencies, while namespaces rely on global availability.
- Loading Method: Modules support static analysis and on-demand loading, while namespaces typically require full loading.
- Modern Development: Modules are an ES6 standard, while namespaces are a TypeScript feature.
In most modern front-end development, modules are recommended as the primary code organization method, with namespaces reserved for specific scenarios like type declaration merging.
Dynamic Import and Code Splitting
ES2020 introduced the dynamic import syntax import()
, which returns a Promise and allows modules to be loaded on-demand at runtime. This is a key mechanism for implementing code splitting.
// Dynamic import example
button.addEventListener('click', async () => {
const module = await import('./module.js');
module.doSomething();
});
Dynamic imports are particularly useful for:
- Route-level code splitting
- On-demand loading of large libraries
- Conditionally loading different implementations
// Load different modules based on conditions
async function getRenderer() {
if (WebGL.isSupported()) {
return await import('./webgl-renderer.js');
} else {
return await import('./canvas-renderer.js');
}
}
Practical Applications of Modules
Using ES Modules in Browsers
Modern browsers natively support ES modules by adding the type="module"
attribute to script tags:
<script type="module" src="app.js"></script>
Module scripts automatically enable strict mode, have their own scope, and support top-level await.
Using ES Modules in Node.js
Node.js has stably supported ES modules since version 12. There are two ways to use them:
- Use the
.mjs
extension - Set
"type": "module"
inpackage.json
// package.json
{
"type": "module"
}
// app.js
import { readFile } from 'fs/promises';
Module Resolution Strategies
Module resolution strategies vary by environment:
- Browsers: Use URL resolution, requiring full paths or correct relative paths.
- Node.js: Supports
node_modules
lookup and file extension completion.
// In browsers, extensions are mandatory
import './module.js';
// In Node.js, extensions can be omitted
import './module';
Interoperability Between Modules and CommonJS
In Node.js environments, ES modules can interoperate with CommonJS modules, but with some limitations:
- ES modules can import CommonJS modules.
- CommonJS modules cannot use
require
to load ES modules. - Special handling is required for default exports during interoperability.
// ES module importing CommonJS
import cjsModule from 'commonjs-module';
console.log(cjsModule.default); // CommonJS exports are on the default property
// Using ES modules in CommonJS (requires dynamic import)
async function loadESM() {
const esm = await import('esm-module');
}
Advanced Module Patterns
Re-export Pattern
Modules can re-export exports from other modules, which is useful for creating library entry files:
// Re-export all named exports
export * from './module1.js';
export * from './module2.js';
// Selective re-export
export { foo, bar as myBar } from './module.js';
// Re-export default exports
export { default } from './module.js';
Aggregate Module Pattern
Large projects can use aggregate modules to organize code structure:
src/
components/
index.js // Aggregate exports
Button.js
Input.js
...
// components/index.js
export { default as Button } from './Button.js';
export { default as Input } from './Input.js';
// ...
// Usage: unified import
import { Button, Input } from './components';
Side-effect Import
Some modules do not export anything but only execute side effects (e.g., polyfills). The standalone import syntax can be used:
import 'polyfill.js';
Module Best Practices
- Single Responsibility: Each module should focus on one specific functionality.
- Explicit Exports: Avoid wildcard imports (
import * as
) unless necessary. - Consistent Naming: Export names should clearly express their purpose.
- Avoid Default Exports: Named exports are better for code maintenance and refactoring.
- Directory Structure: Organize module directories logically and use index files for aggregate exports.
// Recommended: Explicitly import needed functionalities
import { parse, stringify } from 'json-utils';
// Not recommended: Wildcard import
import * as utils from 'json-utils';
Module Performance Considerations
- Bundling Tools: Production environments should use bundling tools (e.g., Webpack, Rollup) to merge modules.
- Code Splitting: Use dynamic imports for on-demand loading.
- Preloading: Use
<link rel="modulepreload">
to hint browsers to preload critical modules. - HTTP/2: Leverage HTTP/2 multiplexing to optimize module loading.
<!-- Preload critical modules -->
<link rel="modulepreload" href="critical-module.js">
Future Developments of Modules
The ECMAScript module system is still evolving, with new features in the proposal stage:
- JSON Modules: Native support for importing JSON files.
- CSS Modules: Proposal to support importing CSS as modules.
- Module Fragments: Allow combining multiple module resources.
- Finer-grained Dynamic Imports: Such as
import.meta.resolve()
.
// Future JSON module proposal
import config from './config.json' assert { type: 'json' };
// Future CSS module proposal
import styles from './styles.css' assert { type: 'css' };
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:模块的静态解析特性
下一篇:动态import()函数