[英]How to setup the TypeScript compiler for the library so that the unused modules will be cut off by Webpack in the dependents projects?
I am sorry about taking your time by making you read this.我很抱歉让您阅读本文而耽误您的时间。 I wrote it to answer the questions like "What are you doing?"
我写它是为了回答诸如“你在做什么?”之类的问题。 and "Why are you doing this?".
和“你为什么要这样做?”。
The library consists from a large number of helper functions an classes.该 库由大量辅助函数和类组成。 In this regard it's similar to lodash ( check the structure of lodash ), but unlike lodash the source code has been organized by multilevel directories .
在这方面它类似于 lodash(检查lodash的结构),但与 lodash 不同的是,源代码是按多级目录组织的。 It's comfortable for the developers but could be not comfortable for the users: to import the desired function to project, the user must need to know where it is, eg:
这对开发人员来说很舒服,但对用户来说可能不舒服:要将所需的功能导入到项目中,用户必须知道它在哪里,例如:
import {
computeFirstItemNumberForSpecificPaginationPage
} from "@yamato-daiwa/es-extensions/Number/Pagination";
To solve this problem most of functions has been imported to index.ts
and exported again from there.为了解决这个问题,大部分函数已经导入到
index.ts
并从那里再次导出。 Now user can get desired function as:现在用户可以获得所需的功能:
import {
computeFirstItemNumberForSpecificPaginationPage
} from "@yamato-daiwa/es-extensions";
Please note that all functions in index.ts
(will be compiled by TypeScript to index.js
) are intended for both BrowserJS and NodeJS.请注意
index.ts
中的所有函数(将由 TypeScript 编译为index.js
)适用于 BrowserJS 和 NodeJS。 The functionality especially for BrowserJS is in BrowserJS.ts
and especially for NodeJS in NodeJS.ts
(currently is almost empty but the reexporting methodology is same). BrowserJS 的功能在
BrowserJS.ts
,尤其是BrowserJS.ts
中的NodeJS.ts
(目前几乎是空的,但NodeJS.ts
方法是相同的)。
Also, until this problem will be solved, I included the compiled JavaScript to the library repository ( Distributable
directory ).此外,在这个问题得到解决之前,我将编译后的 JavaScript 包含到库存储库(可
Distributable
目录)中。
From now, @yamato-daiwa/es-extensions is the library and any project which depends on it is the consuming project .从现在开始,@yamato-daiwa/es-extensions 是图书馆,任何依赖它的项目都是消费项目。
I expected that all unused functions/classes of the consuming project will be cut off by Webpack optimizations .我预计消费项目的所有未使用的函数/类都将被Webpack 优化切断。 For example, in below case I expected that
isUndefined
function only will be left in Webpack bundle:例如,在下面的情况下,我预计
isUndefined
函数只会留在 Webpack 包中:
import { isUndefined } from "@yamato-daiwa/es-extensions"
const test: string | undefined = "ALPHA";
console.log(isUndefined(test));
But in reallity, Webpack left EVERYTHING from index.js
of the library.但实际上,Webpack 离开了库的
index.js
中的所有内容。 I beautified the minified JavaScript built by Webpack;我美化了 Webpack 构建的缩小版 JavaScript; it is like:
它像是:
(() => {
"use strict";
var e = {
5272: (e, t) => {
Object.defineProperty(t, "__esModule", {
value: !0
}), t.default = function(e, t) {
for (const [a, n] of e.entries())
if (t(n)) return a;
return null
}
},
7684: (e, t) => {
Object.defineProperty(t, "__esModule", {
value: !0
}), t.default = function(e, t) {
const a = [];
return e.forEach(((e, n) => {
t(e) && a.push(n)
})), a
}
},
// ...
I suppose everyone understanding that is not acceptable especially for browser applications where each kilobyte on count.我想每个人都明白这是不可接受的,尤其是对于每千字节计数的浏览器应用程序。
How to solve this problem?如何解决这个问题呢? The ideal solution (if it exists) will not touch the source files organization, just change the TypeScript configuration.
理想的解决方案(如果存在)不会触及源文件组织,只需更改 TypeScript 配置即可。
I created one more repository (repro) where you can try above example.我创建了另一个存储库(repro) ,您可以在其中尝试上面的示例。
npm i
command).npm i
命令)。src/index.ts
.src/index.ts
。 It imports isUndefined
function from the library and using it.isUndefined
函数并使用它。npm run ProductionBuild
npm run ProductionBuild
index.js
by tool like beautifier.io .index.js
。 You will see that whole library has been bundled while it's desired that only inUndefined
has been bundled.inUndefined
。The first cause candidate is the using of reexportint pattern, exactly Source/index.ts , Source/BrowserJS.ts and Source/NodeJS .第一个候选原因是使用reexportint模式,确切地说是Source/index.ts 、 Source/BrowserJS.ts和Source/ NodeJS 。 The compiled
index.js
looks like:编译后的
index.js
看起来像:
const isStringifiedNonNegativeIntegerOfRegularNotation_1 = require("./Numbers/isStringifiedNonNegativeIntegerOfRegularNotation");
exports.isStringifiedNonNegativeIntegerOfRegularNotation = isStringifiedNonNegativeIntegerOfRegularNotation_1.default;
const separateEach3DigitsGroupWithComma_1 = require("./Numbers/separateEach3DigitsGroupWithComma");
exports.separateEach3DigitsGroupWithComma = separateEach3DigitsGroupWithComma_1.default;
( Check full file ) ( 检查完整文件)
If to import each function from it's individual module like import isUndefined from "@yamato-daiwa/es-extensions/TypeGuards/isUndefined"
instead of import { isUndefined } from "@yamato-daiwa/es-extensions"
, no redundant code will be output.如果从它的单个模块导入每个函数,例如
import isUndefined from "@yamato-daiwa/es-extensions/TypeGuards/isUndefined"
而不是import { isUndefined } from "@yamato-daiwa/es-extensions"
,则不会出现冗余代码输出。 But as I already told, this solution is unacceptable because the library users must need to know where isUndefined
and other function has been organized.但正如我已经说过的,这个解决方案是不可接受的,因为库用户必须知道
isUndefined
和其他函数的组织位置。
The other cause could be the output modules type.另一个原因可能是输出模块类型。 Currently it's a
CommonJS
.目前它是一个
CommonJS
。 Here is the tsconfig.json
of the library:这是库的
tsconfig.json
:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "Node",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"removeComments": true,
"outDir": "Distributable/",
"declaration": true
},
"include": [ "Source/**/*" ]
}
According to hypothesis, depending on specific modules type the Webpack could bundle the code to monolithic structure where it's impossible to decompose and filter out some modules even if those has not been used.根据假设,根据特定的模块类型,Webpack 可以将代码捆绑到单体结构中,即使这些模块没有被使用,也无法分解和过滤掉一些模块。
Now all these (AMD, UMD, CommonJS) slowly become a part of history, but we still can find them in old scripts.
现在所有这些(AMD、UMD、CommonJS)都慢慢成为历史的一部分,但我们仍然可以在旧脚本中找到它们。
By the way, the TypeScript configuration in consuming project also could affect (included to repro).顺便说一句,消费项目中的 TypeScript 配置也可能会影响(包含在 repro 中)。 Currently it is:
目前是:
{
"compilerOptions": {
"target": "ES2020",
"strict": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"baseUrl": "./",
"paths": {
"@SourceFilesRoot/*": ["./src/*"]
}
}
}
I believe you at least need to set module
so that ES6 or above is ouput.我相信您至少需要设置
module
以便输出 ES6 或更高版本。 Possible values include可能的值包括
"es6"
"es2020"
"esnext"
The webpack documentation of tree-shaking says tree-shaking的webpack 文档说
Tree shaking is a term commonly used in the JavaScript context for dead-code elimination.
摇树是 JavaScript 上下文中常用于消除死代码的术语。 It relies on the static structure of ES2015 module syntax, ie import and export.
它依赖于 ES2015 模块语法的静态结构,即导入和导出。 The name and concept have been popularized by the ES2015 module bundler rollup.
ES2015 module bundler rollup 已经普及了名称和概念。
*Note: moduleResolution:
can remain as "node"
*注:
moduleResolution:
可以保留为"node"
However, a correct module:
setting is not necessarily sufficient, or may not even be desirable.然而,一个正确的
module:
设置不一定是足够的,甚至可能是不可取的。 See the section below:请参阅以下部分:
The webpack 2 release came with built-in support for ES2015 modules (alias harmony modules) as well as unused module export detection.
webpack 2 版本内置支持 ES2015 模块(别名和声模块)以及未使用的模块导出检测。 The new webpack 4 release expands on this capability with a way to provide hints to the compiler via the "sideEffects" package.json property to denote which files in your project are "pure" and therefore safe to prune if unused.
新的 webpack 4 版本扩展了此功能,通过一种方式通过“sideEffects” package.json 属性向编译器提供提示,以表示项目中的哪些文件是“纯”的,因此在未使用时可以安全修剪。
Actually, it appears that the default value for sideEffects:
if NOT specified is false
, so you don't really have to worry about that unless your ES6 code/module has certain side effects so that it SHOULDN'T be tree-shaken.实际上,似乎sideEffects:
if NOT specified 的默认值是false
,所以你真的不必担心这一点,除非你的 ES6 代码/模块有某些副作用,所以它不应该被摇树。
In fact setting sideEffects:false
in the top level package.json
is critical to enable tree-shaking in your project, as shown below.事实上,在顶层
package.json
设置sideEffects:false
对在项目中启用 tree-shaking 至关重要,如下所示。
(In a different project depending upon lodash
, sideEffects
was not critical. That may be because of the difference in library directory structure and index.js
). (在依赖
lodash
的不同项目中, sideEffects
并不重要。这可能是因为库目录结构和index.js
的不同)。
As an example, regular loadash
will not be tree shaken because it is it is not es6.例如,常规
loadash
不会被摇树,因为它不是 es6。 To enable tree shaking in lodash you have to add these packages and explicitly use the es6 version:要在 lodash 中启用摇树,您必须添加这些包并明确使用 es6 版本:
npm i lodash-es
npm i @types/lodash-es
and change you import statements from并更改您的导入语句
import * as _ from "lodash"
to到
import * as _ from "lodash-es"
See this SE answer for a discussion.有关讨论,请参阅此SE 答案。
The babel doc on its own settings for "module" says关于“模块”自己设置的babel 文档说
modules
模块
"amd" |
"amd" | "umd" |
“嗯” | "systemjs" |
"systemjs" | "commonjs" |
"commonjs" | "cjs" |
"cjs" | "auto" |
“自动” | false, defaults to "auto".
false,默认为“自动”。
Enable transformation of ES module syntax to another module type.
启用 ES 模块语法到另一种模块类型的转换。 Note that cjs is just an alias for commonjs.
请注意,cjs 只是 commonjs 的别名。
Setting this to false will preserve ES modules.
将此设置为 false 将保留 ES 模块。 Use this only if you intend to ship native ES Modules to browsers.
仅当您打算将本机 ES 模块发送到浏览器时才使用此选项。 If you are using a bundler with Babel, the default modules: "auto" is always preferred.
如果您在 Babel 中使用捆绑器,则默认模块:“auto”始终是首选。 modules: "auto"
模块:“自动”
By default @babel/preset-env uses caller data to determine whether ES modules and module features (eg import()) should be transformed.
默认情况下@babel/preset-env 使用调用者数据来确定是否应该转换 ES 模块和模块功能(例如 import())。 Generally caller data will be specified in the bundler plugins (eg babel-loader, @rollup/plugin-babel) and thus it is not recommended to pass caller data yourself -- The passed caller may overwrite the one from bundler plugins and in the future you may get suboptimal results if bundlers supports new module features.
通常调用者数据会在 bundler 插件中指定(例如 babel-loader、@rollup/plugin-babel),因此不建议自己传递调用者数据——传递的调用者可能会覆盖来自 bundler 插件的调用者,并且在未来如果捆绑器支持新的模块功能,您可能会得到次优结果。
So I guess if you really need babel (and you might not need it with webpack4) then you should ensure that "caller" is really specifying "false" so the ES6 stays as "ES6".所以我想如果你真的需要 babel(你可能不需要它与 webpack4),那么你应该确保“caller”真的指定了“false”,所以 ES6 保持为“ES6”。 In my setup that successfully minified, I was not using "babel".
在我成功缩小的设置中,我没有使用“babel”。
EDIT: Ran the experimental compile provided on Gihub by the author, but according to the stdout diagnostics, there was not a difference using "module":"ESNext"
compared to "CommonJS".编辑:运行作者在 Gihub 上提供的实验编译,但根据标准输出诊断,与“CommonJS”相比,使用
"module":"ESNext"
没有区别。 Could it be that modules under @yamato-daiwa/
are not pre compiled to es6?会不会是
@yamato-daiwa/
下的模块没有预编译为es6?
package.json
{
"private": true,
"scripts": {
"ProductionBuild": "webpack --mode production"
},
"sideEffects":false,
"devDependencies": {
"ts-loader": "9.2.3",
"typescript": "4.3.2",
"webpack": "5.38.1",
"webpack-cli": "4.7.0",
"webpack-node-externals": "3.0.0",
"@yamato-daiwa/es-extensions": "file:../yamato_daiwa-es_extensions/Distributable/"
}
}
"sideEffects":false,
has been added. "sideEffects":false,
已添加。
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"strict": true,
"module": "es2020",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"baseUrl": "./",
"paths": {
"@SourceFilesRoot/*": ["./src/*"]
}
}
}
Module was changed to "module": "es2020",
模块改为
"module": "es2020",
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "es2020",
"moduleResolution": "Node",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"removeComments": true,
"outDir": "Distributable/",
"declaration": true
},
"include": [ "Source/**/*" ],
}
Module was changed to "module": "es2020",
模块改为
"module": "es2020",
Use the original index.ts
file, with no changes使用原始的
index.ts
文件,没有任何变化
With the above settings I am getting an output index.js
of size 39 bytes:通过上述设置,我得到了一个大小为 39 字节的输出
index.js
:
(()=>{"use strict";console.log(!1)})();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.