treeshake, DCE, terser mangling
όϯυϧ࠷దԽϚχΞΫε@mizchi | Plaid, Incat Techfeed Conference 2022
View Slide
JS Ͱ͔͍ͬͱਏ͘ͳ͍ʁ
JS Ͱ͔͍ͬͱ͖…» ։ൃ࣌» खݩͷϏϧυαΠΫϧ͕͍» npm install Ͱ CI ͕͍» Ϣʔβʔମݧ» μϯϩʔυͰىಈ·Ͱ͕ͯ͘ਏ͍» CPU ࠅͰόοςϦ͕ਏ͍
ϑϩϯτΤϯυվળ㲈Ϗϧυվળ
୭͕ԿΛվળ͢Δͷ͔ʁ» ΞϓϦέʔγϣϯ։ൃऀ: ϚΫϩνϡʔχϯά» Chunk Split / Treeshake» ϥΠϒϥϦ։ൃऀ: ϚΠΫϩνϡʔχϯά» Treeshakable ͳ API ͷఏڙ» mangling
ࠓ͢༰» جૅฤ: օ͕Δ͖ Treeshake+DCE» ্ڃฤ: ϥΠϒϥϦ։ൃऀͷͨΊͷϚΠΫϩνϡʔχϯά
جૅฤ: Treeshake + DCE
ESM Treeshakeόϯυϥ(rollup/webpack)͕ ESM ͷະ༻ import Λআ͢Δػೳimport {a, b} from "x";console.log(a);// όϯυϧ࣌ʹ b ͕ফ͑Δ※ ͨͩ͠ side effect (ޙड़) ͕ͳ͍લఏ
Treeshake ͷલఏΛ͑ΔτοϓϨϕϧͰ෭࡞༻Λى͜͢ͱ Treeshake Ͱ͖ͳ͘ͳΔ// test_shakable.jsconst offset = new Date().getTimezoneOffset();export const getOffset = () => offset;$ npx agadoo test_shakable.js # Rich-Harris/agadooFailed to tree-shake test_shakable.js» new Date().getTimezoneOffset() ͕ Side Effect» جຊతʹτοϓϨϕϧͰ࣮ߦ͞ΕΔίʔυΛॻ͔ͳ͍
DCE: Dead Code Eliminationະ༻ίʔυΛআ͢Δ֤छ minifier ͷػೳ// sourceif(false){ unused; }export const x = true ? f() : -1;function f(){ return 1; unused;} // ΠϯϥΠϯల։// outexport const x=1;
Treeshake+DCEͷ࣮ફ
ݕূ༻ rollup.config.jsimport 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(/* ུ */),],}
ࠓճͷιʔείʔυimport { prod, dev } from "./sub"; // prod=0, dev=1export const ex = process.env.NODE_ENV === "production"? prod: dev;
1. ఆల։» process.env.NODE_ENV=productionimport { prod, dev } from "./sub";export const ex = "production" === "production"? prod: dev;
2. ఆಉ࢜ͷධՁ» "production"==="production" => trueimport { prod, dev } from "./sub";export const ex = true? prod: dev;
3. DCE» true?prod:dev => prodimport { prod } from "./sub";export const ex = prod;
4. Bundle with Treeshake» prod=0 ͷΈల։const prod = 0;export const ex = prod;
5. terser compress» ϩʔΧϧม໊Λѹॖconst o=0;export{ex as o}
Treeshake+DCE ͷ͍ํ» ڥ͝ͱʹఆల։Ͱ if(false){...} ͳ Dead Code Λ࡞Δ» ϥΠϒϥϦ࡞ऀ: treeshakable ͳ API ઃܭΛ͢Δ» αΠζࢹͩͱϝιουνΣʔϯආ͚Δ» ϥΠϒϥϦར༻ऀ: ඞཁͳίʔυ͚ͩ import» ಛʹ import * as ... Λආ͚Δ» Ұ෦ͷϥΠϒϥϦ NODE_ENV=production ͰϏϧυ࣌࠷దԽ
ϚΠΫϩνϡʔχϯά্ڃฤ
terser ͱྑ͘ͳΔ» ΊͬͪΌݡ͍͜ͱΛ͢Δ͚Ͳҙ֎ͱ͙͢ఘΊΔͷͰࢹֶͯ͠΅͏» https://try.terser.org/ ͕༑ୡ» ެ։ API Ҏ֎શ෦ 1~2 จࣈʹ͢Δؾ࣋ͪͰ
compress: Կ͕͘ͳΔ͔ʁ// sourceconst 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;// outconst o="ab";export{o as exported_name_is_not_shrinkable};» ϩʔΧϧม֎ʹग़ͳ͍ͷͰ compress ର (module લఏ)» export ͞ΕΔ໊લ͘ͳΒͳ͍
compress: ϝϯόΞΫηεَ//sourceconst x = {_private_value: 1,f() { return this._private_value;},unused_prop2: 2,unused_prop3: 3,};export const f = x.f;// outconst e={_private_value:1,f(){return this._private_value},unused_prop2:2,unused_prop3:3}.f;export{e as f};
compress: ΦϒδΣΫτΛΊΔͱ//sourceconst private_value = 1;const unused_prop2 = 2;const unused_prop3 = 3;export const f = () => private_value;// outconst o=()=>1;export{o as f};
͞Βʹൃలฤ: ෳճ minify// sourceconst 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)
શ෦ఆԽͳΜͯ͑ΒΕͳ͍ਓmangle.properties.regex ͕࠷ޙͷखஈ// rollup pluginsterser({mangle: {properties: { regex: "^_" }}}),ਖ਼نදݱΛຬͨͨ͠ϓϩύςΟΛ mangle ରʹ͢Δຊʹ ^_ ͕ϓϥΠϕʔτ͔Ͳ͏͔ਓ͕ؒ֬ೝ͠·͠ΐ͏
ϚΠΫϩνϡʔχϯά: TS ฤ(LTͰ࣌ؒແ͍ͷͰεΩοϓ)
TS: enum Λආ͚Δ// sourceenum XXX { AAA, BBB }XXX[XXX.AAA];// outvar XXX;(function (XXX) {XXX[XXX["AAA"] = 0] = "AAA";XXX[XXX["BBB"] = 1] = "BBB";})(XXX || (XXX = {}));XXX[XXX.AAA];
TS: const enum Λ͏// sourceconst enum XXX { AAA, BBB }console.log(XXX.AAA, XXX.BBB);// outconsole.log(0,1);» "preserveConstEnum": false ͰݩΩʔΛফͤΔ» ݩΩʔ͕Βͳ͍ͷͰ XXX[XXX.AAA] Ͱ͖ͳ͍
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());// outconsole.log(new class{constructor(t){this.__private_x=t}_private_method(){return this.__private_x}f(){return this._private_method()}}(1).f());» terser TS ͷܕใͷ߹ͳΜͯΒͳ͍
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 ݸҎ্ͩͱਓ͕ؒ͠ΜͲ͘ͳͬͯ͘Δ)
TS: ύϑΥʔϚϯεͷͨΊͷtsconfig.json{"compilerOptions": {"target": "es2019", // 2017 Ҏ߱ async await Λม͠ͳ͍"importHelpers": true, // tslib Λ͏"preserveConstEnums": false, // enum ͷΠϯϥΠϯల։Λڧ੍"noUnusedLocals": true, // ະ༻มͷܯࠂ"noUnusedParameters": true, // ະ༻Ҿͷܯࠂ"importsNotUsedAsValues": "error", // import type ͷڧ੍}}
࣮ફ݁Ռͷհ
࣮ફ݁Ռ: @mizchi/mints» αΠζಛԽͷ TypeScript ίϯύΠϥ: 8.1 kb(gzip)// npm install --save @mizchi/mintsimport { transformSync } from "@mizchi/mints";const out = transfromSync("const x: number = 1;");console.log(out.code); // const x=1;
Ͳ͏ͬͯখ͔ͨ͘͞͠» ࣗ࡞ύʔαίϯϏωʔλͰ named tuple ͷߏจఆٛΛు͘» ߏจఆٛΛ cbor ͰόΠφϦʹѹॖ» ϥϯλΠϜʹόΠφϦΛΠϯϥΠϯԽ» ϕϯνऔΔͱ֎෦ binary Λ fetch ͢ΔΑΓஅવ͔ͬͨ» (ASIະରԠͰ prettier Λલఏ)
Shakerphobia ͷհ» ࡞ https://shakerphobia.netlify.app/» bundlephobia ͰΘ͔Βͳ͍ treeshake ޙͷαΠζΛܭଌ» skypack + webworker + rollup ͰϒϥβͰϏϧυ
·ͱΊ» ͳΜʹͤΑ Treeshake ͷཧղ͕େࣄ» terser ϝϯόΞΫηεʹऑ͍» ಛʹΦϒδΣΫτఆΛආ͚Α͏» ࠷ऴखஈͱͯ͠ mangle.properties.regex» ϥΠϒϥϦ࡞ऀґଘπϦʔ্ͷෛՙ͔ͩΒؤுΕ(কདྷతʹ TS ܕใͬͯ Side Effect ఆ͢Δ minifier͕ग़Δͱࢥ͏͚Ͳɺݱঢ়ͳ͍Ͱ͢)
͓ΘΓ