Comparing version 2.0.0-rc.9 to 2.0.1
@@ -12,3 +12,3 @@ # Animations | ||
Animations are often used to make applications come alive. Nowadays, browsers have good support for CSS animations, and there are [various](https://greensock.com/gsap) [libraries](http://velocityjs.org/) that provide fast JavaScript-based animations. There's also an upcoming [Web API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) and a [polyfill](https://github.com/web-animations/web-animations-js) if you like living on the bleeding edge. | ||
Animations are often used to make applications come alive. Nowadays, browsers have good support for CSS animations, and there are [various](https://greensock.com/gsap) [libraries](https://velocityjs.org/) that provide fast JavaScript-based animations. There's also an upcoming [Web API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) and a [polyfill](https://github.com/web-animations/web-animations-js) if you like living on the bleeding edge. | ||
@@ -106,2 +106,2 @@ Mithril does not provide any animation APIs per se, since these other options are more than sufficient to achieve rich, complex animations. Mithril does, however, offer hooks to make life easier in some specific cases where it's traditionally difficult to make animations work. | ||
It's also recommended that you avoid the `box-shadow` rule and selectors like `:nth-child`, since these are also resource intensive options. If you want to animate a `box-shadow`, consider [putting the `box-shadow` rule on a pseudo element, and animate that element's opacity instead](http://tobiasahlin.com/blog/how-to-animate-box-shadow/). Other things that can be expensive include large or dynamically scaled images and overlapping elements with different `position` values (e.g. an absolute positioned element over a fixed element). | ||
It's also recommended that you avoid the `box-shadow` rule and selectors like `:nth-child`, since these are also resource intensive options. If you want to animate a `box-shadow`, consider [putting the `box-shadow` rule on a pseudo element, and animate that element's opacity instead](https://tobiasahlin.com/blog/how-to-animate-box-shadow/). Other things that can be expensive include large or dynamically scaled images and overlapping elements with different `position` values (e.g. an absolute positioned element over a fixed element). |
@@ -44,3 +44,3 @@ # API | ||
m.route(document.body, "/home", { | ||
"/home": Home, // defines `http://localhost/#!/home` | ||
"/home": Home, // defines `https://example.com/#!/home` | ||
}) | ||
@@ -47,0 +47,0 @@ ``` |
# Change log | ||
- [v2.0.0-rc](#v200rc) | ||
- [v1.1.6](#v116) | ||
- [v1.1.5](#v115) | ||
- [v1.1.4](#v114) | ||
- [v1.1.3](#v113) | ||
- [v1.1.2](#v112) | ||
- [v1.1.1](#v111) | ||
- [v1.1.0](#v110) | ||
- [v1.0.1](#v101) | ||
- [Migrating from v0.2.x](#migrating-from-v02x) | ||
- [v1.x docs](http://mithril.js.org/archive/v1.1.6/index.html) | ||
- [v0.2 docs](http://mithril.js.org/archive/v0.2.5/index.html) | ||
- [v2.0.1](#v201) | ||
- [v2.0.0](#v200) | ||
- [Migrating from v1.x](migration-v1x.md) | ||
- [Migrating from v0.2.x](migration-v02x.md) | ||
- [v1.x changelog](https://mithril.js.org/archive/v1.1.6/change-log.html) | ||
- [v1.x docs](https://mithril.js.org/archive/v1.1.6/index.html) | ||
- [v0.2 docs](https://mithril.js.org/archive/v0.2.5/index.html) | ||
- [`ospec` change log](https://github.com/MithrilJS/mithril.js/blob/master/ospec/change-log.md) | ||
@@ -22,4 +17,8 @@ - [`mithril/stream` change log](https://github.com/MithrilJS/mithril.js/blob/master/stream/change-log.md) | ||
### v2.0.0-rc | ||
### v2.0.1 | ||
Same as v2.0.0, but with a publish that didn't have a botched upload. | ||
### v2.0.0 | ||
#### Breaking changes | ||
@@ -35,5 +34,4 @@ | ||
- hyperscript: when an attribute is defined on both the first and second argument (as a CSS selector and an `attrs` field, respectively), the latter takes precedence, except for `class` attributes that are still added together. [#2172](https://github.com/MithrilJS/mithril.js/issues/2172) ([#2174](https://github.com/MithrilJS/mithril.js/pull/2174)) | ||
- render: Align custom elements to work like normal elements, minus all the HTML-specific magic. ([#2221](https://github.com/MithrilJS/mithril.js/pull/2221)) | ||
- cast className using toString ([#2309](https://github.com/MithrilJS/mithril.js/pull/2309)) | ||
- render: call attrs' hooks first, with express exception of `onbeforeupdate` to allow attrs to block components from even diffing ([#2297](https://github.com/MithrilJS/mithril.js/pull/2297)) | ||
- render: call attrs' hooks last, with express exception of `onbeforeupdate` to allow attrs to block components from even diffing ([#2297](https://github.com/MithrilJS/mithril.js/pull/2297)) | ||
- API: `m.withAttr` removed. ([#2317](https://github.com/MithrilJS/mithril.js/pull/2317)) | ||
@@ -74,8 +72,3 @@ - request: `data` has now been split to `params` and `body` and `useBody` has been removed in favor of just using `body`. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) | ||
- Link targets can be trivially changed. | ||
- API: Full DOM no longer required to execute `require("mithril")`. You just need to set the necessary globals to *something*, even if `null` or `undefined`, so they can be properly used. ([#2469](https://github.com/MithrilJS/mithril.js/pull/2469) [@isiahmeadows](https://github.com/isiahmeadows)) | ||
- This enables isomorphic use of `m.route.Link` and `m.route.prefix`. | ||
- This enables isomorphic use of `m.request`, provided the `background: true` option is set and that an `XMLHttpRequest` polyfill is included as necessary. | ||
- Note that methods requiring DOM operations will still throw errors, such as `m.render(...)`, `m.redraw()`, and `m.route(...)`. | ||
#### News | ||
@@ -106,2 +99,7 @@ | ||
- route: `m.route.SKIP` can be returned from route resolvers to skip to the next route ([#2469](https://github.com/MithrilJS/mithril.js/pull/2469) [@isiahmeadows](https://github.com/isiahmeadows)) | ||
- API: Full DOM no longer required to execute `require("mithril")`. You just need to set the necessary globals to *something*, even if `null` or `undefined`, so they can be properly used. ([#2469](https://github.com/MithrilJS/mithril.js/pull/2469) [@isiahmeadows](https://github.com/isiahmeadows)) | ||
- This enables isomorphic use of `m.route.Link` and `m.route.prefix`. | ||
- This enables isomorphic use of `m.request`, provided the `background: true` option is set and that an `XMLHttpRequest` polyfill is included as necessary. | ||
- Note that methods requiring DOM operations will still throw errors, such as `m.render(...)`, `m.redraw()`, and `m.route(...)`. | ||
- render: Align custom elements to work like normal elements, minus all the HTML-specific magic. ([#2221](https://github.com/MithrilJS/mithril.js/pull/2221)) | ||
@@ -142,824 +140,1 @@ #### Bug fixes | ||
- request: track xhr replacements correctly ([#2455](https://github.com/MithrilJS/mithril.js/pull/2455) [@isiahmeadows](https://github.com/isiahmeadows)) | ||
--- | ||
### v1.2.0 | ||
#### News | ||
- Promise polyfill implementation separated from polyfilling logic. | ||
- `PromisePolyfill` is now available on the exported/global `m`. | ||
#### Bug fixes | ||
- core: Workaround for [Internet Explorer bug](https://www.tjvantoll.com/2013/08/30/bugs-with-document-activeelement-in-internet-explorer/) when running in an iframe | ||
#### Note | ||
- Stream references no longer magically coerce to their underlying values ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150), stream breaking change: `mithril-stream@2.0.0`) | ||
--- | ||
### v1.1.6 | ||
#### Bug fixes | ||
- core: render() function can no longer prevent from changing `document.activeElement` in lifecycle hooks ([#1988](https://github.com/MithrilJS/mithril.js/pull/1988), [@purplecode](https://github.com/purplecode)) | ||
- core: don't call `onremove` on the children of components that return null from the view [#1921](https://github.com/MithrilJS/mithril.js/issues/1921) [@octavore](https://github.com/octavore) ([#1922](https://github.com/MithrilJS/mithril.js/pull/1922)) | ||
- hypertext: correct handling of shared attributes object passed to `m()`. Will copy attributes when it's necessary [#1941](https://github.com/MithrilJS/mithril.js/issues/1941) [@s-ilya](https://github.com/s-ilya) ([#1942](https://github.com/MithrilJS/mithril.js/pull/1942)) | ||
#### Ospec improvements | ||
- ospec v1.4.0 | ||
- Added support for async functions and promises in tests ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928), [@StephanHoyer](https://github.com/StephanHoyer)) | ||
- Error handling for async tests with `done` callbacks supports error as first argument ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928)) | ||
- Error messages which include newline characters do not swallow the stack trace [#1495](https://github.com/MithrilJS/mithril.js/issues/1495) ([#1984](https://github.com/MithrilJS/mithril.js/pull/1984), [@RodericDay](https://github.com/RodericDay)) | ||
- ospec v2.0.0 (to be released) | ||
- Added support for custom reporters ([#2009](https://github.com/MithrilJS/mithril.js/pull/2020)) | ||
- Make Ospec more [Flems](https://flems.io)-friendly ([#2034](https://github.com/MithrilJS/mithril.js/pull/2034)) | ||
- Works either as a global or in CommonJS environments | ||
- the o.run() report is always printed asynchronously (it could be synchronous before if none of the tests were async). | ||
- Properly point to the assertion location of async errors [#2036](https://github.com/MithrilJS/mithril.js/issues/2036) | ||
- expose the default reporter as `o.report(results)` | ||
- Don't try to access the stack traces in IE9 | ||
--- | ||
### v1.1.5 | ||
#### Bug fixes | ||
- API: If a user sets the Content-Type header within a request's options, that value will be the entire header value rather than being appended to the default value [#1919](https://github.com/MithrilJS/mithril.js/issues/1919) ([#1924](https://github.com/MithrilJS/mithril.js/pull/1924), [@tskillian](https://github.com/tskillian)) | ||
--- | ||
### v1.1.4 | ||
#### Bug fixes | ||
- Fix IE bug where active element is null causing render function to throw error ([#1943](https://github.com/MithrilJS/mithril.js/pull/1943), [@JacksonJN](https://github.com/JacksonJN)) | ||
#### Ospec improvements: | ||
- Log using util.inspect to show object content instead of "[object Object]" ([#1661](https://github.com/MithrilJS/mithril.js/issues/1661), [@porsager](https://github.com/porsager)) | ||
--- | ||
### v1.1.3 | ||
#### Bug fixes | ||
- move out npm dependencies added by mistake | ||
--- | ||
### v1.1.2 | ||
#### Bug fixes | ||
- core: Namespace fixes [#1819](https://github.com/MithrilJS/mithril.js/issues/1819), ([#1825](https://github.com/MithrilJS/mithril.js/pull/1825) [@SamuelTilly](https://github.com/SamuelTilly)), [#1820](https://github.com/MithrilJS/mithril.js/issues/1820) ([#1864](https://github.com/MithrilJS/mithril.js/pull/1864)), [#1872](https://github.com/MithrilJS/mithril.js/issues/1872) ([#1873](https://github.com/MithrilJS/mithril.js/pull/1873)) | ||
- core: Fix select option to allow empty string value [#1814](https://github.com/MithrilJS/mithril.js/issues/1814) ([#1828](https://github.com/MithrilJS/mithril.js/pull/1828) [@spacejack](https://github.com/spacejack)) | ||
- core: Reset e.redraw when it was set to `false` [#1850](https://github.com/MithrilJS/mithril.js/issues/1850) ([#1890](https://github.com/MithrilJS/mithril.js/pull/1890)) | ||
- core: differentiate between `{ value: "" }` and `{ value: 0 }` for form elements [#1595 comment](https://github.com/MithrilJS/mithril.js/pull/1595#issuecomment-304071453) ([#1862](https://github.com/MithrilJS/mithril.js/pull/1862)) | ||
- core: Don't reset the cursor of textareas in IE10 when setting an identical `value` [#1870](https://github.com/MithrilJS/mithril.js/issues/1870) ([#1871](https://github.com/MithrilJS/mithril.js/pull/1871)) | ||
- hypertext: Correct handling of `[value=""]` ([#1843](https://github.com/MithrilJS/mithril.js/issues/1843), [@CreaturesInUnitards](https://github.com/CreaturesInUnitards)) | ||
- router: Don't overwrite the options object when redirecting from `onmatch with m.route.set()` [#1857](https://github.com/MithrilJS/mithril.js/issues/1857) ([#1889](https://github.com/MithrilJS/mithril.js/pull/1889)) | ||
- stream: Move the "use strict" directive inside the IIFE [#1831](https://github.com/MithrilJS/mithril.js/issues/1831) ([#1893](https://github.com/MithrilJS/mithril.js/pull/1893)) | ||
--- | ||
#### Docs / Repo maintenance | ||
Our thanks to [@0joshuaolson1](https://github.com/0joshuaolson1), [@ACXgit](https://github.com/ACXgit), [@cavemansspa](https://github.com/cavemansspa), [@CreaturesInUnitards](https://github.com/CreaturesInUnitards), [@dlepaux](https://github.com/dlepaux), [@isaaclyman](https://github.com/isaaclyman), [@kevinkace](https://github.com/kevinkace), [@micellius](https://github.com/micellius), [@spacejack](https://github.com/spacejack) and [@yurivish](https://github.com/yurivish) | ||
#### Other | ||
- Addition of a performance regression test suite ([#1789](https://github.com/MithrilJS/mithril.js/issues/1789)) | ||
--- | ||
### v1.1.1 | ||
#### Bug fixes | ||
- hyperscript: Allow `0` as the second argument to `m()` - [#1752](https://github.com/MithrilJS/mithril.js/issues/1752) / [#1753](https://github.com/MithrilJS/mithril.js/pull/1753) ([@StephanHoyer](https://github.com/StephanHoyer)) | ||
- hyperscript: restore `attrs.class` handling to what it was in v1.0.1 - [#1764](https://github.com/MithrilJS/mithril.js/issues/1764) / [#1769](https://github.com/MithrilJS/mithril.js/pull/1769) | ||
- documentation improvements ([@JAForbes](https://github.com/JAForbes), [@smuemd](https://github.com/smuemd), [@hankeypancake](https://github.com/hankeypancake)) | ||
--- | ||
### v1.1.0 | ||
#### News | ||
- support for ES6 class components | ||
- support for closure components | ||
- improvements in build and release automation | ||
#### Bug fixes | ||
- fix IE11 input[type] error - [#1610](https://github.com/MithrilJS/mithril.js/issues/1610) | ||
- apply [#1609](https://github.com/MithrilJS/mithril.js/issues/1609) to unkeyed children case | ||
- fix abort detection [#1612](https://github.com/MithrilJS/mithril.js/issues/1612) | ||
- fix input value focus issue when value is loosely equal to old value [#1593](https://github.com/MithrilJS/mithril.js/issues/1593) | ||
--- | ||
### v1.0.1 | ||
#### News | ||
- performance improvements in IE [#1598](https://github.com/MithrilJS/mithril.js/pull/1598) | ||
#### Bug fixes | ||
- prevent infinite loop in non-existent default route - [#1579](https://github.com/MithrilJS/mithril.js/issues/1579) | ||
- call correct lifecycle methods on children of recycled keyed vnodes - [#1609](https://github.com/MithrilJS/mithril.js/issues/1609) | ||
--- | ||
### Migrating from `v0.2.x` | ||
`v1.x` is largely API-compatible with `v0.2.x`, but there are some breaking changes. | ||
If you are migrating, consider using the [mithril-codemods](https://www.npmjs.com/package/mithril-codemods) tool to help automate the most straightforward migrations. | ||
- [`m.prop` removed](#mprop-removed) | ||
- [`m.component` removed](#mcomponent-removed) | ||
- [`config` function](#config-function) | ||
- [Changes in redraw behaviour](#changes-in-redraw-behaviour) | ||
- [No more redraw locks](#no-more-redraw-locks) | ||
- [Cancelling redraw from event handlers](#cancelling-redraw-from-event-handlers) | ||
- [Synchronous redraw removed](#synchronous-redraw-removed) | ||
- [`m.startComputation`/`m.endComputation` removed](#mstartcomputationmendcomputation-removed) | ||
- [Component `controller` function](#component-controller-function) | ||
- [Component arguments](#component-arguments) | ||
- [`view()` parameters](#view-parameters) | ||
- [Passing components to `m()`](#passing-components-to-m) | ||
- [Passing vnodes to `m.mount()` and `m.route()`](#passing-vnodes-to-mmount-and-mroute) | ||
- [`m.route.mode`](#mroutemode) | ||
- [`m.route` and anchor tags](#mroute-and-anchor-tags) | ||
- [Reading/writing the current route](#readingwriting-the-current-route) | ||
- [Accessing route params](#accessing-route-params) | ||
- [Building/Parsing query strings](#buildingparsing-query-strings) | ||
- [Preventing unmounting](#preventing-unmounting) | ||
- [Run code on component removal](#run-code-on-component-removal) | ||
- [`m.request`](#mrequest) | ||
- [`m.deferred` removed](#mdeferred-removed) | ||
- [`m.sync` removed](#msync-removed) | ||
- [`xlink` namespace required](#xlink-namespace-required) | ||
- [Nested arrays in views](#nested-arrays-in-views) | ||
- [`vnode` equality checks](#vnode-equality-checks) | ||
--- | ||
## `m.prop` removed | ||
In `v1.x`, `m.prop()` is now a more powerful stream micro-library, but it's no longer part of core. You can read about how to use the optional Streams module in [the documentation](stream.md). | ||
### `v0.2.x` | ||
```javascript | ||
var m = require("mithril") | ||
var num = m.prop(1) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
var m = require("mithril") | ||
var prop = require("mithril/stream") | ||
var num = prop(1) | ||
var doubled = num.map(function(n) {return n * 2}) | ||
``` | ||
--- | ||
## `m.component` removed | ||
In `v0.2.x` components could be created using either `m(component)` or `m.component(component)`. `v1.x` only supports `m(component)`. | ||
### `v0.2.x` | ||
```javascript | ||
// These are equivalent | ||
m.component(component) | ||
m(component) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
m(component) | ||
``` | ||
--- | ||
## `config` function | ||
In `v0.2.x` mithril provided a single lifecycle method, `config`. `v1.x` provides much more fine-grained control over the lifecycle of a vnode. | ||
### `v0.2.x` | ||
```javascript | ||
m("div", { | ||
config : function(element, isInitialized) { | ||
// runs on each redraw | ||
// isInitialized is a boolean representing if the node has been added to the DOM | ||
} | ||
}) | ||
``` | ||
### `v1.x` | ||
More documentation on these new methods is available in [lifecycle-methods.md](lifecycle-methods.md). | ||
```javascript | ||
m("div", { | ||
// Called before the DOM node is created | ||
oninit : function(vnode) { /*...*/ }, | ||
// Called after the DOM node is created | ||
oncreate : function(vnode) { /*...*/ }, | ||
// Called before the node is updated, return false to cancel | ||
onbeforeupdate : function(vnode, old) { /*...*/ }, | ||
// Called after the node is updated | ||
onupdate : function(vnode) { /*...*/ }, | ||
// Called before the node is removed, return a Promise that resolves when | ||
// ready for the node to be removed from the DOM | ||
onbeforeremove : function(vnode) { /*...*/ }, | ||
// Called before the node is removed, but after onbeforeremove calls done() | ||
onremove : function(vnode) { /*...*/ } | ||
}) | ||
``` | ||
If available the DOM-Element of the vnode can be accessed at `vnode.dom`. | ||
--- | ||
## Changes in redraw behaviour | ||
Mithril's rendering engine still operates on the basis of semi-automated global redraws, but some APIs and behaviours differ: | ||
### No more redraw locks | ||
In v0.2.x, Mithril allowed 'redraw locks' which temporarily prevented blocked draw logic: by default, `m.request` would lock the draw loop on execution and unlock when all pending requests had resolved - the same behaviour could be invoked manually using `m.startComputation()` and `m.endComputation()`. The latter APIs and the associated behaviour has been removed in v1.x. Redraw locking can lead to buggy UIs: the concerns of one part of the application should not be allowed to prevent other parts of the view from updating to reflect change. | ||
### Cancelling redraw from event handlers | ||
`m.mount()` and `m.route()` still automatically redraw after a DOM event handler runs. Cancelling these redraws from within your event handlers is now done by setting the `redraw` property on the passed-in event object to `false`. | ||
#### `v0.2.x` | ||
```javascript | ||
m("div", { | ||
onclick : function(e) { | ||
m.redraw.strategy("none") | ||
} | ||
}) | ||
``` | ||
#### `v1.x` | ||
```javascript | ||
m("div", { | ||
onclick : function(e) { | ||
e.redraw = false | ||
} | ||
}) | ||
``` | ||
### Synchronous redraw removed | ||
In v0.2.x it was possible to force mithril to redraw immediately by passing a truthy value to `m.redraw()`. This behavior complicated usage of `m.redraw()` and caused some hard-to-reason about issues and has been removed. | ||
#### `v0.2.x` | ||
```javascript | ||
m.redraw(true) // redraws immediately & synchronously | ||
``` | ||
#### `v1.x` | ||
```javascript | ||
m.redraw() // schedules a redraw on the next requestAnimationFrame tick | ||
``` | ||
### `m.startComputation`/`m.endComputation` removed | ||
They are considered anti-patterns and have a number of problematic edge cases, so they no longer exist in v1.x. | ||
--- | ||
## Component `controller` function | ||
In `v1.x` there is no more `controller` property in components, use `oninit` instead. | ||
### `v0.2.x` | ||
```javascript | ||
m.mount(document.body, { | ||
controller : function() { | ||
var ctrl = this | ||
ctrl.fooga = 1 | ||
}, | ||
view : function(ctrl) { | ||
return m("p", ctrl.fooga) | ||
} | ||
}) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
m.mount(document.body, { | ||
oninit : function(vnode) { | ||
vnode.state.fooga = 1 | ||
}, | ||
view : function(vnode) { | ||
return m("p", vnode.state.fooga) | ||
} | ||
}) | ||
// OR | ||
m.mount(document.body, { | ||
oninit : function(vnode) { | ||
var state = this // this is bound to vnode.state by default | ||
state.fooga = 1 | ||
}, | ||
view : function(vnode) { | ||
var state = this // this is bound to vnode.state by default | ||
return m("p", state.fooga) | ||
} | ||
}) | ||
``` | ||
--- | ||
## Component arguments | ||
Arguments to a component in `v1.x` must be an object, simple values like `String`/`Number`/`Boolean` will be treated as text children. Arguments are accessed within the component by reading them from the `vnode.attrs` object. | ||
### `v0.2.x` | ||
```javascript | ||
var component = { | ||
controller : function(options) { | ||
// options.fooga === 1 | ||
}, | ||
view : function(ctrl, options) { | ||
// options.fooga == 1 | ||
} | ||
} | ||
m("div", m.component(component, { fooga : 1 })) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
var component = { | ||
oninit : function(vnode) { | ||
// vnode.attrs.fooga === 1 | ||
}, | ||
view : function(vnode) { | ||
// vnode.attrs.fooga == 1 | ||
} | ||
} | ||
m("div", m(component, { fooga : 1 })) | ||
``` | ||
--- | ||
## `view()` parameters | ||
In `v0.2.x` view functions are passed a reference to the `controller` instance and (optionally) any options passed to the component. In `v1.x` they are passed **only** the `vnode`, exactly like the `controller` function. | ||
### `v0.2.x` | ||
```javascript | ||
m.mount(document.body, { | ||
controller : function() {}, | ||
view : function(ctrl, options) { | ||
// ... | ||
} | ||
}) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
m.mount(document.body, { | ||
oninit : function(vnode) { | ||
// ... | ||
}, | ||
view : function(vnode) { | ||
// Use vnode.state instead of ctrl | ||
// Use vnode.attrs instead of options | ||
} | ||
}) | ||
``` | ||
--- | ||
## Passing components to `m()` | ||
In `v0.2.x` you could pass components as the second argument of `m()` w/o any wrapping required. To help with consistency in `v1.x` they must always be wrapped with a `m()` invocation. | ||
### `v0.2.x` | ||
```javascript | ||
m("div", component) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
m("div", m(component)) | ||
``` | ||
--- | ||
## Passing vnodes to `m.mount()` and `m.route()` | ||
In `v0.2.x`, `m.mount(element, component)` tolerated [vnodes](vnodes.md) as second arguments instead of [components](components.md) (even though it wasn't documented). Likewise, `m.route(element, defaultRoute, routes)` accepted vnodes as values in the `routes` object. | ||
In `v1.x`, components are required instead in both cases. | ||
### `v0.2.x` | ||
```javascript | ||
m.mount(element, m('i', 'hello')) | ||
m.mount(element, m(Component, attrs)) | ||
m.route(element, '/', { | ||
'/': m('b', 'bye') | ||
}) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
m.mount(element, {view: function () {return m('i', 'hello')}}) | ||
m.mount(element, {view: function () {return m(Component, attrs)}}) | ||
m.route(element, '/', { | ||
'/': {view: function () {return m('b', 'bye')}} | ||
}) | ||
``` | ||
--- | ||
## `m.route.mode` | ||
In `v0.2.x` the routing mode could be set by assigning a string of `"pathname"`, `"hash"`, or `"search"` to `m.route.mode`. In `v.1.x` it is replaced by `m.route.prefix(prefix)` where `prefix` can be `#`, `?`, or an empty string (for "pathname" mode). The new API also supports hashbang (`#!`), which is the default, and it supports non-root pathnames and arbitrary mode variations such as querybang (`?!`) | ||
### `v0.2.x` | ||
```javascript | ||
m.route.mode = "pathname" | ||
m.route.mode = "search" | ||
``` | ||
### `v1.x` | ||
```javascript | ||
m.route.prefix("") | ||
m.route.prefix("?") | ||
``` | ||
--- | ||
## `m.route()` and anchor tags | ||
Handling clicks on anchor tags via the mithril router is similar to `v0.2.x` but uses a new lifecycle method and API. | ||
### `v0.2.x` | ||
```javascript | ||
// When clicked this link will load the "/path" route instead of navigating | ||
m("a", { | ||
href : "/path", | ||
config : m.route | ||
}) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
// When clicked this link will load the "/path" route instead of navigating | ||
m("a", { | ||
href : "/path", | ||
oncreate : m.route.link | ||
}) | ||
``` | ||
--- | ||
## Reading/writing the current route | ||
In `v0.2.x` all interaction w/ the current route happened via `m.route()`. In `v1.x` this has been broken out into two functions. | ||
### `v0.2.x` | ||
```javascript | ||
// Getting the current route | ||
m.route() | ||
// Setting a new route | ||
m.route("/other/route") | ||
``` | ||
### `v1.x` | ||
```javascript | ||
// Getting the current route | ||
m.route.get() | ||
// Setting a new route | ||
m.route.set("/other/route") | ||
``` | ||
--- | ||
## Accessing route params | ||
In `v0.2.x` reading route params was entirely handled through `m.route.param()`. This API is still available in `v1.x`, and additionally any route params are passed as properties in the `attrs` object on the vnode. | ||
### `v0.2.x` | ||
```javascript | ||
m.route(document.body, "/booga", { | ||
"/:attr" : { | ||
controller : function() { | ||
m.route.param("attr") // "booga" | ||
}, | ||
view : function() { | ||
m.route.param("attr") // "booga" | ||
} | ||
} | ||
}) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
m.route(document.body, "/booga", { | ||
"/:attr" : { | ||
oninit : function(vnode) { | ||
vnode.attrs.attr // "booga" | ||
m.route.param("attr") // "booga" | ||
}, | ||
view : function(vnode) { | ||
vnode.attrs.attr // "booga" | ||
m.route.param("attr") // "booga" | ||
} | ||
} | ||
}) | ||
``` | ||
--- | ||
## Building/Parsing query strings | ||
`v0.2.x` used methods hanging off of `m.route`, `m.route.buildQueryString()` and `m.route.parseQueryString()`. In `v1.x` these have been broken out and attached to the root `m`. | ||
### `v0.2.x` | ||
```javascript | ||
var qs = m.route.buildQueryString({ a : 1 }); | ||
var obj = m.route.parseQueryString("a=1"); | ||
``` | ||
### `v1.x` | ||
```javascript | ||
var qs = m.buildQueryString({ a : 1 }); | ||
var obj = m.parseQueryString("a=1"); | ||
``` | ||
--- | ||
## Preventing unmounting | ||
It is no longer possible to prevent unmounting via `onunload`'s `e.preventDefault()`. Instead you should explicitly call `m.route.set` when the expected conditions are met. | ||
### `v0.2.x` | ||
```javascript | ||
var Component = { | ||
controller: function() { | ||
this.onunload = function(e) { | ||
if (condition) e.preventDefault() | ||
} | ||
}, | ||
view: function() { | ||
return m("a[href=/]", {config: m.route}) | ||
} | ||
} | ||
``` | ||
### `v1.x` | ||
```javascript | ||
var Component = { | ||
view: function() { | ||
return m("a", {onclick: function() {if (!condition) m.route.set("/")}}) | ||
} | ||
} | ||
``` | ||
--- | ||
## Run code on component removal | ||
Components no longer call `this.onunload` when they are being removed. They now use the standardized lifecycle hook `onremove`. | ||
### `v0.2.x` | ||
```javascript | ||
var Component = { | ||
controller: function() { | ||
this.onunload = function(e) { | ||
// ... | ||
} | ||
}, | ||
view: function() { | ||
// ... | ||
} | ||
} | ||
``` | ||
### `v1.x` | ||
```javascript | ||
var Component = { | ||
onremove : function() { | ||
// ... | ||
} | ||
view: function() { | ||
// ... | ||
} | ||
} | ||
``` | ||
--- | ||
## m.request | ||
Promises returned by [m.request](request.md) are no longer `m.prop` getter-setters. In addition, `initialValue`, `unwrapSuccess` and `unwrapError` are no longer supported options. | ||
In addition, requests no longer have `m.startComputation`/`m.endComputation` semantics. Instead, redraws are always triggered when a request promise chain completes (unless `background:true` is set). | ||
### `v0.2.x` | ||
```javascript | ||
var data = m.request({ | ||
method: "GET", | ||
url: "https://api.github.com/", | ||
initialValue: [], | ||
}) | ||
setTimeout(function() { | ||
console.log(data()) | ||
}, 1000) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
var data = [] | ||
m.request({ | ||
method: "GET", | ||
url: "https://api.github.com/", | ||
}) | ||
.then(function (responseBody) { | ||
data = responseBody | ||
}) | ||
setTimeout(function() { | ||
console.log(data) // note: not a getter-setter | ||
}, 1000) | ||
``` | ||
Additionally, if the `extract` option is passed to `m.request` the return value of the provided function will be used directly to resolve the request promise, and the `deserialize` callback is ignored. | ||
--- | ||
## `m.deferred` removed | ||
`v0.2.x` used its own custom asynchronous contract object, exposed as `m.deferred`, which was used as the basis for `m.request`. `v1.x` uses Promises instead, and implements a [polyfill](promises.md) in non-supporting environments. In situations where you would have used `m.deferred`, you should use Promises instead. | ||
### `v0.2.x` | ||
```javascript | ||
var greetAsync = function() { | ||
var deferred = m.deferred() | ||
setTimeout(function() { | ||
deferred.resolve("hello") | ||
}, 1000) | ||
return deferred.promise | ||
} | ||
greetAsync() | ||
.then(function(value) {return value + " world"}) | ||
.then(function(value) {console.log(value)}) //logs "hello world" after 1 second | ||
``` | ||
### `v1.x` | ||
```javascript | ||
var greetAsync = function() { | ||
return new Promise(function(resolve){ | ||
setTimeout(function() { | ||
resolve("hello") | ||
}, 1000) | ||
}) | ||
} | ||
greetAsync() | ||
.then(function(value) {return value + " world"}) | ||
.then(function(value) {console.log(value)}) //logs "hello world" after 1 second | ||
``` | ||
--- | ||
## `m.sync` removed | ||
Since `v1.x` uses standards-compliant Promises, `m.sync` is redundant. Use `Promise.all` instead. | ||
### `v0.2.x` | ||
```javascript | ||
m.sync([ | ||
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }), | ||
m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }), | ||
]) | ||
.then(function (users) { | ||
console.log("Contributors:", users[0].name, "and", users[1].name) | ||
}) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
Promise.all([ | ||
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }), | ||
m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }), | ||
]) | ||
.then(function (users) { | ||
console.log("Contributors:", users[0].name, "and", users[1].name) | ||
}) | ||
``` | ||
--- | ||
## `xlink` namespace required | ||
In `v0.2.x`, the `xlink` namespace was the only supported attribute namespace, and it was supported via special casing behavior. Now namespace parsing is fully supported, and namespaced attributes should explicitly declare their namespace. | ||
### `v0.2.x` | ||
```javascript | ||
m("svg", | ||
// the `href` attribute is namespaced automatically | ||
m("image[href='image.gif']") | ||
) | ||
``` | ||
### `v1.x` | ||
```javascript | ||
m("svg", | ||
// User-specified namespace on the `href` attribute | ||
m("image[xlink:href='image.gif']") | ||
) | ||
``` | ||
--- | ||
## Nested arrays in views | ||
Arrays now represent [fragments](fragment.md), which are structurally significant in v1.x virtual DOM. Whereas nested arrays in v0.2.x would be flattened into one continuous list of virtual nodes for the purposes of diffing, v1.x preserves the array structure - the children of any given array are not considered siblings of those of adjacent arrays. | ||
--- | ||
## `vnode` equality checks | ||
If a vnode is strictly equal to the vnode occupying its place in the last draw, v1.x will skip that part of the tree without checking for mutations or triggering any lifecycle methods in the subtree. The component documentation contains [more detail on this issue](components.md#avoid-creating-component-instances-outside-views). |
@@ -71,5 +71,5 @@ # Contributor Covenant Code of Conduct | ||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, | ||
available at [http://contributor-covenant.org/version/1/4][version] | ||
available at [https://contributor-covenant.org/version/1/4][version] | ||
[homepage]: http://contributor-covenant.org | ||
[version]: http://contributor-covenant.org/version/1/4/ | ||
[homepage]: https://contributor-covenant.org | ||
[version]: https://contributor-covenant.org/version/1/4/ |
@@ -9,4 +9,5 @@ # Components | ||
- [POJO component state](#pojo-component-state) | ||
- [ES6 Classes](#es6-classes) | ||
- [Classes](#classes) | ||
- [Class component state](#class-component-state) | ||
- [Special attributes](#special-attributes) | ||
- [Avoid anti-patterns](#avoid-anti-patterns) | ||
@@ -119,3 +120,3 @@ | ||
#### Closure Component State | ||
#### Closure component state | ||
@@ -188,3 +189,3 @@ In the above examples, each component is defined as a POJO (Plain Old JavaScript Object), which is used by Mithril internally as the prototype for that component's instances. It's possible to use component state with a POJO (as we'll discuss below), but it's not the cleanest or simplest approach. For that we'll use a **_closure component_**, which is simply a wrapper function which _returns_ a POJO component instance, which in turn carries its own, closed-over scope. | ||
#### POJO Component State | ||
#### POJO component state | ||
@@ -253,20 +254,20 @@ It is generally recommended that you use closures for managing component state. If, however, you have reason to manage state in a POJO, the state of a component can be accessed in three ways: as a blueprint at initialization, via `vnode.state` and via the `this` keyword in component methods. | ||
Be aware that when using ES5 functions, the value of `this` in nested anonymous functions is not the component instance. There are two recommended ways to get around this JavaScript limitation, use ES6 arrow functions, or if ES6 is not available, use `vnode.state`. | ||
Be aware that when using ES5 functions, the value of `this` in nested anonymous functions is not the component instance. There are two recommended ways to get around this JavaScript limitation, use arrow functions, or if those are not supported, use `vnode.state`. | ||
--- | ||
### ES6 classes | ||
### Classes | ||
If it suits your needs (like in object-oriented projects), components can also be written using ES6 class syntax: | ||
If it suits your needs (like in object-oriented projects), components can also be written using classes: | ||
```javascript | ||
class ES6ClassComponent { | ||
class ClassComponent { | ||
constructor(vnode) { | ||
this.kind = "ES6 class" | ||
this.kind = "class component" | ||
} | ||
view() { | ||
return m("div", `Hello from an ${this.kind}`) | ||
return m("div", `Hello from a ${this.kind}`) | ||
} | ||
oncreate() { | ||
console.log(`A ${this.kind} component was created`) | ||
console.log(`A ${this.kind} was created`) | ||
} | ||
@@ -276,3 +277,3 @@ } | ||
Component classes must define a `view()` method, detected via `.prototype.view`, to get the tree to render. | ||
Class components must define a `view()` method, detected via `.prototype.view`, to get the tree to render. | ||
@@ -283,17 +284,17 @@ They can be consumed in the same way regular components can. | ||
// EXAMPLE: via m.render | ||
m.render(document.body, m(ES6ClassComponent)) | ||
m.render(document.body, m(ClassComponent)) | ||
// EXAMPLE: via m.mount | ||
m.mount(document.body, ES6ClassComponent) | ||
m.mount(document.body, ClassComponent) | ||
// EXAMPLE: via m.route | ||
m.route(document.body, "/", { | ||
"/": ES6ClassComponent | ||
"/": ClassComponent | ||
}) | ||
// EXAMPLE: component composition | ||
class AnotherES6ClassComponent { | ||
class AnotherClassComponent { | ||
view() { | ||
return m("main", [ | ||
m(ES6ClassComponent) | ||
m(ClassComponent) | ||
]) | ||
@@ -304,3 +305,3 @@ } | ||
#### Class Component State | ||
#### Class component state | ||
@@ -334,3 +335,3 @@ With classes, state can be managed by class instance properties and methods, and accessed via `this`: | ||
Note that we must wrap the event callbacks in arrow functions so that the `this` context is preserved correctly. | ||
Note that we must use arrow functions for the event handler callbacks so the `this` context can be referenced correctly. | ||
@@ -343,3 +344,12 @@ --- | ||
--- | ||
### Special attributes | ||
Mithril places special semantics on several property keys, so you should normally avoid using them in normal component attributes. | ||
- [Lifecycle methods](lifecycle-methods.md): `oninit`, `oncreate`, `onbeforeupdate`, `onupdate`, `onbeforeremove`, and `onremove` | ||
- `key`, which is used to track identity in keyed fragments | ||
- `tag`, which is used to tell vnodes apart from normal attributes objects and other things that are non-vnode objects. | ||
--- | ||
@@ -346,0 +356,0 @@ |
@@ -24,4 +24,4 @@ # Credits | ||
- Arthur Clemens - creator of [Polythene](https://github.com/ArthurClemens/Polythene) and the [HTML-to-Mithril converter](http://arthurclemens.github.io/mithril-template-converter/index.html) | ||
- Arthur Clemens - creator of [Polythene](https://github.com/ArthurClemens/Polythene) and the [HTML-to-Mithril converter](https://arthurclemens.github.io/mithril-template-converter/index.html) | ||
- Stephan Hoyer - creator of [mithril-node-render](https://github.com/StephanHoyer/mithril-node-render), [mithril-query](https://github.com/StephanHoyer/mithril-query) and [mithril-source-hint](https://github.com/StephanHoyer/mithril-source-hint) | ||
- the countless people who have reported and fixed bugs, participated in discussions, and helped promote Mithril |
115
docs/es6.md
@@ -1,2 +0,2 @@ | ||
# ES6 | ||
# ES6+ on legacy browsers | ||
@@ -8,20 +8,35 @@ - [Setup](#setup) | ||
Mithril is written in ES5, and is fully compatible with ES6 as well. ES6 is a recent update to JavaScript that introduces new syntax sugar for various common cases. It's not yet fully supported by all major browsers and it's not a requirement for writing an application, but it may be pleasing to use depending on your team's preferences. | ||
Mithril is written in ES5, but it's fully compatible with ES6 and later as well. All modern browsers do support it natively, up to and even including native module syntax. (They don't support Node's magic module resolution, so you can't use `import * as _ from "lodash-es"` or similar. They just support relative and URL paths.) And so you can feel free to use [arrow functions for your closure components and classes for your class components](components.md). | ||
In some limited environments, it's possible to use a significant subset of ES6 directly without extra tooling (for example, in internal applications that do not support IE). However, for the vast majority of use cases, a compiler toolchain like [Babel](https://babeljs.io) is required to compile ES6 features down to ES5. | ||
But, if like many of us, you still need to support older browsers like Internet Explorer, you'll need to transpile that down to ES5, and this is what this page is all about, using [Babel](https://babeljs.io) to make modern ES6+ code work on older browsers. | ||
--- | ||
### Setup | ||
The simplest way to setup an ES6 compilation toolchain is via [Babel](https://babeljs.io/). | ||
First, if you haven't already, make sure you have [Node](https://nodejs.org/en/) installed. It comes with [npm](https://www.npmjs.com/) pre-bundled, something we'll need soon. | ||
Babel requires NPM, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once NPM is installed, create a project folder and run this command: | ||
Once you've got that downloaded, open a terminal and run these commands: | ||
```bash | ||
npm init -y | ||
# Replace this with the actual path to your project. Quote it if it has spaces, | ||
# and single-quote it if you're on Linux/Mac and it contains a `$` anywhere. | ||
cd "/path/to/your/project" | ||
# If you have a `package.json` there already, skip this command. | ||
npm init | ||
``` | ||
If you want to use Webpack and Babel together, [skip to the section below](#using-babel-with-webpack). | ||
Now, you can go one of a couple different routes: | ||
To install Babel as a standalone tool, use this command: | ||
- [Use Babel standalone, with no bundler at all](#using-babel-standalone) | ||
- [Use Babel and bundle with Webpack](#using-babel-with-webpack) | ||
#### Using Babel standalone | ||
First, we need to install a couple dependencies we need. | ||
- `@babel/cli` installs the core Babel logic as well as the `babel` command. | ||
- `@babel/preset-env` helps Babel know what to transpile and how to transpile them. | ||
```bash | ||
@@ -31,34 +46,65 @@ npm install @babel/cli @babel/preset-env --save-dev | ||
Create a `.babelrc` file: | ||
Now, create a `.babelrc` file and set up with `@babel/preset-env`. | ||
```json | ||
{ | ||
"presets": ["@babel/preset-env"] | ||
"presets": ["@babel/preset-env"], | ||
"sourceMaps": true | ||
} | ||
``` | ||
To run Babel as a standalone tool, run this from the command line: | ||
And finally, if you have *very* specific requirements on what you need to support, you may want to [configure Browserslist](https://github.com/browserslist/browserslist) so Babel (and other libraries) know what features to target. | ||
*By default, if you don't configure anything, Browserslist uses a fairly sensible query: `> 0.5%, last 2 versions, Firefox ESR, not dead`. Unless you have very specific circumstances that require you to change this, like if you need to support IE 8 with a lot of polyfills, don't bother with this step.* | ||
Whenever you want to compile your project, run this command, and everything will be compiled. | ||
```bash | ||
babel src --out-dir bin --source-maps | ||
babel src --out-dir dist | ||
``` | ||
You may find it convenient to use an npm script so you're not having to remember this and typing it out every time. Add a `"build"` field to the `"scripts"` object in your `package.json`: | ||
```json | ||
{ | ||
"scripts": { | ||
"build": "babel src --out-dir dist" | ||
} | ||
} | ||
``` | ||
And now, the command is a little easier to type and remember. | ||
```bash | ||
npm run build | ||
``` | ||
#### Using Babel with Webpack | ||
If you're already using Webpack as a bundler, you can integrate Babel to Webpack by following these steps. | ||
If you want to use Webpack to bundle, it's a few more steps to set up. First, we need to install all the dependencies we need for both Babel and Webpack. | ||
- `webpack` is the core Webpack code and `webpack-cli` gives you the `webpack` command. | ||
- `@babel/core` is the core Babel code, a peer dependency for `babel-loader`. | ||
- `babel-loader` lets you teach Webpack how to use Babel to transpile your files. | ||
- `@babel/preset-env` helps Babel know what to transpile and how to transpile them. | ||
```bash | ||
npm install @babel/core babel-loader @babel/preset-env --save-dev | ||
npm install webpack webpack-cli @babel/core babel-loader @babel/preset-env --save-dev | ||
``` | ||
Create a `.babelrc` file: | ||
Now, create a `.babelrc` file and set up with `@babel/preset-env`. | ||
```json | ||
{ | ||
"presets": ["@babel/preset-env"] | ||
"presets": ["@babel/preset-env"], | ||
"sourceMaps": true | ||
} | ||
``` | ||
Next, create a file called `webpack.config.js` | ||
Next, if you have *very* specific requirements on what you need to support, you may want to [configure Browserslist](https://github.com/browserslist/browserslist) so Babel (and other libraries) know what features to target. | ||
*By default, if you don't configure anything, Browserslist uses a fairly sensible query: `> 0.5%, last 2 versions, Firefox ESR, not dead`. Unless you have very specific circumstances that require you to change this, like if you need to support IE 8 with a lot of polyfills, don't bother with this step.* | ||
And finally, set up Webpack by creating a file called `webpack.config.js`. | ||
```javascript | ||
@@ -68,5 +114,5 @@ const path = require('path') | ||
module.exports = { | ||
entry: './src/index.js', | ||
entry: path.resolve(__dirname, 'src/index.js'), | ||
output: { | ||
path: path.resolve(__dirname, './bin'), | ||
path: path.resolve(__dirname, 'dist'), | ||
filename: 'app.js', | ||
@@ -84,9 +130,14 @@ }, | ||
This configuration assumes the source code file for the application entry point is in `src/index.js`, and this will output the bundle to `bin/app.js`. | ||
This configuration assumes the source code file for the application entry point is in `src/index.js`, and this will output the bundle to `dist/app.js`. | ||
To run the bundler, setup an npm script. Open `package.json` and add this entry under `"scripts"`: | ||
Now, to run the bundler, you just run this command: | ||
```bash | ||
webpack -d --watch | ||
``` | ||
You may find it convenient to use an npm script so you're not having to remember this and typing it out every time. Add a `"build"` field to the `"scripts"` object in your `package.json`: | ||
```json | ||
{ | ||
"name": "my-project", | ||
"scripts": { | ||
@@ -98,3 +149,3 @@ "start": "webpack -d --watch" | ||
You can now then run the bundler by running this from the command line: | ||
And now, the command is a little easier to type and remember. | ||
@@ -105,9 +156,12 @@ ```bash | ||
#### Production build | ||
For production builds, you'll want to minify your scripts. Luckily, this is also pretty easy: it's just running Webpack with a different option. | ||
To generate a minified file, open `package.json` and add a new npm script called `build`: | ||
```bash | ||
webpack -p | ||
``` | ||
You may want to also add this to your npm scripts, so you can build it quickly and easily. | ||
```json | ||
{ | ||
"name": "my-project", | ||
"scripts": { | ||
@@ -120,7 +174,12 @@ "start": "webpack -d --watch", | ||
You can use hooks in your production environment to run the production build script automatically. Here's an example for [Heroku](https://www.heroku.com/): | ||
And then running this is a little easier to remember. | ||
```bash | ||
npm run build | ||
``` | ||
And of course, you can do this in automatic production build scripts, too. Here's how it might look if you're using [Heroku](https://www.heroku.com/), for example: | ||
```json | ||
{ | ||
"name": "my-project", | ||
"scripts": { | ||
@@ -127,0 +186,0 @@ "start": "webpack -d --watch", |
@@ -5,9 +5,8 @@ # Examples | ||
- [Animation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/animation/mosaic.html) | ||
- [Animation](https://raw.githack.com/MithrilJS/mithril.js/master/examples/animation/mosaic.html) | ||
- [Community Added Examples](https://how-to-mithril.js.org) | ||
- [DBMonster](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html) | ||
- [Markdown Editor](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/editor/index.html) | ||
- SVG: [Clock](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/clock.html), [Ring](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/ring.html), [Tiger](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/tiger.html) | ||
- [ThreadItJS](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/threaditjs/index.html) | ||
- [TodoMVC](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/todomvc/index.html) | ||
- [DBMonster](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html) | ||
- [Markdown Editor](https://raw.githack.com/MithrilJS/mithril.js/master/examples/editor/index.html) | ||
- SVG: [Clock](https://raw.githack.com/MithrilJS/mithril.js/master/examples/svg/clock.html), [Ring](https://raw.githack.com/MithrilJS/mithril.js/master/examples/svg/ring.html), [Tiger](https://raw.githack.com/MithrilJS/mithril.js/master/examples/svg/tiger.html) | ||
- [ThreadItJS](https://raw.githack.com/MithrilJS/mithril.js/master/examples/threaditjs/index.html) | ||
- [TodoMVC](https://raw.githack.com/MithrilJS/mithril.js/master/examples/todomvc/index.html) |
@@ -77,3 +77,3 @@ # Framework comparison | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Sample results are shown below: | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/react/index.html) and a [Mithril implementation](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Sample results are shown below: | ||
@@ -143,3 +143,3 @@ React | Mithril | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/angular/index.html) and a [Mithril implementation](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: | ||
@@ -198,3 +198,3 @@ Angular | Mithril | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [Vue implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [Vue implementation](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/vue/index.html) and a [Mithril implementation](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: | ||
@@ -201,0 +201,0 @@ Vue | Mithril |
@@ -125,4 +125,4 @@ # m(selector, attributes, children) | ||
m("a#exit.external[href='http://example.com']", "Leave") | ||
// <a id="exit" class="external" href="http://example.com">Leave</a> | ||
m("a#exit.external[href='https://example.com']", "Leave") | ||
// <a id="exit" class="external" href="https://example.com">Leave</a> | ||
``` | ||
@@ -219,3 +219,3 @@ | ||
And yes, this translates to both attributes and properties, and it works just like they would in the DOM. Using [Brick's `brick-deck`](http://brick.mozilla.io/docs/brick-deck) as an example, they have a `selected-index` attribute with a corresponding `selectedIndex` getter/setter property. | ||
And yes, this translates to both attributes and properties, and it works just like they would in the DOM. Using [Brick's `brick-deck`](https://brick.mozilla.io/docs/brick-deck) as an example, they have a `selected-index` attribute with a corresponding `selectedIndex` getter/setter property. | ||
@@ -237,3 +237,3 @@ ```javascript | ||
"https://example.com", | ||
"http://neverssl.com", | ||
"https://neverssl.com", | ||
"https://google.com", | ||
@@ -444,3 +444,3 @@ ], | ||
// ES6: | ||
// ES6+: | ||
// m("ul", users.map(u => | ||
@@ -469,3 +469,3 @@ // m("li", u.name) | ||
When using hyperscript, it's necessary to convert HTML to hyperscript syntax before the code can be run. To facilitate this, you can [use the HTML-to-Mithril-template converter](http://arthurclemens.github.io/mithril-template-converter/index.html). | ||
When using hyperscript, it's necessary to convert HTML to hyperscript syntax before the code can be run. To facilitate this, you can [use the HTML-to-Mithril-template converter](https://arthurclemens.github.io/mithril-template-converter/index.html). | ||
@@ -472,0 +472,0 @@ --- |
@@ -59,5 +59,5 @@ # Introduction | ||
```markup | ||
```html | ||
<body> | ||
<script src="https://unpkg.com/mithril@next/mithril.js"></script> | ||
<script src="https://unpkg.com/mithril/mithril.js"></script> | ||
<script> | ||
@@ -73,3 +73,3 @@ var root = document.body | ||
<p data-height="265" data-theme-id="light" data-slug-hash="XRrXVR" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Scaffold" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/XRrXVR/">Mithril Scaffold</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p> | ||
<p data-height="265" data-theme-id="light" data-slug-hash="XRrXVR" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Scaffold" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/XRrXVR/">Mithril Scaffold</a> by Pat Cavit (<a href="https://codepen.io/tivac">@tivac</a>) on <a href="https://codepen.io">CodePen</a>.</p> | ||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script> | ||
@@ -101,3 +101,3 @@ | ||
<p data-height="265" data-theme-id="light" data-slug-hash="KmPdOO" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Hello World" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/KmPdOO/">Mithril Hello World</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p> | ||
<p data-height="265" data-theme-id="light" data-slug-hash="KmPdOO" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Hello World" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/KmPdOO/">Mithril Hello World</a> by Pat Cavit (<a href="https://codepen.io/tivac">@tivac</a>) on <a href="https://codepen.io">CodePen</a>.</p> | ||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script> | ||
@@ -141,3 +141,3 @@ | ||
<p data-height="275" data-theme-id="light" data-slug-hash="gWYade" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Simple Mithril Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/gWYade/">Simple Mithril Example</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p> | ||
<p data-height="275" data-theme-id="light" data-slug-hash="gWYade" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Simple Mithril Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/gWYade/">Simple Mithril Example</a> by Pat Cavit (<a href="https://codepen.io/tivac">@tivac</a>) on <a href="https://codepen.io">CodePen</a>.</p> | ||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script> | ||
@@ -180,3 +180,3 @@ | ||
```markup | ||
```html | ||
<main> | ||
@@ -214,3 +214,3 @@ <h1 class="title">My first app</h1> | ||
<p data-height="300" data-theme-id="light" data-slug-hash="rmBOQV" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Component Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/rmBOQV/">Mithril Component Example</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p> | ||
<p data-height="300" data-theme-id="light" data-slug-hash="rmBOQV" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Component Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/rmBOQV/">Mithril Component Example</a> by Pat Cavit (<a href="https://codepen.io/tivac">@tivac</a>) on <a href="https://codepen.io">CodePen</a>.</p> | ||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script> | ||
@@ -247,9 +247,9 @@ | ||
The `"/splash"` right after `root` means that's the default route, i.e. if the hashbang in the URL doesn't point to one of the defined routes (`/splash` and `/hello`, in our case), then Mithril redirects to the default route. So if you open the page in a browser and your URL is `http://localhost`, then you get redirected to `http://localhost/#!/splash`. | ||
The `"/splash"` right after `root` means that's the default route, i.e. if the hashbang in the URL doesn't point to one of the defined routes (`/splash` and `/hello`, in our case), then Mithril redirects to the default route. So if you open the page in a browser and your URL is `https://localhost`, then you get redirected to `https://localhost/#!/splash`. | ||
Also, as you would expect, clicking on the link on the splash page takes you to the click counter screen we created earlier. Notice that now your URL will point to `http://localhost/#!/hello`. You can navigate back and forth to the splash page using the browser's back and next button. | ||
Also, as you would expect, clicking on the link on the splash page takes you to the click counter screen we created earlier. Notice that now your URL will point to `https://localhost/#!/hello`. You can navigate back and forth to the splash page using the browser's back and next button. | ||
#### Live Example | ||
<p data-height="300" data-theme-id="light" data-slug-hash="qmWOvr" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Routing Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/qmWOvr/">Mithril Routing Example</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p> | ||
<p data-height="300" data-theme-id="light" data-slug-hash="qmWOvr" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Routing Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/qmWOvr/">Mithril Routing Example</a> by Pat Cavit (<a href="https://codepen.io/tivac">@tivac</a>) on <a href="https://codepen.io">CodePen</a>.</p> | ||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script> | ||
@@ -263,3 +263,3 @@ | ||
Let's change our click counter to make it save data on a server. For the server, we'll use [REM](http://rem-rest-api.herokuapp.com), a mock REST API designed for toy apps like this tutorial. | ||
Let's change our click counter to make it save data on a server. For the server, we'll use [REM](https://rem-rest-api.herokuapp.com), a mock REST API designed for toy apps like this tutorial. | ||
@@ -302,3 +302,3 @@ First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `body` is the payload that we're sending to the endpoint and `withCredentials` means to enable cookies (a requirement for the REM API to work) | ||
<p data-height="265" data-theme-id="light" data-slug-hash="WjeQBW" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril XHR Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/WjeQBW/">Mithril XHR Example</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p> | ||
<p data-height="265" data-theme-id="light" data-slug-hash="WjeQBW" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril XHR Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/WjeQBW/">Mithril XHR Example</a> by Pat Cavit (<a href="https://codepen.io/tivac">@tivac</a>) on <a href="https://codepen.io">CodePen</a>.</p> | ||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script> | ||
@@ -305,0 +305,0 @@ |
# Installation | ||
- [CDN](#cdn) | ||
- [NPM](#npm) | ||
- [npm](#npm) | ||
- [Quick start with Webpack](#quick-start-with-webpack) | ||
@@ -12,4 +12,4 @@ - [TypeScript](#typescript) | ||
```markup | ||
<script src="https://unpkg.com/mithril@next/mithril.js"></script> | ||
```html | ||
<script src="https://unpkg.com/mithril/mithril.js"></script> | ||
``` | ||
@@ -19,6 +19,6 @@ | ||
### NPM | ||
### npm | ||
```bash | ||
$ npm install mithril@next --save | ||
$ npm install mithril --save | ||
``` | ||
@@ -51,3 +51,3 @@ | ||
```bash | ||
$ npm install mithril@next --save | ||
$ npm install mithril --save | ||
$ npm install webpack webpack-cli --save-dev | ||
@@ -89,7 +89,7 @@ ``` | ||
For production-level projects, the recommended way of installing Mithril is to use NPM. | ||
For production-level projects, the recommended way of installing Mithril is to use npm. | ||
NPM (Node package manager) is the default package manager that is bundled w/ Node.js. It is widely used as the package manager for both client-side and server-side libraries in the JavaScript ecosystem. Download and install [Node.js](https://nodejs.org); NPM will be automatically installed as well. | ||
npm is the default package manager that is bundled w/ Node.js. It is widely used as the package manager for both client-side and server-side libraries in the JavaScript ecosystem. Download and install [Node](https://nodejs.org); npm is bundled with that and installed alongside it. | ||
To use Mithril via NPM, go to your project folder, and run `npm init --yes` from the command line. This will create a file called `package.json`. | ||
To use Mithril via npm, go to your project folder, and run `npm init --yes` from the command line. This will create a file called `package.json`. | ||
@@ -104,3 +104,3 @@ ```bash | ||
```bash | ||
npm install mithril@next --save | ||
npm install mithril --save | ||
``` | ||
@@ -121,7 +121,7 @@ | ||
CommonJS is a de-facto standard for modularizing JavaScript code, and it's used by Node.js, as well as tools like [Browserify](http://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](http://rollupjs.org/) or [Babel](https://babeljs.io/). | ||
CommonJS is a de-facto standard for modularizing JavaScript code, and it's used by Node.js, as well as tools like [Browserify](https://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](https://rollupjs.org/) or [Babel](https://babeljs.io/). | ||
Most browser today do not natively support modularization systems (CommonJS or ES6), so modularized code must be bundled into a single JavaScript file before running in a client-side application. | ||
A popular way for creating a bundle is to setup an NPM script for [Webpack](https://webpack.js.org/). To install Webpack, run this from the command line: | ||
A popular way for creating a bundle is to setup an npm script for [Webpack](https://webpack.js.org/). To install Webpack, run this from the command line: | ||
@@ -149,3 +149,3 @@ ```bash | ||
Now you can run the script via `npm start` in your command line window. This looks up the `webpack` command in the NPM path, reads `index.js` and creates a file called `app.js` which includes both Mithril and the `hello world` code above. If you want to run the `webpack` command directly from the command line, you need to either add `node_modules/.bin` to your PATH, or install webpack globally via `npm install webpack -g`. It's, however, recommended that you always install webpack locally and use npm scripts, to ensure builds are reproducible in different computers. | ||
Now you can run the script via `npm start` in your command line window. This looks up the `webpack` command in the npm path, reads `index.js` and creates a file called `app.js` which includes both Mithril and the `hello world` code above. If you want to run the `webpack` command directly from the command line, you need to either add `node_modules/.bin` to your PATH, or install webpack globally via `npm install webpack -g`. It's, however, recommended that you always install webpack locally and use npm scripts, to ensure builds are reproducible in different computers. | ||
@@ -158,3 +158,3 @@ ``` | ||
```markup | ||
```html | ||
<html> | ||
@@ -170,3 +170,3 @@ <head> | ||
As you've seen above, importing a module in CommonJS is done via the `require` function. You can reference NPM modules by their library names (e.g. `require("mithril")` or `require("jquery")`), and you can reference your own modules via relative paths minus the file extension (e.g. if you have a file called `mycomponent.js` in the same folder as the file you're importing to, you can import it by calling `require("./mycomponent")`). | ||
As you've seen above, importing a module in CommonJS is done via the `require` function. You can reference npm modules by their library names (e.g. `require("mithril")` or `require("jquery")`), and you can reference your own modules via relative paths minus the file extension (e.g. if you have a file called `mycomponent.js` in the same folder as the file you're importing to, you can import it by calling `require("./mycomponent")`). | ||
@@ -199,3 +199,3 @@ To export a module, assign what you want to export to the special `module.exports` object: | ||
``` | ||
```json | ||
{ | ||
@@ -233,3 +233,3 @@ "name": "my-project", | ||
# 1) install | ||
npm install mithril@next --save | ||
npm install mithril --save | ||
npm install budo -g | ||
@@ -250,17 +250,2 @@ | ||
#### Mithril bundler | ||
Mithril comes with a bundler tool of its own. It is sufficient for ES5-based projects that have no other dependencies other than Mithril, but it's currently considered experimental for projects that require other NPM dependencies. It produces smaller bundles than webpack, but you should not use it in production yet. | ||
If you want to try it and give feedback, you can open `package.json` and change the npm script for webpack to this: | ||
``` | ||
{ | ||
"name": "my-project", | ||
"scripts": { | ||
"build": "bundle src/index.js --output bin/app.js --watch" | ||
} | ||
} | ||
``` | ||
#### Vanilla | ||
@@ -270,3 +255,3 @@ | ||
```markup | ||
```html | ||
<html> | ||
@@ -277,3 +262,3 @@ <head> | ||
<body> | ||
<script src="https://unpkg.com/mithril@next/mithril.js"></script> | ||
<script src="https://unpkg.com/mithril/mithril.js"></script> | ||
<script src="index.js"></script> | ||
@@ -280,0 +265,0 @@ </body> |
194
docs/jsx.md
@@ -13,3 +13,3 @@ # JSX | ||
JSX is a syntax extension that enables you to write HTML tags interspersed with JavaScript. It's not part of any JavaScript standards and it's not required for building applications, but it may be more pleasing to use depending on your team's preferences. | ||
JSX is a syntax extension that enables you to write HTML tags interspersed with JavaScript. It's not part of any JavaScript standards and it's not required for building applications, but it may be more pleasing to use depending on you or your team's preferences. | ||
@@ -42,5 +42,5 @@ ```jsx | ||
var greeting = "Hello" | ||
var url = "http://google.com" | ||
var url = "https://google.com" | ||
var link = <a href={url}>{greeting}!</a> | ||
// yields <a href="http://google.com">Hello!</a> | ||
// yields <a href="https://google.com">Hello!</a> | ||
``` | ||
@@ -61,3 +61,3 @@ | ||
Babel requires NPM, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once NPM is installed, create a project folder and run this command: | ||
Babel requires npm, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once npm is installed, create a project folder and run this command: | ||
@@ -191,61 +191,151 @@ ```bash | ||
JSX is essentially a trade-off: it introduces a non-standard syntax that cannot be run without appropriate tooling, in order to allow a developer to write HTML code using curly braces. The main benefit of using JSX instead of regular HTML is that the JSX specification is much stricter and yields syntax errors when appropriate, whereas HTML is far too forgiving and can make syntax issues difficult to spot. | ||
JSX and hyperscript are two different syntaxes you can use for specifying vnodes, and they have different tradeoffs: | ||
Unlike HTML, JSX is case-sensitive. This means `<div className="test"></div>` is different from `<div classname="test"></div>` (all lower case). The former compiles to `m("div", {className: "test"})` and the latter compiles to `m("div", {classname: "test"})`, which is not a valid way of creating a class attribute. Fortunately, Mithril supports standard HTML attribute names, and thus, this example can be written like regular HTML: `<div class="test"></div>`. Also, unlike HTML, JSX is based on XML, so you can do things like `<div class="test" />` as equivalent to `<div class="test"></div>`, where in HTML you can only use the second. | ||
- JSX is much more approachable if you're coming from an HTML/XML background and are more comfortable specifying DOM elements with that kind of syntax. It is also slightly cleaner in many cases since it uses fewer punctuation and the attributes contain less visual noise, so many people find it much easier to read. And of course, many common editors provide autocomplete support for DOM elements in the same way they do for HTML. However, it requires an extra build step to use, editor support isn't as broad as it is with normal JS, and it's considerably more verbose. It's also a bit more verbose when dealing with a lot of dynamic content because you have to use interpolations for everything. | ||
JSX is useful for teams where HTML is primarily written by someone without JavaScript experience, but it requires a significant amount of tooling to maintain (whereas plain HTML can, for the most part, simply be opened in a browser). | ||
- Hyperscript is more approachable if you come from a backend JS background that doesn't involve much HTML or XML. It's more concise with less redundancy, and it provides a CSS-like sugar for static classes, IDs, and other attributes. It also can be used with no build step at all, although [you can add one if you wish](https://github.com/MithrilJS/mopt). And it's slightly easier to work with in the face of a lot of dynamic content, because you don't need to "interpolate" anything. However, the terseness does make it harder to read for some people, especially those less experienced and coming from a front end HTML/CSS/XML background, and I'm not aware of any plugins that auto-complete parts of hyperscript selectors like IDs, classes, and attributes. | ||
Hyperscript is the compiled representation of JSX. It's designed to be readable and can also be used as-is, instead of JSX (as is done in most of the documentation). Hyperscript tends to be terser than JSX for a couple of reasons: | ||
You can see the tradeoffs come into play in more complex trees. For instance, consider this hyperscript tree, adapted from a real-world project by [@isiahmeadows](https://github.com/isiahmeadows/) with some alterations for clarity and readability: | ||
- it does not require repeating the tag name in closing tags when children are present (e.g. `m("div", m("span"))` vs `<div><span /></div>`) | ||
- static attributes can be written using CSS selector syntax (i.e. `m("a.button")` vs `<a class="button"></a>`) | ||
```js | ||
function SummaryView() { | ||
let tag, posts | ||
In addition, since hyperscript is plain JavaScript, it's often more natural to indent than JSX: | ||
function init({attrs}) { | ||
Model.sendView(attrs.tag != null) | ||
if (attrs.tag != null) { | ||
tag = attrs.tag.toLowerCase() | ||
posts = Model.getTag(tag) | ||
} else { | ||
tag = undefined | ||
posts = Model.posts | ||
} | ||
} | ||
```jsx | ||
//JSX | ||
function BigComponent() { | ||
function activate() { /* ... */ } | ||
function deactivate() { /* ... */ } | ||
function update() { /* ... */ } | ||
return { | ||
view: ({attrs}) => ( | ||
<> | ||
{attrs.items.map((item) => <div>{item.name}</div>)} | ||
<div | ||
ondragover={activate} | ||
ondragleave={deactivate} | ||
ondragend={deactivate} | ||
ondrop={update} | ||
onblur={deactivate} | ||
/> | ||
</> | ||
) | ||
} | ||
} | ||
function feed(type, href) { | ||
return m(".feed", [ | ||
type, | ||
m("a", {href}, m("img.feed-icon[src=./feed-icon-16.gif]")), | ||
]) | ||
} | ||
// hyperscript | ||
function BigComponent() { | ||
function activate() { /* ... */ } | ||
function deactivate() { /* ... */ } | ||
function update() { /* ... */ } | ||
return { | ||
view: ({attrs}) => [ | ||
attrs.items.map((item) => m("div", item.name)), | ||
m("div", { | ||
ondragover: this.activate, | ||
ondragleave: this.deactivate, | ||
ondragend: this.deactivate, | ||
ondrop: this.update, | ||
onblur: this.deactivate, | ||
}) | ||
] | ||
} | ||
return { | ||
oninit: init, | ||
// To ensure the tag gets properly diffed on route change. | ||
onbeforeupdate: init, | ||
view: () => | ||
m(".blog-summary", [ | ||
m("p", "My ramblings about everything"), | ||
m(".feeds", [ | ||
feed("Atom", "blog.atom.xml"), | ||
feed("RSS", "blog.rss.xml"), | ||
]), | ||
tag != null | ||
? m(TagHeader, {len: posts.length, tag}) | ||
: m(".summary-header", [ | ||
m(".summary-title", "Posts, sorted by most recent."), | ||
m(TagSearch), | ||
]), | ||
m(".blog-list", posts.map((post) => | ||
m(m.route.Link, { | ||
class: "blog-entry", | ||
href: `/posts/${post.url}`, | ||
}, [ | ||
m(".post-date", post.date.toLocaleDateString("en-US", { | ||
year: "numeric", | ||
month: "long", | ||
day: "numeric", | ||
})), | ||
m(".post-stub", [ | ||
m(".post-title", post.title), | ||
m(".post-preview", post.preview, "..."), | ||
]), | ||
m(TagList, {post, tag}), | ||
]) | ||
)), | ||
]) | ||
} | ||
} | ||
``` | ||
In non-trivial applications, it's possible for components to have more control flow and component configuration code than markup, making a JavaScript-first approach more readable than an HTML-first approach. | ||
Here's the exact equivalent of the above code, using JSX instead. You can see how the two syntaxes differ just in this bit, and what tradeoffs apply. | ||
Needless to say, since hyperscript is pure JavaScript, there's no need to run a compilation step to produce runnable code. | ||
```jsx | ||
function SummaryView() { | ||
let tag, posts | ||
function init({attrs}) { | ||
Model.sendView(attrs.tag != null) | ||
if (attrs.tag != null) { | ||
tag = attrs.tag.toLowerCase() | ||
posts = Model.getTag(tag) | ||
} else { | ||
tag = undefined | ||
posts = Model.posts | ||
} | ||
} | ||
function feed(type, href) { | ||
return ( | ||
<div class="feed"> | ||
{type} | ||
<a href={href}><img class="feed-icon" src="./feed-icon-16.gif" /></a> | ||
</div> | ||
) | ||
} | ||
return { | ||
oninit: init, | ||
// To ensure the tag gets properly diffed on route change. | ||
onbeforeupdate: init, | ||
view: () => ( | ||
<div class="blog-summary"> | ||
<p>My ramblings about everything</p> | ||
<div class="feeds"> | ||
{feed("Atom", "blog.atom.xml")} | ||
{feed("RSS", "blog.rss.xml")} | ||
</div> | ||
{tag != null | ||
? <TagHeader len={posts.length} tag={tag} /> | ||
: ( | ||
<div class="summary-header"> | ||
<div class="summary-title">Posts, sorted by most recent</div> | ||
<TagSearch /> | ||
</div> | ||
) | ||
} | ||
<div class="blog-list"> | ||
{posts.map((post) => ( | ||
<m.route.Link class="blog-entry" href={`/posts/${post.url}`}> | ||
<div class="post-date"> | ||
{post.date.toLocaleDateString("en-US", { | ||
year: "numeric", | ||
month: "long", | ||
day: "numeric", | ||
})} | ||
</div> | ||
<div class="post-stub"> | ||
<div class="post-title">{post.title}</div> | ||
<div class="post-preview">{post.preview}...</div> | ||
</div> | ||
<TagList post={post} tag={tag} /> | ||
</m.route.Link> | ||
))} | ||
</div> | ||
</div> | ||
) | ||
} | ||
} | ||
``` | ||
--- | ||
@@ -257,2 +347,2 @@ | ||
When using hyperscript, you often need to translate HTML to hyperscript syntax to use it. To help speed up this process along, you can use a [community-created HTML-to-Mithril-template converter](http://arthurclemens.github.io/mithril-template-converter/index.html) to do much of it for you. | ||
When using hyperscript, you often need to translate HTML to hyperscript syntax to use it. To help speed up this process along, you can use a [community-created HTML-to-Mithril-template converter](https://arthurclemens.github.io/mithril-template-converter/index.html) to do much of it for you. |
@@ -179,3 +179,3 @@ #!/usr/bin/env node | ||
traverseDirectory("./docs", function(pathname) { | ||
if (pathname.indexOf(".md") > -1 && !pathname.match(/change-log|node_modules/)) { | ||
if (pathname.indexOf(".md") > -1 && !pathname.match(/change-log|migration-|node_modules/)) { | ||
fs.readFile(pathname, "utf8", function(err, data) { | ||
@@ -182,0 +182,0 @@ if (err) console.log(err) |
@@ -9,4 +9,3 @@ - Getting Started | ||
- [JSX](jsx.md) | ||
- [ES6](es6.md) | ||
- [CSS](css.md) | ||
- [ES6+ on legacy browsers](es6.md) | ||
- [Animation](animation.md) | ||
@@ -31,2 +30,3 @@ - [Testing](testing.md) | ||
- [Change log/Migration](change-log.md) | ||
- [v1 Documentation](archive/v1.1.6) | ||
- [v1 Documentation](https://mithril.js.org/archive/v1.1.6/) | ||
- [v0.2 Documentation](https://mithril.js.org/archive/v0.2.5/) |
@@ -25,3 +25,3 @@ # Promise(executor) | ||
A [ES6 Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) polyfill. | ||
An [ES6 Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) polyfill. | ||
@@ -28,0 +28,0 @@ A Promise is a mechanism for working with asynchronous computations. |
@@ -23,2 +23,3 @@ # Mithril Release Processes | ||
4. Replace all existing references to `mithril@next` to `mithril` if moving from a release candidate to stable. | ||
- Note: if making an initial release candidate, don't forget to move all the playground snippets to pull from `mithril@next`! | ||
5. Commit changes to `next` | ||
@@ -25,0 +26,0 @@ |
@@ -46,3 +46,3 @@ # route(root, defaultRoute, routes) | ||
m.route(document.body, "/home", { | ||
"/home": Home, // defines `http://localhost/#!/home` | ||
"/home": Home, // defines `https://localhost/#!/home` | ||
}) | ||
@@ -136,6 +136,2 @@ ``` | ||
m(m.route.Link, {href: "/test", options: {replace: true}}) | ||
// You can even use URL templates this way, the same way you can with | ||
// `m.route.set`. | ||
m(m.route.Link, {href: "/edit/:id", options: {params: {id: item.id}}}) | ||
``` | ||
@@ -306,5 +302,5 @@ | ||
- `m.route.prefix = '#!'` (default) – Using the [fragment identifier](https://en.wikipedia.org/wiki/Fragment_identifier) (aka the hash) portion of the URL. A URL using this strategy typically looks like `http://localhost/#!/page1` | ||
- `m.route.prefix = '?'` – Using the querystring. A URL using this strategy typically looks like `http://localhost/?/page1` | ||
- `m.route.prefix = ''` – Using the pathname. A URL using this strategy typically looks like `http://localhost/page1` | ||
- `m.route.prefix = '#!'` (default) – Using the [fragment identifier](https://en.wikipedia.org/wiki/Fragment_identifier) (aka the hash) portion of the URL. A URL using this strategy typically looks like `https://localhost/#!/page1` | ||
- `m.route.prefix = '?'` – Using the querystring. A URL using this strategy typically looks like `https://localhost/?/page1` | ||
- `m.route.prefix = ''` – Using the pathname. A URL using this strategy typically looks like `https://localhost/page1` | ||
@@ -513,4 +509,4 @@ Using the hash strategy is guaranteed to work in browsers that don't support `history.pushState`, because it can fall back to using `onhashchange`. Use this strategy if you want to keep the hashes purely local. | ||
// set to pathname strategy on a non-root URL | ||
// e.g. if the app lives under `http://localhost/my-app` and something else | ||
// lives under `http://localhost` | ||
// e.g. if the app lives under `https://localhost/my-app` and something else | ||
// lives under `https://localhost` | ||
m.route.prefix = "/my-app" | ||
@@ -517,0 +513,0 @@ ``` |
# Simple application | ||
Let's develop a simple application that covers some of the major aspects of Single Page Applications | ||
Let's develop a simple application that shows off how to do most of the major things you would need to deal with while using Mithril. | ||
An interactive running example can be seen here [flems: Simple Application](https://flems.io/#0=N4IgzgpgNhDGAuEAmIBcICGAHLA6AVmCADQgBmAljEagNqgB2GAthGiAKqQBOAsgPZJoBIqVj8GiSewBuGbgAIuERQF4FwADoMFuhVAph4qBbQC6xbXv38MSADKHjCsgFcGCChIAUASg1W1nrcEPCu3DrMuCEAjq4QRt5aOkGprPAAFoImmiAA4gCiACq5limp1uFQOSAZ8PBYYKgA9M0hzAC0IUYd2BS4GSr8ANau2HjizM19za48YKWBFXoA7hSZAMIhQpIUGFBNCvDc8WXLAL6+S0G4mRAM3m4e8F4P3a5Q8P7Jy9bK3LgDEYFOp3p9cEgMPAMNdrJdrucytdYOEQpITMBEdcoLYkCYnp4fBQkN9YcFQuFItEIHEEvAkmS0qEsniFLlCiUSIyglUanUGk1Wu0unTelh+oNuCMxjhcJNpuLZvNmrkFABqBTEs6-XRrTbbe4vfaHY6nbnw8o3O4PAkvHxgr4BS3Lf5y1GGkEKB3mq6WrEMa5gDAyCD49yEh6k53ksIRBRRWLxRI-HXx5nZNkgAAKHE52p1vMz-MaLTaEE63XgYolQ1G4zl-CmMzmKjAKpA6qUPDd3DR8FwWu51kh0JMrpRvcN+d+eoyW2Qhr2BxMpog07hvrh2nOIERjBYbHQ-0cRhEJBA4kkhtk8i7KhP8E9Kd0EgoDHWY+7OLsD+nMgoEArGGzyvH4TrLCEsaRN4uS4C23AdEC8ClHeAJIbgzDYI84Z2g88FRqmXoUnGzAwZgcE8IhTgdOs5YocAGQhGQNTNMg6ztp28EDkgxAKBIsAhFCobxtE-CuIggJvsMiIKFxlDcEYAByB6dqqqoalxUAYEpB6bhUlx6bo5zbruxD7qw7D-AAYvw3BRIQ56XlI8A3oo1m2cwT7XK+77OLaoEyAwggQN8rrfkg3iBcFuBQscYDcb4-rWP+gHARGYHPkEkGUvGZFkB59FDhUEhgK4ABGzAfi4OGgSF4GERUEC4FgIQhpIAAiEBkBgHz0oZDV-N2QYhn4RWpMZ0bjbxtBjbluRaWVwgLdAKG5FZFAKY+TCsLkvjrhUpG5G+WDiQODAnfAtDwAAnlgECqIgAAe8BmLQWBabAEBZFAQjcKo62bQo20QGYhWTb8PkXSYUSzgAgvU3BkXIUDxCh-k+Mj8Shd2E59rg8k6awnqYxAlz7TqJOfioPZ4wT8DKTt4MbuTQSHSAy1QICGCLVAq0gPY2lbQeu0s9YbPHadEuXe9GCfd9v2qALwLA6DJD1QNkPidDuBwwjSP7Kjavow8JPY9TuOGlzhMQMTBuk3ts3JXbVMAhbkhW-TwtM3oZOzWzZXifAEi4AH9QSFdt33aVFXrKrvG5AAysGEAi9yZj9RNO57iAwPsAL11if2DliBIzmuQo+eF15lopUB1UgRjQVCARFTZSRZGYW+XMF+JKEzd7uhs0wMgYfcrh947ehszCauZQNjFdSYADkzRIUvosQx4gmINrUriU1BgMMMk9GfHnDzLts3pxvg9kZAEYoVFQhyhkVBIGi-XWOnCImdnufoPWYuF5S7XnQAmQuTUWpdQoI9bwS8ADES9fTgP3t4JA-AUSsHdmVQQ10z6rycGDawuQCFGFyBibkaJfppVwhlWabdoKV3ErxUix4nC+E-j7BE04SFsXgM0VAxJyHqyyvcah9d0pPzqnPVuxFGEYB7vAFh3h3J2V4lImKCMwAcPNNw7cvhTLmUPCAOUYBRDAKvNIdAOCkB4LOhdYgIdA4SA0PldEQU7L7AUAARgAGxYEegoAAaioSETAADcmFuAAHM3wmAAEwAAYAkTW0N3KuwAomxIYKgbxyTAk9SDpEjAj0OhrCQJkXJiTqkBPCRNUeDBXAaCyXExJCg2kAGZ8l1O0Gk+CVFgTACQh0Iw10YCoCCgwCAxSYmtPaT47pWA7BIDfNE1AiSekMAoioAZVZaKeWAGVWWwxol7wYHieB3UrkYHCTg7g1DvEBIUGAfgBgkAKHgUgL54TxA4m4KgeBHSgXhJWWAGW11UBlRxLAYYMzsnrPmY8x64SllfNWagAAHE87xABWWpT0qxCHENwKErwJkSGmfU-pwz9moCyCGRQwACUdCJbZUlEhUDuF+ofSlvStkcw0KC8FkLoWwpaTktpbS8XIvqVLDQdyHlPJeW8j5XykC3Nsr9LodgKBzFQB02pODSlgAoAAL3RQqnZRqQWGGFVCjBYr5DwslQs2pqKVkMDWXk7F0rwnlMqXkxJABSTZTiw46EOcc05YlzkAogPGjV9yVC5KVa84kqrvmWoQiSlZeqDXIt+bZAFQKOk2rBVpCFb4eUdHtTCuFcy2neuRe69FTafG+uZaykluFyVTNDaHIOOT6UqHlVGs5FyIAYsnZOupu4LDsykjQegOcDzsEqpkbgVBzxVHYMWQUsxzonIbFMddjEqAAAFvG4Cvb45op7N2cyATdO67AHLnDMOcIAA) | ||
*An interactive example of the end result can be seen [here](https://flems.io/#0=N4IgzgpgNhDGAuEAmIBcIB0sxhAGhADMBLGXVAbVADsBDAWwjRAFVIAnAWQHsloMAVrgKxu1ROOYA3WuwAEbCPIC8c4AB1qc7XKjEw8VHIoBdPJp27utJABl9huYQCu1BMTEAKAJRqLlnXYIeGd2LXoMIIBHZwgDTw0tAOTGeAALXiN1EABxAFEAFWzzJOTLUKgskDT4eAAHMFQAeiag+gBaIIN22jriDDSlbgBrZ166rG56Jt7iJucOMGL-Mp0Ad2J0gGEgvnFiWihGuXh2WJLVgF9vFYCMdIhqTxc3eA8nrucoeF9E1ctFOwMHoDHJVJ9vhgkLR4LRbpZrrdLiVbrBQkFxEZgMjblBrEgjC93F5iEhfvDAsFQuFIhAYnF4AkKSlghkCXJsvkivhmQEKlUavVGi02p0GT0+gMhqNxpNprN5osmtk5ABqOSki7-bQbba7R5vQ7HU7nXmI0p3B5PIlvLwQn5+C2rQFYdEGsFye1mm4WnHUW5gWhSCCE1zEp7kp2UkJhOQRaKxeJ-bVx1mZDkgAAKLG5Wu1-IzgoazVaEA6XXgEv6g3YIzGdQmonlfUVSjAypAaoUHFd7Ax8Awmt5lmhsKMLrRfYNef+urSO2QBoORyMJogM4RPoRmkuIGRNAYTHQgPsBkEwhAonEBuksm7SlP8A9ye0YmI1E2457eJsj5nUmICA1lDV53h8R1ViCGNwk8bIMAWJR2hBeBinvIFkIwehemeMNbSeBD2EjFNPSpWN6FgkBaHgjgkIcdpNjLVDgDSIJCCqJpkE2DsuwIwckDwOQxFgIIYRDONIm4ZxEGBd9hmROReJIdgDAAOUPLsVRVdVeKgWhVMPLcymuQztEuHc9zwA9GGYQEADFuHYCIhHwS8xAkeBb3kOyHPoZ9bjfD9HBtMCpGoXgIF+F0fyQTxQvCjAYVOMA+O8P1LAAoCQPDcCXwCKDqTjCjCB8pjhzKMQwGcAAjehPycXCwIiiDiLKCAMDqIJg3EAARCBCFoL5GRMlqAR7QNgx8MrkjMqNpoEigpsK7JdKq-gVugVDsls4hlKfOhGGybwNzKcjsnfOopMHagLvgCh4AATzqCBlEQAAPeATAoOpdNgCAMigPh2GUbbdrkfaIBMUrZv+AKbqMCI5wAQVqdgKJkKBYlQ4KvHR2JIp7Sd+wwJT9MYD1cYga5ju1CmvyUXsiZJ+A1IO6HN2pgJTpAdaoGBWhVqgTaQFsPS9sPQ6OcsLnzsumXbu+2hfv+wHlBF0Fwch-BmpG2GpPhjAkZRtHDkxrXsaeCn8fpwmDT50mIHJk3KaOxb0qdumgRt8Q7eZ8W2Z0KnFq5qqpPgMQMBD2oxDux7nsqmrNk1gTsgAZSDCAJd5Exhpm3d9xAcHmBF+7JIHZyRDcm90BkeRi9LvyLQy4D6tAnGwr4IiynysiKKw98+ZLqTUIW-3tC5ugpEwx5nGH12dC5uEtdykaWL6owAHImmQ9fJZhtwRMQfXayktq9GoYY59M5PWEWQ7Fuz3ex4oyBw1QuK+CwNJSCQDFhssbOkTmXzoXdAspy6uWvJIdA8ZS5tQ6n1Ygr1PDrwAMTrx9DAk+ngkDcDRIwb2VVeD3WvlvBwUNLDZFIQYbIWJeQYkBllPCOVFrdxgnXKSAlyIngcN4P+AckQzkoZxeATRUCkhodrPKjwGEt2yu-Jqy8u6kTYbQQe8BOGeG8o5AS8iEoozALws0AidzeAslZI8mBsAXivO5ZghCkDELkFdG6AkI6hzEM1YqmIwY+UOHIAAjAANjqK9OQAA1JQ0I6AAG5-BYXYAAc3fEYAATAABhCbE6gM1NAD3rsmeJSTqBGH8Rk0JA0w5ZLHrQV67QNhIHSCUtJzTMk7k0Lk-BzhmqFOSXINJfS5AAGYylZJydQaiiFkLNWQu0Aw90YBGDCtQCAVS4yyCKUYfp-TSmtKSHUGwSB3wJM2aM9p4yCK0W6AxXyyYqqK2GAk4+1B2QoP6m82gqzCHsBkf4kJcgwDcD0EgOQKCkBgtWaIPE7AjAoMGXC1ZhywAK3ukYKqeJYDDFWT04pAztm7O0PssFRyjAAA4-n+IAKz4pOBAd67Q+CiHYDCd4iyxArLaf6c5NEZnXNQBkYM8hkxvUrAyhyzKxBGFcIDM+7LslnL5gLZqiLkWovRZiuJ6zelbLkFS16pzOXOKks1L5Py-kAqBSCsFSBPkOUBp0GwxAFhGEGdSwhtSwDEAAF7ErkCaxCbqEX6BVb6tVWLNU4u1TsvV-hCWHOoMcgJZSAm6tWfUxpia0kAFJ9W5MjmHLQtz7mPMks8mFEBy02u+UoEpZrAWkkteC-wfr2D2sOU6oZ1LIUORhXCwZgakW6RRRqagMr2hotweqpI2LNkDKjas2NPqo3JupcK+lcAxV4VZcsnN4y80Sv5UoY1RankvIgCSs9Z7RkuUgDAcM5AQCBJSagfxe4zDc1kuQKgBdDzMFqukdgpAXIVGYEWYU8xroPLlE0P9LFSAAAF-EYEQ4E6DmxYO83AQ9J6zAwDCWIHUDylwTCXCAA)* | ||
First let's create an entry point for the application. Create a file `index.html`: | ||
```markup | ||
```html | ||
<!doctype html> | ||
@@ -27,3 +27,3 @@ <html> | ||
There are many ways to setup a bundler tool, but most are distributed via NPM. In fact, most modern JavaScript libraries and tools are distributed that way, including Mithril. NPM stands for Node.js Package Manager. To download NPM, [install Node.js](https://nodejs.org/en/); NPM is installed automatically with it. Once you have Node.js and NPM installed, open the command line and run this command: | ||
There are many ways to setup a bundler tool, but most are distributed via npm. In fact, most modern JavaScript libraries and tools are distributed that way, including Mithril. To download npm, [install Node.js](https://nodejs.org/en/); npm is installed automatically with it. Once you have Node.js and npm installed, open the command line and run this command: | ||
@@ -34,3 +34,3 @@ ```bash | ||
If NPM is installed correctly, a file `package.json` will be created. This file will contain a skeleton project meta-description file. Feel free to edit the project and author information in this file. | ||
If npm is installed correctly, a file `package.json` will be created. This file will contain a skeleton project meta-description file. Feel free to edit the project and author information in this file. | ||
@@ -81,4 +81,6 @@ --- | ||
Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](http://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET https://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint. | ||
Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](https://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET https://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint. | ||
*Note: third-party cookies may have to be enabled for the REM endpoint to work.* | ||
```javascript | ||
@@ -142,3 +144,3 @@ // src/models/User.js | ||
- You can use [Babel](es6.md) to transpile ES6+ to ES5 for IE and to transpile [JSX](jsx.md) (an inline HTML-like syntax extension) to appropriate hyperscript calls. | ||
- You can use [ESLint](http://eslint.org/) for easy linting with no special plugins. | ||
- You can use [ESLint](https://eslint.org/) for easy linting with no special plugins. | ||
- You can use [Terser](https://github.com/terser-js/terser) or [UglifyJS](https://github.com/mishoo/UglifyJS2) (ES5 only) to minify your code easily. | ||
@@ -220,11 +222,5 @@ - You can use [Istanbul](https://github.com/istanbuljs/nyc) for code coverage. | ||
Right now, the list looks rather plain because we have not defined any styles. | ||
Right now, the list looks rather plain because we have not defined any styles. So let's add a few of them. Let's first create a file called `styles.css` and include it in the `index.html` file: | ||
There are many similar conventions and libraries that help organize application styles nowadays. Some, like [Bootstrap](http://getbootstrap.com/) dictate a specific set of HTML structures and semantically meaningful class names, which has the upside of providing low cognitive dissonance, but the downside of making customization more difficult. Others, like [Tachyons](http://tachyons.io/) provide a large number of self-describing, atomic class names at the cost of making the class names themselves non-semantic. "CSS-in-JS" is another type of CSS system that is growing in popularity, which basically consists of scoping CSS via transpilation tooling. CSS-in-JS libraries achieve maintainability by reducing the size of the problem space, but come at the cost of having high complexity. | ||
Regardless of what CSS convention/library you choose, a good rule of thumb is to avoid the cascading aspect of CSS. To keep this tutorial simple, we'll just use plain CSS with overly explicit class names, so that the styles themselves provide the atomicity of Tachyons, and class name collisions are made unlikely through the verbosity of the class names. Plain CSS can be sufficient for low-complexity projects (e.g. 3 to 6 man-months of initial implementation time and few project phases). | ||
To add styles, let's first create a file called `styles.css` and include it in the `index.html` file: | ||
```markup | ||
```html | ||
<!doctype html> | ||
@@ -247,10 +243,22 @@ <html> | ||
```css | ||
.user-list {list-style:none;margin:0 0 10px;padding:0;} | ||
.user-list-item {background:#fafafa;border:1px solid #ddd;color:#333;display:block;margin:0 0 1px;padding:8px 15px;text-decoration:none;} | ||
.user-list-item:hover {text-decoration:underline;} | ||
``` | ||
.user-list { | ||
list-style: none; | ||
margin: 0 0 10px; | ||
padding: 0; | ||
} | ||
The CSS above is written using a convention of keeping all styles for a rule in a single line, in alphabetical order. This convention is designed to take maximum advantage of screen real estate, and makes it easier to scan the CSS selectors (since they are always on the left side) and their logical grouping, and it enforces predictable and uniform placement of CSS rules for each selector. | ||
.user-list-item { | ||
background: #fafafa; | ||
border: 1px solid #ddd; | ||
color: #333; | ||
display: block; | ||
margin: 0 0 1px; | ||
padding: 8px 15px; | ||
text-decoration: none; | ||
} | ||
Obviously you can use whatever spacing/indentation convention you prefer. The example above is just an illustration of a not-so-widespread convention that has strong rationales behind it, but deviates from the more widespread cosmetic-oriented spacing conventions. | ||
.user-list-item:hover { | ||
text-decoration: underline; | ||
} | ||
``` | ||
@@ -348,16 +356,60 @@ Reloading the browser window now should display some styled elements. | ||
And let's add some styles to `styles.css`: | ||
And let's add some more styles to `styles.css`: | ||
```css | ||
/* styles.css */ | ||
body,.input,.button {font:normal 16px Verdana;margin:0;} | ||
body, .input, .button { | ||
font: normal 16px Verdana; | ||
margin: 0; | ||
} | ||
.user-list {list-style:none;margin:0 0 10px;padding:0;} | ||
.user-list-item {background:#fafafa;border:1px solid #ddd;color:#333;display:block;margin:0 0 1px;padding:8px 15px;text-decoration:none;} | ||
.user-list-item:hover {text-decoration:underline;} | ||
.user-list { | ||
list-style: none; | ||
margin: 0 0 10px; | ||
padding: 0; | ||
} | ||
.label {display:block;margin:0 0 5px;} | ||
.input {border:1px solid #ddd;border-radius:3px;box-sizing:border-box;display:block;margin:0 0 10px;padding:10px 15px;width:100%;} | ||
.button {background:#eee;border:1px solid #ddd;border-radius:3px;color:#333;display:inline-block;margin:0 0 10px;padding:10px 15px;text-decoration:none;} | ||
.button:hover {background:#e8e8e8;} | ||
.user-list-item { | ||
background: #fafafa; | ||
border: 1px solid #ddd; | ||
color: #333; | ||
display: block; | ||
margin: 0 0 1px; | ||
padding: 8px 15px; | ||
text-decoration: none; | ||
} | ||
.user-list-item:hover { | ||
text-decoration: underline; | ||
} | ||
.label { | ||
display: block; | ||
margin: 0 0 5px; | ||
} | ||
.input { | ||
border: 1px solid #ddd; | ||
border-radius: 3px; | ||
box-sizing: border-box; | ||
display: block; | ||
margin: 0 0 10px; | ||
padding: 10px 15px; | ||
width: 100%; | ||
} | ||
.button { | ||
background: #eee; | ||
border: 1px solid #ddd; | ||
border-radius: 3px; | ||
color: #333; | ||
display: inline-block; | ||
margin: 0 0 10px; | ||
padding: 10px 15px; | ||
text-decoration: none; | ||
} | ||
.button:hover { | ||
background: #e8e8e8; | ||
} | ||
``` | ||
@@ -580,19 +632,69 @@ | ||
Let's add some styles: | ||
And let's update the styles once more: | ||
```css | ||
/* styles.css */ | ||
body,.input,.button {font:normal 16px Verdana;margin:0;} | ||
body, .input, .button { | ||
font: normal 16px Verdana; | ||
margin: 0; | ||
} | ||
.layout {margin:10px auto;max-width:1000px;} | ||
.menu {margin:0 0 30px;} | ||
.layout { | ||
margin: 10px auto; | ||
max-width: 1000px; | ||
} | ||
.user-list {list-style:none;margin:0 0 10px;padding:0;} | ||
.user-list-item {background:#fafafa;border:1px solid #ddd;color:#333;display:block;margin:0 0 1px;padding:8px 15px;text-decoration:none;} | ||
.user-list-item:hover {text-decoration:underline;} | ||
.menu { | ||
margin: 0 0 30px; | ||
} | ||
.label {display:block;margin:0 0 5px;} | ||
.input {border:1px solid #ddd;border-radius:3px;box-sizing:border-box;display:block;margin:0 0 10px;padding:10px 15px;width:100%;} | ||
.button {background:#eee;border:1px solid #ddd;border-radius:3px;color:#333;display:inline-block;margin:0 0 10px;padding:10px 15px;text-decoration:none;} | ||
.button:hover {background:#e8e8e8;} | ||
.user-list { | ||
list-style: none; | ||
margin: 0 0 10px; | ||
padding: 0; | ||
} | ||
.user-list-item { | ||
background: #fafafa; | ||
border: 1px solid #ddd; | ||
color: #333; | ||
display: block; | ||
margin: 0 0 1px; | ||
padding: 8px 15px; | ||
text-decoration: none; | ||
} | ||
.user-list-item:hover { | ||
text-decoration: underline; | ||
} | ||
.label { | ||
display: block; | ||
margin: 0 0 5px; | ||
} | ||
.input { | ||
border: 1px solid #ddd; | ||
border-radius: 3px; | ||
box-sizing: border-box; | ||
display: block; | ||
margin: 0 0 10px; | ||
padding: 10px 15px; | ||
width: 100%; | ||
} | ||
.button { | ||
background: #eee; | ||
border: 1px solid #ddd; | ||
border-radius: 3px; | ||
color: #333; | ||
display: inline-block; | ||
margin: 0 0 10px; | ||
padding: 10px 15px; | ||
text-decoration: none; | ||
} | ||
.button:hover { | ||
background: #e8e8e8; | ||
} | ||
``` | ||
@@ -599,0 +701,0 @@ |
@@ -48,4 +48,4 @@ # stream() | ||
```markup | ||
<script src="https://unpkg.com/mithril@next/stream/stream.js"></script> | ||
```html | ||
<script src="https://unpkg.com/mithril/stream/stream.js"></script> | ||
``` | ||
@@ -52,0 +52,0 @@ |
# Testing | ||
Mithril comes with a testing framework called [ospec](https://github.com/MithrilJS/mithril.js/tree/master/ospec). What makes it different from most test frameworks is that it avoids all configurability for the sake of avoiding [yak shaving](http://catb.org/jargon/html/Y/yak-shaving.html) and [analysis paralysis](https://en.wikipedia.org/wiki/Analysis_paralysis). | ||
- [Setup](#setup) | ||
- [Best practices](#best-practices) | ||
- [Unit testing](#unit-testing) | ||
The easist way to setup the test runner is to create an NPM script for it. Open your project's `package.json` file and edit the `test` line under the `scripts` section: | ||
--- | ||
### Setup | ||
Testing Mithril applications is relatively easy. The easiest way to get started is with [ospec](https://mochajs.org/), [mithril-query](https://github.com/MithrilJS/mithril-query), and JSDOM. Installing those is pretty easy: open up a terminal and run this command. | ||
```bash | ||
npm install --save-dev ospec mithril-query jsdom | ||
``` | ||
And getting them set up is also relatively easy and requires a few short steps: | ||
1. Add a `"test": "mocha"` to your npm scripts in your `package.json` file. This will end up looking something like this, maybe with a few extra fields relevant to your project: | ||
```json | ||
{ | ||
"name": "my-project", | ||
"scripts": { | ||
"test": "ospec" | ||
"test": "ospec --require ./test-setup.js" | ||
} | ||
@@ -16,49 +30,83 @@ } | ||
Remember this is a JSON file, so object key names such as `"test"` must be inside of double quotes. | ||
2. Create a setup file, `test-setup.js`, that looks like this: | ||
To setup a test suite, create a `tests` folder and inside of it, create a test file: | ||
```javascript | ||
// file: tests/math-test.js | ||
var o = require("mithril/ospec/ospec") | ||
var o = require("ospec") | ||
var jsdom = require("jsdom") | ||
var dom = new jsdom.JSDOM("", { | ||
// So we can get `requestAnimationFrame` | ||
pretendToBeVisual: true, | ||
}) | ||
o.spec("math", function() { | ||
o("addition works", function() { | ||
o(1 + 2).equals(3) | ||
}) | ||
// Fill in the globals Mithril needs to operate. Also, the first two are often | ||
// useful to have just in tests. | ||
global.window = dom.window | ||
global.document = dom.window.document | ||
global.requestAnimationFrame = dom.window.requestAnimationFrame | ||
// Require Mithril to make sure it loads properly. | ||
require("mithril") | ||
// And now, make sure JSDOM ends when the tests end. | ||
o.after(function() { | ||
dom.window.close() | ||
}) | ||
``` | ||
To run the test, use the command `npm test`. Ospec considers any JavaScript file inside of a `tests` folder (anywhere in the project) to be a test. | ||
3. Create a component, say `src/my-component.js`, that looks like this: | ||
```javascript | ||
function MyComponent() { | ||
return { | ||
view: function (vnode) { | ||
return m("div", vnode.attrs.text) | ||
} | ||
} | ||
} | ||
module.exports = MyComponent | ||
``` | ||
npm test | ||
``` | ||
--- | ||
4. And finally, create a test file, say `src/tests/my-component.js`, that looks like this: | ||
### Running mithril in a non-browser environment | ||
```javascript | ||
var mq = require("mithril-query") | ||
var o = require("ospec") | ||
Mithril has a few dependencies on globals that exist in all its supported browser environments but are missing in all non-browser environments. To work around this you can use the browser mocks that ship with the mithril npm package. | ||
var MyComponent = require("../my-component.js") | ||
The simplest way to do this is ensure the following snippet of code runs **before** you include mithril itself in your project. | ||
o.spec("MyComponent", function() { | ||
o("things are working", function() { | ||
var out = mq(MyComponent, {text: "What a wonderful day to be alive!"}) | ||
```js | ||
// Polyfill DOM env for mithril | ||
global.window = require("mithril/test-utils/browserMock.js")(); | ||
global.document = window.document; | ||
out.should.contain("day") | ||
}) | ||
}) | ||
``` | ||
Once that snippet has been run you can `require("mithril")` and it should be quite happy. | ||
Once you've got all that set up, in that same terminal you installed everything to, run this command. | ||
```bash | ||
npm test | ||
``` | ||
Provided you have everything set up properly, you should end up with output that looks something like this: | ||
``` | ||
–––––– | ||
All 1 assertions passed in 0ms | ||
``` | ||
--- | ||
### Good testing practices | ||
### Best practices | ||
Generally speaking, there are two ways to write tests: upfront and after the fact. | ||
Testing is relatively straightforward in most cases. Each test usually consists of three parts: set up state, run code, check results. But there are things you do need to keep in mind while you test, to ensure you get the best bang for your buck and to help save you a *lot* of time. | ||
Writing tests upfront requires specifications to be frozen. Upfront tests are a great way of codifying the rules that a yet-to-be-implemented API must obey. However, writing tests upfront may not be a suitable strategy if you don't have a reasonable idea of what your project will look like, if the scope of the API is not well known or if it's likely to change (e.g. based on previous history at the company). | ||
1. First and foremost, you want to write your tests as early in the process as possible. You don't need to write your tests immediately, but you want to at the very least write your tests as you write your code. That way, if things aren't working as you thought they were, you're spending 5 minutes now, knowing exactly what's going on, instead of 5 days straight 6 months later when you're trying to release that amazing app idea that's now spectacularly broken. You don't want to be stuck in *that* rut. | ||
Writing tests after the fact is a way to document the behavior of a system and avoid regressions. They are useful to ensure that obscure corner cases are not inadvertently broken and that previously fixed bugs do not get re-introduced by unrelated changes. | ||
1. Test the API, test the behavior, but don't test the implementation. If you need to test that an event is being fired if a particular action happens, that's all fine and dandy, and feel free to do that. But don't test the entire DOM structure in your test. You don't want to be stuck having to rewrite part of 5 different tests just because you added a simple style-related class. You also don't want to rewrite all your tests simply because you added a new instance method to an object. | ||
1. Don't be afraid to repeat yourself, and only abstract when you're literally doing the same thing tens to hundreds of times in the same file or when you're explicitly generating tests. Normally, in code, it's a good idea to draw a line when you're repeating the same logic more than 2-3 times and abstract it into a function, but when you're testing, even though there's a lot of duplicate logic, that redundancy helps give you context when troubleshooting tests. Remember: tests are specifications, not normal code. | ||
--- | ||
@@ -68,6 +116,19 @@ | ||
Unit testing is the practice of isolating a part of an application (typically a single module), and asserting that, given some inputs, it produces the expected outputs. | ||
Unit testing isolates parts of your application, usually a single module but sometimes even a single function, and tests them as a single "unit". It checks that given a particular input and initial state, it produces the desired output and side effects. This all seems complicated, but I promise, it's not. Here's a couple unit tests for JavaScript's `+` operator, applied to numbers: | ||
Testing a Mithril component is easy. Let's assume we have a simple component like this: | ||
```javascript | ||
o.spec("addition", function() { | ||
o("works with integers", function() { | ||
o(1 + 2).equals(3) | ||
}) | ||
o("works with floats", function() { | ||
// Yep, thanks IEEE-754 floating point for being weird. | ||
o(0.1 + 0.2).equals(0.30000000000000004) | ||
}) | ||
}) | ||
``` | ||
Just like you can unit test simple stuff like that, you can unit test Mithril components, too. Suppose you have this component: | ||
```javascript | ||
@@ -77,30 +138,41 @@ // MyComponent.js | ||
module.exports = { | ||
view: function() { | ||
return m("div", | ||
m("p", "Hello World") | ||
) | ||
function MyComponent() { | ||
return { | ||
view: function(vnode) { | ||
return m("div", [ | ||
vnode.attrs.type === "goodbye" | ||
? "Goodbye, world!" | ||
: "Hello, world!" | ||
]) | ||
} | ||
} | ||
} | ||
module.exports = MyComponent | ||
``` | ||
We can then create a `tests/MyComponent.js` file and create a test for this component like this: | ||
You could easily create a few unit tests for that. | ||
```javascript | ||
var MyComponent = require("MyComponent") | ||
```js | ||
var mq = require("mithril-query") | ||
var MyComponent = require("./MyComponent") | ||
o.spec("MyComponent", function() { | ||
o("returns a div", function() { | ||
var vnode = MyComponent.view() | ||
o(vnode.tag).equals("div") | ||
o(vnode.children.length).equals(1) | ||
o(vnode.children[0].tag).equals("p") | ||
o(vnode.children[0].children).equals("Hello world") | ||
o("says 'Hello, world!' when `type` is `hello`", function() { | ||
var out = mq(MyComponent, {type: "hello"}) | ||
out.should.contain("Hello, world!") | ||
}) | ||
o("says 'Goodbye, world!' when `type` is `goodbye`", function() { | ||
var out = mq(MyComponent, {type: "goodbye"}) | ||
out.should.contain("Goodbye, world!") | ||
}) | ||
o("says 'Hello, world!' when no `type` is given", function() { | ||
var out = mq(MyComponent) | ||
out.should.contain("Hello, world!") | ||
}) | ||
}) | ||
``` | ||
Typically, you wouldn't test the structure of the vnode tree so granularly, and you would instead only test non-trivial, dynamic aspects of the view. A tool that can help making testing easier with deep vnode trees is [Mithril Query](https://github.com/StephanHoyer/mithril-query). | ||
Sometimes, you need to mock the dependencies of a module in order to test the module in isolation. [Mockery](https://github.com/mfncooper/mockery) is one tool that allows you to do that. | ||
As mentioned before, tests are specifications. You can see from the tests how the component is supposed to work, and the component does it very effectively. |
@@ -89,3 +89,3 @@ # trust(html) | ||
// An attack that does not use JavaScript | ||
data.description = "<a href='http://evil.com/login-page-that-steals-passwords.html'>Click here to read more</a>" | ||
data.description = "<a href='https://evil.com/login-page-that-steals-passwords.html'>Click here to read more</a>" | ||
``` | ||
@@ -125,3 +125,3 @@ | ||
```markup | ||
```html | ||
<!-- Load Facebook SDK for JavaScript --> | ||
@@ -139,3 +139,3 @@ <div id="fb-root"></div> | ||
<div class="fb-like" | ||
data-href="http://www.your-domain.com/your-page.html" | ||
data-href="https://www.your-domain.com/your-page.html" | ||
data-layout="standard" | ||
@@ -163,3 +163,3 @@ data-action="like" | ||
m("#fb-root"), | ||
m("#fb-like[data-href=http://www.your-domain.com/your-page.html][data-layout=standard][data-action=like][data-show-faces=true]") | ||
m("#fb-like[data-href=https://www.your-domain.com/your-page.html][data-layout=standard][data-action=like][data-show-faces=true]") | ||
] | ||
@@ -166,0 +166,0 @@ } |
@@ -83,3 +83,3 @@ T.time("Setup"); | ||
" | ", | ||
m("a[href='http://threaditjs.com']", "ThreaditJS Home"), | ||
m("a[href='https://threaditjs.com']", "ThreaditJS Home"), | ||
]), | ||
@@ -86,0 +86,0 @@ m("h2", [ |
@@ -1,2 +0,2 @@ | ||
ospec [![NPM Version](https://img.shields.io/npm/v/ospec.svg)](https://www.npmjs.com/package/ospec) [![NPM License](https://img.shields.io/npm/l/ospec.svg)](https://www.npmjs.com/package/ospec) | ||
ospec [![npm Version](https://img.shields.io/npm/v/ospec.svg)](https://www.npmjs.com/package/ospec) [![npm License](https://img.shields.io/npm/l/ospec.svg)](https://www.npmjs.com/package/ospec) | ||
===== | ||
@@ -374,3 +374,3 @@ | ||
ospec comes with an executable named `ospec`. NPM auto-installs local binaries to `./node_modules/.bin/`. You can run ospec by running `./node_modules/.bin/ospec` from your project root, but there are more convenient methods to do so that we will soon describe. | ||
ospec comes with an executable named `ospec`. npm auto-installs local binaries to `./node_modules/.bin/`. You can run ospec by running `./node_modules/.bin/ospec` from your project root, but there are more convenient methods to do so that we will soon describe. | ||
@@ -377,0 +377,0 @@ ospec doesn't work when installed globally (`npm install -g`). Using global scripts is generally a bad idea since you can end up with different, incompatible versions of the same package installed locally and globally. |
{ | ||
"name": "mithril", | ||
"version": "2.0.0-rc.9", | ||
"version": "2.0.1", | ||
"description": "A framework for building brilliant applications", | ||
@@ -5,0 +5,0 @@ "author": "Leo Horie", |
@@ -1,2 +0,2 @@ | ||
mithril.js [![NPM Version](https://img.shields.io/npm/v/mithril.svg)](https://www.npmjs.com/package/mithril) [![NPM License](https://img.shields.io/npm/l/mithril.svg)](https://www.npmjs.com/package/mithril) [![NPM Downloads](https://img.shields.io/npm/dm/mithril.svg)](https://www.npmjs.com/package/mithril) [![Donate at OpenCollective](https://img.shields.io/opencollective/all/mithriljs.svg?colorB=brightgreen)](https://opencollective.com/mithriljs) | ||
mithril.js [![npm Version](https://img.shields.io/npm/v/mithril.svg)](https://www.npmjs.com/package/mithril) [![npm License](https://img.shields.io/npm/l/mithril.svg)](https://www.npmjs.com/package/mithril) [![npm Downloads](https://img.shields.io/npm/dm/mithril.svg)](https://www.npmjs.com/package/mithril) [![Donate at OpenCollective](https://img.shields.io/opencollective/all/mithriljs.svg?colorB=brightgreen)](https://opencollective.com/mithriljs) | ||
========== | ||
@@ -32,13 +32,15 @@ | ||
```html | ||
<script src="https://unpkg.com/mithril@next/mithril.js"></script> | ||
<!-- or --> | ||
<script src="https://cdn.jsdelivr.net/npm/mithril@next/mithril.js"></script> | ||
<!-- Development: whichever you prefer --> | ||
<script src="https://unpkg.com/mithril/mithril.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/mithril/mithril.js"></script> | ||
<!-- Production: whichever you prefer --> | ||
<script src="https://unpkg.com/mithril/mithril.min.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/mithril/mithril.min.js"></script> | ||
``` | ||
### npm | ||
```bash | ||
# For the most recent stable version | ||
$ npm install mithril --save | ||
# For the most recent unstable version | ||
$ npm install mithril@next --save | ||
npm install mithril --save | ||
``` | ||
@@ -48,2 +50,8 @@ | ||
TypeScript type definitions are available from DefinitelyTyped. They can be installed with: | ||
```bash | ||
$ npm install @types/mithril --save-dev | ||
``` | ||
## Documentation | ||
@@ -50,0 +58,0 @@ |
@@ -1,2 +0,2 @@ | ||
mithril-stream [![NPM Version](https://img.shields.io/npm/v/mithril-stream.svg)](https://www.npmjs.com/package/mithril-stream) [![NPM License](https://img.shields.io/npm/l/mithril-stream.svg)](https://www.npmjs.com/package/mithril-stream) | ||
mithril-stream [![npm Version](https://img.shields.io/npm/v/mithril-stream.svg)](https://www.npmjs.com/package/mithril-stream) [![npm License](https://img.shields.io/npm/l/mithril-stream.svg)](https://www.npmjs.com/package/mithril-stream) | ||
============== | ||
@@ -3,0 +3,0 @@ |
@@ -30,4 +30,4 @@ /* eslint-disable */ | ||
if (open(stream)) { | ||
stream.changing() | ||
stream.state = "active" | ||
stream._changing() | ||
stream._state = "active" | ||
dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) }) | ||
@@ -41,18 +41,15 @@ } | ||
stream.constructor = Stream | ||
stream.state = arguments.length && value !== Stream.SKIP ? "active" : "pending" | ||
stream.parents = [] | ||
stream._state = arguments.length && value !== Stream.SKIP ? "active" : "pending" | ||
stream._parents = [] | ||
stream.changing = function() { | ||
open(stream) && (stream.state = "changing") | ||
stream._changing = function() { | ||
if (open(stream)) stream._state = "changing" | ||
dependentStreams.forEach(function(s) { | ||
s.changing() | ||
s._changing() | ||
}) | ||
} | ||
stream.map = function(fn, ignoreInitial) { | ||
var target = stream.state === "active" && ignoreInitial !== Stream.SKIP | ||
? Stream(fn(value)) | ||
: Stream() | ||
target.parents.push(stream) | ||
stream._map = function(fn, ignoreInitial) { | ||
var target = ignoreInitial ? Stream() : Stream(fn(value)) | ||
target._parents.push(stream) | ||
dependentStreams.push(target) | ||
@@ -63,2 +60,6 @@ dependentFns.push(fn) | ||
stream.map = function(fn) { | ||
return stream._map(fn, stream._state !== "active") | ||
} | ||
var end | ||
@@ -69,5 +70,5 @@ function createEnd() { | ||
if (value === true) { | ||
stream.parents.forEach(function (p) {p.unregisterChild(stream)}) | ||
stream.state = "ended" | ||
stream.parents.length = dependentStreams.length = dependentFns.length = 0 | ||
stream._parents.forEach(function (p) {p._unregisterChild(stream)}) | ||
stream._state = "ended" | ||
stream._parents.length = dependentStreams.length = dependentFns.length = 0 | ||
} | ||
@@ -84,3 +85,3 @@ return value | ||
stream.unregisterChild = function(child) { | ||
stream._unregisterChild = function(child) { | ||
var childIndex = dependentStreams.indexOf(child) | ||
@@ -104,3 +105,3 @@ if (childIndex !== -1) { | ||
throw new Error("Ensure that each item passed to stream.combine/stream.merge/lift is a stream") | ||
return s.state === "active" | ||
return s._state === "active" | ||
}) | ||
@@ -114,5 +115,5 @@ var stream = ready | ||
var mappers = streams.map(function(s) { | ||
return s.map(function(value) { | ||
return s._map(function(value) { | ||
changed.push(s) | ||
if (ready || streams.every(function(s) { return s.state !== "pending" })) { | ||
if (ready || streams.every(function(s) { return s._state !== "pending" })) { | ||
ready = true | ||
@@ -123,3 +124,3 @@ stream(fn.apply(null, streams.concat([changed]))) | ||
return value | ||
}, Stream.SKIP) | ||
}, true) | ||
}) | ||
@@ -179,3 +180,3 @@ | ||
function open(s) { | ||
return s.state === "pending" || s.state === "active" || s.state === "changing" | ||
return s._state === "pending" || s._state === "active" || s._state === "changing" | ||
} | ||
@@ -182,0 +183,0 @@ |
@@ -23,3 +23,3 @@ "use strict" | ||
// This way I'm not also implementing a partial `URL` polyfill. Based on the | ||
// regexp at http://urlregex.com/, but adapted to allow relative URLs and | ||
// regexp at https://urlregex.com/, but adapted to allow relative URLs and | ||
// care only about HTTP(S) URLs. | ||
@@ -26,0 +26,0 @@ var urlHash = "#[?!/+=&;%@.\\w_-]*" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1535819
217
23020
1
74