Node.js ES6模块导入执行顺序困惑及环境变量读取异常问题
将初学Node.js时用CommonJS编写的项目转为ES6模块后,遇到了导入执行顺序的问题:从server.js启动项目,该文件先初始化dotenv配置,再导入app.js,但终端输出显示app.js中打印的NODE_ENV为undefined,server.js中打印的则是development——也就是说app.js的console语句先执行了。查资料发现关于import是否异步的说法不一,特此求解。
server.js 代码:
import dotenv from 'dotenv' // Dot Env Initialization dotenv.config({ path: './.env' }); console.log('NODE_ENV from Server: ' + process.env.NODE_ENV); // 这行在app.js的console之后打印 // Local Imports import app from './app.js'; const PORT = process.env.PORT || 3300; app.listen(PORT, () => { console.log(`App running on PORT: ${PORT}`); })
app.js 代码:
// Module Imports (3rd) import express from 'express'; import morgan from 'morgan'; // Local Imports import routerLogic from './src/routes/routerLogic.js' // Module Initializations const app = express(); // Middleware console.log('NODE_ENV from App.js: ' + process.env.NODE_ENV); // 这行先打印 if (process.env.NODE_ENV === 'development') { app.use(morgan('dev')); } app.use(express.json()); // Routes and Logics app.use('/', routerLogic); export default app;
终端输出:
$ node server.js NODE_ENV from App.js: undefined NODE_ENV from Server.js: development App running on PORT: 3300
问题原因
ES模块采用静态分析机制:Node.js在执行模块的顶层代码之前,会先扫描并加载所有import声明的依赖模块,并且执行这些依赖模块的全部顶层代码。也就是说,不管你把import app from './app.js'写在server.js的哪个位置,它都会在server.js里的dotenv.config()执行前,先加载并执行app.js的所有顶层代码——这时候dotenv还没配置,所以app.js里的process.env.NODE_ENV自然是undefined。
而你之前用CommonJS时没问题,是因为CommonJS的require()是运行时动态执行的:server.js先执行dotenv.config(),再执行require('./app.js'),这时候app.js运行时环境变量已经配置好了。
解决方案
方案1:将dotenv配置抽成独立模块并优先导入
创建一个单独的配置模块(比如config.js):
import dotenv from 'dotenv'; dotenv.config({ path: './.env' });
然后在server.js的最顶部导入这个模块,确保所有其他依赖加载前环境变量已经初始化:
import './config.js'; import app from './app.js'; console.log('NODE_ENV from Server: ' + process.env.NODE_ENV); const PORT = process.env.PORT || 3300; app.listen(PORT, () => { console.log(`App running on PORT: ${PORT}`); })
方案2:启动Node时通过参数预加载dotenv
在启动命令中使用-r参数预先加载dotenv模块,这样在任何代码执行前就完成环境变量配置:
node -r dotenv/config server.js
如果你的.env文件不在默认位置,可以指定路径:
node -r dotenv/config server.js dotenv_config_path=./.env
方案3:延迟读取环境变量(不推荐)
避免在模块顶层直接读取process.env,而是在需要使用的逻辑中延迟读取,比如把morgan的初始化放在一个函数里:
// app.js function setupMiddleware(app) { if (process.env.NODE_ENV === 'development') { app.use(morgan('dev')); } app.use(express.json()); } setupMiddleware(app);
不过这种方法需要调整代码结构,不如前两种直接彻底。
内容的提问来源于stack exchange,提问作者Maaz Rahman

