Keywords: TypeScript | Module Import | ES6 Specification | CommonJS | Express
Abstract: This article provides an in-depth exploration of the two primary module import syntaxes in TypeScript: import/require and import/as. By analyzing ES6 specification requirements, runtime behavior differences, and type safety considerations, it explains why import/require is more suitable for importing callable modules, while import/as creates non-callable module objects. With concrete code examples, it demonstrates best practices in Express/Node.js environments and offers guidance on module system evolution and future syntax selection.
Module System Evolution Background
JavaScript modularization has undergone a long development journey, from early global namespaces to solutions like CommonJS and AMD, ultimately establishing the standard ES Modules syntax in ES6 (ECMAScript 2015). As a superset of JavaScript, TypeScript needs to be compatible with multiple module systems, leading to the coexistence of different import syntaxes.
Core Differences Between Two Import Syntaxes
In TypeScript, import express = require('express') and import * as express from "express" may appear functionally similar but have fundamental differences. The former is TypeScript-specific syntax directly corresponding to CommonJS require behavior; the latter is standard ES Modules syntax creating a module namespace object.
ES6 Specification on Module Objects
According to the ES6 specification, the identifier created by import * as is a module object, with emphasis on it being purely an object. The specification explicitly states that such module objects are never callable or newable—they only contain properties of exported members.
// Legitimate usage
import * as mathUtils from './math';
console.log(mathUtils.PI);
const result = mathUtils.calculate(5);
// According to ES6 spec, the following usage is always illegal
import * as express from 'express';
const app = express(); // Error: module object is not callable
Correct Ways to Import Functions and Classes
When importing callable entities like functions or classes, you should use the import ... = require syntax, or depending on module loader support, the import ... from syntax.
// Correct approach 1: TypeScript-specific syntax
import express = require('express');
const app = express();
// Correct approach 2: ES Modules default import
import express from 'express';
const app = express();
// In supported environments, named imports can also be used
import { Router } from 'express';
const router = Router();
Runtime Compatibility Risks
In some runtime environments and transpilation configurations, using import * as express followed by calling express() might accidentally work, but this relies on specific transpilation behavior or runtime characteristics. This usage violates the ES6 specification and could fail at any time with future TypeScript versions, module loader updates, or runtime environment changes, posing serious compatibility risks.
TypeScript Module Resolution Mechanism
TypeScript's module resolution involves three key aspects: syntax selection, module resolution strategy, and output target configuration. Compiler options like moduleResolution and module directly affect how import statements are transpiled. When the target is set to CommonJS, ES Modules syntax is transpiled into corresponding require calls.
// TypeScript source (ES Modules syntax)
import { createServer } from 'http';
import * as url from 'url';
// Code after transpilation to CommonJS
const http_1 = require('http');
const url = require('url');
CommonJS and ES Modules Interoperability
TypeScript addresses interoperability issues between CommonJS and ES Modules through the esModuleInterop compiler flag. When this option is enabled, TypeScript generates additional helper code to properly handle default exports, making CommonJS module imports more intuitive.
Practical Recommendations and Best Practices
For new projects, it's recommended to prioritize standard ES Modules syntax. When importing callable modules, use the default import syntax import express from 'express'. Only consider using import ... = require syntax for specific compatibility needs or explicit type definition requirements.
Staying updated with TypeScript official documentation and ECMAScript specifications is the best approach to obtain the latest syntax information. As the JavaScript ecosystem evolves, module import syntax continues to be optimized and refined.
Type Safety Considerations
TypeScript's type system can catch incorrect module usage patterns. When using import * as to import a module containing callable exports, TypeScript provides type error messages, helping developers identify issues during the compilation phase.
// TypeScript will report type errors
import * as express from 'express';
const app = express(); // Error: This expression is not callable