浏览器中使用ES模块并兼容ES5:解决dotenv解析报错及打包疑问
解决方案:必须用Webpack(或同类打包工具)处理
为什么直接在浏览器里用原生ES模块会报错?
浏览器原生ES模块只能识别带相对/绝对路径的导入(比如./utils.js、/src/xxx.js),无法解析dotenv、node-fetch这种裸模块标识符(npm包名)。这些包的代码本身是Node环境的,浏览器也无法直接运行,必须通过打包工具转译、合并成浏览器能识别的代码。
另外你的代码里还有两个浏览器不兼容的特性:
process.env是Node环境变量,浏览器没有这个全局对象node-fetch是Node端的fetch实现,浏览器本身已经有原生fetch
用Webpack实现你的两个需求
下面是具体步骤,同时满足「浏览器可作为ES模块引入」和「兼容ES5支持require调用」:
1. 调整原代码
先修正代码里的浏览器不兼容部分:
// 去掉dotenv的导入,改用Webpack注入环境变量 // import * as dotenv from 'dotenv'; // dotenv.config(); // 浏览器有原生fetch,不需要node-fetch // import fetch from 'node-fetch'; // 定义mockLocalStorage,避免未定义报错 const mockLocalStorage = { getItem: (key) => localStorage.getItem(key), setItem: (key, value) => localStorage.setItem(key, value) }; const BASE_URL = process.env.BASE_URL || 'localhost:9000'; const myLocalStorage = process.env.NODE_ENV === 'development' ? mockLocalStorage : window.localStorage; export async function getDeviceId() { const API_URL = `${BASE_URL}/get_device_id`; try { const response = await fetch(API_URL); const uuid = await response.json(); if (uuid && uuid.length > 0) { console.log(uuid) } else { throw new Error('UUID is not available'); } } catch (error) { console.error('Error fetching uuid:', error); } }
2. 安装依赖
执行以下命令安装Webpack和相关工具:
npm install webpack webpack-cli babel-loader @babel/core @babel/preset-env dotenv-webpack --save-dev
3. 编写Webpack配置文件(webpack.config.js)
这个配置会同时生成两个版本的包:
- ES模块版本(供浏览器用
<script type="module">引入) - CommonJS版本(供Node环境用
require调用),且转译为ES5兼容代码
const path = require('path'); const Dotenv = require('dotenv-webpack'); const webpack = require('webpack'); module.exports = [ // 配置1:生成浏览器可用的ES模块 { entry: './src/index.js', output: { filename: 'device-utils.esm.js', path: path.resolve(__dirname, 'dist'), library: { type: 'module' }, environment: { arrowFunction: false // 可选:禁用箭头函数,更好兼容旧浏览器 } }, experiments: { outputModule: true }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { targets: 'last 2 versions, not dead', modules: false // 保持ES模块结构 }] ] } } } ] }, plugins: [ new Dotenv(), // 加载根目录的.env文件 new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) }) ], mode: process.env.NODE_ENV || 'development' }, // 配置2:生成兼容ES5的CommonJS模块(支持require) { entry: './src/index.js', output: { filename: 'device-utils.cjs.js', path: path.resolve(__dirname, 'dist'), library: { type: 'commonjs2' } }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { targets: 'ie 11, last 2 versions', modules: 'commonjs' // 转成CommonJS }] ] } } } ] }, plugins: [ new Dotenv(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) }) ], mode: process.env.NODE_ENV || 'development' } ];
4. 打包并使用
执行打包命令:
# 开发环境 npm run build-dev # 需要在package.json的scripts里加"build-dev": "webpack --mode development" # 生产环境 npm run build # 加"build": "webpack --mode production"浏览器端使用ES模块:
<script type="module"> import { getDeviceId } from '/dist/device-utils.esm.js'; getDeviceId(); </script>Node环境使用require:
const { getDeviceId } = require('./dist/device-utils.cjs.js'); // 注意:Node环境调用getDeviceId会因为没有window和fetch报错,若要在Node用,需要重新引入node-fetch并处理localStorage
关键说明
- 环境变量处理:用
dotenv-webpack加载.env文件,通过DefinePlugin注入到代码中,替代Node的process.env。 - ES5兼容:通过
@babel/preset-env配置目标浏览器,将ES6+代码转译为ES5。 - 多模块输出:Webpack支持多配置数组,同时生成ES模块和CommonJS模块,满足不同场景需求。
内容的提问来源于stack exchange,提问作者Richlewis
相关产品推荐
相关产品推荐

