diff --git a/packages/docusaurus-plugin-content-blog/src/index.js b/packages/docusaurus-plugin-content-blog/src/index.js index 827c18a56c..dceb8cd73c 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.js +++ b/packages/docusaurus-plugin-content-blog/src/index.js @@ -156,24 +156,12 @@ class DocusaurusPluginContentBlog { ); } + getThemePath() { + return path.resolve(__dirname, './theme'); + } + configureWebpack(config, isServer, {getBabelLoader, getCacheLoader}) { return { - resolve: { - alias: { - '@theme/BlogListPage': path.resolve( - __dirname, - './theme/BlogListPage', - ), - '@theme/BlogPostItem': path.resolve( - __dirname, - './theme/BlogPostItem', - ), - '@theme/BlogPostPage': path.resolve( - __dirname, - './theme/BlogPostPage', - ), - }, - }, module: { rules: [ { diff --git a/packages/docusaurus-plugin-content-docs/src/index.js b/packages/docusaurus-plugin-content-docs/src/index.js index e4f457dd41..ea2b4b482e 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.js +++ b/packages/docusaurus-plugin-content-docs/src/index.js @@ -151,19 +151,12 @@ class DocusaurusPluginContentDocs { }); } + getThemePath() { + return path.resolve(__dirname, './theme'); + } + configureWebpack(config, isServer, {getBabelLoader, getCacheLoader}) { return { - resolve: { - alias: { - '@theme/DocItem': path.resolve(__dirname, './theme/DocItem'), - '@theme/DocPage': path.resolve(__dirname, './theme/DocPage'), - '@theme/DocPaginator': path.resolve( - __dirname, - './theme/DocPaginator', - ), - '@theme/DocSidebar': path.resolve(__dirname, './theme/DocSidebar'), - }, - }, module: { rules: [ { diff --git a/packages/docusaurus-theme-classic/src/index.js b/packages/docusaurus-theme-classic/src/index.js index 1f3f3d371d..9fafacab35 100644 --- a/packages/docusaurus-theme-classic/src/index.js +++ b/packages/docusaurus-theme-classic/src/index.js @@ -19,18 +19,8 @@ class DocusaurusThemeDefault { return 'docusaurus-theme-classic'; } - configureWebpack() { - return { - resolve: { - alias: { - '@theme/Footer': path.resolve(__dirname, './theme/Footer'), - '@theme/Layout': path.resolve(__dirname, './theme/Layout'), - '@theme/Navbar': path.resolve(__dirname, './theme/Navbar'), - '@theme/NotFound': path.resolve(__dirname, './theme/NotFound'), - '@theme/Search': path.resolve(__dirname, './theme/Search'), - }, - }, - }; + getThemePath() { + return path.resolve(__dirname, './theme'); } } diff --git a/packages/docusaurus/bin/docusaurus.js b/packages/docusaurus/bin/docusaurus.js index 4bc1e0d27d..ccd565f4d4 100755 --- a/packages/docusaurus/bin/docusaurus.js +++ b/packages/docusaurus/bin/docusaurus.js @@ -12,7 +12,7 @@ const envinfo = require('envinfo'); const semver = require('semver'); const path = require('path'); const program = require('commander'); -const {build, eject, init, deploy, start} = require('../lib'); +const {build, swizzle, init, deploy, start} = require('../lib'); const requiredVersion = require('../package.json').engines.node; if (!semver.satisfies(process.version, requiredVersion)) { @@ -55,10 +55,10 @@ program }); program - .command('eject [siteDir]') - .description('copy the default theme into website folder for customization.') - .action((siteDir = '.') => { - wrapCommand(eject)(path.resolve(siteDir)); + .command('swizzle [componentName] [siteDir]') + .description('Copy the theme files into website folder for customization.') + .action((themeName, componentName, siteDir = '.') => { + wrapCommand(swizzle)(path.resolve(siteDir), themeName, componentName); }); program diff --git a/packages/docusaurus/lib/commands/eject.js b/packages/docusaurus/lib/commands/eject.js deleted file mode 100644 index fb082260db..0000000000 --- a/packages/docusaurus/lib/commands/eject.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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. - */ - -const fs = require('fs-extra'); -const chalk = require('chalk'); -const path = require('path'); - -module.exports = async function eject(siteDir) { - const defaultTheme = path.resolve(__dirname, '..', 'default-theme'); - const customTheme = path.resolve(siteDir, 'theme'); - await fs.copy(defaultTheme, customTheme); - - const relativeDir = path.relative(process.cwd(), customTheme); - console.log( - `\n${chalk.green('Success!')} Copied default theme files to ${chalk.cyan( - relativeDir, - )}.\n`, - ); -}; diff --git a/packages/docusaurus/lib/commands/swizzle.js b/packages/docusaurus/lib/commands/swizzle.js new file mode 100644 index 0000000000..9c1489b775 --- /dev/null +++ b/packages/docusaurus/lib/commands/swizzle.js @@ -0,0 +1,36 @@ +/** + * 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. + */ + +const fs = require('fs-extra'); +const chalk = require('chalk'); +const path = require('path'); +const importFresh = require('import-fresh'); + +module.exports = async function swizzle(siteDir, themeName, componentName) { + const Plugin = importFresh(themeName); + const context = {siteDir}; + const PluginInstance = new Plugin(context); + let fromPath = PluginInstance.getThemePath(); + + if (fromPath) { + let toPath = path.resolve(siteDir, 'theme'); + if (componentName) { + fromPath = path.join(fromPath, componentName); + toPath = path.join(toPath, componentName); + } + await fs.copy(fromPath, toPath); + + const relativeDir = path.relative(process.cwd(), toPath); + const fromMsg = chalk.blue( + componentName ? `${themeName}/${componentName}` : themeName, + ); + const toMsg = chalk.cyan(relativeDir); + console.log( + `\n${chalk.green('Success!')} Copied ${fromMsg} to ${toMsg}.\n`, + ); + } +}; diff --git a/packages/docusaurus/lib/index.js b/packages/docusaurus/lib/index.js index 60c36dc6d5..6b2018f0cb 100644 --- a/packages/docusaurus/lib/index.js +++ b/packages/docusaurus/lib/index.js @@ -8,12 +8,12 @@ const build = require('./commands/build'); const init = require('./commands/init'); const start = require('./commands/start'); -const eject = require('./commands/eject'); +const swizzle = require('./commands/swizzle'); const deploy = require('./commands/deploy'); module.exports = { build, - eject, + swizzle, init, start, deploy, diff --git a/packages/docusaurus/lib/server/index.js b/packages/docusaurus/lib/server/index.js index f3a2c8d61f..244e4b958c 100644 --- a/packages/docusaurus/lib/server/index.js +++ b/packages/docusaurus/lib/server/index.js @@ -51,18 +51,42 @@ module.exports = async function load(siteDir, cliOptions = {}) { const outDir = path.resolve(siteDir, 'build'); const {baseUrl} = siteConfig; - // Resolve custom theme override aliases. - const themeAliases = await loadTheme(siteDir); - // Make a fake plugin to resolve user's theme overrides. - if (themeAliases != null) { - plugins.push({ - configureWebpack: () => ({ - resolve: { - alias: themeAliases, - }, - }), - }); - } + // Default theme components that are essential and must exist in a Docusaurus app + // These can be overriden in plugins/ through component swizzling. + // However, we alias it here first as a fallback. + const themeFallback = path.resolve(__dirname, '../client/theme-fallback'); + let themeAliases = await loadTheme(themeFallback); + + // create theme alias from plugins + await Promise.all( + plugins.map(async plugin => { + if (!plugin.getThemePath) { + return; + } + const aliases = await loadTheme(plugin.getThemePath()); + themeAliases = { + ...themeAliases, + ...aliases, + }; + }), + ); + + // user's own theme alias override. Highest priority + const themePath = path.resolve(siteDir, 'theme'); + const aliases = await loadTheme(themePath); + themeAliases = { + ...themeAliases, + ...aliases, + }; + + // Make a fake plugin to resolve alias theme. + plugins.push({ + configureWebpack: () => ({ + resolve: { + alias: themeAliases, + }, + }), + }); // Routing const { diff --git a/packages/docusaurus/lib/server/load/theme.js b/packages/docusaurus/lib/server/load/theme.js index 010feb35fb..426dcd9d0b 100644 --- a/packages/docusaurus/lib/server/load/theme.js +++ b/packages/docusaurus/lib/server/load/theme.js @@ -10,8 +10,7 @@ const fs = require('fs-extra'); const path = require('path'); const {fileToPath, posixPath, normalizeUrl} = require('@docusaurus/utils'); -module.exports = async function loadTheme(siteDir) { - const themePath = path.resolve(siteDir, 'theme'); +module.exports = async function loadTheme(themePath) { if (!fs.existsSync(themePath)) { return null; } diff --git a/packages/docusaurus/lib/webpack/base.js b/packages/docusaurus/lib/webpack/base.js index f579f67b2e..5f93ea994f 100644 --- a/packages/docusaurus/lib/webpack/base.js +++ b/packages/docusaurus/lib/webpack/base.js @@ -25,7 +25,6 @@ module.exports = function createBaseConfig(props, isServer) { } = props; const isProd = process.env.NODE_ENV === 'production'; - const themeFallback = path.resolve(__dirname, '../client/theme-fallback'); return { mode: isProd ? 'production' : 'development', output: { @@ -44,11 +43,6 @@ module.exports = function createBaseConfig(props, isServer) { alias: { // https://stackoverflow.com/a/55433680/6072730 ejs: 'ejs/ejs.min.js', - // These alias can be overriden in plugins. However, these components are essential - // (e.g: react-loadable requires Loading component) so we alias it here first as fallback. - '@theme/Layout': path.join(themeFallback, 'Layout'), - '@theme/Loading': path.join(themeFallback, 'Loading'), - '@theme/NotFound': path.join(themeFallback, 'NotFound'), '@site': siteDir, '@generated': generatedFilesDir, '@docusaurus': path.resolve(__dirname, '../client/exports'), diff --git a/website/package.json b/website/package.json index 4949a521c3..f4d2447700 100644 --- a/website/package.json +++ b/website/package.json @@ -5,7 +5,7 @@ "scripts": { "start": "docusaurus start", "build": "docusaurus build", - "eject": "docusaurus eject", + "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy" }, "dependencies": {