Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3 proposal #2982

Open
wants to merge 70 commits into
base: main
Choose a base branch
from
Open

v3 proposal #2982

wants to merge 70 commits into from

Commits on Sep 26, 2024

  1. Configuration menu
    Copy the full SHA
    c947ba6 View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    d252b40 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    73a3e86 View commit details
    Browse the repository at this point in the history
  4. Add duplicate key check, deduplicate code, use ES6

    Perf is somewhat improved in spots, but generally the same otherwise.
    dead-claudia committed Sep 26, 2024
    Configuration menu
    Copy the full SHA
    d0cc706 View commit details
    Browse the repository at this point in the history
  5. Configuration menu
    Copy the full SHA
    b160a35 View commit details
    Browse the repository at this point in the history
  6. Drop m.trust support

    Cuts only about 2% of the bundle, but removes a pretty bad pain point in the code. Plus, inserting arbitrary HTML outside a container is usually a recipe for disaster in terms of styling.
    dead-claudia committed Sep 26, 2024
    Configuration menu
    Copy the full SHA
    f9c6e2c View commit details
    Browse the repository at this point in the history

Commits on Sep 27, 2024

  1. Configuration menu
    Copy the full SHA
    ef0ccbf View commit details
    Browse the repository at this point in the history

Commits on Oct 2, 2024

  1. Configuration menu
    Copy the full SHA
    1da9a6a View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    f79bb20 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    c0d5898 View commit details
    Browse the repository at this point in the history
  4. Move state object creation to hyperscript

    It's simpler that way and I don't have to condition it.
    dead-claudia committed Oct 2, 2024
    Configuration menu
    Copy the full SHA
    777542f View commit details
    Browse the repository at this point in the history
  5. Move m.render out of initialization closure

    It's redundant and has been for ages. A v3 increment gives me the ability to minimize surprise.
    dead-claudia committed Oct 2, 2024
    Configuration menu
    Copy the full SHA
    bd9f393 View commit details
    Browse the repository at this point in the history

Commits on Oct 3, 2024

  1. Drop m.request

    dead-claudia committed Oct 3, 2024
    Configuration menu
    Copy the full SHA
    630ee9b View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    64f9587 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    a483ea0 View commit details
    Browse the repository at this point in the history
  4. Configuration menu
    Copy the full SHA
    39fdb30 View commit details
    Browse the repository at this point in the history
  5. Configuration menu
    Copy the full SHA
    3477794 View commit details
    Browse the repository at this point in the history
  6. Add lazy loader

    dead-claudia committed Oct 3, 2024
    Configuration menu
    Copy the full SHA
    518daf7 View commit details
    Browse the repository at this point in the history
  7. Toss the "route" part of the router

    People can just use equality and regexps as needed.
    dead-claudia committed Oct 3, 2024
    Configuration menu
    Copy the full SHA
    e6af29e View commit details
    Browse the repository at this point in the history
  8. Merge Vnode with the hyperscript module

    Also, rename it `m` to make dev stack traces a little more readable, and remove a useless layer of indirection.
    dead-claudia committed Oct 3, 2024
    Configuration menu
    Copy the full SHA
    82fc993 View commit details
    Browse the repository at this point in the history
  9. Configuration menu
    Copy the full SHA
    ee40de5 View commit details
    Browse the repository at this point in the history
  10. Configuration menu
    Copy the full SHA
    a454328 View commit details
    Browse the repository at this point in the history
  11. Configuration menu
    Copy the full SHA
    df363e5 View commit details
    Browse the repository at this point in the history
  12. Configuration menu
    Copy the full SHA
    26d71a1 View commit details
    Browse the repository at this point in the history
  13. Make the event dictionary a proper class

    This makes that class fully monomorphic and also a bit safer.
    dead-claudia committed Oct 3, 2024
    Configuration menu
    Copy the full SHA
    bfb35f9 View commit details
    Browse the repository at this point in the history
  14. Configuration menu
    Copy the full SHA
    038cbce View commit details
    Browse the repository at this point in the history
  15. Drop stale recycling tests

    That's not been there for years
    dead-claudia committed Oct 3, 2024
    Configuration menu
    Copy the full SHA
    bb8e4b4 View commit details
    Browse the repository at this point in the history
  16. Add m.tracked utility for tracking delayed removals

    The long comment at the top of it explains the motivation.
    dead-claudia committed Oct 3, 2024
    Configuration menu
    Copy the full SHA
    1ec7ddc View commit details
    Browse the repository at this point in the history
  17. Configuration menu
    Copy the full SHA
    48933cb View commit details
    Browse the repository at this point in the history
  18. Drop onbeforeremove

    It's now been made redundant with `m.tracked`.
    dead-claudia committed Oct 3, 2024
    Configuration menu
    Copy the full SHA
    9bf6d1f View commit details
    Browse the repository at this point in the history

Commits on Oct 4, 2024

  1. Configuration menu
    Copy the full SHA
    e3edcc3 View commit details
    Browse the repository at this point in the history
  2. Re-duplicate children

    Dropping a premature abstraction, and I have perf concerns.
    
    This new version should hopefully avoid needing arguments allocation
    dead-claudia committed Oct 4, 2024
    Configuration menu
    Copy the full SHA
    73a0d32 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    c3333c6 View commit details
    Browse the repository at this point in the history
  4. Revise a comment

    dead-claudia committed Oct 4, 2024
    Configuration menu
    Copy the full SHA
    c8e48cd View commit details
    Browse the repository at this point in the history
  5. Drop oninit, replace all remaining lifecycle methods with m.layout

    If you remember v0.2, you'll recognize the API here. Thing is, this tries to be a little more rigorous with it by making it a proper vnode instead of just a magic attribute.
    
    Also, as these are the last remaining magic attributes (and `m.route.link` just returns the needed attributes itself), `m.censor` is gone.
    dead-claudia committed Oct 4, 2024
    Configuration menu
    Copy the full SHA
    df0eaeb View commit details
    Browse the repository at this point in the history
  6. Configuration menu
    Copy the full SHA
    b0419a6 View commit details
    Browse the repository at this point in the history
  7. Configuration menu
    Copy the full SHA
    1d9f287 View commit details
    Browse the repository at this point in the history
  8. Migrate components to simple functions and closures

    Part of this meant moving children to an attribute property. I've also added a translation layer of sorts so one can pass them through to DOM vnodes and them "just work", even though the internal structure separates attributes and children.
    
    All in all, this should hopefully result in a lot less memory usage and some faster app load times.
    dead-claudia committed Oct 4, 2024
    Configuration menu
    Copy the full SHA
    bf21066 View commit details
    Browse the repository at this point in the history
  9. Configuration menu
    Copy the full SHA
    91695c6 View commit details
    Browse the repository at this point in the history
  10. Configuration menu
    Copy the full SHA
    618b335 View commit details
    Browse the repository at this point in the history

Commits on Oct 5, 2024

  1. Improve perf tests

    They weren't actually testing the update flow, only reliably the create flow.
    
    Also, they weren't testing `m.mount`, or `m.redraw` in their totality, which is honestly a very important thing to test.
    dead-claudia committed Oct 5, 2024
    Configuration menu
    Copy the full SHA
    eebab6c View commit details
    Browse the repository at this point in the history
  2. Simplify the keyed diff fuzzer

    It shouldn't be testing particular implementation details, just that it works. Also, this one's more likely to exhaustively hit every possibility.
    dead-claudia committed Oct 5, 2024
    Configuration menu
    Copy the full SHA
    4a29a67 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    9331d49 View commit details
    Browse the repository at this point in the history
  4. Split code + tests, move source into dedicated directory

    Aims to ease discoverability. Plus, it's a bit easier to navigate.
    dead-claudia committed Oct 5, 2024
    Configuration menu
    Copy the full SHA
    0dad38c View commit details
    Browse the repository at this point in the history
  5. Configuration menu
    Copy the full SHA
    34588d1 View commit details
    Browse the repository at this point in the history

Commits on Oct 6, 2024

  1. Knock out all the intermediate closures, do a better job cleaning up,…

    … revise a few more things
    dead-claudia committed Oct 6, 2024
    Configuration menu
    Copy the full SHA
    e2c51b3 View commit details
    Browse the repository at this point in the history
  2. Try to get a better profile view

    Didn't fully think through that experiment of file loading - it doesn't also load Mithril, among other things.
    
    Also, remove some redundant conditionals in `src/core.js`.
    dead-claudia committed Oct 6, 2024
    Configuration menu
    Copy the full SHA
    2ad80c7 View commit details
    Browse the repository at this point in the history

Commits on Oct 11, 2024

  1. Improve bundle size, move to type masks to boost performance

    - `function foo(...) {...}` to `var foo = (...) => {...}` saved a fair bit of space. I also replaced other ES5 `function` functions with arrow functions where possible.
    - I inlined several functions. Turns out Terser wasn't inlining them properly (they were getting nonsensically inlined as IIFEs), and it was causing performance to suffer.
    - I dropped the `contenteditable` code, since that was only needed to handle the since-dropped `m.trust`.
    - I switched from tag names to type masks. This enabled several optimizations.
        - Size: I switched from a `switch` to function tables for create and update. Now, it just hinges on how well the CPU predicts it, and modern desktop CPUs very much can chase such multi-level indirect branches.
        - Size: I could blend single-key diffs with element and component tag name diffs, for better performance.
        - Performance: By using an expando bit, I've avoided needing to read the current namespace in some pretty hot loops, including in `setAttr`.
        - Performance: In `setAttr` and other places, I've merged as many as 4 tag comparisons to a single bit comparison.
    - I truncated the vnode properties to single letters. This resulted in a moderate savings.
    - I moved `redraw` to a return value of `m.mount` and also passed it via a context value to components so they can deal with non-global redraws.
    - I re-split `m.layout`.
    
    I also swapped out the benchmark library for something I rolled myself, and used that to set up the benchmarks. Benchmark.js isn't maintained anymore, its built-in output wasn't all that great, and it was forcing me to use globals where I otherwise didn't really need to, so I decided to roll some statistics stuff myself and make something simple. And while I was at it, I simplified the benchmark code by a lot.
    
    I also ripped out the select/option Chrome bug workaround - it doesn't seem to replicate anymore. Removing that brought a slight boost to attribute setting performance and enabled me to factor that code out to something a lot simpler.
    
    Try this page for example:
    
    ```html
    <!doctype html>
    <select>
      <option value="foo">Foo</option>
      <option value="bar">Bar</option>
      <option value="baz">Baz</option>
    </select>
    <script>
    var select = document.querySelector("select")
    
    console.log("s", select.selectedIndex, select.value)
    select.onchange = select.onclick = select.onblur = () => {
      console.log("c", select.selectedIndex, select.value)
    }
    
    var second = select.children[1]
    setInterval(() => {
      console.log("i", select.selectedIndex, select.value)
      second.value = "bar"
      console.log("i", select.selectedIndex, select.value)
    }, 5000)
    </script>
    ```
    dead-claudia committed Oct 11, 2024
    Configuration menu
    Copy the full SHA
    1836602 View commit details
    Browse the repository at this point in the history
  2. Switch from m.layout signal out m.remove, merge create/update cal…

    …lbacks
    
    Also re-added the `vnode === old` optimization, out of necessity for other utilities (there's a long code comment explaining why).
    
    Before this `m.layout` change, code would've looked like this:
    
    ```js
    // For libraries that bind state to the element itself
    function ThirdParty(attrs, old) {
      return m("div", m.layout((elem, signal) => {
        // Do DOM initialization
        Library.initialize(elem)
        // Schedule DOM cleanup
        signal.onabort = () => Library.dispose(elem)
      }))
    }
    
    // For libraries that return an instance
    function ThirdParty() {
      return m("div", m.layout((elem, signal) => {
        // Do DOM initialization
        let instance = new Library(elem)
        // Schedule DOM cleanup
        signal.onabort = () => instance.dispose()
      }))
    }
    ```
    
    The problem with this is it makes logic that only needs to care about removal a lot more complicated than it otherwise needs to be. (See `src/std/init.js` for a concrete example of this, and how this change simplifies those use cases a lot.) It also requires not only an extra closure that necessarily has to lay around until removal, but an entire `AbortController`, so this change ultimately saves a little over 200 bytes per layout/remove pair. (If you have a lot of such elements, this could be noticeable.)
    
    After this change, third-party integration code (the other main motivator for `m.layout` in the first place) would look something like this:
    
    ```js
    // For libraries that bind state to the element itself
    function ThirdParty(attrs, old) {
      return m("div", [
        // Do DOM initialization
        !old && m.layout((elem) => Library.initialize(elem)),
        // Schedule DOM cleanup
        m.remove((elem) => Library.dispose(elem)),
      ])
    }
    
    // For libraries that return an instance
    function ThirdParty() {
      let instance
      return (attrs, old) => m("div", [
        // Do DOM initialization
        !old && m.layout((elem) => instance = new Library(elem)),
        // Schedule DOM cleanup
        m.remove(() => instance.dispose()),
      ])
    }
    ```
    
    It was already possible to initialize an element outside of its scope by saving a reference to it:
    
    ```js
    function ThirdParty(attrs, old) {
      let label, root
      return [
        label = m("label", "Some text"),
        root = m("div.library"),
        m.layout((elem, signal) => {
          let instance = new Library(root.d, label.d)
          signal.onabort = () => instance.dispose()
        }),
      ]
    }
    ```
    
    This change makes that a little cleaner to wire up:
    
    ```js
    function ThirdParty() {
      let instance
      return (attrs, old) => {
        let label, root
        return [
          label = m("label", "Some text"),
          root = m("div.library"),
          !old && m.layout(() => instance = new Library(root.d, label.d)),
          m.remove(() => instance.dispose()),
        ]
      }
    }
    ```
    
    It also fits in a JSX world a little more nicely, but this wasn't something I was specifically seeking out:
    
    ```jsx
    // Old unified
    function ThirdParty() {
      return <div>
        {m.layout((elem, signal) => {
          let instance = new Library(elem)
          signal.onabort = () => instance.dispose()
        })}
      </div>
    }
    
    // Old split
    function ThirdParty(attrs, old) {
      let label, root
      return <>
        {label = <label>Some text</label>}
        {root = <div class="library" />}
        {m.layout((elem, signal) => {
          let instance = new Library(root.d, label.d)
          signal.onabort = () => instance.dispose()
        })}
      </>
    }
    
    // New unified
    function ThirdParty() {
      let instance
      return (attrs, old) => <div>
        {!old && m.layout((elem) => instance = new Library(elem))}
        {m.remove(() => instance.dispose())}
      </div>
    }
    
    // New split
    function ThirdParty() {
      let instance
      return (attrs, old) => {
        let label, root
        return <>
          {label = <label>Some text</label>}
          {root = <div class="library" />}
          {!old && m.layout(() => instance = new Library(root.d, label.d))}
          {m.remove(() => instance.dispose())}
        </>
      }
    }
    ```
    dead-claudia committed Oct 11, 2024
    Configuration menu
    Copy the full SHA
    183cc6f View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    93dc6df View commit details
    Browse the repository at this point in the history
  4. Configuration menu
    Copy the full SHA
    c2b6820 View commit details
    Browse the repository at this point in the history
  5. Optimize stream, remove deprecated bit, simplify combine

    - Did some baseline optimization to it to reduce overhead and memory requirements. Won't be too much of a deal with smaller apps, but users using a lot of streams will appreciate the reduced overhead.
    - Removed the runtime-deprecated `Stream.HALT`.
    - Dropped the source list from `Stream.combine` callbacks' operands. It's almost always available in context anyways.
    dead-claudia committed Oct 11, 2024
    Configuration menu
    Copy the full SHA
    a047b30 View commit details
    Browse the repository at this point in the history
  6. Move redraw to an options bag

    This prepares for future possible render flags, like a "remove on error" flag that might get exposed in the future to enable userland error boundaries.
    dead-claudia committed Oct 11, 2024
    Configuration menu
    Copy the full SHA
    6da068b View commit details
    Browse the repository at this point in the history
  7. Configuration menu
    Copy the full SHA
    6ff0edc View commit details
    Browse the repository at this point in the history

Commits on Oct 12, 2024

  1. Configuration menu
    Copy the full SHA
    d63ed0f View commit details
    Browse the repository at this point in the history
  2. Add bi-edge debouncing + throttling rate limiters

    Proper debouncing and throttling is easy to screw up, and the usual implementations that invoke a provided callback don't compose well. Instead, this provides functions that return promises you can filter on. This is far more composable and far easier to use.
    
    Also, the thing people usually do in a pinch, leading edge throttling, is usually the wrong thing to do. For example, if you're implementing an interactive search, you want the first to go immediately, but still incrementally update while the user is typing.
    dead-claudia committed Oct 12, 2024
    Configuration menu
    Copy the full SHA
    4d5904c View commit details
    Browse the repository at this point in the history
  3. Stub out error handling

    Also made some small (but needed) changes to the entry points and minified scripts.
    
    No specific tests for the error handling yet aside from the existing error-related tests that needed to change. But here's the going idea: error boundaries can be done in userland. Users would do something like this:
    
    ```js
    function slurpContext(context) {
        const chain = []
        while (context !== null && context !== Object.prototype) {
            chain.push(context)
            context = Object.getPrototypeOf(context)
        }
        return chain.reduceRight((a, b) => Object.defineProperties(a, Object.getOwnPropertyDescriptors(b)), {})
    }
    
    function ErrorBoundary(attrs, _, context) {
        return m("div.boundary", [
            m.layout((dom) => {
                try {
                    m.render(dom, m.set(slurpContext(context), attrs.view()), {
                        render: context.render,
                        removeOnThrow: true,
                    })
                } catch (e) {
                    attrs.onerror(e)
                }
            }),
            m.remove((dom) => m.render(dom, null)),
        ])
    }
    ```
    dead-claudia committed Oct 12, 2024
    Configuration menu
    Copy the full SHA
    f3a6f9a View commit details
    Browse the repository at this point in the history
  4. Configuration menu
    Copy the full SHA
    d741b3d View commit details
    Browse the repository at this point in the history
  5. Add ops per sec counter

    dead-claudia committed Oct 12, 2024
    Configuration menu
    Copy the full SHA
    d6b6bda View commit details
    Browse the repository at this point in the history
  6. Optimize error handling

    `finally` is apparently really slow. This improved benchmarks by about 3-4x, bringing them back to roughly what they were when I first cut the branch.
    dead-claudia committed Oct 12, 2024
    Configuration menu
    Copy the full SHA
    c196356 View commit details
    Browse the repository at this point in the history
  7. Ensure is is always set in custom elements

    This matches HTML more closely, and makes for a much better experience with custom elements.
    dead-claudia committed Oct 12, 2024
    Configuration menu
    Copy the full SHA
    2aab01d View commit details
    Browse the repository at this point in the history
  8. Normalize the render vnode input to a single unitary vnode, not an array

    It's more predictable that way, and I expect this to break approximately nobody. (If anything, it's likely to *unbreak* some users, as they may have been assuming this to have been the case from the beginning.)
    dead-claudia committed Oct 12, 2024
    Configuration menu
    Copy the full SHA
    94ed948 View commit details
    Browse the repository at this point in the history
  9. Configuration menu
    Copy the full SHA
    374729d View commit details
    Browse the repository at this point in the history
  10. Configuration menu
    Copy the full SHA
    ab0a28d View commit details
    Browse the repository at this point in the history
  11. Configuration menu
    Copy the full SHA
    33a13cc View commit details
    Browse the repository at this point in the history
  12. Configuration menu
    Copy the full SHA
    cad3725 View commit details
    Browse the repository at this point in the history
  13. Restore current size

    Some point along the way, this got erroneously changed and saved.
    dead-claudia committed Oct 12, 2024
    Configuration menu
    Copy the full SHA
    024150a View commit details
    Browse the repository at this point in the history
  14. Configuration menu
    Copy the full SHA
    c13a7f6 View commit details
    Browse the repository at this point in the history
  15. Stop displaying size in the README

    It's going to unnecessarily complicate the release process, especially now that artifacts aren't saved to the repo anymore.
    dead-claudia committed Oct 12, 2024
    Configuration menu
    Copy the full SHA
    6e50918 View commit details
    Browse the repository at this point in the history
  16. Configuration menu
    Copy the full SHA
    472e59e View commit details
    Browse the repository at this point in the history