Deservicify core (#2458)

* De-servicify router (mostly)

Still uses the redraw service, but it no longer has an intermediate
service of its own.

Also, did a *lot* of test deduplication in this. About 30-40% of the
router service tests were already tested on the main router API instance
itself.

Bundle size decreased from 9560 to 9548 bytes min+gzip.

* Merge `m.mount` + `m.redraw`, update router

Simplifies the router and redraw mechanism, and makes it much easier to
keep predictable.

Bundle size down to 9433 bytes min+gzip, docs updated accordingly.

* Make `mithril/render` just return the `m.render` function directly.

* Deservicify `m.render`, revise `m.route`

- You now have to use `mithril/render/render` directly if you want an
  implicit redraw function. (This will likely be going away in v3.)
- Revise `m.route` to only `key` components

* Add `redraw` to `m.render`, deservicify requests

* Test error logging

* Update docs + changelog [skip ci]
This commit is contained in:
Isiah Meadows 2019-07-07 18:28:43 -04:00 committed by GitHub
parent db277217f8
commit 1f4b2cf49a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 1212 additions and 1393 deletions

View file

@ -54,6 +54,13 @@
- Previously, numeric children weren't coerced. Now, they are.
- Unlikely to break most components, but it *could* break some users.
- This increases consistency with how booleans are handled with children, so it should be more intuitive.
- route: `key` parameter for routes now only works globally for components ([#2458](https://github.com/MithrilJS/mithril.js/pull/2458) [@isiahmeadows](https://github.com/isiahmeadows))
- Previously, it worked for route resolvers, too.
- This lets you ensure global layouts used in `render` still render by diff.
- redraw: `mithril/redraw` now just exposes the `m.redraw` callback ([#2458](https://github.com/MithrilJS/mithril.js/pull/2458) [@isiahmeadows](https://github.com/isiahmeadows))
- The `.schedule`, `.unschedule`, and `.render` properties of the former `redrawService` are all removed.
- If you want to know how to work around it, look at the call to `mount` in Mithril's source for `m.route`. That should help you in finding ways around the removed feature. (It doesn't take that much more code.)
#### News
@ -81,6 +88,7 @@
- route: Use `m.mount(root, null)` to unsubscribe and clean up after a `m.route(root, ...)` call. ([#2453](https://github.com/MithrilJS/mithril.js/pull/2453))
- version: `m.version` returns the previous version string for what's in `next`. ([#2453](https://github.com/MithrilJS/mithril.js/pull/2453))
- If you're using `next`, you should hopefully know what you're doing. If you need stability, don't use `next`. (This is also why I'm not labelling it as a breaking change.)
- render: new `redraw` parameter exposed any time a child event handler is used ([#2458](https://github.com/MithrilJS/mithril.js/pull/2458) [@isiahmeadows](https://github.com/isiahmeadows))
#### Bug fixes

View file

@ -454,7 +454,7 @@ var isError = false
m("div", isError ? "An error occurred" : "Saved") // <div>Saved</div>
```
You cannot use JavaScript statements such as `if` or `for` within JavaScript expressions. It's preferable to avoid using those statements altogether and instead, use the constructs above exclusively in order to keep the structure of the templates linear and declarative, and to avoid deoptimizations.
You cannot use JavaScript statements such as `if` or `for` within JavaScript expressions. It's preferable to avoid using those statements altogether and instead, use the constructs above exclusively in order to keep the structure of the templates linear and declarative.
---

View file

@ -18,7 +18,7 @@ It's small (< 10kb gzip), fast and provides routing and XHR utilities out of the
<div style="display:flex;margin:0 0 30px;">
<div style="width:50%;">
<h5>Download size</h5>
<small>Mithril (9.6kb)</small>
<small>Mithril (9.4kb)</small>
<div style="animation:grow 0.08s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:4%;"></div>
<small style="color:#aaa;">Vue + Vue-Router + Vuex + fetch (40kb)</small>
<div style="animation:grow 0.4s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:20%"></div>

View file

@ -75,7 +75,7 @@ function correctUserList(users) {
}
```
Also, you might want to reinitialize a component. You can use the common pattern of a single-item keyed fragment where you change the key to destroy and reinitialize the element.
Also, you might want to reinitialize a component. You can use the common pattern of a single-child keyed fragment where you change the key to destroy and reinitialize the element.
```javascript
function ResettableToggle() {

View file

@ -3,6 +3,7 @@
- [Description](#description)
- [Signature](#signature)
- [How it works](#how-it-works)
- [Headless mounts](#headless-mounts)
- [Performance considerations](#performance-considerations)
- [Differences from m.render](#differences-from-mrender)
@ -61,6 +62,35 @@ Running `mount(element, OtherComponent)` where `element` is a current mount poin
Using `m.mount(element, null)` on an element with a previously mounted component unmounts it and cleans up Mithril internal state. This can be useful to prevent memory leaks when removing the `root` node manually from the DOM.
#### Headless mounts
In certain more advanced situations, you may want to subscribe and listen for [redraws](autoredraw.md) without rendering anything to the screen. This can be done using a headless mount, created by simply invoking `m.mount` with an element that's not added to the live DOM tree and putting all your useful logic in the component you're mounting with. You still need a `view` in your component, just it doesn't have to return anything useful and it can just return a junk value like `null` or `undefined`.
```javascript
var elem = document.createElement("div")
// Subscribe
m.mount(elem, {
oncreate: function() {
// once added
},
onupdate: function() {
// on each redraw
},
onremove: function() {
// clean up whatever you need
},
// Necessary boilerplate
view: function () {},
})
// Unsubscribe
m.mount(elem, null)
```
There's no need to worry about other mount roots. Multiple roots are supported and they won't step on each other. You can even do the above in a component when integrating with another framework, and it won't be a problem.
---
### Performance considerations

View file

@ -22,12 +22,13 @@ m.render(document.body, "hello")
### Signature
`m.render(element, vnodes)`
`m.render(element, vnodes, redraw)`
Argument | Type | Required | Description
----------- | -------------------- | -------- | ---
`element` | `Element` | Yes | A DOM element that will be the parent node to the subtree
`vnodes` | `Array<Vnode>|Vnode` | Yes | The [vnodes](vnodes.md) to be rendered
`redraw` | `() -> any` | No | A callback invoked each time an event handler in the subtree is invoked
**returns** | | | Returns `undefined`
[How to read signatures](signatures.md)
@ -36,7 +37,9 @@ Argument | Type | Required | Description
### How it works
The `m.render(element, vnodes)` method takes a virtual DOM tree (typically generated via the [`m()` hyperscript function](hyperscript.md)), generates a DOM tree and mounts it on `element`. If `element` already has a DOM tree mounted via a previous `m.render()` call, `vnodes` is diffed against the previous `vnodes` tree and the existing DOM tree is modified only where needed to reflect the changes. Unchanged DOM nodes are not touched at all.
The `m.render(element, vnodes)` method takes a virtual DOM tree (typically generated via the [`m()` hyperscript function](hyperscript.md)), generates a DOM tree and mounts it on `element`. If `element` already has a DOM tree mounted via a previous `m.render()` call, `vnodes` is diffed against the previous `vnodes` tree and the existing DOM tree is modified only where needed to reflect the changes. Unchanged DOM nodes are not touched at all.
If you pass the optional `redraw` argument, that is invoked each time an event handler anywhere in the subtree is called. This is used by [`m.mount`](mount.md) and [`m.redraw`](redraw.md) to implement the [autoredraw](autoredraw.md) functionality, but it's also exposed for more advanced use cases like integration with some third-party frameworks.
`m.render` is synchronous.
@ -66,6 +69,6 @@ Another difference is that `m.render` method expects a [vnode](vnodes.md) (or a
`var render = require("mithril/render")`
The `m.render` module is similar in scope to view libraries like Knockout, React and Vue. It is approximately 500 lines of code (3kb min+gzip) and implements a virtual DOM diffing engine with a modern search space reduction algorithm and DOM recycling, which translate to top-of-class performance, both in terms of initial page load and re-rendering. It has no dependencies on other parts of Mithril and can be used as a standalone library.
The `m.render` module is similar in scope to view libraries like Knockout, React and Vue. It implements a virtual DOM diffing engine with a modern search space reduction algorithm and DOM recycling, which translate to top-of-class performance, both in terms of initial page load and re-rendering. It has no dependencies on other parts of Mithril aside from normalization exposed via `require("mithril/render/vnode")` and can be used as a standalone library.
Despite being incredibly small, the render module is fully functional and self-sufficient. It supports everything you might expect: SVG, custom elements, and all valid attributes and events - without any weird case-sensitive edge cases or exceptions. Of course, it also fully supports [components](components.md) and [lifecycle methods](lifecycle-methods.md).
Despite being relatively small, the render module is fully functional and self-sufficient. It supports everything you might expect: SVG, custom elements, and all valid attributes and events - without any weird case-sensitive edge cases or exceptions. Of course, it also fully supports [components](components.md) and [lifecycle methods](lifecycle-methods.md).

View file

@ -206,7 +206,7 @@ Argument | Type | Description
#### How it works
Routing is a system that allows creating Single-Page-Applications (SPA), i.e. applications that can go from a "page" to another without causing a full browser refresh.
Routing is a system that allows creating Single Page Applications (SPA), i.e. applications that can go from a "page" to another without causing a full browser refresh.
It enables seamless navigability while preserving the ability to bookmark each page individually, and the ability to navigate the application via the browser's history mechanism.
@ -336,6 +336,8 @@ Or even use the [`history state`](#history-state) feature to achieve reloadable
`m.route.set(m.route.get(), null, {state: {key: Date.now()}})`
Note that the key parameter works only for component routes. If you're using a route resolver, you'll need to use a [single-child keyed fragment](keys.md), passing `key: m.route.param("key")`, to accomplish the same.
#### Variadic routes
It's also possible to have variadic routes, i.e. a route with an argument that contains URL pathnames that contain slashes:
@ -739,7 +741,7 @@ m.route(document.body, "/", {
In certain situations, you may find yourself needing to interoperate with another framework like React. Here's how you do it:
- Define all your routes using `m.route` as normal, but make sure you only use it *once*. Multiple route points are not supported.
- When you need to remove routing subscriptions, use `m.mount(root, null)`, using the same root you used `m.route(root, ...)` on.
- When you need to remove routing subscriptions, use `m.mount(root, null)`, using the same root you used `m.route(root, ...)` on. `m.route` uses `m.mount` internally to hook everything up, so it's not magic.
Here's an example with React: