$30 off During Our Annual Pro Sale. View Details »

Managing "side effect" in Frontend Development

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
Tweet

More Decks by Shinobu Hayashi

Other Decks in Technology

Transcript

  1. Managing "side effect" in
    Frontend Development
    ⚡️
    @Frontend Conference Okinawa

    View Slide

  2. $ who am i
    Web Developer at Nikkei
    GitHub: @Shinyaigeek
    Twitter: @Shinyaigeek
    Love: Web, OSS, Beer, Cooking, others...
    Member of pnpm
    Shinobu Hayashi/
    林 仁
    ` `
    ` `

    View Slide

  3. $ 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

    View Slide

  4. $ 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}`;
    }
    ` ` ` `
    ` `

    View Slide

  5. $ 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

    View Slide

  6. $ 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)

    View Slide

  7. $ Frontend Build-Toolchain Features for Code
    Optimization
    Minify
    Dead Code Elimination
    Tree Shaking
    👈
    etc...

    View Slide

  8. $ 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());
    ` `

    View Slide

  9. $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)

    View Slide

  10. $ 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);
    ` `

    View Slide

  11. $ 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
    }
    };
    ` `
    ` `

    View Slide

  12. $ 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

    View Slide

  13. 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);

    View Slide

  14. $ How to accomplish better tree-shaking
    We can handle this with the
    implicit pure functions
    specification but we cannot
    detect this easily...

    View Slide

  15. $ 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
    ✌️ 👉

    View Slide

  16. $ 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";
    ` `
    ` `

    View Slide

  17. $ 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

    View Slide