`). We use the classList API here to add an `exit` class to `
`.
+
+Then we return a [Promise](promise.md) that resolves after half a second. When we return a promise from `onbeforeremove`, Mithril waits until the promise is resolved and only then it removes the element. In this case, it waits half a second, giving the exit animation the exact time it needs to complete.
+
+We can verify that both the enter and exit animations work by mounting the `Toggler` component:
+
+```javascript
+m.mount(document.body, Toggler)
+```
+
+Note that the `onbeforeremove` hook only fires on the element that loses its `parentNode` when an element gets detached from the DOM. This behavior is by design and exists to prevent a potential jarring user experience where every conceivable exit animation on the page would run on a route change. If your exit animation is not running, make sure to attach the `onbeforeremove` handler as high up the tree as it makes sense to ensure that your animation code is called.
+
+---
+
+### Performance
+
+When creating animations, it's recommended that you only use the `opacity` and `transform` CSS rules, since these can be hardware-accelerated by modern browsers and yield better performance than animating `top`, `left`, `width`, and `height`.
+
+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 postioned element over a fixed element).
diff --git a/docs/autoredraw.md b/docs/autoredraw.md
index 97a4575d..eb1f093f 100644
--- a/docs/autoredraw.md
+++ b/docs/autoredraw.md
@@ -87,9 +87,9 @@ m.route(document.body, "/", {
---
-### When Mithril does not redraws
+### When Mithril does not redraw
-Mithril does not redraw after `setTimeout`, `setInterval`, `requestAnimationFrame` and 3rd party library event handlers (e.g. Socket.io callbacks). In those cases, you must manually call [`m.redraw()`](redraw.md).
+Mithril does not redraw after `setTimeout`, `setInterval`, `requestAnimationFrame`, raw `Promise` resolutions and 3rd party library event handlers (e.g. Socket.io callbacks). In those cases, you must manually call [`m.redraw()`](redraw.md).
Mithril also does not redraw after lifecycle methods. Parts of the UI may be redrawn after an `oninit` handler, but other parts of the UI may already have been redrawn when a given `oninit` handler fires. Handlers like `oncreate` and `onupdate` fire after the UI has been redrawn.
diff --git a/docs/change-log.md b/docs/change-log.md
index c97d7d12..eedac49b 100644
--- a/docs/change-log.md
+++ b/docs/change-log.md
@@ -1,6 +1,48 @@
# Change log
+- [v1.1.1](#v111)
+- [v1.1.0](#v110)
+- [v1.0.1](#v101)
- [Migrating from v0.2.x](#migrating-from-v02x)
+- [Older docs](http://mithril.js.org/archive/v0.2.5/index.html)
+
+---
+
+### 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)
---
@@ -16,6 +58,8 @@ If you are migrating, consider using the [mithril-codemods](https://www.npmjs.co
- [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)
@@ -25,21 +69,21 @@ If you are migrating, consider using the [mithril-codemods](https://www.npmjs.co
- [`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.startComputation`/`m.endComputation` removed](#mstartcomputationmendcomputation-removed)
-- [Synchronous redraw removed](#synchronous-redraw-removed)
---
## `m.prop` removed
-In `v1.x`, `m.prop()` is now a more powerful stream micro-library, but it's no longer part of core.
+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`
@@ -110,7 +154,8 @@ m("div", {
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
+ // 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) { /*...*/ }
@@ -133,7 +178,7 @@ In v0.2.x, Mithril allowed 'redraw locks' which temporarily prevented blocked dr
`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`
+#### `v0.2.x`
```javascript
m("div", {
@@ -143,7 +188,7 @@ m("div", {
})
```
-### `v1.x`
+#### `v1.x`
```javascript
m("div", {
@@ -153,6 +198,26 @@ m("div", {
})
```
+### 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
@@ -400,13 +465,16 @@ m.route.set("/other/route")
## Accessing route params
-In `v0.2.x` reading route params was all handled through the `m.route.param()` method. In `v1.x` any route params are passed as the `attrs` object on the vnode passed as the first argument to lifecycle methods/`view`.
+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"
}
@@ -421,9 +489,11 @@ 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"
}
}
})
@@ -431,6 +501,28 @@ m.route(document.body, "/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.
@@ -462,6 +554,40 @@ var Component = {
---
+## 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.
@@ -499,7 +625,7 @@ setTimeout(function() {
}, 1000)
```
-Additionally, if the `extract` option is passed to `m.request` the return value of the provided function will be used directly to resolve its promise, and the `deserialize` callback is ignored.
+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.
---
@@ -526,11 +652,13 @@ greetAsync()
### `v1.x`
```javascript
-var greetAsync = new Promise(function(resolve){
- setTimeout(function() {
- resolve("hello")
- }, 1000)
-})
+var greetAsync = function() {
+ return new Promise(function(resolve){
+ setTimeout(function() {
+ resolve("hello")
+ }, 1000)
+ })
+}
greetAsync()
.then(function(value) {return value + " world"})
@@ -602,27 +730,3 @@ Arrays now represent [fragments](fragment.md), which are structurally significan
## `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).
-
----
-
-## `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.
-
----
-
-## 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
-```
diff --git a/docs/code-of-conduct.md b/docs/code-of-conduct.md
new file mode 100644
index 00000000..50ebe61d
--- /dev/null
+++ b/docs/code-of-conduct.md
@@ -0,0 +1,74 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at [github@patcavit.com](mailto:github@patcavit.com?subject=Mithril Code of Conduct). All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/docs/components.md b/docs/components.md
index 7e454519..4df9dfe4 100644
--- a/docs/components.md
+++ b/docs/components.md
@@ -2,8 +2,9 @@
- [Structure](#structure)
- [Lifecycle methods](#lifecycle-methods)
+- [Syntactic variants](#syntactic-variants)
- [State](#state)
-- [Avoid-anti-patterns](#avoid-anti-patterns)
+- [Avoid anti-patterns](#avoid-anti-patterns)
### Structure
@@ -100,6 +101,107 @@ To learn more about lifecycle methods, [see the lifecycle methods page](lifecycl
---
+### Syntactic variants
+
+#### ES6 classes
+
+Components can also be written using ES6 class syntax:
+
+```javascript
+class ES6ClassComponent {
+ constructor(vnode) {
+ // vnode.state is undefined at this point
+ this.kind = "ES6 class"
+ }
+ view() {
+ return m("div", `Hello from an ${this.kind}`)
+ }
+ oncreate() {
+ console.log(`A ${this.kind} component was created`)
+ }
+}
+```
+
+Component classes must define a `view()` method, detected via `.prototype.view`, to get the tree to render.
+
+They can be consumed in the same way regular components can.
+
+```javascript
+// EXAMPLE: via m.render
+m.render(document.body, m(ES6ClassComponent))
+
+// EXAMPLE: via m.mount
+m.mount(document.body, ES6ClassComponent)
+
+// EXAMPLE: via m.route
+m.route(document.body, "/", {
+ "/": ES6ClassComponent
+})
+
+// EXAMPLE: component composition
+class AnotherES6ClassComponent {
+ view() {
+ return m("main", [
+ m(ES6ClassComponent)
+ ])
+ }
+}
+```
+
+#### Closure components
+
+Functionally minded developers may prefer using the "closure component" syntax:
+
+```javascript
+function closureComponent(vnode) {
+ // vnode.state is undefined at this point
+ var kind = "closure component"
+
+ return {
+ view: function() {
+ return m("div", "Hello from a " + kind)
+ },
+ oncreate: function() {
+ console.log("We've created a " + kind)
+ }
+ }
+}
+```
+
+The returned object must hold a `view` function, used to get the tree to render.
+
+They can be consumed in the same way regular components can.
+
+```javascript
+// EXAMPLE: via m.render
+m.render(document.body, m(closureComponent))
+
+// EXAMPLE: via m.mount
+m.mount(document.body, closuresComponent)
+
+// EXAMPLE: via m.route
+m.route(document.body, "/", {
+ "/": closureComponent
+})
+
+// EXAMPLE: component composition
+function anotherClosureComponent() {
+ return {
+ view: function() {
+ return m("main", [
+ m(closureComponent)
+ ])
+ }
+ }
+}
+```
+
+#### Mixing component kinds
+
+Components can be freely mixed. A Class component can have closure or POJO components as children, etc...
+
+---
+
### State
Like all virtual DOM nodes, component vnodes can have state. Component state is useful for supporting object-oriented architectures, for encapsulation and for separation of concerns.
@@ -108,7 +210,7 @@ The state of a component can be accessed three ways: as a blueprint at initializ
#### At initialization
-Any property attached to the component object is copied for every instance of the component. This allows simple state initialization.
+For POJO components, the component object is the prototype of each component instance, so any property defined on the component object will be accessible as a property of `vnode.state`. This allows simple state initialization.
In the example below, `data` is a property of the `ComponentWithInitialState` component's state object.
@@ -126,6 +228,10 @@ m(ComponentWithInitialState)
//
Initial content
```
+For class components, the state is an instance of the class, set right after the constructor is called.
+
+For closure components, the state is the object returned by the closure, set right after the closure returns. The state object is mostly redundant for closure components (since variables defined in the closure scope can be used instead).
+
#### Via vnode.state
State can also be accessed via the `vnode.state` property, which is available to all lifecycle methods as well as the `view` method of a component.
@@ -174,6 +280,89 @@ Be aware that when using ES5 functions, the value of `this` in nested anonymous
Although Mithril is flexible, some code patterns are discouraged:
+#### Avoid fat components
+
+Generally speaking, a "fat" component is a component that has custom instance methods. In other words, you should avoid attaching functions to `vnode.state` or `this`. It's exceedingly rare to have logic that logically fits in a component instance method and that can't be reused by other components. It's relatively common that said logic might be needed by a different component down the road.
+
+It's easier to refactor code if that logic is placed in the data layer than if it's tied to a component state.
+
+Consider this fat component:
+
+```javascript
+// views/Login.js
+// AVOID
+var Login = {
+ username: "",
+ password: "",
+ setUsername: function(value) {
+ this.username = value
+ },
+ setPassword: function(value) {
+ this.password = value
+ },
+ canSubmit: function() {
+ return this.username !== "" && this.password !== ""
+ },
+ login: function() {/*...*/},
+ view: function() {
+ return m(".login", [
+ m("input[type=text]", {oninput: m.withAttr("value", this.setUsername.bind(this)), value: this.username}),
+ m("input[type=password]", {oninput: m.withAttr("value", this.setPassword.bind(this)), value: this.password}),
+ m("button", {disabled: !this.canSubmit(), onclick: this.login}, "Login"),
+ ])
+ }
+}
+```
+
+Normally, in the context of a larger application, a login component like the one above exists alongside components for user registration and password recovery. Imagine that we want to be able to prepopulate the email field when navigating from the login screen to the registration or password recovery screens (or vice versa), so that the user doesn't need to re-type their email if they happened to fill the wrong page (or maybe you want to bump the user to the registration form if a username is not found).
+
+Right away, we see that sharing the `username` and `password` fields from this component to another is difficult. This is because the fat component encapsulates its state, which by definition makes this state difficult to access from outside.
+
+It makes more sense to refactor this component and pull the state code out of the component and into the application's data layer. This can be as simple as creating a new module:
+
+```javascript
+// models/Auth.js
+// PREFER
+var Auth = {
+ username: "",
+ password: "",
+ setUsername: function(value) {
+ Auth.username = value
+ },
+ setPassword: function(value) {
+ Auth.password = value
+ },
+ canSubmit: function() {
+ return Auth.username !== "" && Auth.password !== ""
+ },
+ login: function() {/*...*/},
+}
+
+module.exports = Auth
+```
+
+Then, we can clean up the component:
+
+```javascript
+// views/Login.js
+// PREFER
+var Auth = require("../models/Auth")
+
+var Login = {
+ view: function() {
+ return m(".login", [
+ m("input[type=text]", {oninput: m.withAttr("value", Auth.setUsername), value: Auth.username}),
+ m("input[type=password]", {oninput: m.withAttr("value", Auth.setPassword), value: Auth.password}),
+ m("button", {disabled: !Auth.canSubmit(), onclick: Auth.login}, "Login"),
+ ])
+ }
+}
+```
+
+This way, the `Auth` module is now the source of truth for auth-related state, and a `Register` component can easily access this data, and even reuse methods like `canSubmit`, if needed. In addition, if validation code is required (for example, for the email field), you only need to modify `setEmail`, and that change will do email validation for any component that modifies an email field.
+
+As a bonus, notice that we no longer need to use `.bind` to keep a reference to the state for the component's event handlers.
+
#### Avoid restrictive interfaces
Try to keep component interfaces generic - using `attrs` and `children` directly - unless the component requires special logic to operate on input.
@@ -206,7 +395,7 @@ var FlexibleComponent = {
#### Don't manipulate `children`
-However, if a component is opinionated in how it applies attributes or children, you should switch to using custom attributes.
+If a component is opinionated in how it applies attributes or children, you should switch to using custom attributes.
Often it's desirable to define multiple sets of children, for example, if a component has a configurable title and body.
diff --git a/docs/contributing.md b/docs/contributing.md
index 5b4770d3..189d0b99 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -4,7 +4,7 @@
## How do I go about contributing ideas or new features?
-Create an [issue thread on Github](https://github.com/lhorie/mithril.js/issues/new) to suggest your idea so the community can discuss it.
+Create an [issue thread on GitHub](https://github.com/MithrilJS/mithril.js/issues/new) to suggest your idea so the community can discuss it.
If the consensus is that it's a good idea, the fastest way to get it into a release is to send a pull request. Without a PR, the time to implement the feature will depend on the bandwidth of the development team and its list of priorities.
@@ -20,12 +20,12 @@ Ideally, the best way to report bugs is to provide a small snippet of code where
To send a pull request:
-- fork the repo (button at the top right in Github)
-- clone the forked repo to your computer (green button in Github)
+- fork the repo (button at the top right in GitHub)
+- clone the forked repo to your computer (green button in GitHub)
- create a feature branch (run `git checkout -b the-feature-branch-name`)
- make your changes
- run the tests (run `npm t`)
-- submit a pull request (go to the pull requests tab in Github, click the green button and select your feature branch)
+- submit a pull request (go to the pull requests tab in GitHub, click the green button and select your feature branch)
diff --git a/docs/credits.md b/docs/credits.md
index 2a0df4d1..7fd2a048 100644
--- a/docs/credits.md
+++ b/docs/credits.md
@@ -4,7 +4,7 @@ Mithril was originally written by Leo Horie, but it is where it is today thanks
Special thanks to:
-- Pat Cavit, who exposed most of the public API for Mithril 1.0 and brought in test coverage
+- Pat Cavit, who exposed most of the public API for Mithril 1.0, brought in test coverage and automated the publishing process
- Isiah Meadows, who brought in linting, modernized the test suite and has been a strong voice in design discussions
- Zoli Kahan, who replaced the original Promise implementation with one that actually worked properly
- Alec Embke, who single-handedly wrote the JSON-P implementation
@@ -17,10 +17,10 @@ Special thanks to:
- Leon Sorokin, for writing a DOM instrumentation tool that helped improve performance in Mithril 1.0
- Jordan Walke, whose work on React was prior art to the implementation of keys in Mithril
- Pierre-Yves Gérardy, who consistently makes high quality contributions
+- Gyandeep Singh, who contributed significant IE performance improvements
Other people who also deserve recognition:
- 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)
- 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
-
diff --git a/docs/css.md b/docs/css.md
new file mode 100644
index 00000000..7664d1ca
--- /dev/null
+++ b/docs/css.md
@@ -0,0 +1,94 @@
+# CSS
+
+- [Vanilla CSS](#vanilla-css)
+- [Tachyons](#tachyons)
+- [CSS-in-JS](#css-in-js)
+
+---
+
+### Vanilla CSS
+
+For various reasons, CSS has a bad reputation and often developers reach for complex tools in an attempt to make styling more manageable. In this section, we'll take a step back and cover some tips on writing plain CSS:
+
+- **Avoid using the space operator** - The vast majority of CSS maintainability issues are due to CSS specificity issues. The space operator defines a descendant (e.g. `.a .b`) and at the same time, it increases the level of specificity for the CSS rules that apply to that selector, sometimes overriding styles unexpectedly.
+
+ Instead, it's preferable to share a namespace prefix in all class names that belong to a logical group of elements:
+
+ ```css
+ /* AVOID */
+ .chat.container {/*...*/}
+ .chat .item {/*...*/}
+ .chat .avatar {/*...*/}
+ .chat .text {/*...*/}
+
+ /* PREFER */
+ .chat-container {/*...*/}
+ .chat-item {/*...*/}
+ .chat-avatar {/*...*/}
+ .chat-text {/*...*/}
+ ```
+
+- **Use only single-class selectors** - This convention goes hand-in-hand with the previous one: avoiding high specificity selectors such as `#foo` or `div.bar` help decrease the likelyhood of specificity conflicts.
+
+ ```css
+ /* AVOID */
+ #home {}
+ input.highlighted {}
+
+ /* PREFER */
+ .home {}
+ .input-highlighted {}
+ ```
+
+- **Develop naming conventions** - You can reduce naming collisions by defining keywords for certain types of UI elements. This is particularly effective when brand names are involved:
+
+ ```css
+ /* AVOID */
+ .twitter {} /* icon link in footer */
+ .facebook {} /* icon link in footer */
+ /* later... */
+ .modal.twitter {} /* tweet modal */
+ .modal.facebook {} /* share modal */
+
+ /* PREFER */
+ .link-twitter {}
+ .link-facebook {}
+ /* later... */
+ .modal-twitter {}
+ .modal-facebook {}
+ ```
+
+---
+
+### Tachyons
+
+[Tachyons](https://github.com/tachyons-css/tachyons) is a CSS framework, but the concept behind it can easily be used without the library itself.
+
+The basic idea is that every class name must declare one and only one CSS rule. For example, `bw1` stands for `border-width:1px;`. To create a complex style, one simply combines class names representing each of the required CSS rules. For example, `.black.bg-dark-blue.br2` styles an element with blue background, black text and a 4px border-radius.
+
+Since each class is small and atomic, it's essentially impossible to run into CSS conflicts.
+
+As it turns out, the Tachyons convention fits extremely well with Mithril and JSX:
+
+```jsx
+var Hero = ".black.bg-dark-blue.br2.pa3"
+
+m.mount(document.body,
Hello)
+// equivalent to `m(".black.bg-dark.br2.pa3", "Hello")`
+```
+
+---
+
+### CSS in JS
+
+In plain CSS, all selectors live in the global scope and are prone to name collisions and specificity conflicts. CSS-in-JS aims to solve the issue of scoping in CSS, i.e. it groups related styles into non-global modules that are invisible to each other. CSS-in-JS is suitable for extremely large dev teams working on a single codebase, but it's not a good choice for most teams.
+
+Nowadays there are [a lot of CSS-in-JS libraries with various degrees of robustness](https://github.com/MicheleBertoli/css-in-js).
+
+The main problem with many of these libraries is that even though they require a non-trivial amount of transpiler tooling and configuration, they also require sacrificing code readability in order to work, e.g. `
` vs `
` (or `m("a.button.danger")` if we're using hyperscript).
+
+Often sacrifices also need to be made at time of debugging, when mapping rendered CSS class names back to their source. Often all you get in browser developer tools is a class like `button_fvp6zc2gdj35evhsl73ffzq_0 danger_fgdl0s2a5fmle5g56rbuax71_0` with useless source maps (or worse, entirely criptic class names).
+
+Another common issue is lack of support for less basic CSS features such as `@keyframes` and `@font-face`.
+
+If you are adamant about using a CSS-in-JS library, consider using [J2C](https://github.com/j2css/j2c), which works without configuration and implements `@keyframes` and `@font-face`.
diff --git a/docs/es6.md b/docs/es6.md
index 2f629223..0192a6ca 100644
--- a/docs/es6.md
+++ b/docs/es6.md
@@ -5,7 +5,7 @@
---
-Mithril is written in ES5, and is fully compatible with ES6 as well.
+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.
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.
@@ -70,10 +70,12 @@ Create a `.babelrc` file:
Next, create a file called `webpack.config.js`
```javascript
+const path = require('path')
+
module.exports = {
entry: './src/index.js',
output: {
- path: './bin',
+ path: path.resolve(__dirname, './bin'),
filename: 'app.js',
},
module: {
diff --git a/docs/examples.md b/docs/examples.md
index 140ad3f6..749900cc 100644
--- a/docs/examples.md
+++ b/docs/examples.md
@@ -2,10 +2,10 @@
Here are some examples of Mithril in action
-- [Animation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/animation/mosaic.html)
-- [DBMonster](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html)
-- [Markdown Editor](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/editor/index.html)
-- SVG: [Clock](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/clock.html), [Ring](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/ring.html), [Tiger](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/tiger.html)
-- [ThreadItJS](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/threaditjs/index.html)
-- [TodoMVC](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/todomvc/index.html)
+- [Animation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/animation/mosaic.html)
+- [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)
diff --git a/docs/favicon.ico b/docs/favicon.ico
new file mode 100644
index 00000000..61807fae
Binary files /dev/null and b/docs/favicon.ico differ
diff --git a/docs/favicon.png b/docs/favicon.png
new file mode 100644
index 00000000..50712630
Binary files /dev/null and b/docs/favicon.png differ
diff --git a/docs/fragment.md b/docs/fragment.md
index 018dc4ab..d3bddc7a 100644
--- a/docs/fragment.md
+++ b/docs/fragment.md
@@ -35,11 +35,11 @@ Generates a fragment [vnode](vnodes.md)
`vnode = m.fragment(attrs, children)`
-Argument | Type | Required | Description
------------ | -------------------- | -------- | ---
-`attrs` | `Object` | Yes | A map of attributes
-`children` | `Array
` | Yes | A list of vnodes
-**returns** | `Vnode` | | A fragment [vnode](vnodes.md)
+Argument | Type | Required | Description
+----------- | --------------------------------------------------- | -------- | ---
+`attrs` | `Object` | Yes | A map of attributes
+`children` | `Array` | Yes | A list of vnodes
+**returns** | `Vnode` | | A fragment [vnode](vnodes.md)
[How to read signatures](signatures.md)
diff --git a/docs/framework-comparison.md b/docs/framework-comparison.md
index 4beb034a..0969b2c9 100644
--- a/docs/framework-comparison.md
+++ b/docs/framework-comparison.md
@@ -12,23 +12,25 @@ If you're reading this page, you probably have used other frameworks to build ap
## Why not [insert favorite framework here]?
-The reality is that most modern frameworks are fast, well-suited to build complex applications, and highly maintainable if you know how to use them effectively. There are examples of highly complex applications in the wild using just about every popular framework: Udemy uses Angular, AirBnB uses React, Gitlab uses Vue, Guild Wars 2 uses Mithril (yes, inside the game!). Clearly, these are all production-quality frameworks.
+The reality is that most modern frameworks are fast, well-suited to build complex applications, and maintainable if you know how to use them effectively. There are examples of highly complex applications in the wild using just about every popular framework: Udemy uses Angular, AirBnB uses React, Gitlab uses Vue, Guild Wars 2 uses Mithril (yes, inside the game!). Clearly, these are all production-quality frameworks.
As a rule of thumb, if your team is already heavily invested in another framework/library/stack, it makes more sense to stick with it, unless your team agrees that there's a very strong reason to justify a costly rewrite.
-If you're starting something new, do consider giving Mithril a try, if nothing else, to see how much value Mithril adopters have been getting out of 8kb (gzipped) of code.
+However, if you're starting something new, do consider giving Mithril a try, if nothing else, to see how much value Mithril adopters have been getting out of 8kb (gzipped) of code. Mithril is used by many well-known companies (e.g. Vimeo, Nike, Fitbit), and it powers large open-sourced platforms too (e.g. Lichess, Flarum).
---
## Why use Mithril?
-In one sentence: because **Mithril is pragmatic**. If you don't believe me, take 10 minutes to go over the [guide](introduction.md) to see how much it accomplishes, compared with official guides for other frameworks.
+In one sentence: because **Mithril is pragmatic**. This [10 minute guide](index.md) is a good example: that's how long it takes to learn components, XHR and routing - and that's just about the right amount of knowledge needed to build useful applications.
-Mithril is all about getting stuff done. It comes out of the box with a compact set of tools that you'll likely need for building Single Page Applications, and no distractions. The Mithril API is small and focused, and it's designed to leverage previous knowledge - e.g. view language is Javascript, HTML attributes have no syntax caveats, Promises are Promises, hyperscript selectors mirror CSS and is JSX-compatible, `m.request` option names mirror [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) - all of this so you can get up to speed fast.
+Mithril is all about getting meaningful work done efficiently. Doing file uploads? [The docs show you how](request.md#file-uploads). Authentication? [Documented too](route.md#authentication). Exit animations? [You got it](animation.md). No extra libraries, no magic.
---
-## React
+## Comparisons
+
+### React
React is a view library maintained by Facebook.
@@ -38,11 +40,11 @@ React and Mithril share a lot of similarities. If you already learned React, you
- They both organize views via components
- They both use Javascript as a flow control mechanism within views
-The most obvious difference between React and Mithril is in their scope. React is a view library, so a typical React-based application relies on third-party libraries for routing, XHR and state management. Using a library oriented approach allows developers to customize their stack to precisely match their needs. The not-so-nice way of saying that is that React-based architectures can vary wildly from project to project.
+The most obvious difference between React and Mithril is in their scope. React is a view library, so a typical React-based application relies on third-party libraries for routing, XHR and state management. Using a library oriented approach allows developers to customize their stack to precisely match their needs. The not-so-nice way of saying that is that React-based architectures can vary wildly from project to project, and that those projects are that much more likely to cross the 1MB size line.
Mithril has built-in modules for common necessities such as routing and XHR, and the [guide](simple-application.md) demonstrates idiomatic usage. This approach is preferable for teams that value consistency and ease of onboarding.
-### Performance
+#### Performance
Both React and Mithril care strongly about rendering performance, but go about it in different ways. In the past React had two DOM rendering implementations (one using the DOM API, and one using `innerHTML`). Its upcoming fiber architecture introduces scheduling and prioritization of units of work. React also has a sophisticated build system that disables various checks and error messages for production deployments, and various browser-specific optimizations. In addition, there are also several performance-oriented libraries that leverage React's `shouldComponentUpdate` hook and immutable data structure libraries' fast object equality checking properties to reduce virtual DOM reconciliation times. Generally speaking, React's approach to performance is to engineer relatively complex solutions.
@@ -50,21 +52,21 @@ Mithril follows the less-is-more school of thought. It has a substantially small
Here's a comparison of library load times, i.e. the time it takes to parse and run the Javascript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop:
-React v15.4.1 | Mithril 1.0
-------------- | -------
-55.8 ms | 4.5 ms
+React | Mithril
+------- | -------
+55.8 ms | 4.5 ms
Library load times matter in applications that don't stay open for long periods of time (for example, anything in mobile) and cannot be improved via caching or other optimization techniques.
-Since this is a micro-benchmark, you are encourage to replicate these tests yourself since hardware can heavily affect the numbers. Note that bundler frameworks like Webpack can move dependencies out before the timer calls to emulate static module resolution, so you should either copy the code from the compiled CDN files or open the output file from the bundler library, and manually add the high resolution timer calls `console.time` and `console.timeEnd` to the bundled script. Avoid using `new Date` and `performance.now`, as those mechanisms are not as statistically accurate.
+Since this is a micro-benchmark, you are encouraged to replicate these tests yourself since hardware can heavily affect the numbers. Note that bundler frameworks like Webpack can move dependencies out before the timer calls to emulate static module resolution, so you should either copy the code from the compiled CDN files or open the output file from the bundler library, and manually add the high resolution timer calls `console.time` and `console.timeEnd` to the bundled script. Avoid using `new Date` and `performance.now`, as those mechanisms are not as statistically accurate.
For your reading convenience, here's a version of that benchmark adapted to use CDNs on the web: the [benchmark for React is here](https://jsfiddle.net/0ovkv64u/), and the [benchmark for Mithril is here](https://jsfiddle.net/o7hxooqL/). Note that we're benchmarking all of Mithril rather than benchmarking only the rendering module (which would be equivalent in scope to React). Also note that this CDN-driven setup incurs some overheads due to fetching resources from disk cache (~2ms per resource). Due to those reasons, the numbers here are not entirely accurate, but they should be sufficient to observe that Mithril's initialization speed is noticeably better than React.
Here's a slightly more meaningful benchmark: measuring the scripting time for creating 10,000 divs (and 10,000 text nodes). Again, here's the benchmark code for [React](https://jsfiddle.net/bfoeay4f/) and [Mithril](https://jsfiddle.net/fft0ht7n/). Their best results are shown below:
-React v15.4.1 | Mithril 1.0
-------------- | -------
-99.7 ms | 42.8 ms
+React | Mithril
+------- | -------
+99.7 ms | 42.8 ms
What these numbers show is that not only does Mithril initializes significantly faster, it can process upwards of 20,000 virtual DOM nodes before React is ready to use.
@@ -72,37 +74,49 @@ What these numbers show is that not only does Mithril initializes significantly
Update performance can be even more important than first-render performance, since updates can happen many times while a Single Page Application is running.
-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/lhorie/mithril.js/rewrite/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/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](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:
-React v15.4.1 | Mithril 1.0
-------------- | -------
-12.1 ms | 6.4 ms
+React | Mithril
+------- | -------
+12.1 ms | 6.4 ms
##### Development performance
Another thing to keep in mind is that because React adds extra checks and helpful error messages in development mode, it is slower in development than the production version used for the benchmarks above. To illustrate, [here's the 10,000 node benchmark from above using the development version of React](https://jsfiddle.net/r1jfckrd/).
-### Complexity
+##### Drop-in replacements
+
+There are [several](https://preactjs.com/) [projects](https://github.com/Lucifier129/react-lite) [that](https://infernojs.org/) [claim](https://github.com/alibaba/rax) API parity with React (some via compatibility layer libraries), but they are not fully compatible (e.g. PropType support is usually stubbed out, synthetic events are sometimes not supported, and some APIs have different semantics). Note that these libraries typically also include features of their own that are not part of the official React API, which may become problematic down the road if one decides to switch back to React Fiber.
+
+Claims about small download size (compared to React) are accurate, but most of these libraries are slightly larger than Mithril's renderer module. Preact is the only exception.
+
+Be wary of aggressive performance claims, as benchmarks used by some of these projects are known to be out-of-date and flawed (in the sense that they can be - and are - exploited). Boris Kaul (author of some of the benchmarks) has [written in detail about how benchmarks are gamed](https://medium.com/@localvoid/how-to-win-in-web-framework-benchmarks-8bc31af76ce7). Another thing to keep in mind is that some benchmarks aggressively use advanced optimization features and thus demonstrate *potential* performance, i.e. performance that is possible given some caveats, but realistically unlikely unless you actively spend the time to go over your entire codebase identifying optimization candidates and evaluating the regression risks brought by the optimization caveats.
+
+In the spirit of demonstrating *typical* performance characteristics, the benchmarks presented in this comparison page are implemented in an apples-to-apples, naive, idiomatic way (i.e. the way you would normally write 99% of your code) and do not employ tricks or advanced optimizations to make one or other framework look artificially better. You are encouraged to contribute a PR if you feel any DbMonster implementation here could be written more idiomatically.
+
+#### Complexity
Both React and Mithril have relatively small API surfaces compared to other frameworks, which help ease learning curve. However, whereas idiomatic Mithril can be written without loss of readability using plain ES5 and no other dependencies, idiomatic React relies heavily on complex tooling (e.g. Babel, JSX plugin, etc), and this level of complexity frequently extends to popular parts of its ecosystem, be it in the form of syntax extensions (e.g. non-standard object spread syntax in Redux), architectures (e.g. ones using immutable data libraries), or bells and whistles (e.g. hot module reloading).
-### Learning curve
+While complex toolchains are also possible with Mithril and other frameworks alike, it's *strongly* recommended that you follow the [KISS](https://en.wikipedia.org/wiki/KISS_principle) and [YAGNI](https://en.wikipedia.org/wiki/You_aren't_gonna_need_it) principles when using Mithril.
+
+#### Learning curve
Both React and Mithril have relatively small learning curves. React's learning curve mostly involves understanding components and their lifecycle. The learning curve for Mithril components is nearly identical. There are obviously more APIs to learn in Mithril, since Mithril also includes routing and XHR, but the learning curve would be fairly similar to learning React, React Router and a XHR library like superagent or axios.
Idiomatic React requires working knowledge of JSX and its caveats, and therefore there's also a small learning curve related to Babel.
-### Documentation
+#### Documentation
-React documentation is clear and well written, and includes a good API reference, tutorials for getting started, as well as pages covering various advanced concepts.
+React documentation is clear and well written, and includes a good API reference, tutorials for getting started, as well as pages covering various advanced concepts. Unfortunately, since React is limited to being only a view library, its documentation does not explore how to use React idiomatically in the context of a real-life application. As a result, there are many popular state management libraries and thus architectures using React can differ drastically from company to company (or even between projects).
-Mithril documentation also includes [introductory](introduction.md) [tutorials](simple-application.md), pages about advanced concepts, and an extensive API reference section, which includes input/output type information, examples for various common use cases and advice against misuse and anti-patterns. It also includes a cheatsheet for quick reference.
+Mithril documentation also includes [introductory](index.md) [tutorials](simple-application.md), pages about advanced concepts, and an extensive API reference section, which includes input/output type information, examples for various common use cases and advice against misuse and anti-patterns. It also includes a cheatsheet for quick reference.
-Unfortunately, since React is limited to being only a view library, its documentation does not explore how to use React in the context of a real-life application. As a result, there are many popular state management libraries and as a result, architectures using React can differ drastically from company to company (or even between projects).
+Mithril documentation also demonstrates simple, close-to-the-metal solutions to common use cases in real-life applications where it's appropriate to inform a developer that web standards may be now on par with larger established libraries.
---
-## Angular
+### Angular
Angular is a web application framework maintained by Google.
@@ -113,7 +127,7 @@ Angular and Mithril are fairly different, but they share a few similarities:
The most obvious difference between Angular and Mithril is in their complexity. This can be seen most easily in how views are implemented. Mithril views are plain Javascript, and flow control is done with Javascript built-in mechanisms such as ternary operators or `Array.prototype.map`. Angular, on the other hand, implements a directive system to extend HTML views so that it's possible to evaluate Javascript-like expressions within HTML attributes and interpolations. Angular actually ships with a parser and a compiler written in Javascript to achieve that. If that doesn't seem complex enough, there's actually two compilation modes (a default mode that generates Javascript functions dynamically for performance, and [a slower mode](https://docs.angularjs.org/api/ng/directive/ngCsp) for dealing with Content Security Policy restrictions).
-### Performance
+#### Performance
Angular has made a lot of progress in terms of performance over the years. Angular 1 used a mechanism known as dirty checking which tended to get slow due to the need to constantly diff large `$scope` structures. Angular 2 uses a template change detection mechanism that is much more performant. However, even despite Angular's improvements, Mithril is often faster than Angular, due to the ease of auditing that Mithril's small codebase size affords.
@@ -125,33 +139,36 @@ Also, remember that frameworks like Angular and Mithril are designed for non-tri
##### Update performance
-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/lhorie/mithril.js/rewrite/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/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](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:
Angular | Mithril
------- | -------
11.5 ms | 6.4 ms
-### Complexity
+#### Complexity
Angular is superior to Mithril in the amount of tools it offers (in the form of various directives and services), but it is also far more complex. Compare [Angular's API surface](https://angular.io/docs/ts/latest/api/) with [Mithril's](api.md). You can make your own judgment on which API is more self-descriptive and more relevant to your needs.
Angular 2 has a lot more concepts to understand: on the language level, Typescript is the recommended language, and on top of that there's also Angular-specific template syntax such as bindings, pipes, "safe navigator operator". You also need to learn about architectural concepts such as modules, components, services, directives, etc, and where it's appropriate to use what.
-### Learning curve
+#### Learning curve
If we compare apples to apples, Angular 2 and Mithril have similar learning curves: in both, components are a central aspect of architecture, and both have reasonable routing and XHR tools.
With that being said, Angular has a lot more concepts to learn than Mithril. It offers Angular-specific APIs for many things that often can be trivially implemented (e.g. pluralization is essentially a switch statement, "required" validation is simply an equality check, etc). Angular templates also have several layers of abstractions to emulate what Javascript does natively in Mithril - Angular's `ng-if`/`ngIf` is a *directive*, which uses a custom *parser* and *compiler* to evaluate an expression string and emulate lexical scoping... and so on. Mithril tends to be a lot more transparent, and therefore easier to reason about.
-### Documentation
+#### Documentation
Angular 2 documentation provides an extensive introductory tutorial, and another tutorial that implements an application. It also has various guides for advanced concepts, a cheatsheet and a style guide. Unfortunately, at the moment, the API reference leaves much to be desired. Several APIs are either undocumented or provide no context for what the API might be used for.
-Mithril documentation includes [introductory](introduction.md) [tutorials](simple-application.md), pages about advanced concepts, and an extensive API reference section, which includes input/output type information, examples for various common use cases and advice against misuse and anti-patterns. It also includes a cheatsheet for quick reference.
+Mithril documentation includes [introductory](index.md) [tutorials](simple-application.md), pages about advanced concepts, and an extensive API reference section, which includes input/output type information, examples for various common use cases and advice against misuse and anti-patterns. It also includes a cheatsheet for quick reference.
+
+Mithril documentation also demonstrates simple, close-to-the-metal solutions to common use cases in real-life applications where it's appropriate to inform a developer that web standards may be now on par with larger established libraries.
+
---
-## Vue
+### Vue
Vue is a view library similar to Angular.
@@ -160,11 +177,11 @@ Vue and Mithril have a lot of differences but they also share some similarities:
- They both use virtual DOM and lifecycle methods
- Both organize views via components
-Vue also provides tools for routing and state management as separate modules. Vue looks very similar to Angular and provides a similar directive system, HTML-based templates and logic flow directives. It differs from Angular in that it implements a monkeypatching reactive system that overwrites native methods in a component's data (whereas Angular 1 uses dirty checking and digest/apply cycles to achieve similar results). Similar to Angular 2, Vue compiles HTML templates into functions, but the compiled functions look more like Mithril or React views, rather than Angular's compiled rendering functions.
+Vue 2 uses a fork of Snabbdom as its virtual DOM system. In addition, Vue also provides tools for routing and state management as separate modules. Vue looks very similar to Angular and provides a similar directive system, HTML-based templates and logic flow directives. It differs from Angular in that it implements a monkeypatching reactive system that overwrites native methods in a component's data tree (whereas Angular 1 uses dirty checking and digest/apply cycles to achieve similar results). Similar to Angular 2, Vue compiles HTML templates into functions, but the compiled functions look more like Mithril or React views, rather than Angular's compiled rendering functions.
-Vue is significantly smaller than Angular when comparing apples to apples, but not as small as Mithril (Vue core is around 23kb gzipped, whereas the equivalent rendering module in Mithril is around 4kb gzipped). Both have similar performance characteristics, but benchmarks often suggest Mithril is slightly faster.
+Vue is significantly smaller than Angular when comparing apples to apples, but not as small as Mithril (Vue core is around 23kb gzipped, whereas the equivalent rendering module in Mithril is around 4kb gzipped). Both have similar performance characteristics, but benchmarks usually suggest Mithril is slightly faster.
-### Performance
+#### Performance
Here's a comparison of library load times, i.e. the time it takes to parse and run the Javascript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop:
@@ -176,24 +193,24 @@ Library load times matter in applications that don't stay open for long periods
##### Update performance
-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/lhorie/mithril.js/rewrite/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/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](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:
Vue | Mithril
------ | -------
9.8 ms | 6.4 ms
-### Complexity
+#### Complexity
-Vue is heavily inspired by Angular and has many things that Angular does (e.g. directives, filters, `v-cloak`), but also has things inspired by React (e.g. components). As of Vue 2.0, it's also possible to write templates using hyperscript/JSX syntax (in addition to single-file components and the various webpack-based language transpilation plugins). Vue provides both bi-directional data binding and an optional Redux-like state management library, and unlike Angular, it provides no style guide. The many-ways-of-doing-one-thing approach can cause architectural fragmentation in long-lived projects.
+Vue is heavily inspired by Angular and has many things that Angular does (e.g. directives, filters, bi-directional bindings, `v-cloak`), but also has things inspired by React (e.g. components). As of Vue 2.0, it's also possible to write templates using hyperscript/JSX syntax (in addition to single-file components and the various webpack-based language transpilation plugins). Vue provides both bi-directional data binding and an optional Redux-like state management library, but unlike Angular, it provides no style guide. The many-ways-of-doing-one-thing approach can cause architectural fragmentation in long-lived projects.
-Mithril has far less concepts and typically organizes applications in terms of components and a data layer. There are no different ways of defining components, and thus there's no need to install different sets of tools to make different flavors work.
+Mithril has far less concepts and typically organizes applications in terms of components and a data layer. All component creation styles in Mithril output the same vnode structure using native Javascript features only. The direct consequence of leaning on the language is less tooling and a simpler project setup.
-### Documentation
+#### Documentation
Both Vue and Mithril have good documentation. Both include a good API reference with examples, tutorials for getting started, as well as pages covering various advanced concepts.
-However, due to Vue's many-ways-to-do-one-thing approach, some things are not adequately documented. For example, there's no documentation on hyperscript syntax or usage.
+However, due to Vue's many-ways-to-do-one-thing approach, some things may not be adequately documented. For example, there's no documentation on hyperscript syntax or usage.
-Mithril documentation typically errs on the side of being overly thorough if a topic involves things outside of the scope of Mithril. For example, when a topic involves a 3rd party library, Mithril documentation walks through the installation process for the 3rd party library.
+Mithril documentation typically errs on the side of being overly thorough if a topic involves things outside of the scope of Mithril. For example, when a topic involves a 3rd party library, Mithril documentation walks through the installation process for the 3rd party library. Mithril documentation also often demonstrates simple, close-to-the-metal solutions to common use cases in real-life applications where it's appropriate to inform a developer that web standards may be now on par with larger established libraries.
-Mithril's tutorials also cover a lot more ground than Vue's: the [Vue tutorial](https://vuejs.org/v2/guide/#Getting-Started) finishes with a static list of foodstuff. [Mithril's 10 minute guide](introduction.md) covers the majority of its API and goes over key aspects of real-life applications, such as fetching data from a server and routing (and there's a [longer, more thorough tutorial](simple-application.md) if that's not enough).
+Mithril's tutorials also cover a lot more ground than Vue's: the [Vue tutorial](https://vuejs.org/v2/guide/#Getting-Started) finishes with a static list of foodstuff. [Mithril's 10 minute guide](index.md) covers the majority of its API and goes over key aspects of real-life applications, such as fetching data from a server and routing (and there's a [longer, more thorough tutorial](simple-application.md) if that's not enough).
diff --git a/docs/generate.js b/docs/generate.js
index a9d0f759..ef3ce424 100644
--- a/docs/generate.js
+++ b/docs/generate.js
@@ -1,15 +1,19 @@
+"use strict"
+
var fs = require("fs")
var path = require("path")
var marked = require("marked")
var layout = fs.readFileSync("./docs/layout.html", "utf-8")
var version = JSON.parse(fs.readFileSync("./package.json", "utf-8")).version
-try {fs.mkdirSync("docs/archive/")} catch (e) {}
-try {fs.mkdirSync("docs/archive/" + version)} catch (e) {}
-try {fs.mkdirSync("docs/archive/" + version + "/lib")} catch (e) {}
-try {fs.mkdirSync("docs/archive/" + version + "/lib/prism")} catch (e) {}
+try {fs.mkdirSync("./dist")} catch (e) {/* ignore */}
+try {fs.mkdirSync("./dist/archive")} catch (e) {/* ignore */}
+try {fs.mkdirSync("./dist/archive/v" + version)} catch (e) {/* ignore */}
-var guides = fs.readFileSync("docs/guides.md", "utf-8")
-var methods = fs.readFileSync("docs/methods.md", "utf-8")
+var guides = fs.readFileSync("docs/nav-guides.md", "utf-8")
+var methods = fs.readFileSync("docs/nav-methods.md", "utf-8")
+
+var index = fs.readFileSync("docs/index.md", "utf-8")
+fs.writeFileSync("README.md", index.replace(/(\]\()(.+?)\.md(\))/g, "$1http://mithril.js.org/$2.html$3"), "utf-8")
generate("docs")
@@ -19,16 +23,16 @@ function generate(pathname) {
generate(pathname + "/" + filename)
})
}
- else if (!pathname.match(/tutorials|archive/)) {
+ else if (!pathname.match(/tutorials|archive|nav-/)) {
if (pathname.match(/\.md$/)) {
var outputFilename = pathname.replace(/\.md$/, ".html")
var markdown = fs.readFileSync(pathname, "utf-8")
+ var anchors = {}
var fixed = markdown
- .replace(/(`[^`]+?)<(.*`)/gim, "$1<$2") // fix generic syntax
.replace(/`((?:\S| -> |, )+)(\|)(\S+)`/gim, function(match, a, b, c) { // fix pipes in code tags
return "" + (a + b + c).replace(/\|/g, "|") + ""
})
- .replace(/(^# .+?(?:\r?\n){2,}?)(?:(-(?:.|\r|\n)+?)((?:\r?\n){2,})|)/m, function(match, title, nav, space) { // inject menu
+ .replace(/(^# .+?(?:\r?\n){2,}?)(?:(-(?:.|\r|\n)+?)((?:\r?\n){2,})|)/m, function(match, title, nav) { // inject menu
var file = path.basename(pathname)
var link = new RegExp("([ \t]*)(- )(\\[.+?\\]\\(" + file + "\\))")
var replace = function(match, space, li, link) {
@@ -40,17 +44,31 @@ function generate(pathname) {
.replace(/(\]\([^\)]+)(\.md)/gim, function(match, path, extension) {
return path + (path.match(/http/) ? extension : ".html")
}) // fix links
+ var markedHtml = marked(fixed)
+ .replace(/(\W)Array<([^/<]+?)>/gim, "$1Array<$2>") // Fix type signatures containing Array<...>
+ var title = fixed.match(/^#([^\n\r]+)/i) || []
var html = layout
- .replace(/\[version\]/, version) // update version
- .replace(/\[body\]/, marked(fixed))
- .replace(/([^<]+?)<\/h5>/gim, function(match, id, text) { // fix anchors
- return "" + text + "
"
+ .replace(/Mithril\.js<\/title>/, "" + title[1] + " - Mithril.js")
+ .replace(/\[version\]/g, version) // update version
+ .replace(/\[body\]/, markedHtml)
+ .replace(/(.+?)<\/h.>/gim, function(match, n, id, text) { // fix anchors
+ var anchor = text.toLowerCase().replace(/<(\/?)code>/g, "").replace(/.+?<\/a>/g, "").replace(/\.|\[|\]|"|\/|\(|\)/g, "").replace(/\s/g, "-");
+
+ if(anchor in anchors) {
+ anchor += ++anchors[anchor]
+ } else {
+ anchors[anchor] = 0;
+ }
+
+ return `${text}`;
})
- fs.writeFileSync("docs/archive/" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
+ fs.writeFileSync("./dist/archive/v" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
+ fs.writeFileSync("./dist/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
}
- else {
- fs.writeFileSync("docs/archive/" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, "utf-8"), "utf-8")
+ else if (!pathname.match(/lint|generate/)) {
+ var encoding = (/\.(ico|png)$/i).test(path.extname(pathname)) ? "binary" : "utf-8";
+ fs.writeFileSync("./dist/archive/v" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, encoding), encoding)
+ fs.writeFileSync("./dist/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, encoding), encoding)
}
}
}
-
diff --git a/docs/hyperscript.md b/docs/hyperscript.md
index 6108f2d5..8ebe297b 100644
--- a/docs/hyperscript.md
+++ b/docs/hyperscript.md
@@ -170,7 +170,7 @@ Mithril supports both strings and objects as valid `style` values. In other word
```javascript
m("div", {style: "background:red;"})
m("div", {style: {background: "red"}})
-m("div[style=background:red")
+m("div[style=background:red]")
```
Using a string as a `style` would overwrite all inline styles in the element if it is redrawn, and not only CSS rules whose values have changed.
@@ -181,7 +181,7 @@ Mithril does not attempt to add units to number values.
### Events
-Mithril supports event handler binding for all DOM events, including events whose specs do not define an `on` property, such as `touchstart`
+Mithril supports event handler binding for all DOM events, including events whose specs do not define an `on${event}` property, such as `touchstart`
```javascript
function doSomething(e) {
diff --git a/docs/introduction.md b/docs/index.md
similarity index 53%
rename from docs/introduction.md
rename to docs/index.md
index 671541e5..22bb9021 100644
--- a/docs/introduction.md
+++ b/docs/index.md
@@ -12,36 +12,71 @@
### What is Mithril?
-Mithril is a client-side Javascript framework for building Single Page Applications.
+Mithril is a modern client-side Javascript framework for building Single Page Applications.
It's small (< 8kb gzip), fast and provides routing and XHR utilities out of the box.
+
+
+
Download size
+
Mithril (8kb)
+
+
Vue + Vue-Router + Vuex + fetch (40kb)
+
+
React + React-Router + Redux + fetch (64kb)
+
+
Angular (135kb)
+
+
+
+
Performance
+
Mithril (6.4ms)
+
+
Vue (9.8ms)
+
+
React (12.1ms)
+
+
Angular (11.5ms)
+
+
+
+
+Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess.
+
If you are an experienced developer and want to know how Mithril compares to other frameworks, see the [framework comparison](framework-comparison.md) page.
----
+Mithril supports browsers all the way back to IE9, no polyfills required.
-Note: This introduction assumes you have basic level of Javacript knowledge. If you don't, there are many great resources to learn. [Speaking Javascript](http://speakingjs.com/es5/index.html) is a good e-book for absolute beginners. If you're already familiar with other programming languages, the [Eloquent Javascript](http://eloquentjavascript.net/) e-book might be more suitable for you. [Codecademy](https://www.codecademy.com/learn/javascript) is another good resource that emphasizes learning via interactivity.
+---
### Getting started
-The easiest way to try out Mithril is to include it from a CDN, and follow this tutorial. It'll cover the majority of the API surface (including routing and XHR) but it'll only take 10 minutes.
+An easy way to try out Mithril is to include it from a CDN and follow this tutorial. It'll cover the majority of the API surface (including routing and XHR) but it'll only take 10 minutes.
Let's create an HTML file to follow along:
```markup
-
-
-
+
+ // your code goes here!
+
+
```
+To make things simpler you can fork this pen which already has the latest version of mithril loaded.
+
+See the Pen Mithril Scaffold by Pat Cavit (@tivac) on CodePen.
+
+
+Mithril is also loaded onto this page already, so you can start poking at the `m` object in the developer console right away if you'd like!
+
---
### Hello world
-Let's start as small as well can: render some text on screen. Copy the code below into your file (and by copy, I mean type it out - you'll learn better)
+Let's start as small as we can: render some text on screen. Copy the code below into your file (and by copy, I mean type it out - you'll learn better)
```javascript
var root = document.body
@@ -57,6 +92,11 @@ m.render(root, "My first app")
As you can see, you use the same code to both create and update HTML. Mithril automatically figures out the most efficient way of updating the text, rather than blindly recreating it from scratch.
+#### Live Example
+
+See the Pen Mithril Hello World by Pat Cavit (@tivac) on CodePen.
+
+
---
### DOM elements
@@ -67,7 +107,7 @@ Let's wrap our text in an `` tag.
m.render(root, m("h1", "My first app"))
```
-The `m()` function can be used to describe any HTML structure you want. So if you to add a class to the ``:
+The `m()` function can be used to describe any HTML structure you want. So if you need to add a class to the ``:
```javascript
m("h1", {class: "title"}, "My first app")
@@ -91,9 +131,14 @@ m("main", [
])
```
+#### Live Example
+
+
See the Pen Simple Mithril Example by Pat Cavit (@tivac) on CodePen.
+
+
Note: If you prefer `` syntax, [it's possible to use it via a Babel plugin](jsx.md).
-```markup
+```jsx
// HTML syntax via Babel's JSX plugin
My first app
@@ -142,7 +187,8 @@ var Hello = {
view: function() {
return m("main", [
m("h1", {class: "title"}, "My first app"),
- m("button", {onclick: function() {count++}}, count + " clicks"), // changed this line
+ // changed the next line
+ m("button", {onclick: function() {count++}}, count + " clicks"),
])
}
}
@@ -156,6 +202,11 @@ You can now update the label of the button by clicking the button. Since we used
If you're wondering about performance, it turns out Mithril is very fast at rendering updates, because it only touches the parts of the DOM it absolutely needs to. So in our example above, when you click the button, the text in it is the only part of the DOM Mithril actually updates.
+#### Live Example
+
+See the Pen Mithril Component Example by Pat Cavit (@tivac) on CodePen.
+
+
---
### Routing
@@ -189,6 +240,11 @@ The `"/splash"` right after `root` means that's the default route, i.e. if the h
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.
+#### Live Example
+
+See the Pen Mithril Routing Example by Pat Cavit (@tivac) on CodePen.
+
+
---
### XHR
@@ -197,16 +253,16 @@ Basically, XHR is just a way to talk to a server.
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.
-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)), `data` is the payload that we're sending to the endpoint and `useCredentials` means to enable cookies (a requirement for the REM API to work)
+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)), `data` is the payload that we're sending to the endpoint and `withCredentials` means to enable cookies (a requirement for the REM API to work)
```javascript
var count = 0
var increment = function() {
m.request({
method: "PUT",
- url: "http://rem-rest-api.herokuapp.com/api/tutorial/1",
+ url: "//rem-rest-api.herokuapp.com/api/tutorial/1",
data: {count: count + 1},
- useCredentials: true,
+ withCredentials: true,
})
.then(function(data) {
count = parseInt(data.count)
@@ -231,6 +287,11 @@ var Hello = {
Clicking the button should now update the count.
+#### Live Example
+
+See the Pen Mithril XHR Example by Pat Cavit (@tivac) on CodePen.
+
+
---
We covered how to create and update HTML, how to create components, routes for a Single Page Application, and interacted with a server via XHR.
diff --git a/docs/installation.md b/docs/installation.md
index ddc85853..c4604ad5 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -8,7 +8,7 @@
If you're new to Javascript or just want a very simple setup to get your feet wet, you can get Mithril from a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network):
```markup
-
+
```
---
@@ -19,7 +19,7 @@ If you're new to Javascript or just want a very simple setup to get your feet we
```bash
# 1) install
-npm install mithril@rewrite --save
+npm install mithril --save
npm install webpack --save
@@ -55,7 +55,7 @@ npm init --yes
Then, to install Mithril, run:
```bash
-npm install mithril@rewrite --save
+npm install mithril --save
```
This will create a folder called `node_modules`, and a `mithril` folder inside of it. It will also add an entry under `dependencies` in the `package.json` file
@@ -178,7 +178,7 @@ Live reload is a feature where code changes automatically trigger the page to re
```bash
# 1) install
-npm install mithril@rewrite --save
+npm install mithril --save
npm install budo -g
# 2) add this line into the scripts section in package.json
@@ -219,7 +219,7 @@ If you don't have the ability to run a bundler script due to company security po
Hello world
-
+