Share
Embed
Managing "side effect" in Frontend Development
In the web frontend development context, we should care about function's side effect not only for simplicity of the function's responsibility, but also for preventing unnecessary/dangerous code from leaking into client side.
This slides show why we should care about side effect in frontend development, and introduce treeche, which is the tool to detect side effect in the ESModule
Shinobu Hayashi
November 17, 2023
Transcript
17 slides
-
$ who am i Web Developer at Nikkei GitHub: @Shinyaigeek
Twitter: @Shinyaigeek Love: Web, OSS, Beer, Cooking, others... Member of pnpm Shinobu Hayashi/ 林 仁 ` ` ` ` -
$ What is "side effect" ? > In computer science,
an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, which is to say if it has any observable effect other than its primary effect of returning a value to the invoker of the operation from: wikipedia expression: file-system I/O, stdio, etc... function: an effect to outside of the function's scope ✅ This talk is aimed on a function’s "side effect" in Frontend context -
$ Example of a function’s side effects readArticle function have
an effect on counter variable which is located in outside of readArticle let counter = 0; const readArticle = function (id) { counter++ // ❗️ return `${some nice article accord with id}`; } ` ` ` ` ` ` -
$ Why Should We Care About Side Effect In Frontend
Development? To keep function simple, of course To eliminate unnecessary code from built output JavaScript ✅ This talk is aimed to built JavaScript -
$ Why Eliminate Unnecessary Code from Built JavaScript? lead to
less bundle size, more UX! prevent danger code from leaking internal code for developing environment-specific code (like server, browser) -
$ Frontend Build-Toolchain Features for Code Optimization Minify Dead Code
Elimination Tree Shaking 👈 etc... -
$ What is Tree Shaking? A. The function to eliminate
unused modules from bundle. Bundler will eliminate getCurrentDay from bundle because it is not used and have no effect on bundled code // module.ts export getCurrentMonth = (date: Date = new Date()) => { return date.getMonth(); } export getCurrentDay = (date: Date = new Date()) => { return date.getDay(); } // app.ts import { getCurrentMonth } from './module.ts'; someNiceFunction(getCurrentMonth()); ` ` -
$The Impact of Side Effects in Frontend Development For example,
what output bundler will generate from the above input… Along with ECMAScript’s specification, ESModule should be evaluated overall when it is imported from others. // config.ts const defineConfig = (config) => { return { version: 1, ...config } } export const configA = defineConfig({ name: "A" }) export const configB = defineConfig({ name: "B" }) export const configForTest = defineConfig({ name: "Testing", debug: "some scare secret" }) // app.ts import { configB } from "./config" console.log(configB) -
$ How to accomplish better tree-shaking Bundler "Because I don’t
know whether defineConfig function have side effect or not…" Output: const defineConfig = (config) => { return { version: 1, ...config } }; defineConfig({ name: "A" }); // Why ❗️❓ app does not use this ❗️ const configB = defineConfig({ name: "B" }); defineConfig({ name: "Testing", debug: "some scare secret" }); // Why ❗️❓ app does not use this ❗️ console.log(configB); ` ` -
$ Eliminating ESModule’s Side Effects from the Bundle Bundler "Because
I don’t know whether defineConfig function have side effect or not…" defineConfig in the example is pure, but perhaps defineConfig may have side effect like below : Human can recognize defineConfig is pure easily, but Bundler cannot recognize defineConfig is pure or with side effect ` ` ` ` ` ` let version = 0; const defineConfig = (config) => { version += 1; return { version, ...config } }; ` ` ` ` -
$ ESModule’s Side Effect with Module Bundler Module Bundler will
bundle also side effect Module Bundler cannot find the facing module's exported member is pure or not -
Eliminating ESModule’s Side Effects from the Bundle example with terser:
terser --compress pure_funcs=[defineConfig] We can eliminate ESModule’s side effect from bundle by specializing defineConfig as a pure function ` ` ` ` const defineConfig = (config) => { return { version: 1, ...config } }; defineConfig({ name: "A" }); // 👈 Terser can remove this because defineConfig is pure and this invoke does not const configB = defineConfig({ name: "B" }); defineConfig({ name: "Testing", debug: "some scare secret" }); // 👈 Terser can remove this because defineConfi console.log(configB); -
$ How to accomplish better tree-shaking We can handle this
with the implicit pure functions specification but we cannot detect this easily... -
$ Treeche, Tree-Shakable Checker Treeche will throw alert for the
input such the former example Treeche is the tool to detect the code which is not tree-shakable in ESModules. 🚨 /~~~/config.ts is not tree-shakable due to the following code: defineConfig({ ^^^^^^^^^^^^^^ name: "A" ^^^^^^^^^^^ }); ^^^ defineConfig({ ^^^^^^^^^^^^^^ name: "Testing", debug: "some scare secret" ^^^^^^^^^^^ }); ^^^ Creator of Treeche ✌️ 👉 -
$ How Treeche works A. Treeche bundles each input modules
internally on memory. Treeche generate JavaScript code like above, and bundle this with rollup by resolving memory prefixed module path as specified input modules. And then, Treeche checks the built output and throw a pretty alert if there are the code other than ImportSpecifier because it indicates this modules is not tree-shakable. Feature Native TypeScript support for input Checking the multiple files with Node.js glob import * from "memory:specified modules"; ` ` ` ` -
$ Summary Frontend devs should care about ESModule's side effect
to reduce bundle size to prevent some dangerous code from leaking treeche is useful to detect side effects in modules