From f813186540e3a45b60675e8517a59eea0727af97 Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Sun, 21 Jan 2018 17:42:55 -0800 Subject: [PATCH] Polyfill support. Use `Modernizr` and `caniuse` to smartly polyfill as much as possible. --- .modernizrrc.js | 15 ++ config/webpack/client.babel.js | 2 + config/webpack/partial/babel.js | 2 +- config/webpack/partial/env.js | 10 + config/webpack/partial/modernizr.js | 15 ++ config/webpack/partial/node.js | 6 + config/webpack/render.babel.js | 8 +- config/webpack/server.babel.js | 7 +- package-lock.json | 248 ++++++++++++++++++++++++ package.json | 9 +- src/client/index.js | 15 +- src/client/polyfill/fetch.js | 19 ++ src/client/polyfill/history.js | 12 ++ src/client/polyfill/index.js | 36 ++++ src/client/polyfill/promise.js | 21 ++ src/component/base/Button/Button.css.js | 5 + src/component/base/Button/Button.js | 10 + src/component/base/Button/index.js | 1 + src/component/root/App/App.js | 3 + src/component/static/Page/Page.js | 16 +- src/render/renderApp.js | 2 + src/render/renderError.js | 2 + src/server/app.js | 1 + src/server/index.js | 2 + src/server/polyfill.js | 23 +++ 25 files changed, 472 insertions(+), 18 deletions(-) create mode 100644 .modernizrrc.js create mode 100644 config/webpack/partial/modernizr.js create mode 100644 config/webpack/partial/node.js create mode 100644 src/client/polyfill/fetch.js create mode 100644 src/client/polyfill/history.js create mode 100644 src/client/polyfill/index.js create mode 100644 src/client/polyfill/promise.js create mode 100644 src/component/base/Button/Button.css.js create mode 100644 src/component/base/Button/Button.js create mode 100644 src/component/base/Button/index.js create mode 100644 src/server/polyfill.js diff --git a/.modernizrrc.js b/.modernizrrc.js new file mode 100644 index 0000000..d409e66 --- /dev/null +++ b/.modernizrrc.js @@ -0,0 +1,15 @@ +// https://github.com/peerigon/modernizr-loader/blob/master/index.js#L25 +module.exports = { + "classPrefix": "", + "options": [], + "feature-detects": [ + "cookies", + "cors", + "css/all", + "es6/promises", + "history", + "intl", + "network/fetch", + "requestanimationframe" + ] +} diff --git a/config/webpack/client.babel.js b/config/webpack/client.babel.js index 1675db9..aac2d67 100644 --- a/config/webpack/client.babel.js +++ b/config/webpack/client.babel.js @@ -3,6 +3,7 @@ import {output, plugin} from 'webpack-partial'; import PagesPlugin from 'pages-webpack-plugin'; import base from './partial/base'; +import modernizr from './partial/modernizr'; const getRender = () => { try { @@ -32,6 +33,7 @@ const createConfig = compose( render: getRender(), }), config); }, + modernizr(), output({ publicPath: '/asset', }), diff --git a/config/webpack/partial/babel.js b/config/webpack/partial/babel.js index df0a76f..5bac723 100644 --- a/config/webpack/partial/babel.js +++ b/config/webpack/partial/babel.js @@ -35,7 +35,7 @@ export default () => (config) => { return [name, { ...config, modules: false, - useBuiltIns: 'usage', + useBuiltIns: false, ignoreBrowserslistConfig: target === 'node', targets: getTargets(target), include: [ diff --git a/config/webpack/partial/env.js b/config/webpack/partial/env.js index 6b73cbe..3ab09e8 100644 --- a/config/webpack/partial/env.js +++ b/config/webpack/partial/env.js @@ -30,9 +30,19 @@ const extract = pipe( const env = (env = {}) => console.log(chalk.bold('📦 Enironment')) || plugin(new DefinePlugin(extract(env))); +const isDev = !process.env.NODE_ENV || process.env.NODE_ENV === 'development'; + export default () => env({ NODE_ENV: { value: 'development', required: true, }, + FORCE_PROMISE_POLYFILL: { + value: isDev ? 'true' : '', + required: false, + }, + FORCE_FETCH_POLYFILL: { + value: isDev ? 'true' : '', + required: false, + }, }); diff --git a/config/webpack/partial/modernizr.js b/config/webpack/partial/modernizr.js new file mode 100644 index 0000000..ad6a6d9 --- /dev/null +++ b/config/webpack/partial/modernizr.js @@ -0,0 +1,15 @@ +import {alias, loader} from 'webpack-partial'; +import {compose} from 'ramda'; +import path from 'path'; + +export default (rc = '.modernizrrc.js') => compose( + loader({ + test: /\.modernizrrc/, + loader: require.resolve('modernizr-loader'), + }), + (config) => alias( + 'modernizr$', + rc.charAt(0) === '/' ? rc : path.join(config.context, rc), + config + ) +); diff --git a/config/webpack/partial/node.js b/config/webpack/partial/node.js new file mode 100644 index 0000000..92a3952 --- /dev/null +++ b/config/webpack/partial/node.js @@ -0,0 +1,6 @@ +import nodeExternals from 'webpack-node-externals'; +import {compose, assoc} from 'ramda'; + +export default compose( + assoc('externals', [nodeExternals()]), +); diff --git a/config/webpack/render.babel.js b/config/webpack/render.babel.js index c53a152..1b67c93 100644 --- a/config/webpack/render.babel.js +++ b/config/webpack/render.babel.js @@ -1,17 +1,17 @@ import {compose} from 'ramda'; -import nodeExternals from 'webpack-node-externals'; + import {output} from 'webpack-partial'; import base from './partial/base'; +import node from './partial/node'; const createConfig = compose( output({ library: 'render', libraryTarget: 'commonjs2', }), + node(), base({name: 'render', target: 'node'}), ); -export default createConfig({ - externals: [nodeExternals()], -}); +export default createConfig({}); diff --git a/config/webpack/server.babel.js b/config/webpack/server.babel.js index 795f692..9dd5c94 100644 --- a/config/webpack/server.babel.js +++ b/config/webpack/server.babel.js @@ -1,12 +1,11 @@ -import nodeExternals from 'webpack-node-externals'; import {compose} from 'ramda'; import base from './partial/base'; +import node from './partial/node'; const createConfig = compose( + node(), base({name: 'server', target: 'node'}), ); -export default createConfig({ - externals: [nodeExternals()], -}); +export default createConfig({}); diff --git a/package-lock.json b/package-lock.json index ce51f4c..e9b5552 100644 --- a/package-lock.json +++ b/package-lock.json @@ -955,6 +955,11 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz", "integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=" }, + "autolinker": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.15.3.tgz", + "integrity": "sha1-NCQX2PLzRhsUzwkIjV7fh5HcmDI=" + }, "autoprefixer": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.5.tgz", @@ -2485,6 +2490,11 @@ "hoek": "2.16.3" } }, + "bowser": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.1.tgz", + "integrity": "sha512-UXti1JB6oK8hO983AImunnV6j/fqAEeDlPXh99zhsP5g32oLbxJJ6qcOaUesR+tqqhnUVQHlRJyD0dfiV0Hxaw==" + }, "brace-expansion": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", @@ -2724,6 +2734,15 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz", "integrity": "sha1-0M6pgfgRjzlhRxr7tDyaHlu/AzI=" }, + "caniuse-support": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/caniuse-support/-/caniuse-support-0.4.1.tgz", + "integrity": "sha1-z4cS/PCXH7feG7E2gRse/XUJSpQ=", + "requires": { + "bowser": "1.9.1", + "caniuse-db": "1.0.30000793" + } + }, "case-sensitive-paths-webpack-plugin": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.1.1.tgz", @@ -4730,6 +4749,11 @@ "escape-string-regexp": "1.0.5" } }, + "file": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/file/-/file-0.2.2.tgz", + "integrity": "sha1-w9/Y+M81Na5FXCtCPC5SY112tNM=" + }, "file-entry-cache": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", @@ -4781,6 +4805,11 @@ "resolved": "https://registry.npmjs.org/find-nearest-file/-/find-nearest-file-1.1.0.tgz", "integrity": "sha512-NMsS0ITOwpBPrHOyO7YUtDhaVEGUKS0kBJDVaWZPuCzO7JMW+uzFQQVts/gPyIV9ioyNWDb5LjhHWXVf1OnBDA==" }, + "find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=" + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -6132,6 +6161,11 @@ "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=" }, + "html5-history-api": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/html5-history-api/-/html5-history-api-4.2.8.tgz", + "integrity": "sha1-PrvDjjbK4NxBD3TCD91ddtk+lqg=" + }, "htmlescape": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", @@ -7639,6 +7673,185 @@ } } }, + "modernizr": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/modernizr/-/modernizr-3.5.0.tgz", + "integrity": "sha1-OWoCIxvcVGKLveLAgTqOiEx+gGA=", + "requires": { + "doctrine": "1.2.3", + "file": "0.2.2", + "find-parent-dir": "0.3.0", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "remarkable": "1.7.1", + "requirejs": "2.1.22", + "yargs": "7.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, + "doctrine": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.2.3.tgz", + "integrity": "sha1-auxrvWLPid1JjK5wwO2fSdqHOmo=", + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "1.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "0.2.1" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "yargs": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.0.2.tgz", + "integrity": "sha1-EVuX3xMhgj6Lhkjolox4JSEiH2c=", + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "requires": { + "camelcase": "3.0.0" + } + } + } + }, + "modernizr-loader": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modernizr-loader/-/modernizr-loader-1.0.1.tgz", + "integrity": "sha1-5SpvmhJXi5RKu9bL1lyGPqSoP0k=" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -10932,6 +11145,31 @@ } } }, + "remarkable": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.1.tgz", + "integrity": "sha1-qspJchALZqZCpjoQIcpLrBvjv/Y=", + "requires": { + "argparse": "0.1.16", + "autolinker": "0.15.3" + }, + "dependencies": { + "argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "requires": { + "underscore": "1.7.0", + "underscore.string": "2.4.0" + } + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" + } + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -11040,6 +11278,11 @@ } } }, + "requirejs": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.1.22.tgz", + "integrity": "sha1-3Xj9LTQYDA1ixyS1uK68BmTgNm8=" + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -12149,6 +12392,11 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" }, + "underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=" + }, "unexpected": { "version": "10.36.3", "resolved": "https://registry.npmjs.org/unexpected/-/unexpected-10.36.3.tgz", diff --git a/package.json b/package.json index 44ec7d3..da63330 100644 --- a/package.json +++ b/package.json @@ -27,14 +27,20 @@ "babel-loader": "^8.0.0-beta.0", "babel-plugin-module-resolver": "^3.0.0", "babel-plugin-react-rename": "^0.1.0", + "bluebird": "^3.5.1", + "caniuse-support": "^0.4.1", "clean-webpack-plugin": "^0.1.17", "cross-loader": "^0.1.0", "css-js-loader": "^0.4.1", "css-loader": "^0.28.9", "extract-css-chunks-webpack-plugin": "^2.0.18", "find-nearest-file": "^1.1.0", + "html5-history-api": "^4.2.8", "htmlescape": "^1.1.1", "midori": "^1.0.0", + "modernizr": "^3.5.0", + "modernizr-loader": "^1.0.1", + "node-fetch": "^1.7.3", "pages-webpack-plugin": "^0.1.0", "postcss-loader": "^2.0.10", "postcss-nesting": "^4.2.1", @@ -54,7 +60,8 @@ "webpack": "^4.0.0-alpha.4", "webpack-cli": "^2.0.4", "webpack-node-externals": "^1.6.0", - "webpack-partial": "^2.2.0" + "webpack-partial": "^2.2.0", + "whatwg-fetch": "^2.0.3" }, "devDependencies": { "adana-dump": "^0.1.0", diff --git a/src/client/index.js b/src/client/index.js index 4a2fa87..b2a2d7b 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -1,5 +1,6 @@ import renderApp from './renderApp'; import parseInitialState from './parseInitialState'; +import polyfill from './polyfill'; import createStore from '/store'; // TODO: This doesn't get included in the bundle for some reason so we have @@ -7,10 +8,14 @@ import createStore from '/store'; import 'extract-css-chunks-webpack-plugin/hotModuleReplacement'; import './global.css'; -const state = parseInitialState(); +const bootstart = () => { + const state = parseInitialState(); -const store = createStore(state); + const store = createStore(state); -// setExceptionHandler(renderError) -// setExceptionHandler(renderApp) -renderApp(store); + // setExceptionHandler(renderError) + // setExceptionHandler(renderApp) + renderApp(store); +}; + +polyfill(bootstart); diff --git a/src/client/polyfill/fetch.js b/src/client/polyfill/fetch.js new file mode 100644 index 0000000..014cf29 --- /dev/null +++ b/src/client/polyfill/fetch.js @@ -0,0 +1,19 @@ +import Modernizr from 'modernizr'; + +export const test = (cb) => { + // Force the client build to use the `whatwg-fetch` polyfill even if a native + // `fetch` implementation is available. This is useful for debugging network + // issues during development. + if (process.env.FORCE_FETCH_POLYFILL) { + cb(false); + return; + } + Modernizr.on('fetch', cb); +}; +export const load = (cb) => require.ensure(['whatwg-fetch'], (require) => { + global.fetch = require('whatwg-fetch'); + cb(); +}, 'fetch.polyfill'); +export const name = 'fetch'; + +export default {test, load, name}; diff --git a/src/client/polyfill/history.js b/src/client/polyfill/history.js new file mode 100644 index 0000000..1ce7841 --- /dev/null +++ b/src/client/polyfill/history.js @@ -0,0 +1,12 @@ +import Modernizr from 'modernizr'; + +export const test = (cb) => Modernizr.on('history', cb); +export const load = (cb) => require.ensure([ + 'html5-history-api/history', +], (require) => { + require('html5-history-api/history'); + cb(); +}, 'history.polyfill'); +export const name = 'history'; + +export default {test, load, name}; diff --git a/src/client/polyfill/index.js b/src/client/polyfill/index.js new file mode 100644 index 0000000..1346987 --- /dev/null +++ b/src/client/polyfill/index.js @@ -0,0 +1,36 @@ +/* @flow */ +import fetch from './fetch'; +import promise from './promise'; +import history from './history'; + +type Callback = () => void; +type Polyfill = { + test: (callback: (result: boolean) => void) => void, + load: (callback: () => void) => void, + name: string, +}; + +const load = ([item, ...rest]: Array, done: Callback) => { + const next = () => { + if (rest.length > 0) { + load(rest, done); + } else { + done(); + } + }; + item.test((result) => { + if (!result) { + item.load(next); + } else { + next(); + } + }); +}; + +const polyfills = [ + fetch, + promise, + history, +]; + +export default (done: Callback) => load(polyfills, done); diff --git a/src/client/polyfill/promise.js b/src/client/polyfill/promise.js new file mode 100644 index 0000000..93f9f5f --- /dev/null +++ b/src/client/polyfill/promise.js @@ -0,0 +1,21 @@ +import Modernizr from 'modernizr'; + +export const test = (cb) => { + // Force the client build to use `bluebird` for all promises even if a + // native implementation is available. This is useful for debugging promise + // issues during development. + if (process.env.FORCE_PROMISE_POLYFILL) { + cb(false); + return; + } + Modernizr.on('promises', cb); +}; +export const load = (cb) => require.ensure([ + 'bluebird/js/browser/bluebird.core', +], (require) => { + global.Promise = require('bluebird/js/browser/bluebird.core'); + cb(); +}, 'promise.polyfill'); +export const name = 'promise'; + +export default {test, load, name}; diff --git a/src/component/base/Button/Button.css.js b/src/component/base/Button/Button.css.js new file mode 100644 index 0000000..63b0395 --- /dev/null +++ b/src/component/base/Button/Button.css.js @@ -0,0 +1,5 @@ +export const BUTTON = { + appearance: 'none', + fontWeight: 'bold', + border: 'solid 4px blue', +}; diff --git a/src/component/base/Button/Button.js b/src/component/base/Button/Button.js new file mode 100644 index 0000000..9efa498 --- /dev/null +++ b/src/component/base/Button/Button.js @@ -0,0 +1,10 @@ +import React from 'react'; +import {BUTTON} from './Button.css'; + +const Button = ({children}) => ( + +); + +export default Button; diff --git a/src/component/base/Button/index.js b/src/component/base/Button/index.js new file mode 100644 index 0000000..67facdb --- /dev/null +++ b/src/component/base/Button/index.js @@ -0,0 +1 @@ +export {default} from './Button'; diff --git a/src/component/root/App/App.js b/src/component/root/App/App.js index 098f406..32a5ae9 100644 --- a/src/component/root/App/App.js +++ b/src/component/root/App/App.js @@ -5,6 +5,8 @@ import {Switch, Match} from 'waygate'; import {compose} from 'ramda'; import {hot} from 'react-hot-loader'; +import Button from '/component/base/Button'; + const App = ({store}) => ( @@ -13,6 +15,7 @@ const App = ({store}) => ( Not found Y. + diff --git a/src/component/static/Page/Page.js b/src/component/static/Page/Page.js index 9828c54..448533f 100644 --- a/src/component/static/Page/Page.js +++ b/src/component/static/Page/Page.js @@ -1,6 +1,6 @@ /* @flow */ import React from 'react'; -import {filter, map, pipe, defaultTo} from 'ramda'; +import {filter, map, pipe, defaultTo, uniqBy, chain, prop} from 'ramda'; import serialize from 'htmlescape'; import type {State, AssetMap, Asset} from '/types'; @@ -38,6 +38,15 @@ type Props = { markup: string, state: State, assets: AssetMap, + preload?: Array, +}; + +const embedAssets = (assets, entries, fn) => { + return pipe( + chain((name) => assets[name] || []), + uniqBy(prop('name')), + fn, + )(entries); }; const Page = ({ @@ -45,6 +54,7 @@ const Page = ({ markup, redirect, state, + preload = [], }: Props) => { return ( @@ -54,7 +64,7 @@ const Page = ({ )} ... - {styles(assets.index)} + {embedAssets(assets, [...preload, 'index'], styles)}
@@ -63,7 +73,7 @@ const Page = ({ id='state' dangerouslySetInnerHTML={{__html: serialize(state)}} /> - {scripts(assets.index)} + {embedAssets(assets, [...preload, 'index'], scripts)} ); diff --git a/src/render/renderApp.js b/src/render/renderApp.js index 65598fe..0a57da6 100644 --- a/src/render/renderApp.js +++ b/src/render/renderApp.js @@ -13,6 +13,7 @@ import extractAssets from './extractAssets'; export default async ({ path, stats, + preload, }) => { const store = createStore(); store.dispatch(navigate(path)); @@ -28,6 +29,7 @@ export default async ({ assets={extractAssets(stats)} redirect={redirect} state={state} + preload={preload} /> )); return Promise.resolve({ diff --git a/src/render/renderError.js b/src/render/renderError.js index 125cc37..2ccc9e4 100644 --- a/src/render/renderError.js +++ b/src/render/renderError.js @@ -9,6 +9,7 @@ import extractAssets from './extractAssets'; export default async ({ error, stats, + preload, }) => { const markup = ReactDOMServer.renderToString(( @@ -17,6 +18,7 @@ export default async ({ )); return Promise.resolve({ diff --git a/src/server/app.js b/src/server/app.js index 8598cba..ad63bf9 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -17,6 +17,7 @@ export default compose( const {markup, redirect} = await renderApp({ stats: req.stats, path: req.url, + preload: req.preload, }); return compose( status(redirect ? 302 : 200), diff --git a/src/server/index.js b/src/server/index.js index e999998..52023ae 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -14,10 +14,12 @@ import {path} from 'midori/match'; // import common from '/server/common'; import app from './app'; +import polyfill from './polyfill'; const createApp = compose( // common, match(path('/favicon.ico'), compose(status(404), send(''))), + polyfill, app, ); diff --git a/src/server/polyfill.js b/src/server/polyfill.js new file mode 100644 index 0000000..c637810 --- /dev/null +++ b/src/server/polyfill.js @@ -0,0 +1,23 @@ +import {getSupport, detectBrowser} from 'caniuse-support'; +import {request, next} from 'midori'; + +export default request((req) => { + if (!req.__browser) { + req.__browser = detectBrowser(req.headers['user-agent']); + } + const browser = req.__browser; + const detect = (feature) => { + return getSupport(feature, browser).level === 'full'; + }; + req.preload = []; + if (!detect('fetch')) { + req.preload.push('fetch.polyfill'); + } + if (!detect('promises')) { + req.preload.push('promise.polyfill'); + } + if (!detect('history')) { + req.preload.push('history.polyfill'); + } + return next; +});