feat(core): allow plugin lifecycles to return relative paths (#6921)

* feat(core): resolve plugin lifecycles returning relative paths

* fix typo

* fix tests

* revert

* rename path -> entryPath
This commit is contained in:
Joshua Chen 2022-03-16 20:47:15 +08:00 committed by GitHub
parent 8d1c1954c1
commit 68aaf9201f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 100 additions and 68 deletions

View file

@ -24,10 +24,10 @@ export default function pluginDebug({
name: 'docusaurus-plugin-debug',
getThemePath() {
return path.resolve(__dirname, '../lib/theme');
return '../lib/theme';
},
getTypeScriptThemePath() {
return path.resolve(__dirname, '../src/theme');
return '../src/theme';
},
async contentLoaded({actions: {createData, addRoute}, allContent}) {

View file

@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import {Joi} from '@docusaurus/utils-validation';
import type {
LoadContext,
@ -28,7 +27,7 @@ export default function pluginGoogleAnalytics(
name: 'docusaurus-plugin-google-analytics',
getClientModules() {
return isProd ? [path.resolve(__dirname, './analytics')] : [];
return isProd ? ['./analytics'] : [];
},
injectHtmlTags() {

View file

@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import {Joi} from '@docusaurus/utils-validation';
import type {
LoadContext,
@ -32,7 +31,7 @@ export default function pluginGoogleGtag(
},
getClientModules() {
return isProd ? [path.resolve(__dirname, './gtag')] : [];
return isProd ? ['./gtag'] : [];
},
injectHtmlTags() {

View file

@ -15,8 +15,6 @@ import type {PluginOptions} from '@docusaurus/plugin-ideal-image';
import {Joi} from '@docusaurus/utils-validation';
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
import path from 'path';
export default function pluginIdealImage(
context: LoadContext,
options: PluginOptions,
@ -29,11 +27,11 @@ export default function pluginIdealImage(
name: 'docusaurus-plugin-ideal-image',
getThemePath() {
return path.resolve(__dirname, '../lib/theme');
return '../lib/theme';
},
getTypeScriptThemePath() {
return path.resolve(__dirname, '../src/theme');
return '../src/theme';
},
getDefaultCodeTranslationMessages() {

View file

@ -64,10 +64,10 @@ export default function pluginPWA(
name: 'docusaurus-plugin-pwa',
getThemePath() {
return path.resolve(__dirname, '../lib/theme');
return '../lib/theme';
},
getTypeScriptThemePath() {
return path.resolve(__dirname, '../src/theme');
return '../src/theme';
},
getClientModules() {
@ -138,7 +138,7 @@ export default function pluginPWA(
const swSourceFileTest = /\.m?js$/;
const swWebpackConfig: Configuration = {
entry: path.resolve(__dirname, 'sw.js'),
entry: require.resolve('./sw.js'),
output: {
path: outDir,
filename: 'sw.js',

View file

@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import {Joi} from '@docusaurus/utils-validation';
import type {
ThemeConfig,
@ -24,7 +23,7 @@ const DEFAULT_OPTIONS = {
injectManifestConfig: {},
pwaHead: [],
swCustom: undefined,
swRegister: path.join(__dirname, 'registerSw.js'),
swRegister: './registerSw.js',
reloadPopup: '@theme/PwaReloadPopup',
};

View file

@ -8,7 +8,6 @@
import type {LoadContext, Plugin, PostCssOptions} from '@docusaurus/types';
import type {ThemeConfig} from '@docusaurus/theme-common';
import {getTranslationFiles, translateThemeConfig} from './translations';
import path from 'path';
import {createRequire} from 'module';
import type {Plugin as PostCssPlugin} from 'postcss';
import rtlcss from 'rtlcss';
@ -112,11 +111,11 @@ export default function docusaurusThemeClassic(
name: 'docusaurus-theme-classic',
getThemePath() {
return path.join(__dirname, '../lib-next/theme');
return '../lib-next/theme';
},
getTypeScriptThemePath() {
return path.resolve(__dirname, '../src/theme');
return '../src/theme';
},
getTranslationFiles: async () => getTranslationFiles({themeConfig}),
@ -137,8 +136,8 @@ export default function docusaurusThemeClassic(
getClientModules() {
const modules = [
require.resolve(getInfimaCSSFile(direction)),
path.resolve(__dirname, './prism-include-languages'),
path.resolve(__dirname, './admonitions.css'),
'./prism-include-languages',
'./admonitions.css',
];
if (customCss) {

View file

@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
import type {LoadContext, Plugin} from '@docusaurus/types';
@ -18,10 +17,10 @@ export default function themeLiveCodeblock(context: LoadContext): Plugin {
name: 'docusaurus-theme-live-codeblock',
getThemePath() {
return path.resolve(__dirname, '../lib/theme');
return '../lib/theme';
},
getTypeScriptThemePath() {
return path.resolve(__dirname, '../src/theme');
return '../src/theme';
},
getDefaultCodeTranslationMessages() {
@ -35,7 +34,7 @@ export default function themeLiveCodeblock(context: LoadContext): Plugin {
return {
resolve: {
alias: {
buble: path.resolve(__dirname, './custom-buble.js'),
buble: require.resolve('./custom-buble.js'),
},
},
};

View file

@ -47,10 +47,10 @@ export default function themeSearchAlgolia(context: LoadContext): Plugin<void> {
name: 'docusaurus-theme-search-algolia',
getThemePath() {
return path.resolve(__dirname, '../lib/theme');
return '../lib/theme';
},
getTypeScriptThemePath() {
return path.resolve(__dirname, '../src/theme');
return '../src/theme';
},
getDefaultCodeTranslationMessages() {

View file

@ -290,6 +290,10 @@ export interface Plugin<Content = unknown> {
export type InitializedPlugin<Content = unknown> = Plugin<Content> & {
readonly options: Required<PluginOptions>;
readonly version: DocusaurusPluginVersionInformation;
/**
* The absolute path to the folder containing the entry point file.
*/
readonly path: string;
};
export type LoadedPlugin<Content = unknown> = InitializedPlugin<Content> & {

View file

@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import {createRequire} from 'module';
import {loadContext, loadPluginConfigs} from '../../server';
import initPlugins, {normalizePluginConfigs} from '../../server/plugins/init';
import type {InitializedPlugin} from '@docusaurus/types';
@ -15,7 +14,6 @@ export async function initSwizzleContext(
siteDir: string,
): Promise<SwizzleContext> {
const context = await loadContext(siteDir);
const pluginRequire = createRequire(context.siteConfigPath);
const pluginConfigs = await loadPluginConfigs(context);
const plugins: InitializedPlugin[] = await initPlugins({
@ -25,7 +23,7 @@ export async function initSwizzleContext(
const pluginsNormalized = await normalizePluginConfigs(
pluginConfigs,
pluginRequire,
context.siteConfigPath,
);
return {

View file

@ -6,6 +6,7 @@
*/
import logger from '@docusaurus/logger';
import path from 'path';
import leven from 'leven';
import _ from 'lodash';
import {askThemeName} from './prompts';
@ -132,8 +133,16 @@ export function getThemePath({
}): string {
const pluginInstance = getPluginByThemeName(plugins, themeName);
const themePath = typescript
? pluginInstance.instance.getTypeScriptThemePath?.()
: pluginInstance.instance.getThemePath?.();
? pluginInstance.instance.getTypeScriptThemePath &&
path.resolve(
pluginInstance.instance.path,
pluginInstance.instance.getTypeScriptThemePath(),
)
: pluginInstance.instance.getThemePath &&
path.resolve(
pluginInstance.instance.path,
pluginInstance.instance.getThemePath(),
);
if (!themePath) {
logger.warn(
typescript

View file

@ -8,5 +8,6 @@
module.exports = function() {
return {
name: 'plugin-empty',
path: __dirname,
};
};

View file

@ -8,6 +8,7 @@
module.exports = function() {
return {
name: 'plugin-foo-bar',
path: __dirname,
getClientModules() {
return ['foo', 'bar'];
},

View file

@ -8,6 +8,7 @@
module.exports = function() {
return {
plugin: 'plugin-hello-world',
path: __dirname,
getClientModules() {
return ['hello', 'world'];
},

View file

@ -21,8 +21,8 @@ describe('loadClientModules', () => {
const clientModules = loadClientModules([pluginFooBar()]);
expect(clientModules).toMatchInlineSnapshot(`
[
"foo",
"bar",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/foo",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/bar",
]
`);
});
@ -34,10 +34,10 @@ describe('loadClientModules', () => {
]);
expect(clientModules).toMatchInlineSnapshot(`
[
"foo",
"bar",
"hello",
"world",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/foo",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/bar",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/hello",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/world",
]
`);
});
@ -49,10 +49,10 @@ describe('loadClientModules', () => {
]);
expect(clientModules).toMatchInlineSnapshot(`
[
"hello",
"world",
"foo",
"bar",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/hello",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/world",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/foo",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/bar",
]
`);
});
@ -65,10 +65,10 @@ describe('loadClientModules', () => {
]);
expect(clientModules).toMatchInlineSnapshot(`
[
"hello",
"world",
"foo",
"bar",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/hello",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/world",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/foo",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/bar",
]
`);
});
@ -81,10 +81,10 @@ describe('loadClientModules', () => {
]);
expect(clientModules).toMatchInlineSnapshot(`
[
"hello",
"world",
"foo",
"bar",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/hello",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/world",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/foo",
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/bar",
]
`);
});

View file

@ -5,10 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/
import type {Plugin} from '@docusaurus/types';
import path from 'path';
import type {LoadedPlugin} from '@docusaurus/types';
export default function loadClientModules(
plugins: Plugin<unknown>[],
plugins: LoadedPlugin<unknown>[],
): string[] {
return plugins.flatMap((plugin) => plugin.getClientModules?.() ?? []);
return plugins.flatMap(
(plugin) =>
plugin.getClientModules?.().map((p) => path.resolve(plugin.path, p)) ??
[],
);
}

View file

@ -176,8 +176,10 @@ export async function loadPluginConfigs(
// - Resolve aliased theme components
// - Inject scripts/stylesheets
function createBootstrapPlugin({
siteDir,
siteConfig,
}: {
siteDir: string;
siteConfig: DocusaurusConfig;
}): LoadedPlugin {
const {
@ -192,6 +194,7 @@ function createBootstrapPlugin({
id: 'default',
},
version: {type: 'synthetic'},
path: siteDir,
getClientModules() {
return siteConfigClientModules;
},
@ -244,6 +247,8 @@ function createMDXFallbackPlugin({
id: 'default',
},
version: {type: 'synthetic'},
// Synthetic, the path doesn't matter much
path: '.',
configureWebpack(config, isServer, {getJSLoader}) {
// We need the mdx fallback loader to exclude files that were already
// processed by content plugins mdx loaders. This works, but a bit
@ -336,7 +341,7 @@ export default ${JSON.stringify(siteConfig, null, 2)};
`,
);
plugins.push(createBootstrapPlugin({siteConfig}));
plugins.push(createBootstrapPlugin({siteDir, siteConfig}));
plugins.push(createMDXFallbackPlugin({siteDir, siteConfig}));
// Load client modules.

View file

@ -19,6 +19,7 @@ exports[`loadPlugins loads plugins 1`] = `
"options": {
"id": "default",
},
"path": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin",
"prop": "a",
"version": {
"type": "local",
@ -31,6 +32,7 @@ exports[`loadPlugins loads plugins 1`] = `
"options": {
"id": "default",
},
"path": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin",
"version": {
"type": "local",
},

View file

@ -6,6 +6,7 @@
*/
import {createRequire} from 'module';
import path from 'path';
import importFresh from 'import-fresh';
import type {
DocusaurusPluginVersionInformation,
@ -32,12 +33,18 @@ export type NormalizedPluginConfig = {
path: string;
module: ImportedPluginModule;
};
/**
* Different from pluginModule.path, this one is always an absolute path used
* to resolve relative paths returned from lifecycles
*/
entryPath: string;
};
async function normalizePluginConfig(
pluginConfig: PluginConfig,
pluginRequire: NodeRequire,
configPath: string,
): Promise<NormalizedPluginConfig> {
const pluginRequire = createRequire(configPath);
// plugins: ['./plugin']
if (typeof pluginConfig === 'string') {
const pluginModuleImport = pluginConfig;
@ -50,6 +57,7 @@ async function normalizePluginConfig(
path: pluginModuleImport,
module: pluginModule,
},
entryPath: pluginPath,
};
}
@ -58,6 +66,7 @@ async function normalizePluginConfig(
return {
plugin: pluginConfig,
options: {},
entryPath: configPath,
};
}
@ -75,6 +84,7 @@ async function normalizePluginConfig(
path: pluginModuleImport,
module: pluginModule,
},
entryPath: pluginPath,
};
}
// plugins: [
@ -83,16 +93,17 @@ async function normalizePluginConfig(
return {
plugin: pluginConfig[0],
options: pluginConfig[1],
entryPath: configPath,
};
}
export async function normalizePluginConfigs(
pluginConfigs: PluginConfig[],
pluginRequire: NodeRequire,
configPath: string,
): Promise<NormalizedPluginConfig[]> {
return Promise.all(
pluginConfigs.map((pluginConfig) =>
normalizePluginConfig(pluginConfig, pluginRequire),
normalizePluginConfig(pluginConfig, configPath),
),
);
}
@ -135,7 +146,7 @@ export default async function initPlugins({
const pluginRequire = createRequire(context.siteConfigPath);
const pluginConfigsNormalized = await normalizePluginConfigs(
pluginConfigs,
pluginRequire,
context.siteConfigPath,
);
async function doGetPluginVersion(
@ -206,6 +217,7 @@ export default async function initPlugins({
...pluginInstance,
options: pluginOptions,
version: pluginVersion,
path: path.dirname(normalizedPluginConfig.entryPath),
};
}

View file

@ -51,7 +51,10 @@ export function loadPluginsThemeAliases({
plugins: LoadedPlugin[];
}): Promise<ThemeAliases> {
const pluginThemes: string[] = plugins
.map((plugin) => plugin.getThemePath?.())
.map(
(plugin) =>
plugin.getThemePath && path.resolve(plugin.path, plugin.getThemePath()),
)
.filter((x): x is string => Boolean(x));
const userTheme = path.resolve(siteDir, THEME_PATH);
return loadThemeAliases([ThemeFallbackDir, ...pluginThemes], [userTheme]);

View file

@ -56,7 +56,7 @@ function getPluginSourceCodeFilePaths(plugin: InitializedPlugin): string[] {
codePaths.push(themePath);
}
return codePaths;
return codePaths.map((p) => nodePath.resolve(plugin.path, p));
}
export async function globSourceCodeFilePaths(

View file

@ -6,7 +6,6 @@
*/
const fs = require('fs');
const path = require('path');
/** @type {import('@docusaurus/types').PluginConfig[]} */
const dogfoodingThemeInstances = [
@ -14,8 +13,7 @@ const dogfoodingThemeInstances = [
function swizzleThemeTests() {
return {
name: 'swizzle-theme-tests',
getThemePath: () =>
path.join(__dirname, '_swizzle_theme_tests/src/theme'),
getThemePath: () => './_swizzle_theme_tests/src/theme',
};
},
];

View file

@ -61,7 +61,7 @@ module.exports = function (context, options) {
## `getThemePath()` {#getThemePath}
Returns the path to the directory where the theme components can be found. When your users call `swizzle`, `getThemePath` is called and its returned path is used to find your theme components.
Returns the path to the directory where the theme components can be found. When your users call `swizzle`, `getThemePath` is called and its returned path is used to find your theme components. Relative paths are resolved against the folder containing the entry point.
For example, your `getThemePath` can be:
@ -73,7 +73,7 @@ module.exports = function (context, options) {
name: 'my-theme',
// highlight-start
getThemePath() {
return path.resolve(__dirname, './theme');
return './theme';
},
// highlight-end
};
@ -103,11 +103,11 @@ module.exports = function (context, options) {
// highlight-start
getThemePath() {
// Where compiled JavaScript output lives
return path.join(__dirname, '../lib/theme');
return '../lib/theme';
},
getTypeScriptThemePath() {
// Where TypeScript source code lives
return path.resolve(__dirname, '../src/theme');
return '../src/theme';
},
// highlight-end
};

View file

@ -205,7 +205,7 @@ const config = {
'queryString',
],
// swRegister: false,
swCustom: path.resolve(__dirname, 'src/sw.js'),
swCustom: require.resolve('./src/sw.js'),
pwaHead: [
{
tagName: 'link',

View file

@ -146,7 +146,7 @@ async function ChangelogPlugin(context, options) {
return config;
},
getThemePath() {
return path.join(__dirname, './theme');
return './theme';
},
getPathsToWatch() {
// Don't watch the generated dir