Upgrade to Pro — share decks privately, control downloads, hide ads and more …

バンドル最適化マニアクス at tfconf

バンドル最適化マニアクス at tfconf

treeshake, DCE, terser mangling

Koutarou Chikuba

May 14, 2022
Tweet

More Decks by Koutarou Chikuba

Other Decks in Programming

Transcript

  1. όϯυϧ࠷దԽϚχΞΫε
    @mizchi | Plaid, Inc
    at Techfeed Conference 2022

    View Slide

  2. JS Ͱ͔͍ͬͱਏ͘ͳ͍ʁ

    View Slide

  3. JS Ͱ͔͍ͬͱ͖…
    » ։ൃ࣌
    » खݩͷϏϧυαΠΫϧ͕஗͍
    » npm install Ͱ CI ͕஗͍
    » Ϣʔβʔମݧ
    » μ΢ϯϩʔυͰىಈ·Ͱ͕஗ͯ͘ਏ͍
    » CPU ࠅ࢖ͰόοςϦ͕ਏ͍

    View Slide

  4. ϑϩϯτΤϯυվળ㲈Ϗϧυվળ

    View Slide

  5. ୭͕ԿΛվળ͢Δͷ͔ʁ
    » ΞϓϦέʔγϣϯ։ൃऀ: ϚΫϩνϡʔχϯά
    » Chunk Split / Treeshake
    » ϥΠϒϥϦ։ൃऀ: ϚΠΫϩνϡʔχϯά
    » Treeshakable ͳ API ͷఏڙ
    » mangling

    View Slide

  6. ࠓ೔࿩͢಺༰
    » جૅฤ: օ͕஌Δ΂͖ Treeshake+DCE
    » ্ڃฤ: ϥΠϒϥϦ։ൃऀͷͨΊͷϚΠΫϩνϡʔχϯά

    View Slide

  7. جૅฤ: Treeshake + DCE

    View Slide

  8. ESM Treeshake
    όϯυϥ(rollup/webpack)͕ ESM ͷະ࢖༻ import Λ࡟আ͢Δػ

    import {a, b} from "x";
    console.log(a);
    // όϯυϧ࣌ʹ b ͕ফ͑Δ
    ※ ͨͩ͠ side effect (ޙड़) ͕ͳ͍લఏ

    View Slide

  9. Treeshake ͷલఏΛ཈͑Δ
    τοϓϨϕϧͰ෭࡞༻Λى͜͢ͱ Treeshake Ͱ͖ͳ͘ͳΔ
    // test_shakable.js
    const offset = new Date().getTimezoneOffset();
    export const getOffset = () => offset;
    $ npx agadoo test_shakable.js # Rich-Harris/agadoo
    Failed to tree-shake test_shakable.js
    » new Date().getTimezoneOffset() ͕ Side Effect
    » جຊతʹτοϓϨϕϧͰ࣮ߦ͞ΕΔίʔυΛॻ͔ͳ͍

    View Slide

  10. DCE: Dead Code Elimination
    ະ࢖༻ίʔυΛ࡟আ͢Δ֤छ minifier ͷػೳ
    // source
    if(false){ unused; }
    export const x = true ? f() : -1;
    function f(){ return 1; unused;} // ΠϯϥΠϯల։
    // out
    export const x=1;

    View Slide

  11. Treeshake+DCEͷ࣮ફ

    View Slide

  12. ݕূ༻ rollup.config.js
    import ts from "rollup-plugin-ts";
    import { terser } from "rollup-plugin-terser";
    import replace from "@rollup/plugin-replace";
    export default {
    plugins: [
    ts({/* ུ */}),
    replace({
    "process.env.NODE_ENV": JSON.stringify('production')
    }),
    terser(/* ུ */),
    ],
    }

    View Slide

  13. ࠓճͷιʔείʔυ
    import { prod, dev } from "./sub"; // prod=0, dev=1
    export const ex = process.env.NODE_ENV === "production"
    ? prod
    : dev;

    View Slide

  14. 1. ఆ਺ల։
    » process.env.NODE_ENV=production
    import { prod, dev } from "./sub";
    export const ex = "production" === "production"
    ? prod
    : dev;

    View Slide

  15. 2. ఆ਺ಉ࢜ͷධՁ
    » "production"==="production" => true
    import { prod, dev } from "./sub";
    export const ex = true
    ? prod
    : dev;

    View Slide

  16. 3. DCE
    » true?prod:dev => prod
    import { prod } from "./sub";
    export const ex = prod;

    View Slide

  17. 4. Bundle with Treeshake
    » prod=0 ͷΈల։
    const prod = 0;
    export const ex = prod;

    View Slide

  18. 5. terser compress
    » ϩʔΧϧม਺໊Λѹॖ
    const o=0;export{ex as o}

    View Slide

  19. Treeshake+DCE ͷ࢖͍ํ
    » ؀ڥ͝ͱʹఆ਺ల։Ͱ if(false){...} ͳ Dead Code Λ࡞Δ
    » ϥΠϒϥϦ࡞ऀ: treeshakable ͳ API ઃܭΛ͢Δ
    » αΠζࢹ఺ͩͱϝιουνΣʔϯ͸ආ͚Δ
    » ϥΠϒϥϦར༻ऀ: ඞཁͳίʔυ͚ͩ import
    » ಛʹ import * as ... Λආ͚Δ
    » Ұ෦ͷϥΠϒϥϦ͸ NODE_ENV=production ͰϏϧυ࣌࠷దԽ

    View Slide

  20. ϚΠΫϩνϡʔχϯά্ڃฤ

    View Slide

  21. terser ͱ஥ྑ͘ͳΔ
    » ΊͬͪΌݡ͍͜ͱΛ͢Δ͚Ͳҙ֎ͱ͙͢ఘΊΔͷͰ໨ࢹֶͯ͠΅͏
    » https://try.terser.org/ ͕༑ୡ
    » ެ։ API Ҏ֎͸શ෦ 1~2 จࣈʹ͢Δؾ࣋ͪͰ

    View Slide

  22. compress: Կ͕୹͘ͳΔ͔ʁ
    // source
    const long_long_name_1: string = 'a';
    const long_long_name_2: string = 'b';
    export const exported_name_is_not_shrinkable =
    long_long_name_1 + long_long_name_2;
    // out
    const o="ab";export{o as exported_name_is_not_shrinkable};
    » ϩʔΧϧม਺͸֎ʹग़ͳ͍ͷͰ compress ର৅ (module લఏ)
    » export ͞ΕΔ໊લ͸୹͘ͳΒͳ͍

    View Slide

  23. compress: ϝϯόΞΫηε͸َ໳
    //source
    const x = {
    _private_value: 1,
    f() { return this._private_value;},
    unused_prop2: 2,
    unused_prop3: 3,
    };
    export const f = x.f;
    // out
    const e={_private_value:1,f(){return this._private_value},
    unused_prop2:2,unused_prop3:3}.f;export{e as f};

    View Slide

  24. compress: ΦϒδΣΫτΛ΍ΊΔͱ
    //source
    const private_value = 1;
    const unused_prop2 = 2;
    const unused_prop3 = 3;
    export const f = () => private_value;
    // out
    const o=()=>1;export{o as f};

    View Slide

  25. ͞Βʹൃలฤ: ෳ਺ճ minify
    // source
    const x = { A: { B: { v: 2, C: { D: { v:4, E: { F: { v: 6 } } }}} }};
    console.log(x.A.B.v,x.A.B.C.D.v,x.A.B.C.D.E.F.v);
    /*1*/ const v={B:{v:2,C:{D:{v:4,E:{F:{v:6}}}}}};console.log(v.B.v,v.B.C.D.v,v.B.C.D.E.F.v);
    /*2*/ const v={v:2,C:{D:{v:4,E:{F:{v:6}}}}};console.log(v.v,v.C.D.v,v.C.D.E.F.v);
    /*3*/ const o=2,v={D:{v:4,E:{F:{v:6}}}};console.log(o,v.D.v,v.D.E.F.v);
    /*4*/ const o={v:4,E:{F:{v:6}}};console.log(2,o.v,o.E.F.v);
    /*5*/ const o=4,c={F:{v:6}};console.log(2,o,c.F.v);
    /*6*/ console.log(2,4,6);
    » terser ͸ΞΫηεઌͷఆ਺൑ఆΛઙ͔͘͠΍ͬͯͳ͍ʂ
    » compress: { passes: 6 } (default: 1)

    View Slide

  26. શ෦ఆ਺ԽͳΜͯ଱͑ΒΕͳ͍ਓ΁
    mangle.properties.regex ͕࠷ޙͷखஈ
    // rollup plugins
    terser({
    mangle: {
    properties: { regex: "^_" }
    }
    }),
    ਖ਼نදݱΛຬͨͨ͠ϓϩύςΟΛ mangle ର৅ʹ͢Δ
    ຊ౰ʹ ^_ ͕ϓϥΠϕʔτ͔Ͳ͏͔͸ਓ͕ؒ֬ೝ͠·͠ΐ͏

    View Slide

  27. ϚΠΫϩνϡʔχϯά: TS ฤ
    (LTͰ͸࣌ؒແ͍ͷͰεΩοϓ)

    View Slide

  28. TS: enum Λආ͚Δ
    // source
    enum XXX { AAA, BBB }
    XXX[XXX.AAA];
    // out
    var XXX;
    (function (XXX) {
    XXX[XXX["AAA"] = 0] = "AAA";
    XXX[XXX["BBB"] = 1] = "BBB";
    })(XXX || (XXX = {}));
    XXX[XXX.AAA];

    View Slide

  29. TS: const enum Λ࢖͏
    // source
    const enum XXX { AAA, BBB }
    console.log(XXX.AAA, XXX.BBB);
    // out
    console.log(0,1);
    » "preserveConstEnum": false ͰݩΩʔΛফͤΔ
    » ݩΩʔ͕࢒Βͳ͍ͷͰ XXX[XXX.AAA] Ͱ͖ͳ͍

    View Slide

  30. TS: private ͸ҙຯͳ͍
    class C {
    constructor(private __private_x: number) {}
    private _private_method() { return this.__private_x;}
    public f() { return this._private_method();}
    }
    console.log(new C(1).f());
    // out
    console.log(new class{constructor(t){this.__private_x=t}_private_method()
    {return this.__private_x}f(){return this._private_method()}}(1).f());
    » terser ͸ TS ͷܕ৘ใͷ౎߹ͳΜͯ஌Βͳ͍

    View Slide

  31. TS: ߏ଄ମʹ named tuple Λ࢖͏
    type Range = [start: number, end: number];
    const range: Range = [1, 3];
    const inRange = (x: number, [start, end]: Range) => {
    return start <= x && x <= end;
    }
    » ݻఆ௕ͷ഑ྻͷϝϯόʹ໊લΛ͚ͭΔ͜ͱ͕Ͱ͖Δ
    » ϓϩύςΟ໊͕ index ͳͷͰม਺໊ͷίετ͕গͳ͍
    » (3 ݸҎ্ͩͱਓ͕ؒ͠ΜͲ͘ͳͬͯ͘Δ)

    View Slide

  32. TS: ύϑΥʔϚϯεͷͨΊͷ
    tsconfig.json
    {
    "compilerOptions": {
    "target": "es2019", // 2017 Ҏ߱͸ async await Λม׵͠ͳ͍
    "importHelpers": true, // tslib Λ࢖͏
    "preserveConstEnums": false, // enum ͷΠϯϥΠϯల։Λڧ੍
    "noUnusedLocals": true, // ະ࢖༻ม਺ͷܯࠂ
    "noUnusedParameters": true, // ະ࢖༻Ҿ਺ͷܯࠂ
    "importsNotUsedAsValues": "error", // import type ͷڧ੍
    }
    }

    View Slide

  33. ࣮ફ݁Ռͷ঺հ

    View Slide

  34. ࣮ફ݁Ռ: @mizchi/mints
    » αΠζಛԽͷ TypeScript ίϯύΠϥ: 8.1 kb(gzip)
    // npm install --save @mizchi/mints
    import { transformSync } from "@mizchi/mints";
    const out = transfromSync("const x: number = 1;");
    console.log(out.code); // const x=1;

    View Slide

  35. Ͳ͏΍ͬͯখ͔ͨ͘͞͠
    » ࣗ࡞ύʔαίϯϏωʔλͰ named tuple ͷߏจఆٛΛు͘
    » ߏจఆٛΛ cbor ͰόΠφϦʹѹॖ
    » ϥϯλΠϜʹ͸όΠφϦΛΠϯϥΠϯԽ
    » ϕϯνऔΔͱ֎෦ binary Λ fetch ͢ΔΑΓஅવ଎͔ͬͨ
    » (ASIະରԠͰ prettier Λલఏ)

    View Slide

  36. View Slide

  37. Shakerphobia ͷ঺հ
    » ੿࡞ https://shakerphobia.netlify.app/
    » bundlephobia Ͱ͸Θ͔Βͳ͍ treeshake ޙͷαΠζΛܭଌ
    » skypack + webworker + rollup Ͱϒϥ΢β಺ͰϏϧυ

    View Slide

  38. ·ͱΊ
    » ͳΜʹͤΑ Treeshake ͷཧղ͕େࣄ
    » terser ͸ϝϯόΞΫηεʹऑ͍
    » ಛʹΦϒδΣΫτ಺ఆ਺Λආ͚Α͏
    » ࠷ऴखஈͱͯ͠ mangle.properties.regex
    » ϥΠϒϥϦ࡞ऀ͸ґଘπϦʔ্ͷෛՙ͔ͩΒؤுΕ
    (কདྷతʹ͸ TS ܕ৘ใ࢖ͬͯ Side Effect ൑ఆ͢Δ minifier
    ͕ग़Δͱࢥ͏͚Ͳɺݱঢ়ͳ͍Ͱ͢)

    View Slide

  39. ͓ΘΓ

    View Slide