简体   繁体   English

如何为库设置 TypeScript 编译器,以便 Webpack 将依赖项目中未使用的模块截断?

[英]How to setup the TypeScript compiler for the library so that the unused modules will be cut off by Webpack in the dependents projects?

Preliminary explanations about subject library学科库初步说明

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目录)中。

Problem问题

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 配置即可。

Repro再现

I created one more repository (repro) where you can try above example.我创建了另一个存储库(repro) ,您可以在其中尝试上面的示例。

Experiment flow实验流程

  1. Get this repository by VCS通过 VCS 获取此存储库
  2. Install dependencies as always ( npm i command).像往常一样安装依赖项( npm i命令)。
  3. Check the src/index.ts .检查src/index.ts It imports isUndefined function from the library and using it.它从库中导入isUndefined函数并使用它。
  4. Run npm run ProductionBuild运行npm run ProductionBuild
  5. Beautify the output index.js by tool like beautifier.io .使用 beautifier.io 之类的工具美化输出index.js You will see that whole library has been bundled while it's desired that only inUndefined has been bundled.您将看到整个库已被捆绑,而希望只捆绑inUndefined

Musings about the cause关于原因的沉思

The first cause candidate is the using of reexportint pattern, exactly Source/index.ts , Source/BrowserJS.ts and Source/NodeJS .第一个候选原因是使用reexportint模式,确切地说是Source/index.tsSource/BrowserJS.tsSource/ 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)都慢慢成为历史的一部分,但我们仍然可以在旧脚本中找到它们。

🌎 javascript.info 🌎 javascript.info

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-shakingwebpack 文档

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:请参阅以下部分:

  • side effects副作用
  • imported libraries which are not ES6导入的不是 ES6 的库
  • if using babel, babel settings may conflict如果使用 babel,babel 设置可能会冲突

side effects副作用

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的不同)。

imported libraries导入的库

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 答案

babel通天塔

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?


The Best Solution最佳解决方案

Issue-WebpackDoesNotCuttOfTheUnusedFunctionality问题-WebpackDoesNotCuttOfTheUnusedFunctionality

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",

yamato_daiwa-es_extensions yamato_daiwa-es_extensions

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.

相关问题 如何防止 webpack 为未使用的模块引发 typescript 错误? - How do I prevent webpack from throwing typescript errors for unused modules? 如何设置 TypeScript + Babel + Webpack? - How to setup TypeScript + Babel + Webpack? 设置一个打字稿库以在Node.js,浏览器和其他打字稿项目中使用 - Setup a typescript library for usage in nodejs, browser and other typescript projects 如何在 typescript 项目的客户端包含模块? - How to include modules in client side in typescript projects? 关于webpack中的nodejs路径,webpack如何在typescript中找到模块? - About nodejs path in webpack, how webpack find modules in typescript? 如何为第三方react javascript模块设置@types,以便可以从打字稿访问它并将其与webpack打包在一起? - how can I setup @types for a third party react javascript module so I can access it from typescript and package it with webpack? 如何使用纯TypeScript库作为Webpack的依赖? - How to use a pure TypeScript library as a dependency with Webpack? 使用Webpack为其他项目编译typescript - Compiling typescript with Webpack for other projects 如何在Node中设置Webpack以支持ES6模块 - How to setup webpack for supporting ES6 Modules in Node 如何使用 Typescript、React 和 Webpack 导入 SCSS 或 CSS 模块 - How to import SCSS or CSS modules with Typescript, React and Webpack
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM