diff --git a/packages/core/src/node/build.ts b/packages/core/src/node/build.ts index 0fb547e4e..c2e693aff 100644 --- a/packages/core/src/node/build.ts +++ b/packages/core/src/node/build.ts @@ -40,31 +40,15 @@ export async function bundle( enableSSG: boolean, ) { try { - if (enableSSG) { - const [clientBuilder, ssrBuilder] = await Promise.all([ - initRsbuild(docDirectory, config, pluginDriver, false), - initRsbuild(docDirectory, config, pluginDriver, true, { - output: { - minify: false, - }, - tools: { - rspack(options) { - options.output.filename = 'main.cjs'; - }, - }, - }), - ]); - await Promise.all([clientBuilder.build(), ssrBuilder.build()]); - } else { - // Only build client bundle - const clientBuilder = await initRsbuild( - docDirectory, - config, - pluginDriver, - false, - ); - await clientBuilder.build(); - } + // if enableSSG, build both client and server bundle + // else only build client bundle + const rsbuild = await initRsbuild( + docDirectory, + config, + pluginDriver, + enableSSG, + ); + await rsbuild.build(); } finally { await writeSearchIndex(config); } diff --git a/packages/core/src/node/initRsbuild.ts b/packages/core/src/node/initRsbuild.ts index 623f1c14b..cd085b534 100644 --- a/packages/core/src/node/initRsbuild.ts +++ b/packages/core/src/node/initRsbuild.ts @@ -52,7 +52,7 @@ function isPluginIncluded(config: UserConfig, pluginName: string): boolean { async function createInternalBuildConfig( userDocRoot: string, config: UserConfig, - isSSR: boolean, + enableSSG: boolean, routeService: RouteService, pluginDriver: PluginDriver, runtimeTempDir: string, @@ -61,10 +61,10 @@ async function createInternalBuildConfig( const CUSTOM_THEME_DIR = config?.themeDir ?? path.join(process.cwd(), 'theme'); const baseOutDir = config?.outDir ?? OUTPUT_DIR; - const outDir = isSSR ? path.join(baseOutDir, 'ssr') : baseOutDir; + const csrOutDir = baseOutDir; + const ssrOutDir = path.join(baseOutDir, 'ssr'); const DEFAULT_THEME = require.resolve('@rspress/theme-default'); - const checkDeadLinks = (config?.markdown?.checkDeadLinks && !isSSR) ?? false; const base = config?.base ?? ''; // In production, we need to add assetPrefix in asset path @@ -95,6 +95,10 @@ async function createInternalBuildConfig( ]; const ssrBrowserslist = ['node >= 14']; + const detectCustomIconAlias = await detectCustomIcon(CUSTOM_THEME_DIR); + const reactSSRAlias = await resolveReactAlias(reactVersion, true); + const reactCSRAlias = await resolveReactAlias(reactVersion, false); + return { plugins: [ ...(isPluginIncluded(config, PLUGIN_REACT_NAME) ? [] : [pluginReact()]), @@ -103,7 +107,6 @@ async function createInternalBuildConfig( rsbuildPluginDocVM({ userDocRoot, config, - isSSR, runtimeTempDir, routeService, pluginDriver, @@ -117,11 +120,9 @@ async function createInternalBuildConfig( printUrls: ({ urls }) => { return urls.map(url => `${url}/${removeLeadingSlash(base)}`); }, - publicDir: isSSR - ? false - : { - name: path.join(userDocRoot, PUBLIC_DIR), - }, + publicDir: { + name: path.join(userDocRoot, PUBLIC_DIR), + }, }, dev: { progressBar: false, @@ -145,19 +146,15 @@ async function createInternalBuildConfig( ].filter(Boolean), }, output: { - target: isSSR ? 'node' : 'web', + assetPrefix, distPath: { - // `root` must be a relative path in Rsbuild - root: path.isAbsolute(outDir) ? path.relative(cwd, outDir) : outDir, + // just for rsbuild preview + root: csrOutDir, }, - overrideBrowserslist: isSSR ? ssrBrowserslist : webBrowserslist, - assetPrefix, }, source: { - entry: { - index: isSSR ? SSR_ENTRY : CLIENT_ENTRY, - }, alias: { + ...detectCustomIconAlias, '@mdx-js/react': require.resolve('@mdx-js/react'), '@theme': [CUSTOM_THEME_DIR, DEFAULT_THEME], '@/theme-default': DEFAULT_THEME, @@ -166,21 +163,16 @@ async function createInternalBuildConfig( 'react-syntax-highlighter': path.dirname( require.resolve('react-syntax-highlighter/package.json'), ), - ...(await resolveReactAlias(reactVersion, isSSR)), - ...(await detectCustomIcon(CUSTOM_THEME_DIR)), '@theme-assets': path.join(DEFAULT_THEME, '../assets'), }, include: [PACKAGE_ROOT, path.join(cwd, 'node_modules', RSPRESS_TEMP_DIR)], define: { 'process.env.__ASSET_PREFIX__': JSON.stringify(assetPrefix), - 'process.env.__SSR__': JSON.stringify(isSSR), 'process.env.__IS_REACT_18__': JSON.stringify(reactVersion === 18), 'process.env.TEST': JSON.stringify(process.env.TEST), }, }, performance: { - // No need to print the server bundles size - printFileSize: !isSSR, chunkSplit: { override: { cacheGroups: { @@ -198,13 +190,17 @@ async function createInternalBuildConfig( }, }, tools: { - bundlerChain(chain, { CHAIN_ID }) { + bundlerChain(chain, { CHAIN_ID, target }) { + const isServer = target === 'node'; const jsModuleRule = chain.module.rule(CHAIN_ID.RULE.JS); const swcLoaderOptions = jsModuleRule .use(CHAIN_ID.USE.SWC) .get('options'); + const checkDeadLinks = + (config?.markdown?.checkDeadLinks && !isServer) ?? false; + chain.module .rule('MDX') .type('javascript/auto') @@ -244,8 +240,62 @@ async function createInternalBuildConfig( .rule('css-virtual-module') .test(/\.rspress[\\/]runtime[\\/]virtual-global-styles/) .merge({ sideEffects: true }); + + if (isServer) { + chain.output.filename('main.cjs'); + } }, }, + environments: { + web: { + source: { + entry: { + index: CLIENT_ENTRY, + }, + alias: { + ...reactCSRAlias, + }, + define: { + 'process.env.__SSR__': JSON.stringify(false), + }, + }, + output: { + target: 'web', + overrideBrowserslist: webBrowserslist, + distPath: { + root: csrOutDir, + }, + }, + }, + ...(enableSSG + ? { + node: { + source: { + entry: { + index: SSR_ENTRY, + }, + alias: { + ...reactSSRAlias, + }, + define: { + 'process.env.__SSR__': JSON.stringify(true), + }, + }, + performance: { + printFileSize: false, + }, + output: { + target: 'node', + overrideBrowserslist: ssrBrowserslist, + distPath: { + root: ssrOutDir, + }, + minify: false, + }, + }, + } + : {}), + }, }; } @@ -253,7 +303,7 @@ export async function initRsbuild( rootDir: string, config: UserConfig, pluginDriver: PluginDriver, - isSSR = false, + enableSSG: boolean, extraRsbuildConfig?: RsbuildConfig, ): Promise { const cwd = process.cwd(); @@ -276,7 +326,7 @@ export async function initRsbuild( const internalRsbuildConfig = await createInternalBuildConfig( userDocRoot, config, - isSSR, + enableSSG, routeService, pluginDriver, runtimeTempDir, diff --git a/packages/core/src/node/runtimeModule/index.ts b/packages/core/src/node/runtimeModule/index.ts index 97bee5958..1e760bfa5 100644 --- a/packages/core/src/node/runtimeModule/index.ts +++ b/packages/core/src/node/runtimeModule/index.ts @@ -54,13 +54,14 @@ export const runtimeModuleFactory: RuntimeModuleFactory[] = [ // We will use this plugin to generate runtime module in browser, which is important to ensure the client have access to some compile-time data export function rsbuildPluginDocVM( - factoryContext: Omit, + factoryContext: Omit, ): RsbuildPlugin { const { pluginDriver } = factoryContext; return { name: 'rsbuild-plugin-doc-vm', setup(api) { - api.modifyBundlerChain(async bundlerChain => { + api.modifyBundlerChain(async (bundlerChain, { target }) => { + const isServer = target === 'node'; // The order should be sync const alias = bundlerChain.resolve.alias.entries(); const runtimeModule: Record = {}; @@ -68,6 +69,7 @@ export function rsbuildPluginDocVM( for (const factory of runtimeModuleFactory) { const moduleResult = await factory({ ...factoryContext, + isSSR: isServer, alias: alias as Record, }); Object.assign(runtimeModule, moduleResult); @@ -82,13 +84,14 @@ export function rsbuildPluginDocVM( } runtimeModule[key] = modulesByPlugin[key]; }); - bundlerChain.plugin('rspress-runtime-module').use( - // @ts-expect-error - new RspackVirtualModulePlugin( - runtimeModule, - factoryContext.runtimeTempDir, - ), - ); + bundlerChain + .plugin('rspress-runtime-module') + .use( + new RspackVirtualModulePlugin( + runtimeModule, + factoryContext.runtimeTempDir, + ), + ); }); }, }; diff --git a/packages/shared/src/runtime-utils/index.ts b/packages/shared/src/runtime-utils/index.ts index 8d0ba4e18..195ec4836 100644 --- a/packages/shared/src/runtime-utils/index.ts +++ b/packages/shared/src/runtime-utils/index.ts @@ -17,11 +17,18 @@ export const DEFAULT_HIGHLIGHT_LANGUAGES = [ ['mdx', 'tsx'], ]; +// TODO: these utils should be divided into node and runtime export const isSCM = () => Boolean(process.env.BUILD_VERSION); export const isProduction = () => process.env.NODE_ENV === 'production'; -export const isDebugMode = () => Boolean(process.env.DOC_DEBUG); +export const isDebugMode = () => { + if (!process.env.DEBUG) { + return false; + } + const values = process.env.DEBUG?.toLocaleLowerCase().split(',') ?? []; + return ['rsbuild', 'builder', '*'].some(key => values.includes(key)); +}; export const cleanUrl = (url: string): string => url.replace(HASH_REGEXP, '').replace(QUERY_REGEXP, '');