ai / size-limit

Calculate the real cost to run your JS app or lib to keep good performance. Show error in pull request if the cost exceeds the limit.
MIT License
6.52k stars 1.82k forks source link

A curious case of wrong size reported #287

Closed unional closed 2 years ago

unional commented 2 years ago
βœ” Adding to empty esbuild project

  ./cjs/index.js
  Size limit: 5 kB
  Size:       4.26 kB with all dependencies, minified and gzipped

  ./esm/index.js
  Size limit: 5 kB
  Size:       2.98 kB with all dependencies, minified and gzipped

  ./tslib/index.js
  Size limit: 50 kB
  Size:       36.82 kB with all dependencies, minified and gzipped

https://github.com/unional/tersify

What's interesting thing is the files being checked are the same.

There is the tsconfig.tslib.json:

{
  "extends": "./tsconfig.esm.json",
  "compilerOptions": {
    "outDir": "tslib"
  }
}

And I double checked the folder content between esm and tslib are identical.

I even just swapped the folder names by doing rename, and it reports the same. the tslib one is still 36.82 kB, even with the content of the esm folder.

I have also tried keeping only one size-limit config. What I have found is that when the folder is not esm, it reports 36.82 kB... 🀷 🌷

ai commented 2 years ago

Try to add import with specific imports to ESM version.

By default, webpack/esbuild tree-shake everything from the ESM lib.

unional commented 2 years ago

It's not related to tree shaking. The weird thing is the output is different just by renaming the folder. Did size-limit cache build artifacts/result somewhere?

ai commented 2 years ago

Did size-limit cache build artifacts/result somewhere?

Should not, but esbuild may have some cache (remove node_modules and install it again to be sure).

unional commented 2 years ago

I take a look at it. Removing node_modules doesn't make a difference.

I have created a branch to demo it: https://github.com/unional/tersify/tree/size-limit

Don't have other ideas why it behaves like that.

ai commented 2 years ago

And I double checked the folder content between esm and tslib are identical.

I cloned repo, called yarn install & yarn build and checked esm/ and tslib/ content. They are very different.

esm/index.js:

export * from './tersible.js';
export { tersify } from './tersify.js';
export * from './types.js';
//# sourceMappingURL=index.js.map

tslib/index.js:

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.tersify = void 0;
__exportStar(require("./tersible.js"), exports);
var tersify_js_1 = require("./tersify.js");
Object.defineProperty(exports, "tersify", { enumerable: true, get: function () { return tersify_js_1.tersify; } });
__exportStar(require("./types.js"), exports);
//# sourceMappingURL=index.js.map

tslib doesn’t use ESM and tree shaking is not working. This is why you have so different size.

unional commented 2 years ago

That branch I build it the tslib extending cjs instead of esm. Sorry. You can see they are the same, but the size-limit result is different:


  ./cjs/index.js
  Size limit: 5 kB
  Size:       4.26 kB with all dependencies, minified and gzipped
  ./esm/index.js
  Size limit: 5 kB
  Size:       2.98 kB with all dependencies, minified and gzipped

  ./tslib/index.js
  Size limit: 50 kB
  Size:       39.05 kB with all dependencies, minified and gzipped
unional commented 2 years ago
// tsconfig.tslib.json
{
  "extends": "./tsconfig.cjs.json",
  "compilerOptions": {
    // "importHelpers": /true,
    "outDir": "tslib"
  }
}
unional commented 2 years ago
  ./cjs/index.js
  Size limit: 5 kB
  Size:       4.26 kB with all dependencies, minified and gzipped

  ./esm/index.js
  Size limit: 5 kB
  Size:       2.98 kB with all dependencies, minified and gzipped

  ./tslib/index.js
  Size limit: 50 kB
  Size:       36.82 kB with all dependencies, minified and gzipped

This is the result when tsconfig.tslib.json is:

  {
  "extends": "./tsconfig.esm.json",
  "compilerOptions": {
    // "importHelpers": /true,
    "outDir": "tslib"
  }
}

So you can see the the size from the two builds are actually different (39.05 vs 36.82), and they are both different to the same cjs and esm folder (4.26, 2.98).

But the folder content is the same

ai commented 2 years ago

Why I had Object.defineProperty(exports, "__esModule", { value: true }); in build dir?

I think you have a problem in environment. You build system is too complicated. It leads to unexpected results.

I recommend to avoid build step for open source projects (I never build my open source project). It will simplify the process a lot and save you from bugs.

unional commented 2 years ago

Why I had Object.defineProperty(exports, "__esModule", { value: true }); in build dir?

That is the output from TypeScript. The cjs build is compiling the code into ES5 ("target": "ES5") by TypeScript.

I use the Compare Folder extensions from VSCode to compare and show the two folders are identical: image

You can compare the esm one if you want, which compiles it to "target": "ES2019" so you don't see those extra stuff. I just pushed a change to branch so that the tslib is based off of esm to make it easier to compare for you.

I recommend to avoid build step for open source projects (I never build my open source project). It will simplify the process a lot and save you from bugs.

It's written in TypeScript so build step is a must. 🌷

ai commented 2 years ago

It's written in TypeScript so build step is a must.

Compile only to ESM. Why do you need tslib and cjs outputs?

unional commented 2 years ago

The cjs is for older NodeJS, browsers and tooling. It will be phase out eventually, but not yet. There are still many problems with ESM only packages (especially in the TypeScript ecosystem).

https://unional.github.io/typescript-guidelines/blog/2022-tsc-esm-fix

Why do you need tslib

For comparing the build size of including the tslib package with the importHelpers: true flag for TypeScript. It's a TypeScript thing.

ai commented 2 years ago

browsers and toolings

You do not need it. Bundlers for browsers work with ESM.

older NodeJS

All current (with security updates) versions support ESM.

unional commented 2 years ago

You do not need it. Bundlers for browsers work with ESM.

Not really, you can see my blog (ts-loader needs workaround. I'm working on a fix for it).

And jest still have to do workarounds for ESM modules.

unional commented 2 years ago

I have figured out what's the problem. It's the browser spec. ./cjs and ./esm folder have browser spec specified, but not ./tslib. So during build, the ./tslib one bundles the NodeJS code instead of the browser code, resulting a much larger bundle as it is including acron.

πŸŽ‰ πŸ¦–