有一天,我想为所有最佳实践创建一个小型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代码。
global.d.ts tsconfig.json 导入输入的示例而不导入JavaScript代码
import 'reflect-metadata';
{
"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中构建。
重要的是要了解有关导入实用程序软件包的几个简单规则。
-
如果您需要的功能很简单,那么最简单的方法不是导入,而是复制该功能并应用文章中第二部分中描述的实践。因此,您不仅可以摆脱潜在的进口开销,而且还可以进一步优化输出文件的大小。
-
尝试使用可用的树木摇动机构的库。这种机制将允许您删除实际未使用的导入的第三方代码。简而言之,每次您编写
import { … } from 'package'
。您指的是包含所有导出库实体的文件 - 函数,类等,这意味着实际上所有这些实体都进入了最终捆绑包,即使仅导入一个函数。但是,由于摇晃树木,在生产模式下的编译阶段,未使用的进口被简单地删除。仅当包装以ESM格式组装时,即使用import/export
语法。 才能使用此机制。
-
如果未以ESM格式组装软件包,请尝试像我在示例中一样导入必要的代码。 Lodash的行为非常人性化,并将功能分为单独的文件。如果您只能导入所需的文件,请进行。
-
如果要使用以通用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.values
和Object.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的功能。但是基于语法的是一个很大的“否”。
生产/开发组件分离
一个简短但有趣的话题。如果突然要留下只有开发人员才能看到的功能 - 例如,函数参数的验证,到控制台的错误输出等。-您应该拆分汇编,仅在开发程序集中留下此附加功能。 P>
很多软件包都可以做到这一点,例如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__
)用所需的值-true
或false
。接下来,您需要在条件下使用创建的标志。当标志在代码中替换时,如果(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
的最大值,因为微型仪不知道进一步的压缩是否安全。因此,如果您经常在对象中参考相同的字段,请为其创建一个单独的变量并使用它。
源代码: 缩小代码(182个字节) 为了清楚起见,我添加了线路断开和凹痕,但是指定文件大小,没有它们。优化之前的示例
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);
};
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};
源代码: 缩小代码(164个字节) 优化后示例
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);
};
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恶化。如果您必须经常在对象中使用特定属性或方法,则可以创建该属性名称或方法的变量。
源代码: 缩小代码(254个字节) 优化之前的示例
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,
};
};
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};
源代码: 缩小代码(234个字节) 使用优化后示例
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],
};
};
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语法与缩影结合使用可能是缩短代码的好方法。
使用箭头功能
在可压缩性方面,箭头功能在所有方面都比经典的功能更好。有两个原因。首先,当通过const
或let
连续宣布箭头时,所有后续的const
或let
都缩短了第一个。其次,箭头函数可以返回值而无需使用返回关键字。
源代码: 缩小代码(126个字节) 优化之前的示例
export function fun1() {
return 1;
}
export function fun2() {
console.log(2);
}
export function fun3() {
console.log(3);
return 3;
}
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};
源代码: 缩小代码(101个字节) 优化后示例
export const fun1 = () => 1;
export const fun2 = () => {
console.log(2);
};
export const fun3 = () => {
console.log(3);
return 3;
}
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而不是此操作员。
源代码: 缩小代码(76个字节) 如您所见,优化之前的示例
export const fun = (a: Record<string, number>, b = 1) => {
return { ...a, b };
};
const s = (s, t = 1) => Object.assign(Object.assign({}, s), {b: t});
export {s as fun};
Object.assign
可能应用两次。
源代码: 缩小代码(61个字节) 优化后示例
export const fun = (a: Record<string, number>, b = 1) => {
return Object.assign({}, a, { b });
};
const s = (s, t = 1) => Object.assign({}, s, {b: t});
export {s as fun};
尝试返回箭头功能中的值
来自“极端”类别的另一种优化。如果这不影响功能组件,则可以从函数返回值。节省很小,但会很小。但是,在函数中只有1个表达的情况下,它起作用。
源代码: 缩小代码(68个字节) 优化之前的示例
document.body.addEventListener('click', () => {
console.log('click');
});
document.body.addEventListener("click",()=>{console.log("click")});
源代码: 缩小代码(66个字节) 优化后示例
document.body.addEventListener('click', () => {
return console.log('click');
});
document.body.addEventListener("click",()=>console.log("click"));
停止在功能中创建变量
“极端”类别的另一种优化。通常,试图减少变量的数量是优化的正常想法 - 如果可以执行内联代码插入,则将其删除它们。但是,出于相同的安全原因,缩影无法独立摆脱所有变量。但是你可以帮助他。
查看库的编译文件。如果它具有某些变量的主体功能,则可以在代码中使用函数参数而不是创建变量。
源代码: 缩小代码(71个字节) 优化之前的示例
export const fun = (a: number, b: number) => {
const c = a + b;
console.log(c);
return c;
};
const o=(o,n)=>{const t=o+n;return console.log(t),t};
export{o as fun};
源代码: 缩小代码(58个字节) 优化后示例
export const fun = (a: number, b: number, c = a + b) => {
console.log(c);
return c;
};
const o = (o, c, e = o + c) => (console.log(e), e);
export {o as fun};
这是一个非常强大的优化,因为最终它不仅可以帮助删除const
,而且还可以删除组装文件中的return
关键字。但是请记住,这种优化仅应应用于私人类方法和未从图书馆导出的功能上,因为您不应通过优化使对库API的理解复杂化。
最小使用常数
再次,“极端”建议。原则上,在组装代码中,let
和const
的使用最少。例如,为此,所有常数都可以在一个接一个地声明。同时,只有当我们尝试在一个地方声明所有常数时,建议才变得极端。
源代码: 缩小代码(67个字节) 优化之前的示例
export const a = 'A';
export class Class {}
export const b = 'B';
const s = "A";
class c {}
const o = "B";
export {c as Class, s as a, o as b};
源代码: 缩小代码(61个字节) 优化后示例
export const a = 'A';
export const b = 'B';
export class Class {}
const s = "A", c = "B";
class o {}
export {o as Class, s as a, c as b};
极端减少的一般建议
实际上,您可以提出很多技巧。因此,总而言之,我决定简单地描述组装的缩小文件的外观。而且,因此,如果您的输出文件与指定的描述不匹配,则可以挤压很多。
-
不应重复。重复功能可以分配给函数,经常使用的对象字段需要写入常数等。
-
应将非缩写代码的量保持在最低限度。这包括经常重复使用此内容,使用嵌套对象或方法等。高度希望最小化的文件包含一个单字母的表达式。
-
function
,return
,const
,let
和其他关键字的数量也应保持在最低限度。使用通过const
声明的箭头函数,连续声明常数,使用参数,而不是声明函数的常数等。
也是最重要的事情。仅当已经应用所有其他优化时,只有在不影响包装功能的情况下,才能求助于极端减少。而且,再次,优化不应影响API(因此,键入)。
结论
在您看来,极端压缩毫无意义,因为在我的示例中,我获得了几十个字节的最大增益。但是实际上,我专门使他们最少代表。在实际条件下,增益可能会更大。
最终,是否诉诸于使用极端建议取决于您。对我来说,关于我是否可以达到最小可能的文件大小,这对我自己来说是一个挑战。但是,如果您仍然想知道它们有多么有用,我可以说它们帮助我将图书馆的规模从2172字节缩小到1594。一方面,只有578个字节,但另一方面只有多达27%总包卷。
感谢您的关注,您可以在评论中分享您的意见。我希望我的文章对您有用 - 如果不是极端的建议,那么至少一般。我很可能没有在文章中指定一些内容。在这种情况下,我很乐意根据您的建议进行补充。