From c06ccc0a07563104a30b2676dc0e1e44acb57151 Mon Sep 17 00:00:00 2001 From: Yangshun Tay Date: Mon, 3 Jun 2019 23:33:00 -0700 Subject: [PATCH] feat(v2): implement client modules API (#1554) * feat(v2): load client modules * docs(v2): update plugins API * misc(v2): change to import --- packages/docusaurus/src/client/App.js | 5 +- .../__tests__/__fixtures__/plugin-empty.js | 5 + .../__tests__/__fixtures__/plugin-foo-bar.js | 8 ++ .../__fixtures__/plugin-hello-world.js | 8 ++ .../client-modules/__tests__/index.test.ts | 94 +++++++++++++++++++ .../src/server/client-modules/index.ts | 24 +++++ packages/docusaurus/src/server/index.ts | 13 +++ .../docusaurus/src/server/plugins/index.ts | 1 + website/docs/plugins-api.md | 19 +++- 9 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-empty.js create mode 100644 packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-foo-bar.js create mode 100644 packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-hello-world.js create mode 100644 packages/docusaurus/src/server/client-modules/__tests__/index.test.ts create mode 100644 packages/docusaurus/src/server/client-modules/index.ts diff --git a/packages/docusaurus/src/client/App.js b/packages/docusaurus/src/client/App.js index 07e9e54e5f..b02fbdb709 100644 --- a/packages/docusaurus/src/client/App.js +++ b/packages/docusaurus/src/client/App.js @@ -8,12 +8,15 @@ import React from 'react'; import {renderRoutes} from 'react-router-config'; -import Head from '@docusaurus/Head'; import routes from '@generated/routes'; import siteConfig from '@generated/docusaurus.config'; + +import Head from '@docusaurus/Head'; import DocusaurusContext from '@docusaurus/context'; import PendingNavigation from './PendingNavigation'; +import '@generated/client-modules'; + function App() { return ( diff --git a/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-empty.js b/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-empty.js new file mode 100644 index 0000000000..8c0267bc46 --- /dev/null +++ b/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-empty.js @@ -0,0 +1,5 @@ +module.exports = function() { + return { + name: 'plugin-empty', + }; +}; diff --git a/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-foo-bar.js b/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-foo-bar.js new file mode 100644 index 0000000000..13f86d13b3 --- /dev/null +++ b/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-foo-bar.js @@ -0,0 +1,8 @@ +module.exports = function() { + return { + name: 'plugin-foo-bar', + getClientModules() { + return ['foo', 'bar']; + }, + }; +}; diff --git a/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-hello-world.js b/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-hello-world.js new file mode 100644 index 0000000000..72fb606391 --- /dev/null +++ b/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-hello-world.js @@ -0,0 +1,8 @@ +module.exports = function() { + return { + plugin: 'plugin-hello-world', + getClientModules() { + return ['hello', 'world']; + }, + }; +}; diff --git a/packages/docusaurus/src/server/client-modules/__tests__/index.test.ts b/packages/docusaurus/src/server/client-modules/__tests__/index.test.ts new file mode 100644 index 0000000000..21ef38902e --- /dev/null +++ b/packages/docusaurus/src/server/client-modules/__tests__/index.test.ts @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; + +import {loadClientModules} from '../index'; +import {LoadContext} from '../../index'; + +const pluginEmpty = require('./__fixtures__/plugin-empty'); +const pluginFooBar = require('./__fixtures__/plugin-foo-bar'); +const pluginHelloWorld = require('./__fixtures__/plugin-hello-world'); + +describe('loadClientModules', () => { + test('empty', () => { + const clientModules = loadClientModules([pluginEmpty()]); + expect(clientModules).toMatchInlineSnapshot(`Array []`); + }); + + test('non-empty', () => { + const clientModules = loadClientModules([pluginFooBar()]); + expect(clientModules).toMatchInlineSnapshot(` + Array [ + "foo", + "bar", + ] + `); + }); + + test('multiple non-empty', () => { + const clientModules = loadClientModules([ + pluginFooBar(), + pluginHelloWorld(), + ]); + expect(clientModules).toMatchInlineSnapshot(` + Array [ + "foo", + "bar", + "hello", + "world", + ] + `); + }); + + test('multiple non-empty different order', () => { + const clientModules = loadClientModules([ + pluginHelloWorld(), + pluginFooBar(), + ]); + expect(clientModules).toMatchInlineSnapshot(` + Array [ + "hello", + "world", + "foo", + "bar", + ] + `); + }); + + test('empty and non-empty', () => { + const clientModules = loadClientModules([ + pluginHelloWorld(), + pluginEmpty(), + pluginFooBar(), + ]); + expect(clientModules).toMatchInlineSnapshot(` + Array [ + "hello", + "world", + "foo", + "bar", + ] + `); + }); + + test('empty and non-empty different order', () => { + const clientModules = loadClientModules([ + pluginHelloWorld(), + pluginFooBar(), + pluginEmpty(), + ]); + expect(clientModules).toMatchInlineSnapshot(` + Array [ + "hello", + "world", + "foo", + "bar", + ] + `); + }); +}); diff --git a/packages/docusaurus/src/server/client-modules/index.ts b/packages/docusaurus/src/server/client-modules/index.ts new file mode 100644 index 0000000000..bf1773209b --- /dev/null +++ b/packages/docusaurus/src/server/client-modules/index.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {Plugin} from '../plugins'; + +import _ from 'lodash'; + +export function loadClientModules(plugins: Plugin[]): string[] { + return _.compact( + _.flatten( + plugins.map(plugin => { + if (!plugin.getClientModules) { + return null; + } + + return plugin.getClientModules(); + }), + ), + ); +} diff --git a/packages/docusaurus/src/server/index.ts b/packages/docusaurus/src/server/index.ts index 173ab8fb44..9a68d129aa 100644 --- a/packages/docusaurus/src/server/index.ts +++ b/packages/docusaurus/src/server/index.ts @@ -17,6 +17,8 @@ import {loadThemeAlias} from './themes'; import {loadPlugins} from './plugins'; import {loadRoutes} from './routes'; import {loadPresets} from './presets'; +import {loadClientModules} from './client-modules'; + import {GENERATED_FILES_DIR_NAME, CONFIG_FILE_NAME} from '../constants'; export interface CLIOptions { @@ -96,6 +98,16 @@ export async function load( }), }); + // Load client modules. + const clientModules = loadClientModules(plugins); + const genClientModules = generate( + generatedFilesDir, + 'client-modules.js', + `export default [\n${clientModules + .map(module => ` require(${JSON.stringify(module)}),`) + .join('\n')}\n];\n`, + ); + // Routing const { registry, @@ -128,6 +140,7 @@ ${Object.keys(registry) const genRoutes = generate(generatedFilesDir, 'routes.js', routesConfig); await Promise.all([ + genClientModules, genSiteConfig, genRegistry, genRoutesChunkNames, diff --git a/packages/docusaurus/src/server/plugins/index.ts b/packages/docusaurus/src/server/plugins/index.ts index d3e99381fc..b54febd9ad 100644 --- a/packages/docusaurus/src/server/plugins/index.ts +++ b/packages/docusaurus/src/server/plugins/index.ts @@ -26,6 +26,7 @@ export interface Plugin { configureWebpack?(config: Configuration, isServer: boolean): Configuration; getThemePath?(): string; getPathsToWatch?(): string[]; + getClientModules?(): string[]; } export interface PluginConfig { diff --git a/website/docs/plugins-api.md b/website/docs/plugins-api.md index dba6ac29eb..2fd1b79234 100644 --- a/website/docs/plugins-api.md +++ b/website/docs/plugins-api.md @@ -66,8 +66,12 @@ module.exports = function(context, opts) { const options = {...DEFAULT_OPTIONS, ...options}; return { - // Namespace used for directories to cache the intermediate data for each plugin. - name: 'docusaurus-cool-plugin', + // A compulsory field used as the namespace for directories to cache + // the intermediate data for each plugin. + // If you're writing your own local plugin, you will want it to + // be unique in order not to potentially conflict with imported plugins. + // A good way will be to add your own project name within. + name: 'docusaurus-my-project-cool-plugin', async loadContent() { // The loadContent hook is executed after siteConfig and env has been loaded @@ -107,6 +111,17 @@ module.exports = function(context, opts) { getPathsToWatch() { // Path to watch }, + + getThemePath() { + // Returns the path to the directory where the theme components can + // be found. + }, + + getClientModules() { + // Return an array of paths to the modules that are to be imported + // in the client bundle. These modules are imported globally before + // React even renders the initial UI. + }, }; }; ```