Skip to content

Instantly share code, notes, and snippets.

@hyrious
Created August 4, 2022 01:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hyrious/3040183d0b966f9696dca20ec9bdfb2d to your computer and use it in GitHub Desktop.
Save hyrious/3040183d0b966f9696dca20ec9bdfb2d to your computer and use it in GitHub Desktop.
How to write pure JS
import esbuild from "esbuild";
import { rollup } from "rollup";
import * as terser from "terser";
async function runEsbuild(code) {
let result = await esbuild.transform(code, { treeShaking: true });
return result.code;
}
async function runRollup(code) {
let modules = { "main.js": code };
let bundle = await rollup({
input: Object.keys(modules)[0],
plugins: [
{
resolveId(importee, importer) {
if (!importer) return importee;
if (importee[0] !== ".") return false;
},
load(id) {
return modules[id];
},
},
],
onwarn: () => {},
});
let generated = await bundle.generate({ format: "es" });
return generated.output[0].code;
}
async function runTerser(code) {
const result = await terser.minify(code, { module: true });
return result.code;
}
let count = 1;
async function runTests(code) {
function runX(f, name) {
console.log("--", name, "--");
return f(code).then(console.log).catch(console.error);
}
console.log(`== Test ${count++} ==`);
console.log(code);
console.log();
await runX(runTerser, "terser");
await runX(runEsbuild, "esbuild");
await runX(runRollup, "rollup");
}
// NB. tests that comment out are almost the same
// await runTests(`let a = /* @__PURE__ */ f()`);
// await runTests(`let a = /* @__PURE__ */ f.g()`);
// await runTests(`let a = /* @__PURE__ */ f().g()`);
// await runTests(`let a = /* @__PURE__ */ f().g`);
// await runTests(`let a = /* @__PURE__ */ f.g`);
// await runTests(`let a = /* @__PURE__ */ f['g']()`);
// await runTests(`let a = /* @__PURE__ */ f()[g]()`);
// await runTests(`let a = /* @__PURE__ */ f[g]`);
await runTests(`let a = Object.prototype.hasOwnProperty`);
await runTests(`let a = Object.hasOwn`);
// await runTests(`let a = Whatever.whatever`);
await runTests(`let a = /* @__PURE__ */ import('b')`);
await runTests(`let a = new Map()`);
@hyrious
Copy link
Author

hyrious commented Aug 4, 2022

Conclusions:

  1. Both of them (esbuild, rollup, terser) maintain some sort of known globals to help minifying by trusting code authors won't run into some edge cases (like modify a global builtin function). Their lists are here:
  2. Without finding the intersection of them, there exists some basic rules to write pure codes. I'll try to state them here:
    • Literals must be pure. Don't include any runtime calculations in your literal expressions.
      • Example of pure literal: let a = 1
      • Example of maybe-not-pure literal: let a = 1 << 1
        • There's a workaround to use basic operations in esbuild via enum in TypeScript format.
      • Example of non-analyzable literal: let a = {}; a.b = 1
        • This requires additional tracking algorithm to solve the final pureness of a, which is not supported in esbuild.
    • Pure annotations marks the right-most function call as safe to remove, examples:
      • let a = /* @__PURE__ */ f.g().h().i
        The return value of f.g().h() is pure, but it is then be used in $ret.i, which makes the whole expression not pure. If you need an explanation: there may exist a get i() {} which has side effects.
      • let a = /* @__PURE__ */ (() => { many.sideEffect.here() })()
        The whole iife function call is safe to remove if a is not used somewhere else.
    • But minifiers always have an option to ignore annotations:
    • So if you're a pedantic guy, you should only follow the first rule.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment