mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 23:57:22 +02:00
feat: code split & use react helmet
This commit is contained in:
parent
bf1e30dc52
commit
406106b67e
19 changed files with 241 additions and 146 deletions
|
@ -1,3 +1,4 @@
|
||||||
generated
|
generated
|
||||||
__fixtures__
|
__fixtures__
|
||||||
dist
|
dist
|
||||||
|
website
|
|
@ -4,7 +4,8 @@ const chalk = require('chalk');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const globby = require('globby');
|
const globby = require('globby');
|
||||||
const load = require('../load');
|
const load = require('../load');
|
||||||
const createProdConfig = require('../webpack/prod');
|
const createServerConfig = require('../webpack/server');
|
||||||
|
const createClientConfig = require('../webpack/client');
|
||||||
|
|
||||||
function compile(config) {
|
function compile(config) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -35,11 +36,14 @@ module.exports = async function build(siteDir, cliOptions = {}) {
|
||||||
|
|
||||||
const props = await load(siteDir);
|
const props = await load(siteDir);
|
||||||
|
|
||||||
// create compiler from generated webpack config
|
const serverConfig = createServerConfig(props).toConfig();
|
||||||
const config = createProdConfig(props).toConfig();
|
const clientConfig = createClientConfig(props).toConfig();
|
||||||
|
|
||||||
// compile!
|
// we build the client bundles first
|
||||||
await compile(config);
|
await compile(clientConfig);
|
||||||
|
|
||||||
|
// then we build the server bundles (render the static HTML and pick client bundle)
|
||||||
|
await compile(serverConfig);
|
||||||
|
|
||||||
// copy static files
|
// copy static files
|
||||||
const {outDir} = props;
|
const {outDir} = props;
|
||||||
|
|
|
@ -10,8 +10,9 @@ const serveStatic = require('koa-static');
|
||||||
const history = require('connect-history-api-fallback');
|
const history = require('connect-history-api-fallback');
|
||||||
const portfinder = require('portfinder');
|
const portfinder = require('portfinder');
|
||||||
const serve = require('webpack-serve');
|
const serve = require('webpack-serve');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const load = require('../load');
|
const load = require('../load');
|
||||||
const createDevConfig = require('../webpack/dev');
|
const createClientConfig = require('../webpack/client');
|
||||||
|
|
||||||
async function getPort(reqPort) {
|
async function getPort(reqPort) {
|
||||||
portfinder.basePort = parseInt(reqPort, 10) || 3000;
|
portfinder.basePort = parseInt(reqPort, 10) || 3000;
|
||||||
|
@ -52,7 +53,18 @@ module.exports = async function start(siteDir, cliOptions = {}) {
|
||||||
const {baseUrl} = props;
|
const {baseUrl} = props;
|
||||||
|
|
||||||
// create compiler from generated webpack config
|
// create compiler from generated webpack config
|
||||||
const config = createDevConfig(props).toConfig();
|
let config = createClientConfig(props);
|
||||||
|
|
||||||
|
const {siteConfig} = props;
|
||||||
|
config.plugin('html-webpack-plugin').use(HtmlWebpackPlugin, [
|
||||||
|
{
|
||||||
|
hash: true,
|
||||||
|
template: path.resolve(__dirname, '../core/devTemplate.ejs'),
|
||||||
|
filename: 'index.html',
|
||||||
|
title: siteConfig.title
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
config = config.toConfig();
|
||||||
const compiler = webpack(config);
|
const compiler = webpack(config);
|
||||||
|
|
||||||
// webpack-serve
|
// webpack-serve
|
||||||
|
|
19
lib/core/clientEntry.js
Normal file
19
lib/core/clientEntry.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {BrowserRouter} from 'react-router-dom';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import App from './App';
|
||||||
|
import prerender from './prerender';
|
||||||
|
import routes from '@generated/routes'; // eslint-disable-line
|
||||||
|
|
||||||
|
// Client side render (e.g: running in browser) to become single-page application (SPA)
|
||||||
|
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
||||||
|
prerender(routes, window.location.pathname).then(() => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>,
|
||||||
|
document.getElementById('app')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import {BrowserRouter} from 'react-router-dom';
|
|
||||||
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
ReactDOM.render(
|
|
||||||
<BrowserRouter>
|
|
||||||
<App />
|
|
||||||
</BrowserRouter>,
|
|
||||||
document.getElementById('app')
|
|
||||||
);
|
|
18
lib/core/prerender.js
Normal file
18
lib/core/prerender.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import {matchRoutes} from 'react-router-config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This helps us to make sure all the async component for that particular route
|
||||||
|
* is loaded before rendering. This is to avoid loading screens on first page load
|
||||||
|
*/
|
||||||
|
export default function prerender(routeConfig, providedLocation) {
|
||||||
|
const matches = matchRoutes(routeConfig, providedLocation);
|
||||||
|
return Promise.all(
|
||||||
|
matches.map(match => {
|
||||||
|
const {component} = match.route;
|
||||||
|
if (component && component.preload) {
|
||||||
|
return component.preload();
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import {BrowserRouter, StaticRouter} from 'react-router-dom';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import ReactDOMServer from 'react-dom/server';
|
|
||||||
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
// Client side render (e.g: running in browser) to become single-page application (SPA)
|
|
||||||
if (typeof document !== 'undefined') {
|
|
||||||
ReactDOM.render(
|
|
||||||
<BrowserRouter>
|
|
||||||
<App />
|
|
||||||
</BrowserRouter>,
|
|
||||||
document.getElementById('app')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renderer for static-site-generator-webpack-plugin (async rendering via callbacks)
|
|
||||||
export default function render(locals, callback) {
|
|
||||||
const context = {};
|
|
||||||
const body = ReactDOMServer.renderToString(
|
|
||||||
<StaticRouter location={locals.path} context={context}>
|
|
||||||
<App />
|
|
||||||
</StaticRouter>
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build HTML template
|
|
||||||
const assets = Object.keys(locals.webpackStats.compilation.assets);
|
|
||||||
const css = assets.filter(value => value.match(/\.css$/));
|
|
||||||
const js = assets.filter(value => value.match(/\.js$/));
|
|
||||||
const {title, baseUrl, lang = 'en', template} = locals;
|
|
||||||
const html = template({body, baseUrl, css, js, title, lang});
|
|
||||||
|
|
||||||
callback(null, html);
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="<%- lang%>">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
||||||
<title><%- title %></title>
|
|
||||||
<% css.forEach(function(file){ %>
|
|
||||||
<link href="<%-baseUrl %><%- file %>" rel="stylesheet">
|
|
||||||
<% }); %>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"><%- body %></div>
|
|
||||||
<% js.forEach(function(file){ %>
|
|
||||||
<script src="<%-baseUrl %><%- file %>"></script>
|
|
||||||
<% }); %>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
59
lib/core/serverEntry.js
Normal file
59
lib/core/serverEntry.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {StaticRouter} from 'react-router-dom';
|
||||||
|
import ReactDOMServer from 'react-dom/server';
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
|
import App from './App';
|
||||||
|
import prerender from './prerender';
|
||||||
|
import routes from '@generated/routes'; // eslint-disable-line
|
||||||
|
import webpackClientStats from '@build/client.stats.json'; //eslint-disable-line
|
||||||
|
|
||||||
|
// Renderer for static-site-generator-webpack-plugin (async rendering via promises)
|
||||||
|
export default function render(locals) {
|
||||||
|
return prerender(routes, locals.path).then(() => {
|
||||||
|
const context = {};
|
||||||
|
const appHtml = ReactDOMServer.renderToString(
|
||||||
|
<StaticRouter location={locals.path} context={context}>
|
||||||
|
<App />
|
||||||
|
</StaticRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
const helmet = Helmet.renderStatic();
|
||||||
|
const htmlAttributes = helmet.htmlAttributes.toString();
|
||||||
|
const bodyAttributes = helmet.bodyAttributes.toString();
|
||||||
|
const metaStrings = [
|
||||||
|
helmet.title.toString(),
|
||||||
|
helmet.meta.toString(),
|
||||||
|
helmet.link.toString()
|
||||||
|
];
|
||||||
|
const metaHtml = metaStrings.filter(Boolean).join('\n ');
|
||||||
|
|
||||||
|
const assets = webpackClientStats.assetsByChunkName.main;
|
||||||
|
const jsFiles = assets.filter(value => value.match(/\.js$/));
|
||||||
|
const cssFiles = assets.filter(value => value.match(/\.css$/));
|
||||||
|
const {baseUrl} = locals;
|
||||||
|
|
||||||
|
const html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html${htmlAttributes ? ` ${htmlAttributes}` : ''}>
|
||||||
|
<head>
|
||||||
|
${metaHtml}
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
${cssFiles.map(
|
||||||
|
cssFile =>
|
||||||
|
`<link rel="stylesheet" type="text/css" href="${baseUrl}${cssFile}" />`
|
||||||
|
)}
|
||||||
|
</head>
|
||||||
|
<body${bodyAttributes ? ` ${bodyAttributes}` : ''}>
|
||||||
|
<div id="app">${appHtml}</div>
|
||||||
|
${jsFiles.map(
|
||||||
|
jsFile =>
|
||||||
|
`<script type="text/javascript" src="${baseUrl}${jsFile}"></script>`
|
||||||
|
)}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
return html;
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,40 +1,32 @@
|
||||||
const {fileToComponentName} = require('./utils');
|
|
||||||
|
|
||||||
async function genRoutesConfig({docsData = [], pagesData = []}) {
|
async function genRoutesConfig({docsData = [], pagesData = []}) {
|
||||||
function genDocsRoute({path: docsPath, source}) {
|
function genDocsRoute({path: docsPath, source}) {
|
||||||
const componentName = fileToComponentName(source);
|
|
||||||
return `
|
return `
|
||||||
{
|
{
|
||||||
path: ${JSON.stringify(docsPath)},
|
path: ${JSON.stringify(docsPath)},
|
||||||
exact: true,
|
exact: true,
|
||||||
component: (props) => (
|
component: Loadable({
|
||||||
<Docs {...props}>
|
loader: () => import('@docs/${source}'),
|
||||||
<${componentName} />
|
loading: Loading,
|
||||||
</Docs>
|
render(loaded, props) {
|
||||||
)
|
let Content = loaded.default;
|
||||||
|
return <Docs {...props}><Content /></Docs>;
|
||||||
|
}
|
||||||
|
})
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function genDocsImport({source}) {
|
|
||||||
const componentName = fileToComponentName(source);
|
|
||||||
return `import ${componentName} from '@docs/${source}';`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function genPagesRoute({path: pagesPath, source}) {
|
function genPagesRoute({path: pagesPath, source}) {
|
||||||
const componentName = fileToComponentName(source);
|
|
||||||
return `
|
return `
|
||||||
{
|
{
|
||||||
path: ${JSON.stringify(pagesPath)},
|
path: ${JSON.stringify(pagesPath)},
|
||||||
exact: true,
|
exact: true,
|
||||||
component: ${componentName}
|
component: Loadable({
|
||||||
|
loader: () => import('@pages/${source}'),
|
||||||
|
loading: Loading
|
||||||
|
})
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function genPagesImport({source}) {
|
|
||||||
const componentName = fileToComponentName(source);
|
|
||||||
return `import ${componentName} from '@pages/${source}';`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const notFoundRoute = `,
|
const notFoundRoute = `,
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
|
@ -43,10 +35,10 @@ async function genRoutesConfig({docsData = [], pagesData = []}) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`import React from 'react';\n` +
|
`import React from 'react';\n` +
|
||||||
|
`import Loading from '@theme/Loading';\n` +
|
||||||
|
`import Loadable from 'react-loadable';\n` +
|
||||||
`import Docs from '@theme/Docs';\n` +
|
`import Docs from '@theme/Docs';\n` +
|
||||||
`import NotFound from '@theme/NotFound';\n` +
|
`import NotFound from '@theme/NotFound';\n` +
|
||||||
`${pagesData.map(genPagesImport).join('\n')}\n` +
|
|
||||||
`${docsData.map(genDocsImport).join('\n')}\n` +
|
|
||||||
`const routes = [${docsData.map(genDocsRoute).join(',')},${pagesData
|
`const routes = [${docsData.map(genDocsRoute).join(',')},${pagesData
|
||||||
.map(genPagesRoute)
|
.map(genPagesRoute)
|
||||||
.join(',')}${notFoundRoute}\n];\n` +
|
.join(',')}${notFoundRoute}\n];\n` +
|
||||||
|
|
15
lib/theme/Loading.js
Normal file
15
lib/theme/Loading.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default props => {
|
||||||
|
if (props.error) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Error!{' '}
|
||||||
|
<button type="button" onClick={props.retry}>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
};
|
|
@ -4,7 +4,7 @@ const path = require('path');
|
||||||
|
|
||||||
const mdLoader = require.resolve('./loader/markdown');
|
const mdLoader = require.resolve('./loader/markdown');
|
||||||
|
|
||||||
module.exports = function createBaseConfig(props) {
|
module.exports = function createBaseConfig(props, isServer) {
|
||||||
const {
|
const {
|
||||||
siteConfig,
|
siteConfig,
|
||||||
outDir,
|
outDir,
|
||||||
|
@ -34,6 +34,7 @@ module.exports = function createBaseConfig(props) {
|
||||||
.set('@site', siteDir)
|
.set('@site', siteDir)
|
||||||
.set('@docs', docsDir)
|
.set('@docs', docsDir)
|
||||||
.set('@pages', pagesDir)
|
.set('@pages', pagesDir)
|
||||||
|
.set('@build', outDir)
|
||||||
.set('@generated', path.resolve(__dirname, '../core/generated'))
|
.set('@generated', path.resolve(__dirname, '../core/generated'))
|
||||||
.set('@core', path.resolve(__dirname, '../core'))
|
.set('@core', path.resolve(__dirname, '../core'))
|
||||||
.end();
|
.end();
|
||||||
|
@ -44,7 +45,8 @@ module.exports = function createBaseConfig(props) {
|
||||||
.loader('babel-loader')
|
.loader('babel-loader')
|
||||||
.options({
|
.options({
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
presets: ['env', 'react']
|
presets: ['env', 'react'],
|
||||||
|
plugins: [isServer ? 'dynamic-import-node' : 'syntax-dynamic-import']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
lib/webpack/client.js
Normal file
20
lib/webpack/client.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
const path = require('path');
|
||||||
|
const webpackNiceLog = require('webpack-nicelog');
|
||||||
|
const {StatsWriterPlugin} = require('webpack-stats-plugin');
|
||||||
|
const createBaseConfig = require('./base');
|
||||||
|
|
||||||
|
module.exports = function createClientConfig(props) {
|
||||||
|
const config = createBaseConfig(props);
|
||||||
|
|
||||||
|
config.entry('main').add(path.resolve(__dirname, '../core/clientEntry.js'));
|
||||||
|
|
||||||
|
// write webpack stats object to a file so we can
|
||||||
|
// programmatically refer to the correct bundle path in Node.js server.
|
||||||
|
config
|
||||||
|
.plugin('stats')
|
||||||
|
.use(StatsWriterPlugin, [{filename: 'client.stats.json'}]);
|
||||||
|
|
||||||
|
config.plugin('niceLog').use(webpackNiceLog, [{name: 'Client'}]);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
|
@ -1,24 +0,0 @@
|
||||||
const path = require('path');
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const webpackNiceLog = require('webpack-nicelog');
|
|
||||||
const createBaseConfig = require('./base');
|
|
||||||
|
|
||||||
module.exports = function createDevConfig(props) {
|
|
||||||
const config = createBaseConfig(props);
|
|
||||||
|
|
||||||
config.entry('main').add(path.resolve(__dirname, '../core/devEntry.js'));
|
|
||||||
|
|
||||||
const {siteConfig} = props;
|
|
||||||
config.plugin('html-webpack-plugin').use(HtmlWebpackPlugin, [
|
|
||||||
{
|
|
||||||
inject: false,
|
|
||||||
hash: true,
|
|
||||||
template: path.resolve(__dirname, '../core/devTemplate.ejs'),
|
|
||||||
filename: 'index.html',
|
|
||||||
title: siteConfig.title
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
config.plugin('niceLog').use(webpackNiceLog, [{name: 'Development'}]);
|
|
||||||
|
|
||||||
return config;
|
|
||||||
};
|
|
|
@ -1,15 +1,16 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
|
||||||
const ejs = require('ejs');
|
|
||||||
const staticSiteGenerator = require('static-site-generator-webpack-plugin');
|
const staticSiteGenerator = require('static-site-generator-webpack-plugin');
|
||||||
const webpackNiceLog = require('webpack-nicelog');
|
const webpackNiceLog = require('webpack-nicelog');
|
||||||
const createBaseConfig = require('./base');
|
const createBaseConfig = require('./base');
|
||||||
|
|
||||||
module.exports = function createProdConfig(props) {
|
module.exports = function createProdConfig(props) {
|
||||||
const config = createBaseConfig(props);
|
const config = createBaseConfig(props, true);
|
||||||
|
|
||||||
config.entry('main').add(path.resolve(__dirname, '../core/prodEntry.js'));
|
config.entry('main').add(path.resolve(__dirname, '../core/serverEntry.js'));
|
||||||
config.output.libraryTarget('umd');
|
|
||||||
|
config.target('node');
|
||||||
|
|
||||||
|
config.output.filename('server.bundle.js').libraryTarget('commonjs2');
|
||||||
|
|
||||||
// Workaround for Webpack 4 Bug (https://github.com/webpack/webpack/issues/6522)
|
// Workaround for Webpack 4 Bug (https://github.com/webpack/webpack/issues/6522)
|
||||||
config.output.globalObject('this');
|
config.output.globalObject('this');
|
||||||
|
@ -18,25 +19,19 @@ module.exports = function createProdConfig(props) {
|
||||||
|
|
||||||
// static site generator webpack plugin
|
// static site generator webpack plugin
|
||||||
const paths = [...docsData, ...pagesData].map(data => data.path);
|
const paths = [...docsData, ...pagesData].map(data => data.path);
|
||||||
const template = ejs.compile(
|
|
||||||
fs.readFileSync(
|
|
||||||
path.resolve(__dirname, '../core/prodTemplate.ejs'),
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
config.plugin('siteGenerator').use(staticSiteGenerator, [
|
config.plugin('siteGenerator').use(staticSiteGenerator, [
|
||||||
{
|
{
|
||||||
entry: 'main',
|
entry: 'main',
|
||||||
locals: {
|
locals: {
|
||||||
title: siteConfig.title || 'Munseo',
|
baseUrl: siteConfig.baseUrl
|
||||||
baseUrl: siteConfig.baseUrl,
|
|
||||||
template
|
|
||||||
},
|
},
|
||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
// show compilation progress bar and build time
|
// show compilation progress bar and build time
|
||||||
config.plugin('niceLog').use(webpackNiceLog, [{name: 'Production'}]);
|
config
|
||||||
|
.plugin('niceLog')
|
||||||
|
.use(webpackNiceLog, [{name: 'Server', color: 'yellow'}]);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
|
@ -42,6 +42,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-core": "^6.26.3",
|
"babel-core": "^6.26.3",
|
||||||
"babel-loader": "^7.1.5",
|
"babel-loader": "^7.1.5",
|
||||||
|
"babel-plugin-dynamic-import-node": "^2.0.0",
|
||||||
|
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||||
"babel-preset-env": "^1.7.0",
|
"babel-preset-env": "^1.7.0",
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-preset-react": "^6.24.1",
|
||||||
"chalk": "^2.4.1",
|
"chalk": "^2.4.1",
|
||||||
|
@ -49,7 +51,6 @@
|
||||||
"commander": "^2.16.0",
|
"commander": "^2.16.0",
|
||||||
"connect-history-api-fallback": "^1.5.0",
|
"connect-history-api-fallback": "^1.5.0",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"ejs": "^2.6.1",
|
|
||||||
"front-matter": "^2.3.0",
|
"front-matter": "^2.3.0",
|
||||||
"fs-extra": "^7.0.0",
|
"fs-extra": "^7.0.0",
|
||||||
"globby": "^8.0.1",
|
"globby": "^8.0.1",
|
||||||
|
@ -65,6 +66,8 @@
|
||||||
"prismjs": "^1.15.0",
|
"prismjs": "^1.15.0",
|
||||||
"react": "^16.4.1",
|
"react": "^16.4.1",
|
||||||
"react-dom": "^16.4.1",
|
"react-dom": "^16.4.1",
|
||||||
|
"react-helmet": "^5.2.0",
|
||||||
|
"react-loadable": "^5.5.0",
|
||||||
"react-router-config": "^1.0.0-beta.4",
|
"react-router-config": "^1.0.0-beta.4",
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"remarkable": "^1.7.1",
|
"remarkable": "^1.7.1",
|
||||||
|
@ -74,7 +77,8 @@
|
||||||
"webpack": "^4.16.3",
|
"webpack": "^4.16.3",
|
||||||
"webpack-chain": "^4.8.0",
|
"webpack-chain": "^4.8.0",
|
||||||
"webpack-nicelog": "^2.2.1",
|
"webpack-nicelog": "^2.2.1",
|
||||||
"webpack-serve": "^2.0.2"
|
"webpack-serve": "^2.0.2",
|
||||||
|
"webpack-stats-plugin": "^0.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TicTacToe from './tictactoe';
|
import Helmet from 'react-helmet';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
|
|
||||||
export default class Home extends React.Component {
|
export default class Home extends React.Component {
|
||||||
|
@ -12,10 +12,9 @@ export default class Home extends React.Component {
|
||||||
));
|
));
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<Helmet title="Homepage" />
|
||||||
<h2> Available Urls </h2>
|
<h2> Available Urls </h2>
|
||||||
<ul>{routeLinks}</ul>
|
<ul>{routeLinks}</ul>
|
||||||
<h2> Play some TicTacToe </h2>
|
|
||||||
<TicTacToe />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
import style from './tictactoe.css';
|
import style from './tictactoe.css';
|
||||||
|
|
||||||
function Square(props) {
|
function Square(props) {
|
||||||
|
@ -125,6 +126,7 @@ class Game extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.game}>
|
<div className={style.game}>
|
||||||
|
<Helmet title="Tic Tac Toe" />
|
||||||
<div className={style.gameBoard}>
|
<div className={style.gameBoard}>
|
||||||
<Board squares={current.squares} onClick={i => this.handleClick(i)} />
|
<Board squares={current.squares} onClick={i => this.handleClick(i)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
53
yarn.lock
53
yarn.lock
|
@ -723,6 +723,13 @@ babel-plugin-check-es2015-constants@^6.22.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
babel-runtime "^6.22.0"
|
babel-runtime "^6.22.0"
|
||||||
|
|
||||||
|
babel-plugin-dynamic-import-node@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.0.0.tgz#d6fc3f6c5e3bdc34e49c15faca7ce069755c0a57"
|
||||||
|
dependencies:
|
||||||
|
babel-plugin-syntax-dynamic-import "^6.18.0"
|
||||||
|
object.assign "^4.1.0"
|
||||||
|
|
||||||
babel-plugin-istanbul@^4.1.6:
|
babel-plugin-istanbul@^4.1.6:
|
||||||
version "4.1.6"
|
version "4.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
|
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
|
||||||
|
@ -740,6 +747,10 @@ babel-plugin-syntax-async-functions@^6.8.0:
|
||||||
version "6.13.0"
|
version "6.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
|
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
|
||||||
|
|
||||||
|
babel-plugin-syntax-dynamic-import@^6.18.0:
|
||||||
|
version "6.18.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
|
||||||
|
|
||||||
babel-plugin-syntax-exponentiation-operator@^6.8.0:
|
babel-plugin-syntax-exponentiation-operator@^6.8.0:
|
||||||
version "6.13.0"
|
version "6.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
|
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
|
||||||
|
@ -1900,7 +1911,7 @@ decode-uri-component@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||||
|
|
||||||
deep-equal@~1.0.1:
|
deep-equal@^1.0.1, deep-equal@~1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||||
|
|
||||||
|
@ -2136,10 +2147,6 @@ ee-first@1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||||
|
|
||||||
ejs@^2.6.1:
|
|
||||||
version "2.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
|
|
||||||
|
|
||||||
electron-to-chromium@^1.3.47:
|
electron-to-chromium@^1.3.47:
|
||||||
version "1.3.52"
|
version "1.3.52"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.52.tgz#d2d9f1270ba4a3b967b831c40ef71fb4d9ab5ce0"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.52.tgz#d2d9f1270ba4a3b967b831c40ef71fb4d9ab5ce0"
|
||||||
|
@ -2490,6 +2497,10 @@ execa@^0.8.0:
|
||||||
signal-exit "^3.0.0"
|
signal-exit "^3.0.0"
|
||||||
strip-eof "^1.0.0"
|
strip-eof "^1.0.0"
|
||||||
|
|
||||||
|
exenv@^1.2.1:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
|
||||||
|
|
||||||
exit@^0.1.2:
|
exit@^0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
||||||
|
@ -5401,7 +5412,7 @@ prompts@^0.1.9:
|
||||||
kleur "^2.0.1"
|
kleur "^2.0.1"
|
||||||
sisteransi "^0.1.1"
|
sisteransi "^0.1.1"
|
||||||
|
|
||||||
prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
|
prop-types@^15.5.0, prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
|
||||||
version "15.6.2"
|
version "15.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -5547,6 +5558,21 @@ react-error-overlay@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.0.tgz#d198408a85b4070937a98667f500c832f86bd5d4"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.0.tgz#d198408a85b4070937a98667f500c832f86bd5d4"
|
||||||
|
|
||||||
|
react-helmet@^5.2.0:
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.0.tgz#a81811df21313a6d55c5f058c4aeba5d6f3d97a7"
|
||||||
|
dependencies:
|
||||||
|
deep-equal "^1.0.1"
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
prop-types "^15.5.4"
|
||||||
|
react-side-effect "^1.1.0"
|
||||||
|
|
||||||
|
react-loadable@^5.5.0:
|
||||||
|
version "5.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-loadable/-/react-loadable-5.5.0.tgz#582251679d3da86c32aae2c8e689c59f1196d8c4"
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.5.0"
|
||||||
|
|
||||||
react-router-config@^1.0.0-beta.4:
|
react-router-config@^1.0.0-beta.4:
|
||||||
version "1.0.0-beta.4"
|
version "1.0.0-beta.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-1.0.0-beta.4.tgz#d202496dd0eabdf06cf24eb0793031f6891eef01"
|
resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-1.0.0-beta.4.tgz#d202496dd0eabdf06cf24eb0793031f6891eef01"
|
||||||
|
@ -5574,6 +5600,13 @@ react-router@^4.3.1:
|
||||||
prop-types "^15.6.1"
|
prop-types "^15.6.1"
|
||||||
warning "^4.0.1"
|
warning "^4.0.1"
|
||||||
|
|
||||||
|
react-side-effect@^1.1.0:
|
||||||
|
version "1.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.5.tgz#f26059e50ed9c626d91d661b9f3c8bb38cd0ff2d"
|
||||||
|
dependencies:
|
||||||
|
exenv "^1.2.1"
|
||||||
|
shallowequal "^1.0.1"
|
||||||
|
|
||||||
react@^16.4.1:
|
react@^16.4.1:
|
||||||
version "16.4.1"
|
version "16.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32"
|
||||||
|
@ -6055,6 +6088,10 @@ sha.js@^2.4.0, sha.js@^2.4.8:
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
shallowequal@^1.0.1:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
|
||||||
|
|
||||||
shebang-command@^1.2.0:
|
shebang-command@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||||
|
@ -6970,6 +7007,10 @@ webpack-sources@^1.0.1, webpack-sources@^1.1.0:
|
||||||
source-list-map "^2.0.0"
|
source-list-map "^2.0.0"
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
|
webpack-stats-plugin@^0.2.1:
|
||||||
|
version "0.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.2.1.tgz#1f5bac13fc25d62cbb5fd0ff646757dc802b8595"
|
||||||
|
|
||||||
webpack@^4.16.3:
|
webpack@^4.16.3:
|
||||||
version "4.16.3"
|
version "4.16.3"
|
||||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.16.3.tgz#861be3176d81e7e3d71c66c8acc9bba35588b525"
|
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.16.3.tgz#861be3176d81e7e3d71c66c8acc9bba35588b525"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue