From 4a641092dc08d441458724ed020ae4d0f935fb35 Mon Sep 17 00:00:00 2001 From: Isiah Meadows Date: Tue, 27 Nov 2018 18:04:15 -0500 Subject: [PATCH] Officially drop IE9-10 support, pull out our hacks (#2296) - I also fixed a bunch of related comments - I had to polyfill `requestAnimationFrame` for Node - Drive-by: run `eslint . --fix` - Drive-by: update transpiling info in CONTRIBUTING.md - Drive-by: we aren't the only ones going semicolon-free --- README.md | 2 +- api/redraw.js | 11 +++-------- api/tests/test-redraw.js | 13 +++++++++++++ docs/change-log.md | 1 + docs/contributing.md | 8 +++----- docs/index.md | 2 +- docs/lint.js | 2 +- docs/redraw.md | 4 ++-- docs/route.md | 6 +++--- module/module.js | 2 ++ ospec/tests/test-ospec.js | 2 +- render/render.js | 4 ++-- request/tests/test-request.js | 6 +++--- test-utils/domMock.js | 6 +++--- test-utils/tests/test-components.js | 2 +- test-utils/tests/test-domMock.js | 26 +++++++++++++------------- tests/test-api.js | 2 +- 17 files changed, 54 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 5f2e6bb3..322fa278 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A modern client-side Javascript framework for building Single Page Applications. Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍. -Browsers all the way back to IE9 are supported, no polyfills required 👌. +Mithril supports IE11, Firefox ESR, and the last two versions of Firefox, Edge, Safari, and Chrome. No polyfills required. 👌 ## Installation diff --git a/api/redraw.js b/api/redraw.js index a6549975..aa513e38 100644 --- a/api/redraw.js +++ b/api/redraw.js @@ -3,18 +3,13 @@ var coreRenderer = require("../render/render") function throttle(callback) { - //60fps translates to 16.6ms, round it down since setTimeout requires int - var delay = 16 - var last = 0, pending = null - var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout + var pending = null return function() { - var elapsed = Date.now() - last if (pending === null) { - pending = timeout(function() { + pending = requestAnimationFrame(function() { pending = null callback() - last = Date.now() - }, delay - elapsed) + }) } } } diff --git a/api/tests/test-redraw.js b/api/tests/test-redraw.js index 65831bb0..0652f351 100644 --- a/api/tests/test-redraw.js +++ b/api/tests/test-redraw.js @@ -5,6 +5,19 @@ var domMock = require("../../test-utils/domMock") var throttleMocker = require("../../test-utils/throttleMock") var apiRedraw = require("../../api/redraw") +// Because Node doesn't have this. +if (typeof requestAnimationFrame !== "function") { + global.requestAnimationFrame = (function (delay, last) { + return function(callback) { + var elapsed = Date.now() - last + return setTimeout(function() { + callback() + last = Date.now() + }, delay - elapsed) + } + })(16, 0) +} + o.spec("redrawService", function() { var root, redrawService, $document o.beforeEach(function() { diff --git a/docs/change-log.md b/docs/change-log.md index 47525c41..816a9a14 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -36,6 +36,7 @@ #### News +- Mithril now only officially supports IE11, Firefox ESR, and the last two versions of Chrome/FF/Edge/Safari. ([#2296](https://github.com/MithrilJS/mithril.js/pull/2296)) - API: Introduction of `m.redraw.sync()` ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592)) - API: Event handlers may also be objects with `handleEvent` methods ([#1949](https://github.com/MithrilJS/mithril.js/pull/1949), [#2222](https://github.com/MithrilJS/mithril.js/pull/2222)). - API: `m.route.link` accepts an optional `options` object ([#1930](https://github.com/MithrilJS/mithril.js/pull/1930)) diff --git a/docs/contributing.md b/docs/contributing.md index 4e672470..febd682c 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -79,17 +79,15 @@ This simplifies the workflow for bug fixes, which means they can be fixed faster -## Why doesn't the Mithril codebase use ES6 via Babel? Would a PR to upgrade be welcome? +## Why doesn't the Mithril codebase use ES6 via Babel or Bublé? Would a PR to upgrade be welcome? -Being able to run Mithril raw source code in IE is a requirement for all browser-related modules in this repo. - -In addition, ES6 features are usually less performant than equivalent ES5 code, and transpiled code is bulkier. +Being able to run Mithril's raw source code in all supported browsers is a requirement for all browser-related modules in this repo. In addition, transpiled code is generally much bulkier. ## Why doesn't the Mithril codebase use trailing semi-colons? Would a PR to add them be welcome? -I don't use them. Adding them means the semi-colon usage in the codebase will eventually become inconsistent. +I don't use them. Adding them means the semi-colon usage in the codebase will eventually become inconsistent. Besides, [we aren't the only one who've decided to drop the semicolon](https://standardjs.com/#who-uses-javascript-standard-style). (We don't use Standard, though.) diff --git a/docs/index.md b/docs/index.md index 97dd2b13..979212db 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,7 +44,7 @@ Mithril is used by companies like Vimeo and Nike, and open source platforms like 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. +Mithril supports IE11, Firefox ESR, and the last two versions of Firefox, Edge, Safari, and Chrome. No polyfills required. --- diff --git a/docs/lint.js b/docs/lint.js index 9fcb93b4..6a3d8cb9 100644 --- a/docs/lint.js +++ b/docs/lint.js @@ -183,4 +183,4 @@ traverseDirectory("./docs", function(pathname) { }) } }) -.then(process.exit) + .then(process.exit) diff --git a/docs/redraw.md b/docs/redraw.md index 0d079592..f53612a5 100644 --- a/docs/redraw.md +++ b/docs/redraw.md @@ -44,7 +44,7 @@ When callbacks outside of Mithril run, you need to notify Mithril's rendering en To trigger a redraw, call `m.redraw()`. Note that `m.redraw` only works if you used `m.mount` or `m.route`. If you rendered via `m.render`, you should use `m.render` to redraw. -`m.redraw()` always triggers an asynchronous redraws, whereas `m.redraw.sync()` triggers a synchronous one. `m.redraw()` is tied to `window.requestAnimationFrame()` (we provide a fallback for IE9). It will thus typically fire at most 60 times per second. It may fire faster if your monitor has a higher refresh rate. +`m.redraw()` always triggers an asynchronous redraws, whereas `m.redraw.sync()` triggers a synchronous one. `m.redraw()` is tied to `window.requestAnimationFrame()`. It will thus typically fire at most 60 times per second. It may fire faster if your monitor has a higher refresh rate. `m.redraw.sync()` is mostly intended to make videos play work in iOS. That only works in response to user-triggered events. It comes with several caveat: @@ -52,4 +52,4 @@ To trigger a redraw, call `m.redraw()`. Note that `m.redraw` only works if you u - `m.redraw.sync()` called from an event handler can cause the DOM to be modified while an event is bubbling. Depending on the structure of the old and new DOM trees, the event can finish the bubbling phase in the new tree and trigger unwanted handlers. - It is not throttled. One call to `m.redraw.sync()` causes immediately one `m.render()` call per root registered with [`m.mount()`](mount.md) or [`m.route()`](route.md). -`m.redraw()` doesn't have any of those issues, you can call it from wherever you like. \ No newline at end of file +`m.redraw()` doesn't have any of those issues, you can call it from wherever you like. diff --git a/docs/route.md b/docs/route.md index c48f0e56..07357a8d 100644 --- a/docs/route.md +++ b/docs/route.md @@ -218,11 +218,11 @@ The routing strategy dictates how a library might actually implement routing. Th - Using the querystring. A URL using this strategy typically looks like `http://localhost/?/page1` - Using the pathname. A URL using this strategy typically looks like `http://localhost/page1` -Using the hash strategy is guaranteed to work in browsers that don't support `history.pushState` (namely, Internet Explorer 9), because it can fall back to using `onhashchange`. Use this strategy if you want to support IE9. +Using the hash strategy is guaranteed to work in browsers that don't support `history.pushState`, because it can fall back to using `onhashchange`. Use this strategy if you want to keep the hashes purely local. -The querystring strategy also technically works in IE9, but it falls back to reloading the page. Use this strategy if you want to support anchored links and you are not able to make the server-side necessary to support the pathname strategy. +The querystring strategy allows server-side detection, but it doesn't appear as a normal path. Use this strategy if you want to support and potentially detect anchored links server-side and you are not able to make the changes necessary to support the pathname strategy (like if you're using Apache and can't modify your .htaccess). -The pathname strategy produces the cleanest looking URLs, but does not work in IE9 *and* requires setting up the server to serve the single page application code from every URL that the application can route to. Use this strategy if you want cleaner-looking URLs and do not need to support IE9. +The pathname strategy produces the cleanest looking URLs, but requires setting up the server to serve the single page application code from every URL that the application can route to. Use this strategy if you want cleaner-looking URLs. Single page applications that use the hash strategy often use the convention of having an exclamation mark after the hash to indicate that they're using the hash as a routing mechanism and not for the purposes of linking to anchors. The `#!` string is known as a *hashbang*. diff --git a/module/module.js b/module/module.js index b81953b8..a9656542 100644 --- a/module/module.js +++ b/module/module.js @@ -16,6 +16,8 @@ window.module = { set exports(value) {require.$$modules[require.$$current()] = value}, } +window.global = window + function require(name) { var relative = require.$$current() var slashIndex = relative.lastIndexOf("/") diff --git a/ospec/tests/test-ospec.js b/ospec/tests/test-ospec.js index 470a3b61..5e92d770 100644 --- a/ospec/tests/test-ospec.js +++ b/ospec/tests/test-ospec.js @@ -676,7 +676,7 @@ o.spec("the done parser", function() { var threw = false oo("test", function(/*hey */ /**/ //ho - done /*hey + done /*hey */ /**/ //huuu , timeout ) { diff --git a/render/render.js b/render/render.js index 69c74d3b..e0bf7ffc 100644 --- a/render/render.js +++ b/render/render.js @@ -35,7 +35,7 @@ module.exports = function($window) { } } - // IE9 - IE11 (at least) throw an UnspecifiedError when accessing document.activeElement when + // IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when // inside an iframe. Catch and swallow this error, and heavy-handidly return null. function activeElement() { try { @@ -892,7 +892,7 @@ module.exports = function($window) { vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]) updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) dom.vnodes = vnodes - // document.activeElement can return null in IE https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement + // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement if (active != null && activeElement() !== active && typeof active.focus === "function") active.focus() for (var i = 0; i < hooks.length; i++) hooks[i]() } diff --git a/request/tests/test-request.js b/request/tests/test-request.js index e69a93d7..bb52f6b3 100644 --- a/request/tests/test-request.js +++ b/request/tests/test-request.js @@ -415,9 +415,9 @@ o.spec("xhr", function() { xhr({method: "GET", url: "/item", config: handleAbort}).catch(function() { failed = true }) - .then(function() { - resolved = true - }) + .then(function() { + resolved = true + }) }) o("doesn't fail on file:// status 0", function(done) { mock.$defineRoutes({ diff --git a/test-utils/domMock.js b/test-utils/domMock.js index 6ccb468e..840fda08 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -473,9 +473,9 @@ module.exports = function(options) { if (!this.hasAttribute("type")) return "text" var type = this.getAttribute("type") return (/^(?:radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image)$/) - .test(type) - ? type - : "text" + .test(type) + ? type + : "text" }, set: typeSetter, enumerable: true, diff --git a/test-utils/tests/test-components.js b/test-utils/tests/test-components.js index e0ede703..3094cff2 100644 --- a/test-utils/tests/test-components.js +++ b/test-utils/tests/test-components.js @@ -37,7 +37,7 @@ o.spec("test-utils/components", function() { if (component.kind !== "constructible") { o(cmp2).deepEquals(methods) } else { - // deepEquals doesn't search the prototype, do it manually + // deepEquals doesn't search the prototype, do it manually o(cmp2 != null).equals(true) o(cmp2.view).equals(methods.view) o(cmp2.oninit).equals(methods.oninit) diff --git a/test-utils/tests/test-domMock.js b/test-utils/tests/test-domMock.js index 805f7b92..5c22c53d 100644 --- a/test-utils/tests/test-domMock.js +++ b/test-utils/tests/test-domMock.js @@ -1430,22 +1430,22 @@ o.spec("domMock", function() { o(input.type).equals("text") }) "radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image" - .split("|").forEach(function(type) { - o("can be set to " + type, function(){ - var input = $document.createElement("input") - input.type = type + .split("|").forEach(function(type) { + o("can be set to " + type, function(){ + var input = $document.createElement("input") + input.type = type - o(input.getAttribute("type")).equals(type) - o(input.type).equals(type) - }) - o("bad values set the attribute, but the getter corrects to 'text', " + type, function(){ - var input = $document.createElement("input") - input.type = "badbad" + type + o(input.getAttribute("type")).equals(type) + o(input.type).equals(type) + }) + o("bad values set the attribute, but the getter corrects to 'text', " + type, function(){ + var input = $document.createElement("input") + input.type = "badbad" + type - o(input.getAttribute("type")).equals("badbad" + type) - o(input.type).equals("text") + o(input.getAttribute("type")).equals("badbad" + type) + o(input.type).equals("text") + }) }) - }) }) o.spec("textarea[value]", function() { o("reads from child if no value was ever set", function() { diff --git a/tests/test-api.js b/tests/test-api.js index 49938d82..d298b4e1 100644 --- a/tests/test-api.js +++ b/tests/test-api.js @@ -24,7 +24,7 @@ o.spec("api", function() { o("works", function() { o(typeof m.version).equals("string") o(m.version.indexOf(".") > -1).equals(true) - o(/\d/.test(m.version)).equals(true) + o((/\d/).test(m.version)).equals(true) }) }) o.spec("m.trust", function() {