diff --git a/README.md b/README.md index b6e99098..bfe22f41 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,6 @@ There are over 4000 assertions in the test suite, and tests cover even difficult ## Modularity -Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at 7.59 KB min+gzip +Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at 7.60 KB min+gzip In addition, Mithril is now completely modular: you can import only the modules that you need and easily integrate 3rd party modules if you wish to use a different library for routing, ajax, and even rendering diff --git a/api/tests/test-router.js b/api/tests/test-router.js index d70a7c23..6a281237 100644 --- a/api/tests/test-router.js +++ b/api/tests/test-router.js @@ -1074,43 +1074,6 @@ o.spec("route", function() { }) }) - o("calling route.set invalidates pending onmatch resolution", function(done) { - var rendered = false - var resolved - $window.location.href = prefix + "/a" - route(root, "/a", { - "/a": { - onmatch: function() { - return new Promise(function(resolve) { - callAsync(function() { - callAsync(function() { - resolve({view: function() {}}) - }) - }) - }) - }, - render: function(vnode) { - rendered = true - resolved = "a" - } - }, - "/b": { - view: function() { - resolved = "b" - } - } - }) - - route.set("/b") - - callAsync(function() { - o(rendered).equals(false) - o(resolved).equals("b") - - done() - }) - }) - o("calling route.set invalidates pending onmatch resolution", function(done) { var rendered = false var resolved diff --git a/bundler/tests/test-bundler.js b/bundler/tests/test-bundler.js index 60859a11..3055bf7c 100644 --- a/bundler/tests/test-bundler.js +++ b/bundler/tests/test-bundler.js @@ -214,14 +214,14 @@ o.spec("bundler", function() { write("c.js", `var cc = 2\nmodule.exports = cc`) bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(`new function() {\nvar x = {}\var bb = 1\nnx.b = bb\nvar cc = 1\nx.c = cc\n}`) + o(read("out.js")).equals(`new function() {\nvar x = {}\nvar bb = 1\nx.b = bb\nvar cc = 2\nx.c = cc\n}`) remove("a.js") remove("b.js") remove("c.js") remove("out.js") }) - o("works if assigned to property", function() { + o("works if assigned to property using bracket notation", function() { write("a.js", `var x = {}\nx["b"] = require("./b")\nx["c"] = require("./c")`) write("b.js", `var bb = 1\nmodule.exports = bb`) write("c.js", `var cc = 2\nmodule.exports = cc`) diff --git a/docs/api.md b/docs/api.md index cf25070e..872ab605 100644 --- a/docs/api.md +++ b/docs/api.md @@ -45,19 +45,19 @@ m.route(document.body, "/home", { }) ``` -#### m.route.set(path) - [docs](route.md#routeset) +#### m.route.set(path) - [docs](route.md#mrouteset) ```javascript m.route.set("/home") ``` -#### m.route.get() - [docs](route.md#routeget) +#### m.route.get() - [docs](route.md#mrouteget) ```javascript var currentRoute = m.route.get() ``` -#### m.route.prefix(prefix) - [docs](route.md#routeprefix) +#### m.route.prefix(prefix) - [docs](route.md#mrouteprefix) Call this before `m.route()` @@ -65,7 +65,7 @@ Call this before `m.route()` m.route.prefix("#!") ``` -#### m.route.link() - [docs](route.md#routelink) +#### m.route.link() - [docs](route.md#mroutelink) ```javascript m("a[href='/Home']", {oncreate: m.route.link}, "Go to home page") @@ -171,4 +171,3 @@ var Counter = { m.mount(document.body, Counter) ``` - diff --git a/docs/autoredraw.md b/docs/autoredraw.md new file mode 100644 index 00000000..97a4575d --- /dev/null +++ b/docs/autoredraw.md @@ -0,0 +1,122 @@ +# The auto-redraw system + +Mithril implements a virtual DOM diffing system for fast rendering, and in addition, it offers various mechanisms to gain granular control over the rendering of an application. + +When used idiomatically, Mithril employs an auto-redraw system that synchronizes the DOM whenever changes are made in the data layer. The auto-redraw system becomes enabled when you call `m.mount` or `m.route` (but it stays disabled if your app is bootstrapped solely via `m.render` calls). + +The auto-redraw system simply consists of triggering a re-render function behind the scenes after certain functions complete. + +### After event handlers + +Mithril automatically redraws after DOM event handlers that are defined in a Mithril view: + +```javascript +var MyComponent = { + view: function() { + return m("div", {onclick: doSomething}) + } +} + +function doSomething() { + // a redraw happens synchronously after this function runs +} + +m.mount(document.body, MyComponent) +``` + +You can disable an auto-redraw for specific events by setting `e.redraw` to `false`. + +```javascript +var MyComponent = { + view: function() { + return m("div", {onclick: doSomething}) + } +} + +function doSomething(e) { + e.redraw = false + // no longer triggers a redraw when the div is clicked +} + +m.mount(document.body, MyComponent) +``` + + +### After m.request + +Mithril automatically redraws after [`m.request`](request.md) completes: + +```javascript +m.request("/api/v1/users").then(function() { + // a redraw happens after this function runs +}) +``` + +You can disable an auto-redraw for a specific request by setting the `background` option to true: + +```javascript +m.request("/api/v1/users", {background: true}).then(function() { + // does not trigger a redraw +}) +``` + + +### After route changes + +Mithril automatically redraws after [`m.route.set()`](route.md#mrouteset) calls (or route changes via links that use [`m.route.link`](route.md#mroutelink) + +```javascript +var RoutedComponent = { + view: function() { + return [ + // a redraw happens asynchronously after the route changes + m("a", {href: "/", oncreate: m.route.link}), + m("div", { + onclick: function() { + m.route.set("/") + } + }), + ] + } +} + +m.route(document.body, "/", { + "/": RoutedComponent, +}) +``` + +--- + +### When Mithril does not redraws + +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 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. + +If you need to explicitly trigger a redraw within a lifecycle method, you should call `m.redraw()`, which will trigger an asynchronous redraw. + +```javascript +var StableComponent = { + oncreate: function(vnode) { + vnode.state.height = vnode.dom.offsetHeight + m.redraw() + }, + view: function() { + return m("div", "This component is " + vnode.state.height + "px tall") + } +} +``` + +Mithril does not auto-redraw vnode trees that are rendered via `m.render`. This means redraws do not occur after event changes and `m.request` calls for templates that were rendered via `m.render`. Thus, if your architecture requires manual control over when rendering occurs (as can sometimes be the case when using libraries like Redux), you should use `m.render` instead of `m.mount`. + +Remember that `m.render` expects a vnode tree, and `m.mount` expects a component: + +```javascript +// wrap the component in a m() call for m.render +m.render(document.body, m(MyComponent)) + +// don't wrap the component for m.mount +m.mount(document.body, MyComponent) +``` + +Mithril may also avoid auto-redrawing if the frequency of requested redraws is higher than one animation frame (typically around 16ms). This means, for example, that when using fast-firing events like `onresize` or `onscroll`, Mithril will automatically throttle the number of redraws to avoid lag. diff --git a/docs/change-log.md b/docs/change-log.md index b67fb4c7..c97d7d12 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -69,14 +69,14 @@ In `v0.2.x` components could be created using either `m(component)` or `m.compon ```javascript // These are equivalent -m.component(component); -m(component); +m.component(component) +m(component) ``` ### `v1.x` ```javascript -m(component); +m(component) ``` --- @@ -93,7 +93,7 @@ m("div", { // runs on each redraw // isInitialized is a boolean representing if the node has been added to the DOM } -}); +}) ``` ### `v1.x` @@ -114,7 +114,7 @@ m("div", { 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`. @@ -138,7 +138,7 @@ In v0.2.x, Mithril allowed 'redraw locks' which temporarily prevented blocked dr ```javascript m("div", { onclick : function(e) { - m.redraw.strategy("none"); + m.redraw.strategy("none") } }) ``` @@ -148,7 +148,7 @@ m("div", { ```javascript m("div", { onclick : function(e) { - e.redraw = false; + e.redraw = false } }) ``` @@ -164,15 +164,15 @@ In `v1.x` there is no more `controller` property in components, use `oninit` ins ```javascript m.mount(document.body, { controller : function() { - var ctrl = this; + var ctrl = this - ctrl.fooga = 1; + ctrl.fooga = 1 }, view : function(ctrl) { - return m("p", ctrl.fooga); + return m("p", ctrl.fooga) } -}); +}) ``` ### `v1.x` @@ -180,29 +180,29 @@ m.mount(document.body, { ```javascript m.mount(document.body, { oninit : function(vnode) { - vnode.state.fooga = 1; + vnode.state.fooga = 1 }, view : function(vnode) { - return m("p", vnode.state.fooga); + 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 + var state = this // this is bound to vnode.state by default - state.fooga = 1; + state.fooga = 1 }, view : function(vnode) { - var state = this; // this is bound to vnode.state by default + var state = this // this is bound to vnode.state by default - return m("p", state.fooga); + return m("p", state.fooga) } -}); +}) ``` --- @@ -222,9 +222,9 @@ var component = { view : function(ctrl, options) { // options.fooga == 1 } -}; +} -m("div", m.component(component, { fooga : 1 })); +m("div", m.component(component, { fooga : 1 })) ``` ### `v1.x` @@ -238,9 +238,9 @@ var component = { view : function(vnode) { // vnode.attrs.fooga == 1 } -}; +} -m("div", m(component, { fooga : 1 })); +m("div", m(component, { fooga : 1 })) ``` --- @@ -258,7 +258,7 @@ m.mount(document.body, { view : function(ctrl, options) { // ... } -}); +}) ``` ### `v1.x` @@ -273,7 +273,7 @@ m.mount(document.body, { // Use vnode.state instead of ctrl // Use vnode.attrs instead of options } -}); +}) ``` --- @@ -285,13 +285,13 @@ In `v0.2.x` you could pass components as the second argument of `m()` w/o any wr ### `v0.2.x` ```javascript -m("div", component); +m("div", component) ``` ### `v1.x` ```javascript -m("div", m(component)); +m("div", m(component)) ``` --- @@ -305,8 +305,8 @@ 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.mount(element, m('i', 'hello')) +m.mount(element, m(Component, attrs)) m.route(element, '/', { '/': m('b', 'bye') @@ -316,8 +316,8 @@ m.route(element, '/', { ### `v1.x` ```javascript -m.mount(element, {view: function () {return m('i', 'hello')}}); -m.mount(element, {view: function () {return m(Component, attrs)}}); +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')}} @@ -333,15 +333,15 @@ In `v0.2.x` the routing mode could be set by assigning a string of `"pathname"`, ### `v0.2.x` ```javascript -m.route.mode = "pathname"; -m.route.mode = "search"; +m.route.mode = "pathname" +m.route.mode = "search" ``` ### `v1.x` ```javascript -m.route.prefix(""); -m.route.prefix("?"); +m.route.prefix("") +m.route.prefix("?") ``` --- @@ -383,17 +383,17 @@ In `v0.2.x` all interaction w/ the current route happened via `m.route()`. In `v m.route() // Setting a new route -m.route("/other/route"); +m.route("/other/route") ``` ### `v1.x` ```javascript // Getting the current route -m.route.get(); +m.route.get() // Setting a new route -m.route.set("/other/route"); +m.route.set("/other/route") ``` --- @@ -408,10 +408,10 @@ In `v0.2.x` reading route params was all handled through the `m.route.param()` m m.route(document.body, "/booga", { "/:attr" : { view : function() { - m.route.param("attr"); // "booga" + m.route.param("attr") // "booga" } } -}); +}) ``` ### `v1.x` @@ -420,13 +420,13 @@ m.route(document.body, "/booga", { m.route(document.body, "/booga", { "/:attr" : { oninit : function(vnode) { - vnode.attrs.attr; // "booga" + vnode.attrs.attr // "booga" }, view : function(vnode) { - vnode.attrs.attr; // "booga" + vnode.attrs.attr // "booga" } } -}); +}) ``` --- @@ -511,16 +511,16 @@ Additionally, if the `extract` option is passed to `m.request` the return value ```javascript var greetAsync = function() { - var deferred = m.deferred(); + var deferred = m.deferred() setTimeout(function() { - deferred.resolve("hello"); - }, 1000); - return deferred.promise; -}; + 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 + .then(function(value) {console.log(value)}) //logs "hello world" after 1 second ``` ### `v1.x` @@ -528,13 +528,13 @@ greetAsync() ```javascript var greetAsync = new Promise(function(resolve){ setTimeout(function() { - resolve("hello"); - }, 1000); -}); + resolve("hello") + }, 1000) +}) greetAsync() .then(function(value) {return value + " world"}) - .then(function(value) {console.log(value)}); //logs "hello world" after 1 second + .then(function(value) {console.log(value)}) //logs "hello world" after 1 second ``` --- @@ -551,7 +551,7 @@ m.sync([ m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }), ]) .then(function (users) { - console.log("Contributors:", users[0].name, "and", users[1].name); + console.log("Contributors:", users[0].name, "and", users[1].name) }) ``` @@ -563,7 +563,7 @@ Promise.all([ m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }), ]) .then(function (users) { - console.log("Contributors:", users[0].name, "and", users[1].name); + console.log("Contributors:", users[0].name, "and", users[1].name) }) ``` @@ -618,11 +618,11 @@ In v0.2.x it was possible to force mithril to redraw immediately by passing a tr ### `v0.2.x` ```javascript -m.redraw(true); // redraws immediately & synchronously +m.redraw(true) // redraws immediately & synchronously ``` ### `v1.x` ```javascript -m.redraw(); // schedules a redraw on the next requestAnimationFrame tick +m.redraw() // schedules a redraw on the next requestAnimationFrame tick ``` diff --git a/docs/components.md b/docs/components.md index 47f8d1c3..7e454519 100644 --- a/docs/components.md +++ b/docs/components.md @@ -28,13 +28,13 @@ m(Example) ### Passing data to components -Data can be passed to component instances through an `attrs` object as a parameter in the hyperscript function: +Data can be passed to component instances by passing an `attrs` object as the second parameter in the hyperscript function: ```javascript m(Example, {name: "Floyd"}) ``` -`attrs` data can be accessed in the component's view or lifecycle methods via the `vnode`: +This data can be accessed in the component's view or lifecycle methods via the `vnode.attrs`: ```javascript var Example = { @@ -44,7 +44,7 @@ var Example = { } ``` -NOTE: Lifecycle methods can also be provided via attrs, so you should avoid using the lifecycle method names for your own callbacks as they will be invoked by Mithril. Use lifecycle methods in `attrs` only when you specifically wish to create lifecycle hooks. +NOTE: Lifecycle methods can also be provided via the `attrs` object, so you should avoid using the lifecycle method names for your own callbacks as they would also be invoked by Mithril. Use lifecycle methods in `attrs` only when you specifically wish to create lifecycle hooks. --- @@ -108,7 +108,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 deep-cloned for every instance of the component. This allows simple state initialization. +Any property attached to the component object is copied for every instance of the component. This allows simple state initialization. In the example below, `data` is a property of the `ComponentWithInitialState` component's state object. diff --git a/docs/es6.md b/docs/es6.md new file mode 100644 index 00000000..2f629223 --- /dev/null +++ b/docs/es6.md @@ -0,0 +1,133 @@ +# ES6 + +- [Setup](#setup) +- [Using Babel with Webpack](#using-babel-with-webpack) + +--- + +Mithril is written in ES5, and is fully compatible with ES6 as well. + +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. + +### Setup + +The simplest way to setup an ES6 compilation toolchain is via [Babel](https://babeljs.io/). + +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: + +```bash +npm init -y +``` + +If you want to use Webpack and Babel together, [skip to the section below](#using-babel-with-webpack). + +To install Babel as a standalone tool, use this command: + +```bash +npm install babel-cli babel-preset-es2015 babel-plugin-transform-react-jsx --save-dev +``` + +Create a `.babelrc` file: + +```json +{ + "presets": ["es2015"], + "plugins": [ + ["transform-react-jsx", { + "pragma": "m" + }] + ] +} +``` + +To run Babel as a standalone tool, run this from the command line: + +```bash +babel src --out-dir bin --source-maps +``` + +#### Using Babel with Webpack + +If you're already using Webpack as a bundler, you can integrate Babel to Webpack by following these steps. + +```bash +npm install babel-core babel-loader babel-preset-es2015 babel-plugin-transform-react-jsx --save-dev +``` + +Create a `.babelrc` file: + +```json +{ + "presets": ["es2015"], + "plugins": [ + ["transform-react-jsx", { + "pragma": "m" + }] + ] +} +``` + +Next, create a file called `webpack.config.js` + +```javascript +module.exports = { + entry: './src/index.js', + output: { + path: './bin', + filename: 'app.js', + }, + module: { + loaders: [{ + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader' + }] + } +} +``` + +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`. + +To run the bundler, setup an npm script. Open `package.json` and add this entry under `"scripts"`: + +```json +{ + "name": "my-project", + "scripts": { + "start": "webpack -d --watch" + } +} +``` + +You can now then run the bundler by running this from the command line: + +```bash +npm start +``` + +#### Production build + +To generate a minified file, open `package.json` and add a new npm script called `build`: + +```json +{ + "name": "my-project", + "scripts": { + "start": "webpack -d --watch", + "build": "webpack -p" + } +} +``` + +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/): + +```json +{ + "name": "my-project", + "scripts": { + "start": "webpack -d --watch", + "build": "webpack -p", + "heroku-postbuild": "webpack -p" + } +} +``` diff --git a/docs/framework-comparison.md b/docs/framework-comparison.md index 029d0319..95aa2137 100644 --- a/docs/framework-comparison.md +++ b/docs/framework-comparison.md @@ -1,12 +1,30 @@ # Framework comparison +- [Why not X?](#why-not-insert-favorite-framework-here) +- [Why use Mithril?](#why-use-mithril) - [React](#react) - [Angular](#angular) - [Vue](#vue) If you're reading this page, you probably have used other frameworks to build applications, and you want to know if Mithril would help you solve your problems more effectively. -In this page, you will find common arguments about other frameworks and comments on where Mithril is similar or why it differs from them. +--- + +## 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. + +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. + +--- + +## 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. + +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. --- @@ -14,15 +32,15 @@ In this page, you will find common arguments about other frameworks and comments React is a view library maintained by Facebook. -React and Mithril share a lot of similarities +React and Mithril share a lot of similarities. If you already learned React, you already know almost all you need to build apps with Mithril. - They both use virtual DOM, lifecycle methods and key-based reconciliation - 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 too much choice can lead to analysis paralysis, excessive configuration/boilerplate complexity, and [bikeshedding](https://en.wiktionary.org/wiki/bikeshedding). At worst, it can lead to a hodge-podge of different dependencies and architectures, making it difficult for new team members to transfer knowledge from one project to the next. +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. -Mithril has built-in modules for common necessities such as routing and XHR. This batteries-included approach is preferable for teams that value consistency and ease of onboarding. +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 @@ -80,6 +98,8 @@ React documentation is clear and well written, and includes a good API reference 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. +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). + --- ## Angular @@ -121,7 +141,7 @@ Angular 2 has a lot more concepts to understand: on the language level, Typescri 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. +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 @@ -140,9 +160,9 @@ 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 API 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 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 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. +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. ### Performance @@ -164,14 +184,16 @@ Vue | Mithril ### Complexity -One could argue that Vue templates are more complex than Mithril due to the fact that they use Vue-specific syntax for logic flow, whereas Mithril view language is always just Javascript. +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. -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) so Vue codebases may be less consistent across projects and have higher onboarding costs in terms of technologies compared to idiomatic Mithril projects. - -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 has a risk of causing architectural fragmentation in long-lived projects. - -The Mithril [tutorial](simple-application.md) implements an *idiomatic* application. This means that while it's *possible* to structure a Mithril codebase in different ways, there's a recommended way to architecture the application. +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. ### Documentation -Both Vue and Mithril have thorough documentation. Both include a good API reference with examples, tutorials for getting started, as well as pages covering various advanced concepts. \ No newline at end of file +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. + +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'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 [lomger, more thorough tutorial](simple-application.md) if that's not enough). \ No newline at end of file diff --git a/docs/generate.js b/docs/generate.js index f8763522..a9d0f759 100644 --- a/docs/generate.js +++ b/docs/generate.js @@ -37,8 +37,11 @@ function generate(pathname) { var modified = guides.match(link) ? guides.replace(link, replace) : methods.replace(link, replace) return title + modified + "\n\n" }) - .replace(/\.md/gim, ".html") // fix links + .replace(/(\]\([^\)]+)(\.md)/gim, function(match, path, extension) { + return path + (path.match(/http/) ? extension : ".html") + }) // fix links var html = layout + .replace(/\[version\]/, version) // update version .replace(/\[body\]/, marked(fixed)) .replace(/