diff --git a/v2/lib/commands/start.js b/v2/lib/commands/start.js index baa9c9d34d..734523f5e8 100644 --- a/v2/lib/commands/start.js +++ b/v2/lib/commands/start.js @@ -83,42 +83,40 @@ module.exports = async function start(siteDir, cliOptions = {}) { const compiler = webpack(config); // webpack-serve - setTimeout(async () => { - await serve( - {}, - { - compiler, - open: true, - devMiddleware: { - logLevel: 'silent', - }, - hotClient: { - port: hotPort, - logLevel: 'error', - }, - logLevel: 'error', - port, - host, - add: app => { - // serve static files - const staticDir = path.resolve(siteDir, 'static'); - if (fs.existsSync(staticDir)) { - app.use(mount(baseUrl, serveStatic(staticDir))); - } - - // enable HTTP range requests - app.use(range); - - // rewrite request to `/` since dev is only a SPA - app.use( - convert( - history({ - rewrites: [{from: /\.html$/, to: '/'}], - }), - ), - ); - }, + await serve( + {}, + { + compiler, + open: true, + devMiddleware: { + logLevel: 'silent', }, - ); - }, 1000); + hotClient: { + port: hotPort, + logLevel: 'error', + }, + logLevel: 'error', + port, + host, + add: app => { + // serve static files + const staticDir = path.resolve(siteDir, 'static'); + if (fs.existsSync(staticDir)) { + app.use(mount(baseUrl, serveStatic(staticDir))); + } + + // enable HTTP range requests + app.use(range); + + // rewrite request to `/` since dev is only a SPA + app.use( + convert( + history({ + rewrites: [{from: /\.html$/, to: '/'}], + }), + ), + ); + }, + }, + ); }; diff --git a/v2/lib/load/index.js b/v2/lib/load/index.js index 3b2f86dff7..203b6df67c 100644 --- a/v2/lib/load/index.js +++ b/v2/lib/load/index.js @@ -49,7 +49,7 @@ module.exports = async function load(siteDir) { // pages const pagesDir = path.resolve(siteDir, 'pages'); - const pagesMetadatas = await loadPages(pagesDir); + const pagesMetadatas = await loadPages({pagesDir, env, siteConfig}); await generate( 'pagesMetadatas.js', `export default ${JSON.stringify(pagesMetadatas, null, 2)};`, diff --git a/v2/lib/load/pages.js b/v2/lib/load/pages.js index 7af30239c0..4c4ab0f97d 100644 --- a/v2/lib/load/pages.js +++ b/v2/lib/load/pages.js @@ -1,16 +1,57 @@ const globby = require('globby'); -const {encodePath, fileToPath} = require('./utils'); +const path = require('path'); +const {encodePath, fileToPath, idx} = require('./utils'); -async function loadPages(pagesDir) { +async function loadPages({pagesDir, env, siteConfig}) { const pagesFiles = await globby(['**/*.js'], { cwd: pagesDir, }); - const pagesMetadatas = await Promise.all( - pagesFiles.map(async source => ({ - path: encodePath(fileToPath(source)), - source, - })), + const {baseUrl} = siteConfig; + + /* Prepare metadata container */ + const pagesMetadatas = []; + + /* Translation */ + const translationEnabled = idx(env, ['translation', 'enabled']); + const enabledLanguages = + translationEnabled && idx(env, ['translation', 'enabledLanguages']); + const enabledLangTags = + (enabledLanguages && enabledLanguages.map(lang => lang.tag)) || []; + const defaultLangTag = idx(env, ['translation', 'defaultLanguage', 'tag']); + + await Promise.all( + pagesFiles.map(async relativeSource => { + const source = path.join(pagesDir, relativeSource); + const pathName = encodePath(fileToPath(relativeSource)); + if (translationEnabled && enabledLangTags.length > 0) { + enabledLangTags.forEach(langTag => { + /* default lang should also be available. E.g: /en/users and /users is the same */ + if (langTag === defaultLangTag) { + pagesMetadatas.push({ + permalink: pathName.replace(/^\//, baseUrl), + language: langTag, + source, + }); + } + + const metadata = { + permalink: pathName.replace(/^\//, `${baseUrl}${langTag}/`), + language: langTag, + source, + }; + pagesMetadatas.push(metadata); + }); + + // for defaultLanguage + } else { + const metadata = { + permalink: pathName.replace(/^\//, baseUrl), + source, + }; + pagesMetadatas.push(metadata); + } + }), ); return pagesMetadatas; } diff --git a/v2/lib/load/routes.js b/v2/lib/load/routes.js index 2c63a747d3..8aaec70059 100644 --- a/v2/lib/load/routes.js +++ b/v2/lib/load/routes.js @@ -20,14 +20,23 @@ async function genRoutesConfig({docsMetadatas = {}, pagesMetadatas = []}) { }`; } - function genPagesRoute({path: pagesPath, source}) { + function genPagesRoute(metadata) { + const {permalink, source} = metadata; return ` { - path: ${JSON.stringify(pagesPath)}, + path: ${JSON.stringify(permalink)}, exact: true, component: Loadable({ - loader: () => import('@pages/${source}'), - loading: Loading + loader: () => import(${JSON.stringify(source)}), + loading: Loading, + render(loaded, props) { + let Content = loaded.default; + return ( + + + + ); + } }) }`; } @@ -47,6 +56,7 @@ async function genRoutesConfig({docsMetadatas = {}, pagesMetadatas = []}) { `import Loadable from 'react-loadable';\n` + `import Loading from '@theme/Loading';\n` + `import Docs from '@theme/Docs';\n` + + `import Pages from '@theme/Pages';\n` + `import NotFound from '@theme/NotFound';\n` + `const routes = [${docsRoutes},${pagesMetadatas .map(genPagesRoute) diff --git a/v2/lib/load/theme.js b/v2/lib/load/theme.js index 3a5b6cfc4a..efc472a0c6 100644 --- a/v2/lib/load/theme.js +++ b/v2/lib/load/theme.js @@ -7,7 +7,7 @@ module.exports = function loadConfig(siteDir) { ? customThemePath : path.resolve(__dirname, '../theme'); - const themeComponents = ['Docs', 'Loading', 'NotFound', 'Markdown']; + const themeComponents = ['Docs', 'Pages', 'Loading', 'NotFound', 'Markdown']; themeComponents.forEach(component => { if (!require.resolve(path.join(themePath, component))) { throw new Error( diff --git a/v2/lib/theme/Docs/index.js b/v2/lib/theme/Docs/index.js index 3aff930ce5..6e9f0d9be4 100644 --- a/v2/lib/theme/Docs/index.js +++ b/v2/lib/theme/Docs/index.js @@ -52,10 +52,14 @@ export default class Docs extends React.Component { docsSidebars, metadata, } = this.props; + const {language, version} = metadata; return ( {(metadata && metadata.title) || siteConfig.title} + {language && } + {language && } + {version && }
{this.renderSidebar(metadata, docsSidebars, docsMetadatas)}
diff --git a/v2/lib/theme/Layout/index.js b/v2/lib/theme/Layout/index.js index 775f1ae279..d35f93c6a8 100644 --- a/v2/lib/theme/Layout/index.js +++ b/v2/lib/theme/Layout/index.js @@ -5,15 +5,18 @@ import styles from './styles.css'; /* eslint-disable react/prefer-stateless-function */ export default class Layout extends React.Component { render() { - const {children, pagesMetadatas, docsMetadatas = {}, location} = this.props; - const docsLinks = Object.values(docsMetadatas).map(data => ({ - path: `${data.permalink}`, - })); - const routeLinks = [...pagesMetadatas, ...docsLinks].map( + const { + children, + pagesMetadatas = [], + docsMetadatas = {}, + location, + } = this.props; + const docsFlatMetadatas = Object.values(docsMetadatas); + const routeLinks = [...pagesMetadatas, ...docsFlatMetadatas].map( data => - data.path !== location.pathname && ( -
  • - {data.path} + data.permalink !== location.pathname && ( +
  • + {data.permalink}
  • ), ); diff --git a/v2/lib/theme/Pages/index.js b/v2/lib/theme/Pages/index.js new file mode 100644 index 0000000000..12b394ea2f --- /dev/null +++ b/v2/lib/theme/Pages/index.js @@ -0,0 +1,21 @@ +/* eslint-disable */ +import React from 'react'; +import {Link} from 'react-router-dom'; +import Helmet from 'react-helmet'; +import Layout from '@theme/Layout'; // eslint-disable-line + +export default class Pages extends React.Component { + render() { + const {metadata, children, siteConfig} = this.props; + const {language} = metadata; + return ( + + + {language && } + {language && } + + {children} + + ); + } +} diff --git a/v2/lib/webpack/server.js b/v2/lib/webpack/server.js index fce92c67fb..ec285e672b 100644 --- a/v2/lib/webpack/server.js +++ b/v2/lib/webpack/server.js @@ -17,10 +17,10 @@ module.exports = function createServerConfig(props) { const {siteConfig, docsMetadatas, pagesMetadatas} = props; // static site generator webpack plugin - const docsLinks = Object.values(docsMetadatas).map(data => ({ - path: `${data.permalink}`, - })); - const paths = [...docsLinks, ...pagesMetadatas].map(data => data.path); + const docsFlatMetadatas = Object.values(docsMetadatas); + const paths = [...docsFlatMetadatas, ...pagesMetadatas].map( + data => data.permalink, + ); config.plugin('siteGenerator').use(staticSiteGenerator, [ { entry: 'main', diff --git a/v2/test/__fixtures__/simple-site/pages/hello/world.js b/v2/test/__fixtures__/simple-site/pages/hello/world.js index 838f36bb1a..8ad467948a 100644 --- a/v2/test/__fixtures__/simple-site/pages/hello/world.js +++ b/v2/test/__fixtures__/simple-site/pages/hello/world.js @@ -1,17 +1,16 @@ import React from 'react'; import Helmet from 'react-helmet'; -import Layout from '@theme/Layout'; export default class World extends React.Component { render() { return ( - +
    World
    Hello World
    - +
    ); } } diff --git a/v2/test/__fixtures__/simple-site/pages/index.js b/v2/test/__fixtures__/simple-site/pages/index.js index 162626515d..023f0fb2f9 100644 --- a/v2/test/__fixtures__/simple-site/pages/index.js +++ b/v2/test/__fixtures__/simple-site/pages/index.js @@ -1,17 +1,16 @@ import React from 'react'; import Helmet from 'react-helmet'; -import Layout from '@theme/Layout'; export default class Home extends React.Component { render() { return ( - +
    Home
    Home ...
    - +
    ); } } diff --git a/v2/test/__fixtures__/translated-site/pages/hello/world.js b/v2/test/__fixtures__/translated-site/pages/hello/world.js index 838f36bb1a..8ad467948a 100644 --- a/v2/test/__fixtures__/translated-site/pages/hello/world.js +++ b/v2/test/__fixtures__/translated-site/pages/hello/world.js @@ -1,17 +1,16 @@ import React from 'react'; import Helmet from 'react-helmet'; -import Layout from '@theme/Layout'; export default class World extends React.Component { render() { return ( - +
    World
    Hello World
    - +
    ); } } diff --git a/v2/test/__fixtures__/translated-site/pages/index.js b/v2/test/__fixtures__/translated-site/pages/index.js index 162626515d..023f0fb2f9 100644 --- a/v2/test/__fixtures__/translated-site/pages/index.js +++ b/v2/test/__fixtures__/translated-site/pages/index.js @@ -1,17 +1,16 @@ import React from 'react'; import Helmet from 'react-helmet'; -import Layout from '@theme/Layout'; export default class Home extends React.Component { render() { return ( - +
    Home
    Home ...
    - +
    ); } } diff --git a/v2/test/__fixtures__/transversioned-site/pages/hello/world.js b/v2/test/__fixtures__/transversioned-site/pages/hello/world.js index 838f36bb1a..8ad467948a 100644 --- a/v2/test/__fixtures__/transversioned-site/pages/hello/world.js +++ b/v2/test/__fixtures__/transversioned-site/pages/hello/world.js @@ -1,17 +1,16 @@ import React from 'react'; import Helmet from 'react-helmet'; -import Layout from '@theme/Layout'; export default class World extends React.Component { render() { return ( - +
    World
    Hello World
    - +
    ); } } diff --git a/v2/test/__fixtures__/transversioned-site/pages/index.js b/v2/test/__fixtures__/transversioned-site/pages/index.js index 162626515d..023f0fb2f9 100644 --- a/v2/test/__fixtures__/transversioned-site/pages/index.js +++ b/v2/test/__fixtures__/transversioned-site/pages/index.js @@ -1,17 +1,16 @@ import React from 'react'; import Helmet from 'react-helmet'; -import Layout from '@theme/Layout'; export default class Home extends React.Component { render() { return ( - +
    Home
    Home ...
    - +
    ); } } diff --git a/v2/test/__fixtures__/versioned-site/pages/hello/world.js b/v2/test/__fixtures__/versioned-site/pages/hello/world.js index 838f36bb1a..8ad467948a 100644 --- a/v2/test/__fixtures__/versioned-site/pages/hello/world.js +++ b/v2/test/__fixtures__/versioned-site/pages/hello/world.js @@ -1,17 +1,16 @@ import React from 'react'; import Helmet from 'react-helmet'; -import Layout from '@theme/Layout'; export default class World extends React.Component { render() { return ( - +
    World
    Hello World
    - +
    ); } } diff --git a/v2/test/__fixtures__/versioned-site/pages/index.js b/v2/test/__fixtures__/versioned-site/pages/index.js index 162626515d..023f0fb2f9 100644 --- a/v2/test/__fixtures__/versioned-site/pages/index.js +++ b/v2/test/__fixtures__/versioned-site/pages/index.js @@ -1,17 +1,16 @@ import React from 'react'; import Helmet from 'react-helmet'; -import Layout from '@theme/Layout'; export default class Home extends React.Component { render() { return ( - +
    Home
    Home ...
    - +
    ); } } diff --git a/v2/test/load/__fixtures__/simple-pages/bar/baz.js b/v2/test/load/__fixtures__/simple-pages/bar/baz.js deleted file mode 100644 index 75a8c70015..0000000000 --- a/v2/test/load/__fixtures__/simple-pages/bar/baz.js +++ /dev/null @@ -1,3 +0,0 @@ -import React from 'react'; - -export default () =>
    Baz
    ; diff --git a/v2/test/load/__fixtures__/simple-pages/foo.js b/v2/test/load/__fixtures__/simple-pages/foo.js deleted file mode 100644 index 3b52ec615c..0000000000 --- a/v2/test/load/__fixtures__/simple-pages/foo.js +++ /dev/null @@ -1,3 +0,0 @@ -import React from 'react'; - -export default () =>
    Foo
    ; diff --git a/v2/test/load/__fixtures__/simple-pages/foo/index.js b/v2/test/load/__fixtures__/simple-pages/foo/index.js deleted file mode 100644 index 5faf67ae13..0000000000 --- a/v2/test/load/__fixtures__/simple-pages/foo/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import React from 'react'; - -export default () =>
    Foo in subfolder
    ; diff --git a/v2/test/load/__fixtures__/simple-pages/index.js b/v2/test/load/__fixtures__/simple-pages/index.js deleted file mode 100644 index 13063810a7..0000000000 --- a/v2/test/load/__fixtures__/simple-pages/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import React from 'react'; - -export default () =>
    Index
    ; diff --git a/v2/test/load/pages.test.js b/v2/test/load/pages.test.js index 84d165430d..252556fec3 100644 --- a/v2/test/load/pages.test.js +++ b/v2/test/load/pages.test.js @@ -1,35 +1,116 @@ import loadPages from '@lib/load/pages'; import path from 'path'; +import loadSetup from '../loadSetup'; describe('loadPages', () => { - test('valid pages', async () => { - const pagesDir = path.join(__dirname, '__fixtures__', 'simple-pages'); - const pagesMetadatas = await loadPages(pagesDir); - pagesMetadatas.sort((a, b) => a.path > b.path); // because it was unordered + test('simple website', async () => { + const {pagesDir, env, siteConfig} = await loadSetup('simple'); + const pagesMetadatas = await loadPages({pagesDir, env, siteConfig}); expect(pagesMetadatas).toEqual([ { - path: '/', - source: 'index.js', + permalink: '/', + source: path.join(pagesDir, 'index.js'), }, { - path: '/bar/baz', - source: 'bar/baz.js', - }, - { - path: '/foo', - source: 'foo.js', - }, - { - path: '/foo/', - source: 'foo/index.js', + permalink: '/hello/world', + source: path.join(pagesDir, 'hello', 'world.js'), + }, + ]); + }); + + test('versioned website', async () => { + const {pagesDir, env, siteConfig} = await loadSetup('versioned'); + const pagesMetadatas = await loadPages({pagesDir, env, siteConfig}); + expect(pagesMetadatas).toEqual([ + { + permalink: '/', + source: path.join(pagesDir, 'index.js'), + }, + { + permalink: '/hello/world', + source: path.join(pagesDir, 'hello', 'world.js'), + }, + ]); + }); + + test('versioned & translated website', async () => { + const {pagesDir, env, siteConfig} = await loadSetup('transversioned'); + const pagesMetadatas = await loadPages({pagesDir, env, siteConfig}); + expect(pagesMetadatas).toEqual([ + { + language: 'en', + permalink: '/', + source: path.join(pagesDir, 'index.js'), + }, + { + language: 'en', + permalink: '/en/', + source: path.join(pagesDir, 'index.js'), + }, + { + language: 'ko', + permalink: '/ko/', + source: path.join(pagesDir, 'index.js'), + }, + { + language: 'en', + permalink: '/hello/world', + source: path.join(pagesDir, 'hello', 'world.js'), + }, + { + language: 'en', + permalink: '/en/hello/world', + source: path.join(pagesDir, 'hello', 'world.js'), + }, + { + language: 'ko', + permalink: '/ko/hello/world', + source: path.join(pagesDir, 'hello', 'world.js'), + }, + ]); + }); + + test('translated website', async () => { + const {pagesDir, env, siteConfig} = await loadSetup('translated'); + const pagesMetadatas = await loadPages({pagesDir, env, siteConfig}); + expect(pagesMetadatas).toEqual([ + { + language: 'en', + permalink: '/', + source: path.join(pagesDir, 'index.js'), + }, + { + language: 'en', + permalink: '/en/', + source: path.join(pagesDir, 'index.js'), + }, + { + language: 'ko', + permalink: '/ko/', + source: path.join(pagesDir, 'index.js'), + }, + { + language: 'en', + permalink: '/hello/world', + source: path.join(pagesDir, 'hello', 'world.js'), + }, + { + language: 'en', + permalink: '/en/hello/world', + source: path.join(pagesDir, 'hello', 'world.js'), + }, + { + language: 'ko', + permalink: '/ko/hello/world', + source: path.join(pagesDir, 'hello', 'world.js'), }, ]); - expect(pagesMetadatas).not.toBeNull(); }); test('invalid pages', async () => { - const nonExistingDir = path.join(__dirname, '__fixtures__', 'nonExisting'); - const pagesMetadatas = await loadPages(nonExistingDir); + const {env, siteConfig} = await loadSetup('simple'); + const pagesDir = path.join(__dirname, '__fixtures__', 'nonExisting'); + const pagesMetadatas = await loadPages({pagesDir, env, siteConfig}); expect(pagesMetadatas).toEqual([]); }); }); diff --git a/v2/website/pages/index.js b/v2/website/pages/index.js index a79f52ddb8..ac2f305f2c 100644 --- a/v2/website/pages/index.js +++ b/v2/website/pages/index.js @@ -1,18 +1,17 @@ import React from 'react'; import Helmet from 'react-helmet'; -import Layout from '@theme/Layout'; import Todo from '@site/components/Todo'; export default class Home extends React.Component { render() { return ( - +
    Todo App - +
    ); } } diff --git a/v2/website/pages/tictactoe.js b/v2/website/pages/tictactoe.js index 41072c4387..b7aa6baacd 100644 --- a/v2/website/pages/tictactoe.js +++ b/v2/website/pages/tictactoe.js @@ -1,17 +1,16 @@ import React from 'react'; import Helmet from 'react-helmet'; -import Layout from '@theme/Layout'; import Tictactoe from '@site/components/Tictactoe'; export default class Home extends React.Component { render() { return ( - +
    Tic Tac Toe - +
    ); } } diff --git a/v2/website/pages/youtube.js b/v2/website/pages/youtube.js index 2aeac31350..9e39698617 100644 --- a/v2/website/pages/youtube.js +++ b/v2/website/pages/youtube.js @@ -1,7 +1,6 @@ import React from 'react'; import Helmet from 'react-helmet'; import YouTube from 'react-youtube'; -import Layout from '@theme/Layout'; export default class Player extends React.Component { render() { @@ -14,15 +13,15 @@ export default class Player extends React.Component { }; return ( - +
    My Youtube -

    +

    {/* this is a React-youtube component */} -

    - +
    +
    ); }