将应用程序的一部分分解到它自己的 NPM 包中会导致应用程序的整体体积变大

Factoring out part of app into its own NPM package results in larger overall app size

我们在所有项目中都使用 create-react-app 和 Typescript,并且出现了一个稍大的常用 React 组件模块。试图将这些分解到它自己的 NPM 包中(为了更容易维护和更好地重用),这里称为 PackageA,我们已经达到了测试应用程序的整体大小的情况,这里称为 TestApp,比以前大(当相同的代码存在于同一应用程序的代码库中时)。 TestApp 是一个非常初级的应用程序,它基本上只是展示了 PackageA 中组件的一些(但不是全部)部分,以前是项目本身内部的组件,现在删除了这部分,而是从中导入私人出版 PackageA.

在使用 TestApp 中的组件分解出 PackageA 之前初始 JS 块的大小(假设“main”是来自项目本身的代码,并且认为另一个块包含依赖项):

TestApp 中分解出 PackageA 后的大小:

可以看出,整体大小随着 13 kB gzipped 和 56 kB unzip 的增加而增加。这个 对应于压缩后 ~ 6% 和解压缩后 ~ 8% 的增加。这不是特别多,但我仍然希望它们有些相似。

更多信息

这种规模增加的可能来源是什么?我们可以从哪里开始寻找?也许这种增加可能在于代码在包中的转换方式,并且 create-react-app 以某种方式能够更有效地为项目本身包含的代码而不是导入代码执行此操作。我知道这是一个棘手的问题,可能有很多答案,而且很难回答。

这是分解出的tsconfig.json PackageA:

{
  "compilerOptions": {
    "target": "es6",
    "lib": ["es6", "dom"],
    "jsx": "react-jsx",
    "module": "es6",
    "rootDir": "./src",
    "moduleResolution": "node",
    "declaration": true,
    "sourceMap": true,
    "outDir": "./build/esm",
    "inlineSources": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["./src"],
  "exclude": ["**/*.test.tsx", "**/*.test.ts", "**/*.stories.tsx"]
}

我知道这个问题可能在很大程度上取决于您所处的情况,但我仍然认为我会分享我的过程。

测量尺寸

通常在构建时,您的框架会输出构建工件的大小。在 create-react-app 的情况下,这些大小似乎是压缩版本的大小。

您还可以检查您的浏览器开发工具并注意下载包的大小,这通常不是 gzip 压缩后的大小,而是实际大小。

在分解某些东西或进行某种比较时,请注意更改前后的不同大小。给出关于这些尺寸应该如何改变的某种直觉来评估是否是这种情况。

正在分析捆绑包

对于 NextJS,您可以使用 @next/bundle-analyzer,在大多数其他情况下,您可以使用 source-map-explorer。后者使用源映射将捆绑代码映射到源;如果源本身有源映射,则使用这些源,否则您将看到对 node_modules 的引用。对于 source-map-explorer,可能值得同时研究视觉表示和 json 表示,因为对于很多来源,视觉表示可能会抑制某个组件或库的参与json表示不会。

使用 source-map-explorer,您可以对应用程序的不同版本进行一些有价值的比较。

分析源代码到生产代码的特殊情况

例如 source-map-explorer 在确定某些源代码在某些上下文中比在另一个上下文中扩展得更多后,您可能需要进一步研究该问题。使用源地图(在开发模式或生产模式下,如果它们可用的话),您可能想要跟踪输出包中源代码特定部分的印记。许多浏览器支持将捆绑代码映射到源代码(给定源映射),但并非所有浏览器都允许您做相反的事情。但是,Firefox 具有此功能,它允许您在给定捆绑代码的情况下查看源代码,反之亦然。当您尝试跟踪某些源代码在输出中实际产生的结果时,这非常有用。

我的用例呢?

好吧,下面列出了导致我的案例大小增加的原因:

  • 我从包中的索引导入,索引导入了包的所有导出项。这不应该是 esm 模块的问题,但是在 package.json 中没有 sideEffects: false 标志会有一些歧义。尽管 MY 包中未使用的源未添加到输出包中,但不能排除一些间接依赖项没有副作用,因此将它们添加到包中,即使导入它们的组件甚至没有被使用。将 sideEffects: false 添加到我的包的 package.json 解决了这个问题并减小了包的大小。

  • 在我的主项目中保留使用 create-react-app 捆绑的组件时,由于 @babel/plugin-transform-runtime 的存在,Babel 在所有转译文件中添加的一些便利方法被删除插入。这个插件删除了这些方便的方法,因为当 运行 这些文件(它们可以从中导入)时保证存在一些 Babel 运行时。当相同的配置用于分解组件时,尺寸缩小得更多。

  • 最后,在撰写本文时,tsc 编译器输出的代码似乎比 Babel 更冗长(参见 TypeScript: tsc transpiles jsx to more verbose code than babel)。在转译包时,我首先使用 tsc,在开始使用 Babel 后,大小缩小得更多。

总而言之,通过上述更改,输出最终比重构前大 1.7 kB(解压缩),这是一个既可以理解又可以接受的结果。