将应用程序的一部分分解到它自己的 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”是来自项目本身的代码,并且认为另一个块包含依赖项):
- Gzipped:“主”块 ~ 31 kB,具有外部依赖性的块 ~ 193 kB(来自构建应用程序后的输出)
- 解压缩:“主”块 ~ 93 kB,具有外部依赖性的块 ~ 653 kB(来自浏览器)
从 TestApp
中分解出 PackageA
后的大小:
- Gzipped:主块 ~ 22 kB,具有外部依赖性的块 ~ 215 kB(来自构建应用程序后的输出)
- 解压缩:主块 ~ 57 kB,具有外部依赖性的块 ~ 745 kB(来自浏览器)
可以看出,整体大小随着 13 kB gzipped 和 56 kB unzip 的增加而增加。这个
对应于压缩后 ~ 6% 和解压缩后 ~ 8% 的增加。这不是特别多,但我仍然希望它们有些相似。
更多信息
PackageA
的内容发布为 es6 模块以允许 tree-shaking,这似乎可以正常工作,因为 PackageA
未使用的部分不会发送到 [=13= 的输出块中].
PackageA
的缩小由 UglifyJS 在使用 --compress
和 --mangle
选项发布之前完成。
- 源映射不包含在
PackageA
的源文件中,只能单独使用。
PackageA
的 package.json
中 dependencies
下列出的唯一软件包未在 TestApp
的其他任何地方使用,随后从 TestApp
中删除的 package.json
同时被添加到 PackageA
的 package.json
。使用了相同的版本。 PackageA
的所有其他依赖项都列在 peerDependencies
. 下
- 在删除
node_modules
文件夹和 运行 全新安装的依赖项后验证了所有大小。
PackageA
的内容与从 TestApp
中删除的文件夹完全相同,除了添加的 index.ts
页面导出其他文件的内容。此文件解压后大小 < 3 kB。
这种规模增加的可能来源是什么?我们可以从哪里开始寻找?也许这种增加可能在于代码在包中的转换方式,并且 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(解压缩),这是一个既可以理解又可以接受的结果。
我们在所有项目中都使用 create-react-app
和 Typescript,并且出现了一个稍大的常用 React 组件模块。试图将这些分解到它自己的 NPM 包中(为了更容易维护和更好地重用),这里称为 PackageA
,我们已经达到了测试应用程序的整体大小的情况,这里称为 TestApp
,比以前大(当相同的代码存在于同一应用程序的代码库中时)。 TestApp
是一个非常初级的应用程序,它基本上只是展示了 PackageA
中组件的一些(但不是全部)部分,以前是项目本身内部的组件,现在删除了这部分,而是从中导入私人出版 PackageA
.
在使用 TestApp
中的组件分解出 PackageA
之前初始 JS 块的大小(假设“main”是来自项目本身的代码,并且认为另一个块包含依赖项):
- Gzipped:“主”块 ~ 31 kB,具有外部依赖性的块 ~ 193 kB(来自构建应用程序后的输出)
- 解压缩:“主”块 ~ 93 kB,具有外部依赖性的块 ~ 653 kB(来自浏览器)
从 TestApp
中分解出 PackageA
后的大小:
- Gzipped:主块 ~ 22 kB,具有外部依赖性的块 ~ 215 kB(来自构建应用程序后的输出)
- 解压缩:主块 ~ 57 kB,具有外部依赖性的块 ~ 745 kB(来自浏览器)
可以看出,整体大小随着 13 kB gzipped 和 56 kB unzip 的增加而增加。这个 对应于压缩后 ~ 6% 和解压缩后 ~ 8% 的增加。这不是特别多,但我仍然希望它们有些相似。
更多信息
PackageA
的内容发布为 es6 模块以允许 tree-shaking,这似乎可以正常工作,因为PackageA
未使用的部分不会发送到 [=13= 的输出块中].PackageA
的缩小由 UglifyJS 在使用--compress
和--mangle
选项发布之前完成。- 源映射不包含在
PackageA
的源文件中,只能单独使用。 PackageA
的package.json
中dependencies
下列出的唯一软件包未在TestApp
的其他任何地方使用,随后从TestApp
中删除的package.json
同时被添加到PackageA
的package.json
。使用了相同的版本。PackageA
的所有其他依赖项都列在peerDependencies
. 下
- 在删除
node_modules
文件夹和 运行 全新安装的依赖项后验证了所有大小。 PackageA
的内容与从TestApp
中删除的文件夹完全相同,除了添加的index.ts
页面导出其他文件的内容。此文件解压后大小 < 3 kB。
这种规模增加的可能来源是什么?我们可以从哪里开始寻找?也许这种增加可能在于代码在包中的转换方式,并且 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(解压缩),这是一个既可以理解又可以接受的结果。