You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写浏览器用crypto、服务端回退到node:crypto的npm包

自动适配浏览器/服务端的Crypto方案(无需用户额外配置)

针对你的react-querybuilder包在不同环境下的crypto适配需求,这里提供两种无需用户手动配置的解决方案:

方案一:单文件自动适配(改进现有代码)

现有代码的环境判断不够精准,在Webpack5这类默认不polyfill的构建工具中可能失效。下面是优化后的代码,能自动识别环境并加载对应crypto实现:

let cryptoModule: Crypto | undefined;

// 优先匹配浏览器环境的crypto
if (typeof window !== 'undefined' && window.crypto) {
  cryptoModule = window.crypto;
} else if (typeof globalThis !== 'undefined' && globalThis.crypto) {
  cryptoModule = globalThis.crypto;
} 
// Node.js环境下加载内置webcrypto
else if (typeof process !== 'undefined' && process.release?.name === 'node') {
  // 动态import避免浏览器打包时引入node模块
  const { webcrypto } = await import('node:crypto');
  cryptoModule = webcrypto as Crypto;
}

// 兜底处理Webpack5/CRA v5这类需要polyfill的场景
if (!cryptoModule) {
  try {
    // 自动加载crypto-browserify作为polyfill
    const crypto = await import('crypto-browserify');
    cryptoModule = crypto.webcrypto as Crypto;
  } catch (e) {
    throw new Error('crypto模块未找到,请确保环境支持Web Crypto API或安装crypto-browserify');
  }
}

const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
const position19vals = ["8", "9", "a", "b"];
const re = /[xy]/g;
const container = new Uint32Array(32);

export const generateID = () => {
  if (!cryptoModule) throw new Error('crypto模块初始化失败');
  cryptoModule.getRandomValues(container);
  let i = -1;
  return template.replaceAll(re, (char) => {
    i++;
    return char === "y"
      ? position19vals[container[i] % 4]
      : (container[i] % 16).toString(16);
  });
};

关键优化点:

  • 明确区分浏览器/Node.js环境,避免在浏览器中触发Node模块的require逻辑
  • 采用动态import加载node:crypto和crypto-browserify,只有在对应环境才会引入,不增加无关环境的打包体积
  • 兜底逻辑自动处理Webpack5/CRA v5这类需要polyfill的场景,用户无需手动配置polyfill规则

如果需要同步初始化(避免await),可以用条件require配合打包工具的tree-shaking:

// Node.js环境同步加载
else if (typeof process !== 'undefined' && process.release?.name === 'node') {
  const { webcrypto } = require('node:crypto');
  cryptoModule = webcrypto as Crypto;
}

这种方式需要确保打包工具(如Webpack)能正确tree-shake掉Node环境的代码,避免浏览器打包时引入多余模块。

方案二:分环境构建(客户端/服务端分开打包)

如果接受拆分代码的方案,分环境构建会更清晰,且打包体积更小。用Rollup、Vite等工具配置两个入口:

1. 客户端入口(client.ts)

const cryptoModule = window.crypto;

const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
const position19vals = ["8", "9", "a", "b"];
const re = /[xy]/g;
const container = new Uint32Array(32);

export const generateID = () => {
  cryptoModule.getRandomValues(container);
  let i = -1;
  return template.replaceAll(re, (char) => {
    i++;
    return char === "y"
      ? position19vals[container[i] % 4]
      : (container[i] % 16).toString(16);
  });
};

2. 服务端入口(server.ts)

import { webcrypto } from 'node:crypto';
const cryptoModule = webcrypto;

const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
const position19vals = ["8", "9", "a", "b"];
const re = /[xy]/g;
const container = new Uint32Array(32);

export const generateID = () => {
  cryptoModule.getRandomValues(container);
  let i = -1;
  return template.replaceAll(re, (char) => {
    i++;
    return char === "y"
      ? position19vals[container[i] % 4]
      : (container[i] % 16).toString(16);
  });
};

3. 配置package.json入口

在package.json中指定不同环境的加载入口,让工具自动匹配:

"main": "./dist/server.cjs",
"module": "./dist/server.esm.js",
"browser": "./dist/client.esm.js",
"types": "./dist/types/index.d.ts"

这样浏览器环境会自动加载browser字段对应的文件,Node.js环境加载main/module字段的文件,用户无需任何额外配置。

方案选择

  • 方案一适合不想拆分代码的场景,兼容性更广,但需要处理异步加载或依赖打包工具的tree-shaking
  • 方案二更轻量、清晰,适合对打包体积和性能有要求的场景

内容的提问来源于stack exchange,提问作者Jake Boone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 23:31:05