ES Modules vs CommonJS: Fixing ‘Cannot Use Import’ Error – wiki基地

ES Modules vs CommonJS: Fixing ‘Cannot Use Import’ Error

JavaScript’s evolution has brought about powerful features, but also new challenges, particularly in module management. The “Cannot use import statement outside a module” error is a common stumbling block, highlighting the fundamental differences between two dominant module systems: CommonJS (CJS) and ECMAScript Modules (ESM). Understanding these systems and how to properly configure them is crucial for modern JavaScript development.

ES Modules (ESM): The Modern Standard

Introduced in ECMAScript 2015 (ES6), ES Modules are the official, standardized module system for JavaScript. They offer a clean, static structure for organizing code, making dependencies explicit and enabling advanced features like tree-shaking for optimized bundle sizes.

Key characteristics of ESM:
* Uses import for bringing in modules and export for exposing functionality.
* Modules are typically parsed at compile time, allowing for static analysis.
* Supported natively by modern web browsers and recent versions of Node.js.
* Promotes a more declarative and maintainable codebase.

Example:
“`javascript
// myModule.js
export const myValue = 42;
export function myFunction() { // }

// main.js
import { myValue, myFunction } from ‘./myModule.js’;
console.log(myValue);
myFunction();
“`

CommonJS (CJS): Node.js’s Workhorse

CommonJS predates ESM and became the de facto module system for Node.js. It’s a dynamic module system, meaning modules are loaded synchronously at runtime. While powerful and widely adopted in the Node.js ecosystem, its synchronous nature can sometimes lead to performance bottlenecks in browser environments.

Key characteristics of CJS:
* Uses require() for importing modules and module.exports or exports for exposing functionality.
* Modules are loaded synchronously when require() is called.
* The default module system for .js files in older and default Node.js configurations.

Example:
“`javascript
// myModule.js
const myValue = 42;
function myFunction() { // }
module.exports = { myValue, myFunction };

// main.js
const { myValue, myFunction } = require(‘./myModule.js’);
console.log(myValue);
myFunction();
“`

The “Cannot Use Import Statement Outside a Module” Error

This error typically occurs when the JavaScript runtime (Node.js or a browser) encounters an import statement in a context where it expects CommonJS syntax. Essentially, the environment is treating your file as a CommonJS module, but you’re trying to use ES Module syntax.

Common scenarios leading to this error:

  1. Node.js Default Behavior: By default, Node.js interprets .js files as CommonJS. If you write import statements in such a file without specific configuration, Node.js will throw this error.
  2. Missing type="module" in package.json: For Node.js projects intending to use ESM, the package.json file needs a clear signal.
  3. Incorrect File Extensions: While Node.js can infer module type, using explicit file extensions like .mjs for ESM and .cjs for CJS helps avoid ambiguity.
  4. Browser <script> Tag: In HTML, a <script> tag loading a JavaScript file that uses import must have type="module".
  5. Build Tool Misconfiguration: Tools like Webpack, Rollup, or Babel, if not correctly configured, might fail to transpile or bundle ES Modules appropriately for the target environment.
  6. Mixing Module Types Without Care: Attempting to import a CJS module directly into an ESM file or vice-versa without proper interoperability strategies can cause issues.

Solutions to Resolve the Error

Resolving this error involves explicitly telling your JavaScript runtime or build tools which module system you’re using.

  1. For Node.js Projects (Recommended: type: "module" in package.json)
    The most straightforward way to enable ESM in a Node.js project is by adding "type": "module" to your package.json file:

    json
    {
    "name": "my-project",
    "version": "1.0.0",
    "type": "module",
    "main": "index.js"
    }

    This instructs Node.js to treat all .js files within that package as ES Modules. If you still need to use CommonJS files within this ESM-enabled project, you can rename them with a .cjs extension (e.g., legacy.cjs).

  2. Use .mjs File Extension (Node.js)
    Alternatively, you can rename your JavaScript files that use import/export to have a .mjs extension (e.g., main.mjs). This explicitly tells Node.js to interpret that specific file as an ES Module, regardless of the type field in package.json. This is useful for projects that need to mix both CJS and ESM without affecting the entire project’s module type.

  3. Revert to CommonJS Syntax
    If ES Modules are not a strict requirement for a particular file or project, simply switch back to require() and module.exports for your module interactions.

    “`javascript
    // Instead of: import { something } from ‘./module.js’;
    const { something } = require(‘./module.js’);

    // Instead of: export default something;
    // or: export const something = …;
    module.exports = { something };
    “`

  4. For Browser-Side JavaScript: Add type="module" to Script Tags
    When including JavaScript files that use import in an HTML page, ensure the <script> tag specifies type="module":

    html
    <script type="module" src="main.js"></script>

  5. Transpile with Babel
    For older environments or broader compatibility, you can use a transpiler like Babel. Configure Babel to transform ES Module syntax into CommonJS or another compatible format using presets like @babel/preset-env. This allows you to write modern ES Module code that can run in environments that only support CJS.

  6. Update Bundler Configuration
    If you’re using a module bundler (Webpack, Rollup, Vite, etc.), ensure its configuration is set up to correctly handle ES Modules. These tools are designed to resolve module dependencies and often transpile code for compatibility. Consult your bundler’s documentation for specific configurations related to ESM.

  7. Dynamic import() for Interoperability
    When you need to import a CommonJS module from an ES Module (or vice-versa) in a mixed environment, dynamic import() can be a lifesaver. This allows you to asynchronously load modules.

    javascript
    // In an ESM file, importing a CJS module dynamically
    async function loadCJSModule() {
    const cjsModule = await import('./path/to/cjs-module.js');
    console.log(cjsModule);
    }
    loadCJSModule();

  8. TypeScript Configuration
    For TypeScript projects, ensure your tsconfig.json file is correctly configured to output the desired module system. The module and moduleResolution options are critical. For modern Node.js ESM, you might use "module": "ESNext" and "moduleResolution": "NodeNext" (or "nodenext").

Best Practices

To prevent future “Cannot use import statement outside a module” errors and maintain a healthy codebase:

  • Consistency is Key: Aim to use a single module system (preferably ESM for new projects) across your entire project to minimize confusion and interoperability issues.
  • Stay Updated: Keep your Node.js version, browser, and build tools up-to-date. Newer versions often come with improved ESM support and fewer quirks.
  • Explicit Configuration: Always be explicit about your module system. Use type: "module" in package.json or appropriate file extensions (.mjs, .cjs).
  • Understand Dependencies: Be aware of the module systems used by your project’s dependencies, especially third-party libraries. If you’re importing a CJS library into an ESM project, you might need specific bundler configurations or dynamic imports.

By understanding the distinctions between ES Modules and CommonJS and applying the appropriate solutions and best practices, developers can confidently navigate the modern JavaScript landscape, eliminating the frustrating “Cannot use import statement outside a module” error and building more robust and maintainable applications.

滚动至顶部