mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-03 11:22:30 +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';
|
||||
|
||||
type SiteConfigSlice = Parameters<
|
||||
typeof createJsLoaderFactory
|
||||
>[0]['siteConfig'];
|
||||
|
||||
describe('createJsLoaderFactory', () => {
|
||||
function testJsLoaderFactory(
|
||||
siteConfig?: PartialDeep<
|
||||
Parameters<typeof createJsLoaderFactory>[0]['siteConfig']
|
||||
>,
|
||||
) {
|
||||
function testJsLoaderFactory(siteConfig?: {
|
||||
webpack?: SiteConfigSlice['webpack'];
|
||||
future?: PartialDeep<SiteConfigSlice['future']>;
|
||||
}) {
|
||||
return createJsLoaderFactory({
|
||||
siteConfig: {
|
||||
...siteConfig,
|
||||
webpack: {
|
||||
jsLoader: 'babel',
|
||||
...siteConfig?.webpack,
|
||||
},
|
||||
webpack: siteConfig?.webpack,
|
||||
future: fromPartial({
|
||||
...siteConfig?.future,
|
||||
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 () => {
|
||||
const createJsLoader = await testJsLoaderFactory({
|
||||
webpack: {jsLoader: 'babel'},
|
||||
|
|
|
@ -62,26 +62,24 @@ export async function createJsLoaderFactory({
|
|||
}): Promise<ConfigureWebpackUtils['getJSLoader']> {
|
||||
const currentBundler = await getCurrentBundler({siteConfig});
|
||||
const isSWCLoader = siteConfig.future.experimental_faster.swcJsLoader;
|
||||
if (currentBundler.name === 'rspack') {
|
||||
return isSWCLoader
|
||||
if (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()
|
||||
: BabelJsLoaderFactory;
|
||||
: createSwcJsLoaderFactory();
|
||||
}
|
||||
|
||||
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) {
|
||||
return ({isServer}) => jsLoader(isServer);
|
||||
}
|
||||
if (siteConfig.future?.experimental_faster.swcJsLoader) {
|
||||
return createSwcJsLoaderFactory();
|
||||
}
|
||||
if (jsLoader === 'babel') {
|
||||
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
|
||||
// Use an object type ({isServer}) so that it conforms to jsLoaderFactory
|
||||
// Eventually deprecate this if swc loader becomes stable?
|
||||
jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule);
|
||||
jsLoader?: 'babel' | ((isServer: boolean) => RuleSetRule);
|
||||
};
|
||||
/** Markdown-related options. */
|
||||
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 {getRoutesPaths, handleDuplicateRoutes} from './routes';
|
||||
import {createSiteStorage} from './storage';
|
||||
import {emitSiteMessages} from './siteMessages';
|
||||
import type {LoadPluginsResult} from './plugins/plugins';
|
||||
import type {
|
||||
DocusaurusConfig,
|
||||
|
@ -54,7 +55,9 @@ export type LoadContextParams = {
|
|||
localizePath?: boolean;
|
||||
};
|
||||
|
||||
export type LoadSiteParams = LoadContextParams;
|
||||
export type LoadSiteParams = LoadContextParams & {
|
||||
isReload?: boolean;
|
||||
};
|
||||
|
||||
export type Site = {
|
||||
props: Props;
|
||||
|
@ -236,7 +239,7 @@ async function createSiteFiles({
|
|||
* lifecycles to generate content and other data. It is side-effect-ful because
|
||||
* 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', () =>
|
||||
loadContext(params),
|
||||
);
|
||||
|
@ -252,6 +255,11 @@ export async function loadSite(params: LoadContextParams): Promise<Site> {
|
|||
globalData,
|
||||
});
|
||||
|
||||
// For now, we don't re-emit messages on site reloads, it's too verbose
|
||||
if (!params.isReload) {
|
||||
await emitSiteMessages({site});
|
||||
}
|
||||
|
||||
return site;
|
||||
}
|
||||
|
||||
|
@ -259,7 +267,10 @@ export async function reloadSite(site: Site): Promise<Site> {
|
|||
// TODO this can be optimized, for example:
|
||||
// - plugins loading same data as before should not recreate routes/bundles
|
||||
// - codegen does not need to re-run if nothing changed
|
||||
return loadSite(site.params);
|
||||
return loadSite({
|
||||
...site.params,
|
||||
isReload: true,
|
||||
});
|
||||
}
|
||||
|
||||
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