一个 API 设计上的失误
意识到自己的设计失误,源于同事的一次开发报错
事情回到软件库设计当初,我需要为内部封装一个浏览器环境判断库,这个库需要的功能非常简单,判断是否在某些宿主环境中打开,比如移动端、微信、微博,以及我们自己的 APP,等等,实现思路也非常简单,拿到 UA 进行分析就行了
但是在 API 设计上,我有了两个思路。思路一,导出一个方法,该方法返回一个对象,包含我们需要的数据,思路二更粗暴,直接返回对象
// 方案一import browser from 'browser'const { isMobile, isWeibo } = browser()// 方案二import browser from 'browser'const { isMobile, isWeibo } = browser
我用了方案二,我的想法是 API 设计越简单越好,调用越方便越好,在导入库的时候,直接会去获取 navigator.userAgent
,这就有一个局限性,这个库只能运行在客户端,因为 server 上是没有 navigator.userAgent
的(但是我们可以做兼容,这个后面说)。当时我的想法是,我们的项目基本都是客户端项目,问题不大,如果是 Node 项目,不要去引这个包就行了
但是同事用在了 Nuxt 项目上。如果项目中用如上方式引入了库,打包的时候会把库分别打包到客户端和服务端的 bundle 中,这就意味着这个库会在服务端运行(因为设计是引入即执行)。我们可以临时用两个方案去解决这个问题,一是在 beforeCreate 和 created 外的其他钩子里,用 require 的方式去引这个包,保证只在客户端执行,但是我们其他包的引入方式都是用 ES6 import 的形式,这个特立独行就显的不优雅,而且没法做 tree shaking(尽管可能也不需要);另一个方案是修改库源码,兼容 Node server,当判断非客户端时候,直接返回空对象,但是这也有瑕疵,服务端也会去执行包的代码,尽管这并不需要
综上,我反思后觉得这个 API 设计是失误的,应该采用方案一,真正的核心代码运行还是交给业务方去做,其实细想,我所使用的开源库就没见过二的方式,也是有原因的
此外对于 Nuxt,我还有另外一个想法,能否客户端/服务端分别打包自己需要的代码呢?如上 browser 库只是客户端需要,服务端的 bundle 中就不用打包进去了,但是按照现在打包软件的打包方式来说,我觉得是做不到的
最后的假设,仿佛有另外一种做法,使用 Nuxt 插件,可以自定义插件作用的范围(客户端还是服务端,or both)