极大地减少了NPM软件包的大小
#javascript #网络开发人员 #typescript #npm

有一天,我想为所有最佳实践创建一个小型NPM库 - 具有测试覆盖范围,不错的文档,维护有效的版本控制和ChangElog等。我甚至详细描述了库的库解决方案以及如何使用它。我在创建它时感兴趣的任务之一是最小化输出NPM软件包的大小 - 另一个程序员将​​使用的代码。在本文中,我想描述为了实现所需目标而应用的方法。

我本人认为,NPM软件包的开发人员应特别注意他们提供给其他开发人员的包装。因为即使最终产品的开发人员甚至使用最先进的微型仪,如果NPM软件包的开发人员无济于事,他们也不会总是能够最大程度地优化输出捆绑包大小。

顺便说一句,我只讨论将捆绑到客户端代码中的那些软件包。下面的材料与后端软件包无关。

第一部分。一般建议

我决定将文章分为两个部分。在第一个中,我将告诉您有关一般技巧的信息,例如关于设置依赖关系或组装过程。而且有必要诉诸于他们。

在第二部分中,我将讲述如何编写代码以使软件包更小。在那里,一些技巧将是极端的 - 它们可以极大地影响包装尺寸,但也会极大地降低开发人员体验。因此,应用这些技术取决于您。

导入第三方软件包

让我们从最简单,最容易理解的开始。关于如何导入第三方软件包的代码,有一些简单的规则。

首先,如果您要创建一个库,您确定您和最终产品的开发人员都将使用特定的第三方软件包,则应将其标记为外部依赖关系。例如,如果您正在为React应用程序开发UI套件,则应将'react'标记为外部依赖项。

汇总中外部依赖配置的示例
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default {
  input: 'main.js',

  output: {
    file: 'bundle.js',
    format: 'iife',
    name: 'MyModule'
  },

  plugins: [
    // nodeResolve и commonjs are needed for enabling
    //  the opportunity to import modules.
    nodeResolve(),
    commonjs(),
  ],

  // And here you declare a list of external dependencies
  external: [
    'react',
  ],
};

如果标记为外部库的软件包,它的代码不会捆绑到软件包的代码中。

// If this is your code
import find from 'lodash/find';
export const myFind = (arr: unknown[]) => find(arr, )

// And you marked lodash/find as an external dependency
// This will be your NPM package code
import e from"lodash/find";let o=o=>e(o,e=>e>0);export{o as getPositiveNumbers};

// But if lodash/find is not marked, the content of lodash/find
// will be inserted into your code entirely, so your package
// will grow by 14 KB.

第二个主要提示是 - 您的库中应有最少的导入数量。有关下面的更多信息。

多填充

无需导入任何多填充以增加与不同浏览器的兼容性。如有必要,这将由最终产品的开发人员处理。如果您和另一个开发人员都粘贴了polyfill,它将被应用两次,并且通常将无法摆脱这种最典型的代码。

同时,某些多填充物的存在对于包装的正确运行至关重要。例如,如果它使用旧版本的装饰器,则很可能需要'reflect-medata'软件包。但是即使在这种情况下,您也无需自己导入任何东西。最好在包装的文档中指出这一需求,因此最终产品的开发人员将自己处理。进口多填充的责任应始终落在它们的肩膀上。

当您使用Typescript和包裹需要从polyfill键入键入时该怎么办?例如,'reflect-metadata'扩展了Reflect对象的键入。而且似乎由于不可能导入包裹,因此将不可能从中键入打字。但是不,很有可能在不导入JavaScript代码的情况下导入键入。

为此,足以创建一个使用扩展名 **。d.ts 的文件,在其中您必须导入polyfill,然后您必须在您的** tsconfig.json 。和VOILã!您的项目中出现了必要的键入,并且第三方软件包中不会有代码,因为具有扩展名 * .d.t.ts * *,原则上,无法生成JavaScript代码。

导入输入的示例而不导入JavaScript代码

global.d.ts

import 'reflect-metadata';

tsconfig.json

{
  "compilerOptions": {
    ...
  },
  "include": [
    "src",
    // Важно указать ваш созданный d.ts файл
    "global.d.ts"
  ],
}

实用程序包

用实用程序软件包的情况更加复杂。如果已经实现了您需要的功能,则使用某种实用程序包是非常合理的。但是,如果您不知道树木摇晃如何工作,如何组装导入的图书馆以及如何工作,那么您可能会从包装的杂草丛生的大小中遭受很大的痛苦。

在上面的示例中,我使用了lodash,并获得了14 kb的额外。但是,情况可能会变得更糟。看看下面的示例。从功能上讲,我编写了2个相同的导入。但是,在第一种情况下,导入后,包装的大小增加了70 kb。几乎是5倍。所有这些是因为在第一个情况下,我导入了整个库,而第二个则仅一个特定的文件。

import { find } from 'lodash';
import find from 'lodash/find';

因此,我强烈建议您在2023年不要在NPM软件包中使用'lodash'。它提供的大多数功能已经在JavaScript中构建。

重要的是要了解有关导入实用程序软件包的几个简单规则。

  1. 如果您需要的功能很简单,那么最简单的方法不是导入,而是复制该功能并应用文章中第二部分中描述的实践。因此,您不仅可以摆脱潜在的进口开销,而且还可以进一步优化输出文件的大小。

  2. 尝试使用可用的树木摇动机构的库。这种机制将允许您删除实际未使用的导入的第三方代码。简而言之,每次您编写import { … } from 'package'。您指的是包含所有导出库实体的文件 - 函数,类等,这意味着实际上所有这些实体都进入了最终捆绑包,即使仅导入一个函数。但是,由于摇晃树木,在生产模式下的编译阶段,未使用的进口被简单地删除。仅当包装以ESM格式组装时,即使用import/export语法。

  3. 才能使用此机制。
  4. 如果未以ESM格式组装软件包,请尝试像我在示例中一样导入必要的代码。 Lodash的行为非常人性化,并将功能分为单独的文件。如果您只能导入所需的文件,请进行。

  5. 如果要使用以通用JS格式(使用require的地方)编写的软件包,并且仅由一个文件组成,则这是一个不好的软件包。不要使用它。该软件包的开发人员没有考虑过其他开发人员将如何使用它。回到第一点,然后自己编写或复制功能。但!当然,我们只谈论那些软件包,除了您需要的功能外,还有不必要的代码。如果您需要整个图书馆,则不必这样受苦。

小型

minifier用于减小捆绑包的大小。他们可以删除未使用的代码,缩短表达式等等。现在已经有几个受欢迎的缩影了,它们继续出现:更熟悉的人 - 用javaScript编写-Terser和uglifyjs,即使是Babel也有其自己的Minifier版本,还有更现代的SWC(Rust)和Esbuild (用GO编写),还有其他许多鲜为人知的缩影。我建议您查看this repository。它包含各种流行微型的最新测试结果。

这是这些测试的简短描述。

  • 不同的修饰符可以提供不同的优化级别。前5名的差异平均为1-2%,但总共可以达到不同微型级的差异。

  • 微型速度速度的差异可能会大不相同数百次。但是,我不会谈论本文中的工作速度 - 现在我们只需要压缩质量。

不同的缩影可以对您的项目产生不同的结果。如果您真的想达到最小文件大小,则可以自己运行测试并为自己找到最佳的缩影。

至于我自己,就目前而言,我更喜欢使用SWC的缩影。它可以将const转换为let,默认情况下不会像泰瑟(Terser)那样添加额外的括号,它可以在您的变量上进行内装。

顺便说一句,谈到了额外的牙套。如果您不知道,Terser和其他一些工具添加了此类额外的牙套,因为OptimizeJS基准显示出它会影响JavaScript代码解析的速度。但是后来,V8开发人员描述了这种技术破坏性。 Optimezejs的主要开发商也放弃了他的项目。因此,如果您注意到工具会生成额外的牙套 - 尝试将其删除。

eCmascript版本

ecmascript功能可以分为2组 - 添加新对象或扩展其API的组,以及那些更改语言语法的对象。这是another repository,它方便地包含了一年中的所有Ecmascript特征,并具有其描述和示例。如果您查看ES2017更新,则第一组功能将包含Object.valuesObject.entries功能,以及第二组 - 异步功能。

有趣的是,在不同的组中,较旧浏览器的向后兼容性支持以不同的方式实现。对于第一组的功能,您需要添加polyfills,如上所述,NPM软件包的开发人员不应这样做。但是有了第二组的特征,一切都更加复杂。

如果旧浏览器看到async关键字,那么无论使用哪种多填充物,它都不会理解它的意思。因此,第二组功能必须编译成浏览器所感知的形式。

const func = async ({ a, b, ...other }) => {
    console.log(a, b, other);
};

将代码编译为ES5
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
var func = function (_a) { return __awaiter(void 0, void 0, void 0, function () {
    var a = _a.a, b = _a.b, other = __rest(_a, ["a", "b"]);
    return __generator(this, function (_b) {
        console.log(a, b, other);
        return [2 /*return*/];
    });
}); };

实现了创建异步功能的可能性(等待,__ generator),以及使用REST操作员( rest)的可能性。编译器还编写了其他代码,以便没有在函数参数中使用对象驱动器,而是有一个ES5兼容语法。

编译的代码看起来像是一团糟。为了提供向后的功能,编译器生成的__await__generator功能是因为存在async关键字和__rest功能。

NPM软件包开发人员的任务是决定将其编译为哪个ES版本。如果使用异步功能,然后为ES5编译了库,则将创建额外的代码。如果最终产品的开发人员执行相同的操作,则将添加两次异步功能的代码。相反,如果他为ES2017编译了库,则事实证明,最终您的代码不必要地向后的能力,您可以简单地使用ASYNC/等待的现代语法。

“老信徒”可能认为将代码编译到ES5(甚至陷入ES3)仍然是最合乎逻辑的,因为这样,最终产品的开发人员在需要的情况下可能不会使用像Babel这样的工具ES5。而且,对我来说,他们会错的。 ES6(ES2015)现在得到98%的浏览器支持;支持那些不使用它的浏览器是结束或计划完成的;因此,现在远离ES5中的汇编的趋势明显。同时,与其他过渡相比,编译ES6> ES5对包装大小的影响最大,因为ES6对语言的语法带来了很多改进。此外,ES6语法对于文章第二部分中描述的提示是必需的。

另外,使用旧语法会影响您的性能,但这是另一篇文章。

那么,我们应该将代码编译到哪个版本中?如果您正在编写供自己使用的代码 - 为自己或正在从事的项目编写代码,请指定主项目中指定的版本。对于其他开发人员,我建议使用ES6。所有以兼容性的名义。但是,不要忘记在文档中指定您使用的ES的哪个版本。以防万一,让其他开发人员了解您的包裹可能需要其他汇编。

但这不是全部。您不仅需要为特定版本编译代码。您还应该谨慎使用与要使用的版本相比,将ES的后期版本的语法使用。例如,如果将软件包编译到ES2015中,则不应使用ES2017的功能,例如。异步函数。

那不是那么可怕的

ES新版本中的大多数功能仅是“糖”。您可以使用承诺代替Object.entries -Object.keys而不是Array.prototype.includes -Array.prototype.find,而不是异步功能。如果仍然没有功能的类似物,您可以自己写。

// ESNext syntax
const func = async () => {
  const result = await otherFunc();
  console.log(result);
  return result.data;
};

// ES6 syntax
const func = () => {
  return new Promise(resolve => {
    otherFunc().then(result => {
      console.log(result);
      then(result.data);
    });
  });
};

// ==================

// ESNext syntax
if ([1, 2, 3].includes(anyConst)) { /* ... */ }

// ES6 syntax
if (!![1, 2, 3].find(it => it === anyConst)) { /* ... */ }

// ==================

// ESNext syntax
Object.entries(anyObj).forEach(([key, value]) => { /* ... */ });

// ES6 syntax
Object.keys(anyObj).forEach(key => {
  const value = anyObj[key];
  /* ... */
});

重要的是,尽管要谨慎,但允许使用基于Polifill的功能。但是基于语法的是一个很大的“否”。

生产/开发组件分离

一个简短但有趣的话题。如果突然要留下只有开发人员才能看到的功能 - 例如,函数参数的验证,到控制台的错误输出等。-您应该拆分汇编,仅在开发程序集中留下此附加功能。

很多软件包都可以做到这一点,例如React,MOBX或Redux工具包。实际上,组织非常简单。

源代码
export const someFunc = (a: number, b: number) => {
  if (__DEV__) {
    if (typeof a !== 'number' || typeof b !== 'number') {
      console.error('Incorrect usage of someFunc');
    }
  }

  console.log(a, b);
  return a + b;
};

rolup.config.js
import typescript from '@rollup/plugin-typescript';
import define from 'rollup-plugin-define';

export default [false, true].map(isDev => ({
  input: 'src/index.tsx',
  output: {
    file: `dist/your-package-name.${isDev ? 'development' : 'production'}.js`,
    preserveModulesRoot: 'src',
    format: 'esm',
  },
  plugins: [
    typescript(),
    define({
      replacements: {
        __DEV__: JSON.stringify(isDev),
      },
    }),
  ],
}));

输出dev版本
const someFunc = (a, b) => {
    {
        if (typeof a !== 'number' || typeof b !== 'number') {
            console.error('Incorrect usage of someFunc');
        }
    }
    console.log(a, b);
    return a + b;
};

export { someFunc };

输出产品版本
const someFunc = (a, b) => {
    console.log(a, b);
    return a + b;
};

export { someFunc };

让我们正式化它。在代码的编译阶段,您足以替换某些字符序列(在我的情况下,__DEV__)用所需的值-truefalse。接下来,您需要在条件下使用创建的标志。当标志在代码中替换时,如果(true){...}和(false){...}获得条件。然后if(false){...}代码被切断,因为它永远不会被调用。

有两个文件,您需要以某种方式将它们替换为最终产品开发人员的组装。在此之前,足以参考主包装文件中的NODE_ENV环境变量。同时,最终产品的开发人员不必在使用软件包-WebPack时配置此变量,例如,它本身将其配置。

,要使用有效的文件,您必须在主文件中添加条件。

// process.env.NODE_ENV sets on the final project side
if (process.env.NODE_ENV === 'production') {
  // using production version
  module.exports = require('./dist/react-vvm.production.js');
} else {
  // using development version
  module.exports = require('./dist/react-vvm.development.js');
}

此外,我还可以说,它无需缩小包装的开发组件。它是“开发人员”,因此开发人员可以主动与之互动。与最小化的代码互动非常困难。

第二部分。 DX更糟糕,但结果更好

最终产品的开发人员可以负担得起编写代码,以便对他来说很方便 - 默认情况下,他们的代码库将更大。 NPM软件包的开发人员具有较小的代码库,因此,在某些时候编写“美丽”代码以降低最终尺寸是很大的。

这是由于minifier不是魔术工具的事实。开发人员必须与他合作 - 他以某种方式编写代码,以便更小的人可以更多地挤压他的代码。

可重复性和可重复性

当然,这是一种一般实践,但也会影响包装的大小。不应该有可重复的代码。

功能创建

如果代码中有一系列重复的代码 - 部分或完全完全,请尝试将重复的功能分离为单独的函数。我认为此项目不需要示例。

对象的属性

还有几个有趣的东西。如果您在代码中使用object.subObject.field的表达式,则小型仪将能够将此表达式压缩至o.subObject.field的最大值,因为微型仪不知道进一步的压缩是否安全。因此,如果您经常在对象中参考相同的字段,请为其创建一个单独的变量并使用它。

优化之前的示例

源代码:

import { SomeClass } from 'some-class';

export const func = () => {
  const obj = new SomeClass();
  console.log(obj.subObject.field1);
  console.log(obj.subObject.field2);
  console.log(obj.subObject.field3);
};

缩小代码(182个字节)

为了清楚起见,我添加了线路断开和凹痕,但是指定文件大小,没有它们。

import {SomeClass as o} from "some-class";

const e = () => {
    const e = new o;
    console.log(e.subObject.field1), console.log(e.subObject.field2), console.log(e.subObject.field3)
};
export {e as func};

优化后示例

源代码:

import { SomeClass } from 'some-class';

export const func = () => {
  const obj = new SomeClass();
  const sub = obj.subObject;
  console.log(sub.field1);
  console.log(sub.field2);
  console.log(sub.field3);
};

缩小代码(164个字节)

import {SomeClass as o} from "some-class";

const e = () => {
    const e = (new o).subObject;
    console.log(e.field1), console.log(e.field2), console.log(e.field3)
};
export {e as func};

下一个优化可以称为 Extreme ,因为它确实使DX恶化。如果您必须经常在对象中使用特定属性或方法,则可以创建该属性名称或方法的变量。

优化之前的示例

源代码:

import { useEffect, useLayoutEffect, useRef } from 'react';

export const useRenderCounter = () => {
  const refSync = useRef(0);
  const refAsync = useRef(0);

  useLayoutEffect(() => {
    refSync.current++;
  });

  useEffect(() => {
    refAsync.current++;
  });

  console.log(refSync.current, refAsync.current);

  return {
    syncCount: refSync.current,
    asyncCount: refAsync.current,
  };
};

缩小代码(254个字节)

import {useRef as r, useLayoutEffect as e, useEffect as t} from "react";

const n = () => {
  let n = r(0), u = r(0);
  return e(() => {
    n.current++
  }), t(() => {
    u.current++
  }), console.log(n.current, u.current), {syncCount: n.current, asyncCount: u.current}
};
export {n as useRenderCounter};

优化后示例

源代码:

import { useEffect, useLayoutEffect, useRef } from 'react';

const CURRENT = 'current';

export const useRenderCounter = () => {
  const refSync = useRef<number>(0);
  const refAsync = useRef<number>(0);

  useLayoutEffect(() => {
    refSync[CURRENT]++;
  });

  useEffect(() => {
    refAsync[CURRENT]++;
  });

  console.log(refSync[CURRENT], refAsync[CURRENT]);

  return {
    syncCount: refSync[CURRENT],
    asyncCount: refAsync[CURRENT],
  };
};

缩小代码(234个字节)

import {useRef as e, useLayoutEffect as r, useEffect as t} from "react";

const n = "current", o = () => {
  let o = e(0), u = e(0);
  return r(() => {
    o[n]++
  }), t(() => {
    u[n]++
  }), console.log(o[n], u[n]), {syncCount: o[n], asyncCount: u[n]}
};
export {o as useRenderCounter};

使用current的次数越多,此优化就越有效。


使用ES6语法

将ES6语法与缩影结合使用可能是缩短代码的好方法。

使用箭头功能

在可压缩性方面,箭头功能在所有方面都比经典的功能更好。有两个原因。首先,当通过constlet连续宣布箭头时,所有后续的constlet都缩短了第一个。其次,箭头函数可以返回值而无需使用返回关键字。

优化之前的示例

源代码:

export function fun1() {
  return 1;
}

export function fun2() {
  console.log(2);
}

export function fun3() {
  console.log(3);
  return 3;
}

缩小代码(126个字节)

function n() {
    return 1
}

function o() {
    console.log(2)
}

function t() {
    return console.log(3), 3
}

export {n as fun1, o as fun2, t as fun3};

优化后示例

源代码:

export const fun1 = () => 1;

export const fun2 = () => {
  console.log(2);
};

export const fun3 = () => {
  console.log(3);
  return 3;
}

缩小代码(101个字节)

const o = () => 1, l = () => {
    console.log(2)
}, c = () => (console.log(3), 3);
export {o as fun1, l as fun2, c as fun3};

对象。分配和传播操作员

这是第一部分中描述的一般规则的特定情况。 ES6中的对象中没有传播运算符。因此,如果您在ES6下编译您的库,我建议您使用object.sign而不是此操作员。

优化之前的示例

源代码:

export const fun = (a: Record<string, number>, b = 1) => {
  return { ...a, b };
};

缩小代码(76个字节)

const s = (s, t = 1) => Object.assign(Object.assign({}, s), {b: t});
export {s as fun};

如您所见,Object.assign可能应用两次。


优化后示例

源代码:

export const fun = (a: Record<string, number>, b = 1) => {
  return Object.assign({}, a, { b });
};

缩小代码(61个字节)

const s = (s, t = 1) => Object.assign({}, s, {b: t});
export {s as fun};

尝试返回箭头功能中的值

来自“极端”类别的另一种优化。如果这不影响功能组件,则可以从函数返回值。节省很小,但会很小。但是,在函数中只有1个表达的情况下,它起作用。

优化之前的示例

源代码:

document.body.addEventListener('click', () => {
  console.log('click');
});

缩小代码(68个字节)

document.body.addEventListener("click",()=>{console.log("click")});

优化后示例

源代码:

document.body.addEventListener('click', () => {
  return console.log('click');
});

缩小代码(66个字节)

document.body.addEventListener("click",()=>console.log("click"));

停止在功能中创建变量

“极端”类别的另一种优化。通常,试图减少变量的数量是优化的正常想法 - 如果可以执行内联代码插入,则将其删除它们。但是,出于相同的安全原因,缩影无法独立摆脱所有变量。但是你可以帮助他。

查看库的编译文件。如果它具有某些变量的主体功能,则可以在代码中使用函数参数而不是创建变量。

优化之前的示例

源代码:

export const fun = (a: number, b: number) => {
  const c = a + b;
  console.log(c);
  return c;
};

缩小代码(71个字节)

const o=(o,n)=>{const t=o+n;return console.log(t),t};
export{o as fun};

优化后示例

源代码:

export const fun = (a: number, b: number, c = a + b) => {
  console.log(c);
  return c;
};

缩小代码(58个字节)

const o = (o, c, e = o + c) => (console.log(e), e);
export {o as fun};

这是一个非常强大的优化,因为最终它不仅可以帮助删除const,而且还可以删除组装文件中的return关键字。但是请记住,这种优化仅应应用于私人类方法和未从图书馆导出的功能上,因为您不应通过优化使对库API的理解复杂化。

最小使用常数

再次,“极端”建议。原则上,在组装代码中,letconst的使用最少。例如,为此,所有常数都可以在一个接一个地声明。同时,只有当我们尝试在一个地方声明所有常数时,建议才变得极端。

优化之前的示例

源代码:

export const a = 'A';

export class Class {}

export const b = 'B';

缩小代码(67个字节)

const s = "A";

class c {}

const o = "B";
export {c as Class, s as a, o as b};

优化后示例

源代码:

export const a = 'A';
export const b = 'B';

export class Class {}

缩小代码(61个字节)

const s = "A", c = "B";

class o {}

export {o as Class, s as a, c as b};

极端减少的一般建议

实际上,您可以提出很多技巧。因此,总而言之,我决定简单地描述组装的缩小文件的外观。而且,因此,如果您的输出文件与指定的描述不匹配,则可以挤压很多。

  1. 不应重复。重复功能可以分配给函数,经常使用的对象字段需要写入常数等。

  2. 应将非缩写代码的量保持在最低限度。这包括经常重复使用此内容,使用嵌套对象或方法等。高度希望最小化的文件包含一个单字母的表达式。

  3. functionreturnconstlet和其他关键字的数量也应保持在最低限度。使用通过const声明的箭头函数,连续声明常数,使用参数,而不是声明函数的常数等。

也是最重要的事情。仅当已经应用所有其他优化时,只有在不影响包装功能的情况下,才能求助于极端减少。而且,再次,优化不应影响API(因此,键入)。

结论

在您看来,极端压缩毫无意义,因为在我的示例中,我获得了几十个字节的最大增益。但是实际上,我专门使他们最少代表。在实际条件下,增益可能会更大。

最终,是否诉诸于使用极端建议取决于您。对我来说,关于我是否可以达到最小可能的文件大小,这对我自己来说是一个挑战。但是,如果您仍然想知道它们有多么有用,我可以说它们帮助我将图书馆的规模从2172字节缩小到1594。一方面,只有578个字节,但另一方面只有多达27%总包卷。

感谢您的关注,您可以在评论中分享您的意见。我希望我的文章对您有用 - 如果不是极端的建议,那么至少一般。我很可能没有在文章中指定一些内容。在这种情况下,我很乐意根据您的建议进行补充。