需求背景
ts
import { readFileSync } from "fs"
我们知道,web是不支持nodejs fs
模块,更不能读取磁盘文件,但是如果我要使用webpack打包为web版本,以上的代码是肯定过不去的。
所以我不得不在代码中作如下判断
ts
if (!isWeb) {
const { readFileSync } = require("fs")
// ...
}
是的,我判断在非web环境我才调用引入fs模块,并且使用相关的api,这样也能解决问题。
但是当情况复杂起来,这样解决方案就行不通了,会让代码难以维护。
比如以下的情况,期望的代码是如下,也能正常通过webpack的打包
ts
import express, { Express } from "express"
let expressInstance: Express | null = null;
if(!isWeb){
expressInstance = express();
}
很明显express
是不支持web环境的,所以你可能会这么改
ts
let expressInstance = null; // 变量的类型标注丢失
if(!isWeb){
const express = require("express"); // 在ts里面写require,让代码很蹩脚
expressInstance = express();
}
怎么看,这个代码都不是太尽如人意,而且上边的2个问题,就是让代码难以维护的罪魁祸首。
因为我的cc-plugin是可以同时发布web版本和nodejs版本,所以才出现这么奇葩的需求。
解决办法
让我再阐明下我的需求,我希望代码完全的使用typescript易于维护,并且webpack能够自动兼容一些nodejs的本地接口,这些接口在web环境不实现也行,其实压根也实现不了。
因为有isWeb
的判断,所以从代码编写角度来说,以下的写法肯定是最优解
ts
import express, { Express } from "express"
let expressInstance: Express | null = null;
if(!isWeb){
expressInstance = express();
}
那问题就指向了,有什么办法可以让预期的代码通过webpack编译呢?
如果你对webpack非常熟悉,肯定知道fallback,有许多nodejs的模块都有对应的browserify,可以让其在浏览器环境正常运行。
browserify简单来说,其原理就是使用纯js实现对应模块的api,保证其在浏览器里面也能正常工作,这和游戏引擎跨平台的道理是一样的。
而这里我的需求是仅仅保证webpack能通过编译即可,在web环境里面,我是不会调用到express的相关api的,顺着这个思路想下去,再结合fallback的思想,很自然的我就想到了解决办法:
实现一个express的browserify,相关的接口全部为空实现,这样就能保证webpack能够正常编译,而且能够正常import "express"
,虽然这样导入的express模块是一个空壳,但是我代码层面在web环境是不会调用到相关接口的,所以这样子也算解决了我的问题。
在webpack里面强制替换模块,使用的是webpack resolve alias,所以在webpack配置里面
json
{
resolve:{
alias:{
"express": "express-browserify"
}
}
}
当打包web版本时,使用以上配置,node版本时,还正常使用express
,这样子就不用来回改代码了,至此问题就完美解决了,以下是我写的几个模块的browserify空实现
总结
其实我看了几个fallback的browserify,并不是所有的nodejs本地api都支持了,有些browserify也是空实现,看来大家的思路都是相同的,浏览器办不到的就不实现,保证能正常编译就行了。