如何创建同时支持浏览器与Node.js的npm包?
优雅实现跨浏览器/Node.js的npm包方案
嘿,这个问题我太熟了——很多人刚开始写跨环境npm包的时候,都会写出一堆乱糟糟的环境判断代码,别担心,现在有非常成熟且干净的方案来解决!我分两种场景给你讲:纯手动实现(不用打包工具)和更推荐的工具链方案(优雅且易维护)。
一、纯手动实现:精简的UMD风格导出
如果你不想引入打包工具,想手动搞定跨环境导出,可以用**简化版UMD(通用模块定义)**逻辑,比你之前的写法干净很多,同时支持现代浏览器的ES模块和Node.js的CommonJS/ES模块:
// 核心功能代码,先写好你的业务逻辑 const myExport = { coreMethod() { return "搞定跨环境啦!"; }, helperFunc() { return "这是辅助方法"; } }; // 跨环境导出逻辑,只写这一段就够 (function (global, factory) { if (typeof module === "object" && typeof module.exports === "object") { // Node.js 环境,支持CommonJS导出 module.exports = myExport; // 额外支持ES模块的default导出(兼容Node.js ES模块) if (typeof exports === "object") { exports.default = myExport; Object.assign(exports, myExport); } } else if (typeof define === "function" && define.amd) { // 兼容AMD模块(比如RequireJS) define([], factory); } else if (typeof global !== "undefined") { // 浏览器环境:挂载到window全局 global.myExport = myExport; // 支持现代浏览器的ES模块导出 if (typeof export !== "undefined") { export default myExport; export const { coreMethod, helperFunc } = myExport; } } })(typeof window !== "undefined" ? window : globalThis, function () { return myExport; });
这个写法的好处是:
- 兼容Node.js的
require()和import - 兼容老浏览器的全局变量挂载
- 兼容现代浏览器的ES模块导入
- 甚至支持AMD模块加载器(虽然现在用的人不多,但聊胜于无)
二、更推荐:用打包工具生成多格式产物
如果你的包复杂度较高,或者想长期维护,用Rollup或Webpack打包是最优解——它们能帮你自动生成多种模块格式,不用手动写环境判断,代码更干净,用户体验也更好。这里以Rollup为例(它天生适合打包库,比Webpack更轻量):
步骤1:写ES模块风格的核心代码
先把你的业务逻辑写成标准ES模块,不用管环境导出:
// src/index.js export const coreMethod = () => "搞定跨环境啦!"; export const helperFunc = () => "这是辅助方法"; // 也可以导出默认对象 export default { coreMethod, helperFunc };
步骤2:配置Rollup生成多格式产物
- 安装依赖:
npm install rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs --save-dev
- 创建
rollup.config.js配置文件:
export default { input: "src/index.js", // 你的源码入口 output: [ // 1. CommonJS格式:给Node.js的require()用 { file: "dist/my-package.cjs.js", format: "cjs" }, // 2. ES模块格式:给现代浏览器/Node.js的import用 { file: "dist/my-package.esm.js", format: "es" }, // 3. UMD格式:兼容老浏览器、全局变量、AMD模块 { file: "dist/my-package.umd.js", format: "umd", name: "myExport" // 浏览器全局变量名 } ], plugins: [ // 处理第三方依赖的解析 require("@rollup/plugin-node-resolve")(), // 把CommonJS依赖转成ES模块 require("@rollup/plugin-commonjs")() ] };
步骤3:在package.json里配置入口字段
让不同环境的工具自动识别正确的产物:
{ "name": "my-package", "version": "1.0.0", "main": "dist/my-package.cjs.js", // Node.js CommonJS入口 "module": "dist/my-package.esm.js", // ES模块入口(给webpack/Rollup等工具) "browser": "dist/my-package.umd.js", // 浏览器环境专用入口 "scripts": { "build": "rollup -c" // 执行打包的命令 } }
这样用户怎么用你的包?
- Node.js环境:
// CommonJS const myExport = require("my-package"); // ES模块(需要你的package.json加"type": "module") import myExport from "my-package"; - 现代浏览器:
<script type="module"> import myExport from "./dist/my-package.esm.js"; console.log(myExport.coreMethod()); </script> - 老浏览器:
<script src="./dist/my-package.umd.js"></script> <script> console.log(window.myExport.coreMethod()); </script>
额外小技巧
- 如果不需要兼容老环境,甚至可以只提供ES模块格式,现在Node.js已经原生支持ES模块(只要package.json里加
"type": "module"),现代浏览器也支持<script type="module">,代码会更简洁。 - 测试跨环境可以用Jest + jsdom,既能测试Node.js逻辑,也能模拟浏览器环境测试全局变量挂载。
内容的提问来源于stack exchange,提问作者junvar




