不要再依赖 CommonJS 了

      最后更新:2020-05-21 11:09:11 手机定位技术交流文章

      在本文中,我们将研究什么是CommonJS,以及为什么它会使您的JavaScript包变得过大。为了确保绑定器能够成功优化应用程序的大小,请避免依赖CommonJS模块,并在整个应用程序中使用ES2015模块语法。

      这篇文章最初发表在网站上,在原作者敏科·格切夫的授权下,由InfoQ中文网站翻译和分享。

      什么是共性?

      CommonJS是2009年的标准,它为JavaScript模块建立了一个合同。它最初是打算用于除网络浏览器以外的场景,主要用于服务器端应用程序。

      使用CommonJS,您可以定义模块,从中导出函数,并将它们导入其他模块。例如,下面的代码片段定义了一个导出五个函数的模块:加、减、乘、除和最大值:

      复制代码

      //utils . jsconst { MaxBy } = require(' lodash-es ');const fns = { add: (a,b) =>。a + b,减去:(a,b)= > 1。a - b,乘法:(a,b)= > 1。a * b,除:(a,b)= > 1。a / b,最大值:arr =>。maxBy(arr)};object . key(fns)。forEach(fnName =>。模块.导出[fnName]= fns[fnName]);稍后,另一个模块可以导入和使用这些功能:

      复制代码

      // index.jsconst { add } = require('。/utils ');console.log(add(1,2));{1}使用node调用index.js将在控制台中输出数字3。

      由于2010年初浏览器中缺乏标准化的模块系统,CommonJS也成为了JavaScript客户端库的一种流行模块格式。

      共性如何影响最终包装尺寸?

      服务器端的JavaScript应用程序的大小不如浏览器中的重要,因此CommonJS在设计时没有考虑对包大小的控制。与此同时,分析表明,JavaScript的包大小仍然是减缓浏览器应用程序速度的主要因素之一。

      JavaScript打包器和迷你器,如webpack和terser,执行各种优化措施来减小应用程序的大小。他们在构建过程中分析您的应用程序,并试图删除尽可能多的未使用的源代码。

      例如,在上面的代码片段中,您的最终包应该只包含add函数,因为这是您从utils.js导入index.js的唯一符号

      我们使用以下网络包配置来构建此应用程序:

      复制代码

      const path = require(' path ');module . exports = { entry:' index . js ',输出:{ filename: 'out.js ',路径:path.resolve(__dirname,' dist '),},模式:' production ',};这里,我们指定使用生产模式优化,并使用index.js作为入口点。调用webpack后,如果我们查看输出大小,我们将看到以下内容:

      复制代码

      $ cd dist & amp& amp请注意这个包裹的尺寸是625千磅。查看输出,我们会发现utils.js中的所有函数,加上lodash中的许多模块。虽然我们没有在index.js中使用lodash,但是它也被添加到了输出中,这给我们的生产资产增加了很多额外的负担。

      现在让我们将模块格式改为ECMAScript 2015,然后再试一次。这一次,utils.js将如下所示:

      复制代码

      导出常量add = (a,b) =>。a+b;导出常量减去= (a,b) =>。a-b;导出常量乘数= (a,b)= > 1。a * b;导出常量divide = (a,b) =>。a/b;从“lodash-es”导入{ MaxBy };导出常量最大值= arr =>。maxBy(arr);和index.js将使用ES2015模块语法从utils.js导入:

      复制代码

      从'导入{ add }。/utils ';console.log(add(1,2));使用相同的webpack配置,我们可以构建应用程序并打开输出文件。现在大小只有40字节,输出如下:

      复制代码

      (()=>。{“”使用严格的“”;console . log(1+2)})();请注意,最终的包没有包含任何我们在utils.js中没有使用的函数,也没有lodash的迹象!此外,TERSER(网络包使用的JavaScript压缩器)在控制台日志中嵌入了添加功能

      您可能会问,为什么使用CommonJS会导致输出包大近16,000倍?当然,上面的应用只是一个简单的例子。实际应用中的体积差异可能不是很大,但是CommonJS也可能会给您的生产和构造增加很大的负担。

      一般来说,通用组件模块很难优化,因为它们比专家系统模块更具动态性。为确保打包程序和压缩器能够成功优化应用程序,请避免依赖CommonJS模块,并在整个应用程序中使用ES2015模块语法。

      请注意,即使您在index.js中使用ES2015,如果您使用CommonJS,应用程序的包大小也会受到影响。

      为什么CommonJS使应用程序变得更大?

      为了回答这个问题,我们将研究网络包中ModuleConcatenationPlugin的行为,然后讨论静态分析。这个插件将所有模块合并成一个闭包,使您的代码在浏览器中执行得更快。让我们看一个例子:

      复制代码

      // utils.jsexport常量add = (a,b) =>。a+b;导出常量减去= (a,b) =>。a-b;复制代码

      // index.jsimport { add } from。/utils ';常量减去= (a,b) =>。a-b;console.log(add(1,2));{1}如上所示,我们有一个ES2015模块,并将其导入索引。我们还定义了一个减法函数。我们可以使用与上面相同的webpack配置来构建项目,但是这次我们将禁用最小化:

      复制代码

      const path = require(' path ');module . exports = { entry:' index . js ',输出:{ filename: 'out.js ',路径:path.resolve(__dirname,' dist '),},优化:{ minimize: false },模式:' production ',};查看生成的输出:

      复制代码

      /* * * * */(()= & gt。{//WebPackBootTrap/* * * * */“”使用严格的“”;//串接模块:。/utils.js**const add = (a,b) =>。a+b;常量减去= (a,b) =>。a-b;//串接模块:。/index . js * * const index _ subtract =(a,b) =>。a-b;**console.log(add(1,2));* */* * * * */})();在上面的输出中,所有函数的名称都相同空。为了防止冲突,webpack将index.js中的减法函数重命名为index_subtract。

      如果让压缩器处理上面的源代码,它将:

      删除未使用的减函数和索引减函数删除所有注释和冗余空格在控制台中内联添加函数的主体开发人员。因为webpack可以静态地(在构建时)理解我们从utils.js导入和导出的符号,所以它可以实现shake树优化。

      默认情况下,专家系统模块支持这种行为,因为它们比普通组件更容易执行静态分析。

      让我们看看完全相同的例子,但是这次我们将utils.js改为使用CommonJS模块:

      复制代码

      //utils . jsconst { MaxBy } = require(' lodash-es ');const fns = { add: (a,b) =>。a + b,减去:(a,b)= > 1。a - b,乘法:(a,b)= > 1。a * b,除:(a,b)= > 1。a / b,最大值:arr =>。maxBy(arr)};object . key(fns)。forEach(fnName =>。模块.导出[fnName]= fns[fnName]);这个小的更新将显著影响输出。受文章长度的限制,我在此仅分享其中的几篇:

      复制代码

      ...(()=>。{“”使用严格的“”;/* harmony IMPORT */var _ utils _ _ WEBSPACK _ IMPORTED _ MODULE _ 0 _ _ = _ _ WEBSPACK _ require _ _(288);常量减去= (a,b) =>。a-b;console.log((0,_ utils _ _ WEBSPACK _ IMPORTED _ MODULE _ 0 _ _/*。添加*/。IH)(1,2));})();请注意,最终的包包含一些webpack“运行时”:即负责从打包的模块中导入/导出函数的注入代码。这一次,我们不是将utils.js和index.js中的所有符号放在相同的名称空下,而是在运行时动态请求__webpack_require__的add函数。

      这是必要的,因为使用CommonJS,我们可以从任何表达式中获取导出名称。例如,以下代码是绝对有效的构造:

      复制代码

      模块.导出[本地存储. getItem(数学.随机())]=()= & gt;{…};打包程序无法知道在构建时导出的符号的名称,因为此处需要的信息是在用户浏览器的上下文中,并且仅在运行时可用。

      这样,压缩器就不能从index.js的依赖关系中知道它使用了什么,所以它不能优化无用的代码。我们还可以观察到第三方模块具有完全相同的行为。如果我们从node_modules导入CommonJS模块,您的构建工具链将无法正确优化它。

      基于共性的摇树优化

      因为CommonJS模块是动态定义的,所以它们更难分析。例如,与普通JS相比,ES模块中的导入位置总是一个文本(前者是一个表达式)。

      在某些情况下,如果您使用的库遵循关于CommonJS用法的特殊约定,您可以使用这个第三方webpack插件在构建时删除未使用的导出。然而,尽管这个插件增加了对shake树优化的支持,但它并没有涵盖依赖关系使用CommonJS的所有可能方式。这意味着您无法获得与专家系统模块相同的保证。此外,除了默认的webpack行为之外,它还增加了构建过程的额外成本。

      结论

      简而言之,为了确保打包程序能够成功优化您的应用程序,请避免依赖CommonJS模块,并在整个应用程序中使用ES2015模块语法。

      本文由 在线网速测试 整理编辑,转载请注明出处,原文链接:https://www.wangsu123.cn/news/7149.html

          热门文章

          文章分类