我已经看到很多关于这个主题的困惑,即使是经验丰富的JavaScript开发人员也很难掌握其中的微妙之处。所以我认为值得为此编写一个简短的教程。我已经看到很多关于这个主题的困惑,即使是经验丰富的JavaScript开发人员也很难掌握其中的微妙之处。所以我认为值得为此编写一个简短的教程。假设你有一个JavaScript模块,你想为Node和浏览器发布到npm。但要小心!这个特定模块的实现在Node版本和浏览器版本之间略有不同。这种情况经常发生,因为Node和浏览器之间存在许多细微的环境差异。在这种情况下,有一些巧妙的方法可以解决问题,尤其是当您尝试使用尽可能小的浏览器包进行优化时。让我们构建一个JS包让我们编写一个名为base64-encode-string的小型JavaScript包。它所做的只是将字符串作为输入并输出它的base64编码版本。对于浏览器,这很简单:我们只需要使用内置的btoa函数:module.exports=function(string){returnbtoa(string);};但是,Node.js中没有btoa功能。因此,我们需要自己创建一个Buffer,并对其调用buffer.toString():module.exports=function(string){returnBuffer.from(string,'binary').toString('base64');};对于字符串,两者都应提供其正确的base64编码版本,例如:varb64encode=require('base64-encode-string');b64encode('foo');//Zm9vb64encode('foobar');//Zm9vYmFy现在我们只需要一些方法来检测我们是否在浏览器或节点上运行,这样我们就可以确定我们使用的是正确的版本。Browserify和Webpack都定义了一个名为process.browser的字段返回true(译者注:即在浏览器环境下),但是在Node上这个字段返回false。所以我们只需要简单地:if(process.browser){module.exports=function(string){returnbtoa(string);};}else{module.exports=function(string){returnBuffer.from(string,'binary').toString('base64');};}现在我们只需要将文件命名为index.js,输入npmpublish就大功告成了,对吧?好吧,这个方法可行,但不幸的是,这个实现有一个巨大的性能问题。由于我们的index.js文件包含对Node的本机进程和Buffer模块的引用,Browserify和Webpack都会自动导入它们的polyfill以将它们捆绑到这些模块中。对于这个简单的九行模块,我计算出Browserify和Webpack将创建一个24.7KB(7.6KBmin+gz)的压缩包。这种东西,用的空间太大了,因为在浏览器中,只需要btoa来表示这个。“浏览器”字段,我怎么爱你?如果您在Browserify或Webpack文档中寻找有关如何解决此问题的提示,您可能最终会找到node-browser-resolve。这是package.json中“browser”字段的规范,可以用来定义浏览器版本构建时应该替换什么。使用这种技术,我们可以将以下段落添加到我们的package.json中:{/*...*/"browser":{"./index.js":"./browser.js"}}然后拆分函数分为两个不同的文件:index.js和browser.js://index.jsmodule.exports=function(string){returnBuffer.from(string,'binary').toString('base64');};//browser.jsmodule.exports=function(string){returnbtoa(string);};通过这次改进,Browserify和Webpack会给出更合理的打包方式:Browserify的压缩包是511字节(315min+gz),Webpack的压缩包是550字节(297min+gz)。当我们将包发布到npm时,在Node中运行require('base64-encode-string')的人将获得代码的Node版本,而在Browserfy和Webpack中运行的人将获得代码的浏览器版本。对于Rollup,它稍微复杂一些,但没有太多额外的工作。Rollup用户需要使用rollup-plugin-node-resolve并在选项中将browser设置为true。不幸的是,对于jspm,不支持“浏览器”字段,但jspm用户可以通过require('base64-encode-string/browser')或jspminstallnpm:base64-encode-string-o"{main:'browser.js'}"来解决这个问题。或者,包作者可以在他们的package.json中指定一个“jspm”字段。高级提示这种直接的“浏览器”方法效果很好,但对于较大的项目,我发现它在package.json和代码库之间引入了一种尴尬的耦合。例如,我们的package.json很快就会变成这样:{/*...*/"browser":{"./index.js":"./browser.js","./widget.js":"./widget-browser.js","./doodad.js":"./doodad-browser.js",/*etc.*/}}在这种情况下,任何时候你想要一个对于浏览器友好的模块,您需要创建两个单独的文件,并记住在“浏览器”字段中添加额外的一行以连接它们。还要注意不要拼错任何东西!而且,您会发现自己将少量代码提取到单独的模块中,只是因为您想避免if(process.browser){}检查。当这些*-browser.js文件累积时,它们会开始使代码库难以导航。如果这种情况变得太难处理,还有其他一些解决方案。我自己的偏好是使用Rollup作为构建工具,自动将单个代码库拆分为单独的index.js和browser.js文件。这具有将您提供给用户的代码去模块化、节省空间和时间的附加值。为此,首先安装rollup和rollup-plugin-replace,然后定义一个rollup.config.js文件:importreplacefrom'rollup-plugin-replace';exportdefault{entry:'src/index.js',format:'cjs',plugins:[replace({'process.browser':!!process.env.BROWSER})]};(我们将使用process.env.BROWSER作为一种在浏览器构建和Node构建之间轻松切换的方式。)接下来,我们可以使用正常的process.browser条件创建一个具有单个函数的src/index.js文件:exportdefaultfunctionbase64Encode(string){if(process.browser){returnbtoa(string);}else{returnBuffer.from(string,'binary').toString('base64');}}然后在package.json中添加prepublish步骤生成文件:{/*...*/"scripts":{"prepublish":"rollup-c>index.js&&BROWSER=truerollup-c>browser.js"}}生成的文件非常简单易读://index.js'usestrict';functionbase64Encode(string){{returnBuffer.from(string,'binary').toString('base64');}}module.exports=base64Encode;//browser.js'usestrict';functionbase64Encode(string){{returnbtoa(string);}}module.exports=base64Encode;你会注意到Rollup会根据需要自动将process.browser转换为true或false,然后去除死代码。因此在生成的浏览器包中不会有对进程或缓冲区的引用。使用这种技术,你可以在你的代码库中有任意数量的process.browser开关,发布的结果是两个小的集中式index.js和browser.js文件,其中对于Node只有Node相关的代码,对于Browsersonly有特定于浏览器的代码。作为附带好处,您可以配置Rollup以生成ES模块构建、IIFE构建或UMD构建。如果你想要一个示例,请查看我的项目marky,这是一个具有多个Rollup构建目标的简单库。这篇文章中描述的实际项目(base64-encode-string)也发布在npm上,您可以查看它以了解它是如何实现的。源代码在GitHub上。
