feat(v2): easier plugin theme components swizzling (#1436)

* feat(v2): easier plugin theme components override

* add context

* refactor again

* rename eject -> swizzle

* nits
This commit is contained in:
Endi 2019-05-07 13:56:43 +07:00 committed by GitHub
parent 3298d8cd23
commit 0834784455
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 91 additions and 90 deletions

View file

@ -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: [
{

View file

@ -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: [
{

View file

@ -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');
}
}

View file

@ -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 <themeName> [componentName] [siteDir]')
.description('Copy the theme files into website folder for customization.')
.action((themeName, componentName, siteDir = '.') => {
wrapCommand(swizzle)(path.resolve(siteDir), themeName, componentName);
});
program

View file

@ -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`,
);
};

View file

@ -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`,
);
}
};

View file

@ -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,

View file

@ -51,10 +51,35 @@ 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) {
// 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: {
@ -62,7 +87,6 @@ module.exports = async function load(siteDir, cliOptions = {}) {
},
}),
});
}
// Routing
const {

View file

@ -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;
}

View file

@ -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'),

View file

@ -5,7 +5,7 @@
"scripts": {
"start": "docusaurus start",
"build": "docusaurus build",
"eject": "docusaurus eject",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy"
},
"dependencies": {