mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-07 05:12:31 +02:00
refactor(faster,bundler,core): improve js loader DX (#10655)
This commit is contained in:
parent
bdf55eda22
commit
bcfa3b1128
7 changed files with 218 additions and 27 deletions
|
@ -10,19 +10,19 @@ import {createJsLoaderFactory} from '../jsLoader';
|
||||||
|
|
||||||
import type {RuleSetRule} from 'webpack';
|
import type {RuleSetRule} from 'webpack';
|
||||||
|
|
||||||
|
type SiteConfigSlice = Parameters<
|
||||||
|
typeof createJsLoaderFactory
|
||||||
|
>[0]['siteConfig'];
|
||||||
|
|
||||||
describe('createJsLoaderFactory', () => {
|
describe('createJsLoaderFactory', () => {
|
||||||
function testJsLoaderFactory(
|
function testJsLoaderFactory(siteConfig?: {
|
||||||
siteConfig?: PartialDeep<
|
webpack?: SiteConfigSlice['webpack'];
|
||||||
Parameters<typeof createJsLoaderFactory>[0]['siteConfig']
|
future?: PartialDeep<SiteConfigSlice['future']>;
|
||||||
>,
|
}) {
|
||||||
) {
|
|
||||||
return createJsLoaderFactory({
|
return createJsLoaderFactory({
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
...siteConfig,
|
...siteConfig,
|
||||||
webpack: {
|
webpack: siteConfig?.webpack,
|
||||||
jsLoader: 'babel',
|
|
||||||
...siteConfig?.webpack,
|
|
||||||
},
|
|
||||||
future: fromPartial({
|
future: fromPartial({
|
||||||
...siteConfig?.future,
|
...siteConfig?.future,
|
||||||
experimental_faster: fromPartial({
|
experimental_faster: fromPartial({
|
||||||
|
@ -43,6 +43,52 @@ describe('createJsLoaderFactory', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('createJsLoaderFactory accepts babel loader preset', async () => {
|
||||||
|
const createJsLoader = await testJsLoaderFactory({
|
||||||
|
webpack: {jsLoader: 'babel'},
|
||||||
|
});
|
||||||
|
expect(createJsLoader({isServer: true}).loader).toBe(
|
||||||
|
require.resolve('babel-loader'),
|
||||||
|
);
|
||||||
|
expect(createJsLoader({isServer: false}).loader).toBe(
|
||||||
|
require.resolve('babel-loader'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('createJsLoaderFactory accepts custom loader', async () => {
|
||||||
|
const createJsLoader = await testJsLoaderFactory({
|
||||||
|
webpack: {
|
||||||
|
jsLoader: (isServer) => {
|
||||||
|
return {loader: `my-loader-${isServer ? 'server' : 'client'}`};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(createJsLoader({isServer: true}).loader).toBe('my-loader-server');
|
||||||
|
expect(createJsLoader({isServer: false}).loader).toBe('my-loader-client');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('createJsLoaderFactory rejects custom loader when using faster swc loader', async () => {
|
||||||
|
await expect(() =>
|
||||||
|
testJsLoaderFactory({
|
||||||
|
future: {
|
||||||
|
experimental_faster: {
|
||||||
|
swcJsLoader: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
webpack: {
|
||||||
|
jsLoader: (isServer) => {
|
||||||
|
return {loader: `my-loader-${isServer ? 'server' : 'client'}`};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"You can't use siteConfig.webpack.jsLoader and siteConfig.future.experimental_faster.swcJsLoader at the same time.
|
||||||
|
To avoid any configuration ambiguity, you must make an explicit choice:
|
||||||
|
- If you want to use Docusaurus Faster and SWC (recommended), remove siteConfig.webpack.jsLoader
|
||||||
|
- If you want to use a custom JS loader, use siteConfig.future.experimental_faster.swcJsLoader: false"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
it('createJsLoaderFactory accepts loaders with preset', async () => {
|
it('createJsLoaderFactory accepts loaders with preset', async () => {
|
||||||
const createJsLoader = await testJsLoaderFactory({
|
const createJsLoader = await testJsLoaderFactory({
|
||||||
webpack: {jsLoader: 'babel'},
|
webpack: {jsLoader: 'babel'},
|
||||||
|
|
|
@ -62,26 +62,24 @@ export async function createJsLoaderFactory({
|
||||||
}): Promise<ConfigureWebpackUtils['getJSLoader']> {
|
}): Promise<ConfigureWebpackUtils['getJSLoader']> {
|
||||||
const currentBundler = await getCurrentBundler({siteConfig});
|
const currentBundler = await getCurrentBundler({siteConfig});
|
||||||
const isSWCLoader = siteConfig.future.experimental_faster.swcJsLoader;
|
const isSWCLoader = siteConfig.future.experimental_faster.swcJsLoader;
|
||||||
if (currentBundler.name === 'rspack') {
|
if (isSWCLoader) {
|
||||||
return isSWCLoader
|
if (siteConfig.webpack?.jsLoader) {
|
||||||
|
throw new Error(
|
||||||
|
`You can't use siteConfig.webpack.jsLoader and siteConfig.future.experimental_faster.swcJsLoader at the same time.
|
||||||
|
To avoid any configuration ambiguity, you must make an explicit choice:
|
||||||
|
- If you want to use Docusaurus Faster and SWC (recommended), remove siteConfig.webpack.jsLoader
|
||||||
|
- If you want to use a custom JS loader, use siteConfig.future.experimental_faster.swcJsLoader: false`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return currentBundler.name === 'rspack'
|
||||||
? createRspackSwcJsLoaderFactory()
|
? createRspackSwcJsLoaderFactory()
|
||||||
: BabelJsLoaderFactory;
|
: createSwcJsLoaderFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsLoader = siteConfig.webpack?.jsLoader ?? 'babel';
|
const jsLoader = siteConfig.webpack?.jsLoader ?? 'babel';
|
||||||
if (
|
|
||||||
jsLoader instanceof Function &&
|
|
||||||
siteConfig.future?.experimental_faster.swcJsLoader
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
"You can't use a custom webpack.jsLoader and experimental_faster.swcJsLoader at the same time",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (jsLoader instanceof Function) {
|
if (jsLoader instanceof Function) {
|
||||||
return ({isServer}) => jsLoader(isServer);
|
return ({isServer}) => jsLoader(isServer);
|
||||||
}
|
}
|
||||||
if (siteConfig.future?.experimental_faster.swcJsLoader) {
|
|
||||||
return createSwcJsLoaderFactory();
|
|
||||||
}
|
|
||||||
if (jsLoader === 'babel') {
|
if (jsLoader === 'babel') {
|
||||||
return BabelJsLoaderFactory;
|
return BabelJsLoaderFactory;
|
||||||
}
|
}
|
||||||
|
|
2
packages/docusaurus-types/src/config.d.ts
vendored
2
packages/docusaurus-types/src/config.d.ts
vendored
|
@ -431,7 +431,7 @@ export type DocusaurusConfig = {
|
||||||
// TODO Docusaurus v4
|
// TODO Docusaurus v4
|
||||||
// Use an object type ({isServer}) so that it conforms to jsLoaderFactory
|
// Use an object type ({isServer}) so that it conforms to jsLoaderFactory
|
||||||
// Eventually deprecate this if swc loader becomes stable?
|
// Eventually deprecate this if swc loader becomes stable?
|
||||||
jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule);
|
jsLoader?: 'babel' | ((isServer: boolean) => RuleSetRule);
|
||||||
};
|
};
|
||||||
/** Markdown-related options. */
|
/** Markdown-related options. */
|
||||||
markdown: MarkdownConfig;
|
markdown: MarkdownConfig;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
content doesn't matter
|
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* 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 {fromPartial} from '@total-typescript/shoehorn';
|
||||||
|
import {collectAllSiteMessages} from '../siteMessages';
|
||||||
|
|
||||||
|
function siteDirFixture(name: string) {
|
||||||
|
return path.resolve(__dirname, '__fixtures__', 'siteMessages', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('collectAllSiteMessages', () => {
|
||||||
|
describe('uselessBabelConfigMessages', () => {
|
||||||
|
async function getMessagesFor({
|
||||||
|
siteDir,
|
||||||
|
swcJsLoader,
|
||||||
|
}: {
|
||||||
|
siteDir: string;
|
||||||
|
swcJsLoader: boolean;
|
||||||
|
}) {
|
||||||
|
return collectAllSiteMessages(
|
||||||
|
fromPartial({
|
||||||
|
site: {
|
||||||
|
props: {
|
||||||
|
siteDir,
|
||||||
|
siteConfig: {
|
||||||
|
future: {
|
||||||
|
experimental_faster: {
|
||||||
|
swcJsLoader,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('warns for useless babel config file when SWC enabled', async () => {
|
||||||
|
const messages = await getMessagesFor({
|
||||||
|
siteDir: siteDirFixture('siteWithBabelConfigFile'),
|
||||||
|
swcJsLoader: true,
|
||||||
|
});
|
||||||
|
expect(messages).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"message": "Your site is using the SWC js loader. You can safely remove the Babel config file at \`packages/docusaurus/src/server/__tests__/__fixtures__/siteMessages/siteWithBabelConfigFile/babel.config.js\`.",
|
||||||
|
"type": "warning",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not warn for babel config file when SWC disabled', async () => {
|
||||||
|
const messages = await getMessagesFor({
|
||||||
|
siteDir: siteDirFixture('siteWithBabelConfigFile'),
|
||||||
|
swcJsLoader: false,
|
||||||
|
});
|
||||||
|
expect(messages).toMatchInlineSnapshot(`[]`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -27,6 +27,7 @@ import {
|
||||||
import {generateSiteFiles} from './codegen/codegen';
|
import {generateSiteFiles} from './codegen/codegen';
|
||||||
import {getRoutesPaths, handleDuplicateRoutes} from './routes';
|
import {getRoutesPaths, handleDuplicateRoutes} from './routes';
|
||||||
import {createSiteStorage} from './storage';
|
import {createSiteStorage} from './storage';
|
||||||
|
import {emitSiteMessages} from './siteMessages';
|
||||||
import type {LoadPluginsResult} from './plugins/plugins';
|
import type {LoadPluginsResult} from './plugins/plugins';
|
||||||
import type {
|
import type {
|
||||||
DocusaurusConfig,
|
DocusaurusConfig,
|
||||||
|
@ -54,7 +55,9 @@ export type LoadContextParams = {
|
||||||
localizePath?: boolean;
|
localizePath?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LoadSiteParams = LoadContextParams;
|
export type LoadSiteParams = LoadContextParams & {
|
||||||
|
isReload?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type Site = {
|
export type Site = {
|
||||||
props: Props;
|
props: Props;
|
||||||
|
@ -236,7 +239,7 @@ async function createSiteFiles({
|
||||||
* lifecycles to generate content and other data. It is side-effect-ful because
|
* lifecycles to generate content and other data. It is side-effect-ful because
|
||||||
* it generates temp files in the `.docusaurus` folder for the bundler.
|
* it generates temp files in the `.docusaurus` folder for the bundler.
|
||||||
*/
|
*/
|
||||||
export async function loadSite(params: LoadContextParams): Promise<Site> {
|
export async function loadSite(params: LoadSiteParams): Promise<Site> {
|
||||||
const context = await PerfLogger.async('Load context', () =>
|
const context = await PerfLogger.async('Load context', () =>
|
||||||
loadContext(params),
|
loadContext(params),
|
||||||
);
|
);
|
||||||
|
@ -252,6 +255,11 @@ export async function loadSite(params: LoadContextParams): Promise<Site> {
|
||||||
globalData,
|
globalData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// For now, we don't re-emit messages on site reloads, it's too verbose
|
||||||
|
if (!params.isReload) {
|
||||||
|
await emitSiteMessages({site});
|
||||||
|
}
|
||||||
|
|
||||||
return site;
|
return site;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +267,10 @@ export async function reloadSite(site: Site): Promise<Site> {
|
||||||
// TODO this can be optimized, for example:
|
// TODO this can be optimized, for example:
|
||||||
// - plugins loading same data as before should not recreate routes/bundles
|
// - plugins loading same data as before should not recreate routes/bundles
|
||||||
// - codegen does not need to re-run if nothing changed
|
// - codegen does not need to re-run if nothing changed
|
||||||
return loadSite(site.params);
|
return loadSite({
|
||||||
|
...site.params,
|
||||||
|
isReload: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function reloadSitePlugin(
|
export async function reloadSitePlugin(
|
||||||
|
|
69
packages/docusaurus/src/server/siteMessages.ts
Normal file
69
packages/docusaurus/src/server/siteMessages.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* 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 _ from 'lodash';
|
||||||
|
import {getCustomBabelConfigFilePath} from '@docusaurus/babel';
|
||||||
|
import logger from '@docusaurus/logger';
|
||||||
|
import type {Site} from './site';
|
||||||
|
|
||||||
|
type Params = {site: Site};
|
||||||
|
|
||||||
|
type SiteMessage = {type: 'warning' | 'error'; message: string};
|
||||||
|
|
||||||
|
type SiteMessageCreator = (params: Params) => Promise<SiteMessage[]>;
|
||||||
|
|
||||||
|
const uselessBabelConfigMessages: SiteMessageCreator = async ({site}) => {
|
||||||
|
const {
|
||||||
|
props: {siteDir, siteConfig},
|
||||||
|
} = site;
|
||||||
|
if (siteConfig.future.experimental_faster.swcJsLoader) {
|
||||||
|
const babelConfigFilePath = await getCustomBabelConfigFilePath(siteDir);
|
||||||
|
if (babelConfigFilePath) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: `Your site is using the SWC js loader. You can safely remove the Babel config file at ${logger.code(
|
||||||
|
path.relative(process.cwd(), babelConfigFilePath),
|
||||||
|
)}.`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function collectAllSiteMessages(
|
||||||
|
params: Params,
|
||||||
|
): Promise<SiteMessage[]> {
|
||||||
|
const messageCreators: SiteMessageCreator[] = [uselessBabelConfigMessages];
|
||||||
|
return (
|
||||||
|
await Promise.all(
|
||||||
|
messageCreators.map((createMessages) => createMessages(params)),
|
||||||
|
)
|
||||||
|
).flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function printSiteMessages(siteMessages: SiteMessage[]): void {
|
||||||
|
const [errors, warnings] = _.partition(
|
||||||
|
siteMessages,
|
||||||
|
(sm) => sm.type === 'error',
|
||||||
|
);
|
||||||
|
if (errors.length > 0) {
|
||||||
|
logger.error(`Docusaurus site errors:
|
||||||
|
- ${errors.map((sm) => sm.message).join('\n- ')}`);
|
||||||
|
}
|
||||||
|
if (warnings.length > 0) {
|
||||||
|
logger.warn(`Docusaurus site warnings:
|
||||||
|
- ${warnings.map((sm) => sm.message).join('\n- ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function emitSiteMessages(params: Params): Promise<void> {
|
||||||
|
const siteMessages = await collectAllSiteMessages(params);
|
||||||
|
printSiteMessages(siteMessages);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue