如何解决为浏览器设计的应用在服务器端运行的问题?

我遇到一个需求,工程中,有个Node.js技术栈的代码库要用到。但是这个代码库默认发布出的代码,都是按 ESM 模式发布,服务器端的工程,都是按’CommonJS’模式操作,根本没有办法 ‘import xxx’ 这个代码库,只要导入就会报错。

Node.js 里面有所谓的’dynamic import’,我也做了测试,动态导入没有解决这个问题。因为它并不是只有一个文件,而是所有的文件都按 ESM 模式做了处理。

重新编译发布这个库么?非常复杂,光配置这个环境就要一大堆的设定。

反复测试之下,找到了一个比较好的解决方案:用 Babel 转换,用编译工具把 ESM 模块转换成 CommonJS 模块。

1、安装工具包

npm install --save-dev @babel/core @babel/cli @babel/preset-env 
npm install --save-dev @babel/plugin-transform-modules-commonjs
Code language: Bash (bash)

2、在项目根目录下创建一个.babelrc文件,并添加以下内容:

{
 "presets": ["@babel/preset-env"], 
"plugins": ["@babel/plugin-transform-modules-commonjs"] 
}
Code language: JSON / JSON with Comments (json)

3、操作转换:

运行Babel来转换你的ESM模块。你可以创建一个npm脚本来方便执行这个操作。在

package.json中添加一个脚本命令:

"scripts": { "build": "babel src --out-dir lib" }Code language: JavaScript (javascript)

4、导入转换后的模块:

const myModule = require('./lib/commonjs-module'); 
// 如果使用 TypeScript,可以改成 import
Code language: JavaScript (javascript)

事情到这里还没有结束。

目前模块是可以顺利引入了,可是运行会报错:global 对象没有 document/window 属性。

这个原因很容易理解:服务器端的 js 引擎确实是没有浏览器端的这个属性的,但是有一个 ‘jsdom’ 库可以用来模拟。

不过,npm 安装 jsdom 之后,global.document = dom.window.document 这种操作是不行的。因为 global 对象在Node.js 中是只读的,不能直接设置属性。

解决办法是:

Object.defineProperty(global, 'window', {
    value: dom.window,
    writable: true,
    configurable: true
});

Object.defineProperty(global, 'document', {
    value: dom.window.document,
    writable: true,
    configurable: true
});
Code language: JavaScript (javascript)

如此,这个程序包就顺利使用了。因为只使用其部分功能,不涉及浏览器层面的图像、字体渲染,这些问题就不再考虑了。

Leave a Comment