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

标签 javascript node.js typescript webpack

关于学科库的初步解释
很抱歉让你读到这篇文章来耽误你的时间。我写它是为了回答诸如“你在做什么?”之类的问题。和“你为什么要这样做?”。
library由大量的辅助函数组成一个类。在这方面它类似于 lodash ( check the structure of lodash ),但与 lodash 不同的是,源代码由 multilevel directories 组织。 .这对开发人员来说很舒服,但对用户来说可能不舒服:要将所需的功能导入项目,用户必须知道它在哪里,例如。 G。:

import { 
  computeFirstItemNumberForSpecificPaginationPage
} from "@yamato-daiwa/es-extensions/Number/Pagination";
为了解决这个问题,大部分功能都被导入到 index.ts 并从那里再次导出。现在用户可以获得所需的功能:
import { 
  computeFirstItemNumberForSpecificPaginationPage 
} from "@yamato-daiwa/es-extensions";
请注意 index.ts 中的所有功能(将由 TypeScript 编译为 index.js )适用于 BrowserJS 和 NodeJS。专门针对 BrowserJS 的功能位于 BrowserJS.ts 尤其是对于 NodeJS.ts 中的 NodeJS (目前几乎是空的,但再导出的方法是相同的)。
此外,在解决此问题之前,我将编译后的 JavaScript 包含到库存储库中( Distributable directory)。
问题
从现在开始,@yamato-daiwa/es-extensions 是 图书馆 任何依赖它的项目都是消费项目 .
我预计消费项目的所有未使用的函数/类将被 Webpack optimizations 切断。 .例如,在以下情况下,我预计 isUndefined函数只会留在 Webpack 包中:
import { isUndefined } from "@yamato-daiwa/es-extensions"

const test: string | undefined = "ALPHA";
console.log(isUndefined(test));
但实际上,Webpack 离开了 index.js 的所有内容的图书馆。我美化了 Webpack 构建的缩小版 JavaScript;它像是:
(() => {
    "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
                }
            },
  // ...
我想每个人都理解这是 Not Acceptable ,特别是对于每千字节计数的浏览器应用程序。
如何解决这个问题呢?理想的解决方案(如果存在)不会触及源文件组织,只需更改 TypeScript 配置。
复制品
我创建了 one more repository (repro)您可以在哪里尝试上面的示例。
实验流程
  • 通过 VCS
  • 获取此存储库
  • 像往常一样安装依赖项(npm i 命令)。
  • 查看 src/index.ts .它进口 isUndefined库中的函数并使用它。
  • 运行npm run ProductionBuild
  • 美化输出index.js通过 beautifier.io 等工具.您将看到整个库已被捆绑,而希望只有 inUndefined已捆绑。

  • 关于原因的沉思
    第一个候选原因是使用 reexportint 模式,正是 Source/index.ts , Source/BrowserJS.tsSource/NodeJS .编译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)
    如果从它的单个模块导入每个函数,如 import isUndefined from "@yamato-daiwa/es-extensions/TypeGuards/isUndefined"而不是 import { isUndefined } from "@yamato-daiwa/es-extensions" ,不会输出多余的代码。但正如我已经说过的,这种解决方案是 Not Acceptable ,因为图书馆用户必须知道 isUndefined 的位置。并组织了其他功能。
    另一个原因可能是输出模块类型。目前是 CommonJS .这里是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/**/*" ]
    }
    
    根据假设,根据特定的模块类型,Webpack 可以将代码捆绑到单体结构中,即使没有使用这些模块,也无法分解和过滤掉一些模块。

    Now all these (AMD, UMD, CommonJS) slowly become a part of history, but we still can find them in old scripts.

    🌎 javascript.info


    顺便说一句,消费项目中的 TypeScript 配置也可能会影响(包括在 repro 中)。目前是:
    {
      "compilerOptions": {
    
        "target": "ES2020",
        "strict": true,
    
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true,
        "experimentalDecorators": true,
        "skipLibCheck": true,
    
        "baseUrl": "./",
        "paths": {
          "@SourceFilesRoot/*": ["./src/*"]
        }
      }
    }
    

    最佳答案

    相信你至少需要设置module以便输出 ES6 或更高版本。可能的值包括

  • "es6"
  • "es2020"
  • "esnext"

  • webpack documentation of tree-shaking

    Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e. import and export. The name and concept have been popularized by the ES2015 module bundler rollup.


    *注:moduleResolution:可以保留为 "node"但是,正确的 module:设置不一定是足够的,甚至可能是不可取的。请参阅以下部分:
  • 副作用
  • 非 ES6 的导入库
  • 如果使用 babel,babel 设置可能会冲突

  • 副作用

    The webpack 2 release came with built-in support for ES2015 modules (alias harmony modules) as well as unused module export detection. 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.


    实际上,sideEffects: 的默认值似乎如果未指定是 false ,所以你真的不必担心,除非你的 ES6 代码/模块有某些副作用,所以它不应该被摇树。
    事实上设置sideEffects:false在顶层package.json对于在您的项目中启用 tree-shaking 至关重要,如下所示。
    (在取决于 lodash 的不同项目中,sideEffects 并不重要。这可能是因为库目录结构和 index.js 的差异)。
    导入的库
    例如,常规 loadash不会被树动摇,因为它不是 es6。
    要在 lodash 中启用树抖动,您必须添加这些包并明确使用 es6 版本:
    npm i lodash-es
    npm i @types/lodash-es
    
    并更改您的导入语句
    import * as _ from "lodash"
    
    import * as _ from "lodash-es"
    
    看到这个SE answer进行讨论。
    通天塔
    babel doc on its own settings for "module"

    modules

    "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false, defaults to "auto".

    Enable transformation of ES module syntax to another module type. Note that cjs is just an alias for commonjs.

    Setting this to false will preserve ES modules. Use this only if you intend to ship native ES Modules to browsers. If you are using a bundler with Babel, the default modules: "auto" is always preferred. modules: "auto"

    By default @babel/preset-env uses caller data to determine whether ES modules and module features (e.g. import()) should be transformed. Generally caller data will be specified in the bundler plugins (e.g. 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.


    所以我想如果你真的需要 babel(你可能不需要 webpack4),那么你应该确保“调用者”确实指定了“假”,所以 ES6 保持为“ES6”。在我成功缩小的设置中,我没有使用“babel”。

    编辑:运行作者在 Gihub 上提供的实验编译,但根据标准输出诊断,使用 "module":"ESNext" 没有区别与“CommonJS”相比。会不会是 @yamato-daiwa/ 下的模块?没有预编译成es6?

    最佳解决方案
    问题-WebpackDoesNotCuttOfTheUnusedFunctionalitypackage.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,已添加。tsconfig.json
    {
      "compilerOptions": {
    
        "target": "ES2020",
        "strict": true,
        "module": "es2020",
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true,
        "experimentalDecorators": true,
        "skipLibCheck": true,
    
        "baseUrl": "./",
        "paths": {
          "@SourceFilesRoot/*": ["./src/*"]
        }
      }
    }
    
    模块更改为 "module": "es2020",yamato_daiwa-es_extensionstsconfig.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": "es2020", 使用原文index.ts文件,没有更改
    通过上述设置,我得到一个输出 index.js大小为 39 个字节:
    (()=>{"use strict";console.log(!1)})();
    

    关于javascript - 如何为库设置 TypeScript 编译器,以便 Webpack 在依赖项目中切断未使用的模块?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68340624/

    相关文章:

    javascript - 如何为我自己的搜索引擎获取初始索引数据?

    javascript - 创建从 C# 方法到 JavaScript 文件的 JavaScript 代码

    node.js - 使用 Node Js 的 Twitter Webhooks,身份验证问题

    javascript - 如何将 SQLite 结果传递给 Nodejs 中的回调

    javascript - 使用字节将字母数字代码解码为键值对象

    Angular 2等待同步方法

    angular - 如何向守卫传递参数?

    javascript - ajax数据加载后DataTables手动设置分页

    javascript - 在异步函数中执行 createStore 时未定义存储?

    javascript - 如何在 Angular 2 中使用 ngIf-else 中的表达式