Skip to content

Commit d6612a3

Browse files
authored
feat(theme)!: use named export instead of default export, for circular imports (#1873)
1 parent 471b29b commit d6612a3

File tree

39 files changed

+208
-177
lines changed

39 files changed

+208
-177
lines changed
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import Theme from 'rspress/theme';
21
import { Layout as BaseLayout } from 'rspress/theme';
32

43
const Layout = () => {
@@ -11,9 +10,5 @@ const Layout = () => {
1110
);
1211
};
1312

14-
export default {
15-
...Theme,
16-
Layout,
17-
};
18-
13+
export { Layout };
1914
export * from 'rspress/theme';

packages/cli/theme.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export * from '@rspress/core/theme';
2-
export { default } from '@rspress/core/theme';

packages/core/src/node/initRsbuild.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
inlineThemeScript,
2828
isProduction,
2929
} from './constants';
30+
import { hintThemeBreakingChange } from './logger/hint';
3031
import type { RouteService } from './route/RouteService';
3132
import { initRouteService } from './route/init';
3233
import { rsbuildPluginDocVM } from './runtimeModule';
@@ -87,6 +88,8 @@ async function createInternalBuildConfig(
8788
return icon;
8889
};
8990

91+
await hintThemeBreakingChange(CUSTOM_THEME_DIR);
92+
9093
const [detectCustomIconAlias, reactCSRAlias, reactSSRAlias] =
9194
await Promise.all([
9295
detectCustomIcon(CUSTOM_THEME_DIR),
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { readFile } from 'node:fs/promises';
2+
import { join } from 'node:path';
3+
import { logger } from '@rsbuild/core';
4+
import picocolors from 'picocolors';
5+
import { pathExists } from '../utils';
6+
7+
const THEME_DEFAULT_EXPORT_PATTERN = /export default \{(.*?)\}/m;
8+
9+
/**
10+
* breaking change
11+
*/
12+
export async function hintThemeBreakingChange(customThemeDir: string) {
13+
const fileList = ['index.ts', 'index.tsx', 'index.js', 'index.mjs'];
14+
let useDefaultExportFilePath: string | null = null;
15+
for (const file of fileList) {
16+
const filePath = join(customThemeDir, file);
17+
if (await pathExists(filePath)) {
18+
const content = await readFile(filePath, { encoding: 'utf-8' });
19+
if (THEME_DEFAULT_EXPORT_PATTERN.test(content)) {
20+
useDefaultExportFilePath = filePath;
21+
}
22+
}
23+
}
24+
if (useDefaultExportFilePath) {
25+
logger.warn(
26+
`[Rspress] Theme breaking change: The theme/index is now using namedExports instead of defaultExports, please update your config file in ${useDefaultExportFilePath}`,
27+
picocolors.red(`
28+
- import Theme from '@rspress/theme-default';
29+
- export default {
30+
- ...Theme,
31+
- Layout,
32+
- };
33+
- export * from 'rspress/theme';`) +
34+
picocolors.green(`+ import { Layout } from '@rspress/theme-default';
35+
36+
+ export { Layout };
37+
+ export * from 'rspress/theme';
38+
`),
39+
);
40+
}
41+
}

packages/core/src/runtime/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
type PageData,
1313
cleanUrl,
1414
} from '@rspress/shared';
15-
import Theme from '@theme';
15+
import { Layout } from '@theme';
1616
import React, { useContext, useLayoutEffect } from 'react';
1717
import { HelmetProvider } from 'react-helmet-async';
1818
import globalComponents from 'virtual-global-components';
@@ -152,7 +152,7 @@ export function App({ helmetContext }: { helmetContext?: object }) {
152152

153153
return (
154154
<HelmetProvider context={helmetContext}>
155-
<Theme.Layout />
155+
<Layout />
156156
{
157157
// Global UI
158158
!hideGlobalUIComponents &&

packages/core/src/runtime/clientEntry.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { BrowserRouter, DataContext, ThemeContext } from '@rspress/runtime';
22
import { isProduction } from '@rspress/shared';
3+
import { setup, useThemeState } from '@theme';
34
import { useMemo, useState } from 'react';
45
import siteData from 'virtual-site-data';
56
import { App, initPageData } from './App';
67

78
const enableSSG = siteData.ssg;
89

910
// eslint-disable-next-line import/no-commonjs
10-
const { default: Theme, useThemeState } = require('@theme');
1111

1212
export async function renderInBrowser() {
1313
const container = document.getElementById('root')!;
@@ -52,5 +52,5 @@ export async function renderInBrowser() {
5252
}
5353

5454
renderInBrowser().then(() => {
55-
Theme.setup();
55+
setup();
5656
});

packages/core/theme.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export * from '@rspress/theme-default';
2-
export { default } from '@rspress/theme-default';

packages/document/docs/en/guide/advanced/custom-theme.mdx

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,27 @@ By default, you need to create a `theme` directory under the project root direct
2222
You can write the `theme/index.tsx` file as follows:
2323

2424
```tsx title="theme/index.tsx"
25-
import Theme from 'rspress/theme';
25+
import { Layout as BasicLayout } from 'rspress/theme';
2626

27-
const Layout = () => <Theme.Layout beforeNavTitle={<div>some content</div>} />;
27+
const Layout = () => <BasicLayout beforeNavTitle={<div>some content</div>} />;
2828

29-
export default {
30-
...Theme,
31-
Layout,
32-
};
29+
export { Layout };
3330

3431
export * from 'rspress/theme';
3532
```
3633

37-
On the one hand, you need to export a theme configuration object through `export default`, on the other hand, you need to export all named exported content through `export * from 'rspress/theme'` so as to ensure your theme works fine.
34+
On the one hand, you need to export a theme configuration object through `export`, on the other hand, you need to export all named exported content through `export * from 'rspress/theme'` so as to ensure your theme works fine.
3835

3936
### 2. Use slot
4037

4138
It is worth noting that the `Layout` component has designed a series of props to support slot elements. You can use these props to extend the layout of the default theme. For example, change the above `Layout` component to the following form:
4239

4340
```tsx title="theme/index.tsx"
44-
import Theme from 'rspress/theme';
41+
import { Layout as BasicLayout } from 'rspress/theme';
4542

4643
// Show all props below
4744
const Layout = () => (
48-
<Theme.Layout
45+
<BasicLayout
4946
/* Before home hero */
5047
beforeHero={<div>beforeHero</div>}
5148
/* After home hero */
@@ -93,11 +90,8 @@ const Layout = () => (
9390
/>
9491
);
9592

96-
export default {
97-
...Theme,
98-
Layout,
99-
};
100-
93+
export { Layout };
94+
// re-export
10195
export * from 'rspress/theme';
10296
```
10397

@@ -107,29 +101,28 @@ In addition to the slot method, if you want to extend the default theme componen
107101
as well as other Rspress [built-in components](https://github.com/web-infra-dev/rspress/tree/main/packages/theme-default/src/components)
108102

109103
```tsx title="theme/index.tsx"
110-
import Theme, { Search } from 'rspress/theme';
104+
import { Search as BasicSearch, Layout as BasicLayout } from 'rspress/theme';
111105

112-
// Use slot
113-
const Layout = () => <Theme.Layout beforeNavTitle={<div>some content</div>} />;
114106
// Custom Home Page
115107
const HomeLayout = () => <div>Home</div>;
116108
// Custom 404 page
117109
const NotFoundLayout = () => <div>404</div>;
118-
119-
export default {
120-
...Theme,
121-
Layout,
122-
HomeLayout,
123-
NotFoundLayout,
124-
};
110+
// Use slot
111+
const Layout = () => (
112+
<BasicLayout
113+
beforeNavTitle={<div>some content</div>}
114+
HomeLayout={HomeLayout}
115+
NotFoundLayout={NotFoundLayout}
116+
/>
117+
);
125118

126119
// Custom Search Component
127-
const MySearch = () => (
120+
const Search = () => (
128121
<div className="my-search">
129-
<Search />
122+
<BasicSearch />
130123
</div>
131124
);
132-
export { MySearch as Search };
125+
export { Search, HomeLayout, NotFoundLayout };
133126
// re-export
134127
export * from 'rspress/theme';
135128
```
@@ -172,12 +165,10 @@ function Layout() {
172165
// The setup function will be called when the page is initialized. It is generally used to monitor global events, and it can be an empty function
173166
const setup = () => {};
174167

168+
// Export Layout component and setup function
169+
export { Layout, setup };
175170
// Export all content of the default theme to ensure that your theme configuration can work properly
176171
export * from 'rspress/theme';
177-
178-
// Export Layout component and setup function
179-
// Note: both must export
180-
export default { Layout, setup };
181172
```
182173

183174
Layout component will be used to render the layout of the entire document site, you can introduce your custom components in this component, for example:

packages/document/docs/zh/guide/advanced/custom-theme.mdx

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,27 @@
2222
你可以使用如下的方式来书写 `theme/index.tsx` 文件:
2323

2424
```tsx title="theme/index.tsx"
25-
import Theme from 'rspress/theme';
25+
import { Layout as BasicLayout } from 'rspress/theme';
2626

27-
const Layout = () => <Theme.Layout beforeNavTitle={<div>some content</div>} />;
27+
const Layout = () => <BasicLayout beforeNavTitle={<div>some content</div>} />;
2828

29-
export default {
30-
...Theme,
31-
Layout,
32-
};
29+
export { Layout };
3330

3431
export * from 'rspress/theme';
3532
```
3633

37-
一方面你需要通过 `export default` 导出一个主题配置对象,另一方面你需要通过 `export * from 'rspress/theme'` 导出所有具名导出的内容,这样才能保证你的主题配置能够正常工作。
34+
一方面你需要通过 `export` 导出一个主题配置对象,另一方面你需要通过 `export * from 'rspress/theme'` 导出所有具名导出的内容,这样才能保证你的主题配置能够正常工作。
3835

3936
### 2. 使用插槽
4037

4138
值得注意的是,`Layout` 组件设计了一系列的 props 支持插槽元素,你可以通过这些 props 来扩展默认主题的布局,比如将上面的 `Layout` 组件改成如下的形式:
4239

4340
```tsx title="theme/index.tsx"
44-
import Theme from 'rspress/theme';
41+
import { Layout as BasicLayout } from 'rspress/theme';
4542

4643
// 以下展示所有的 Props
4744
const Layout = () => (
48-
<Theme.Layout
45+
<BasicLayout
4946
/* Home 页 Hero 部分之前 */
5047
beforeHero={<div>beforeHero</div>}
5148
/* Home 页 Hero 部分之后 */
@@ -93,11 +90,8 @@ const Layout = () => (
9390
/>
9491
);
9592

96-
export default {
97-
...Theme,
98-
Layout,
99-
};
100-
93+
export { Layout };
94+
// 重导出
10195
export * from 'rspress/theme';
10296
```
10397

@@ -107,29 +101,28 @@ export * from 'rspress/theme';
107101
以及其他 Rspress [内置的组件](https://github.com/web-infra-dev/rspress/tree/main/packages/theme-default/src/components)。比如:
108102

109103
```tsx title="theme/index.tsx"
110-
import Theme, { Search } from 'rspress/theme';
104+
import { Search as BasicSearch, Layout as BasicLayout } from 'rspress/theme';
111105

112-
// 传入插槽
113-
const Layout = () => <Theme.Layout beforeNavTitle={<div>some content</div>} />;
114106
// 定制 Home 页面
115107
const HomeLayout = () => <div>Home</div>;
116108
// 定制 404 页面
117109
const NotFoundLayout = () => <div>404</div>;
118-
119-
export default {
120-
...Theme,
121-
Layout,
122-
HomeLayout,
123-
NotFoundLayout,
124-
};
110+
// 传入插槽
111+
const Layout = () => (
112+
<BasicLayout
113+
beforeNavTitle={<div>some content</div>}
114+
HomeLayout={HomeLayout}
115+
NotFoundLayout={NotFoundLayout}
116+
/>
117+
);
125118

126119
// 定制 Search 组件
127-
const MySearch = () => (
120+
const Search = () => (
128121
<div className="my-search">
129-
<Search />
122+
<BasicSearch />
130123
</div>
131124
);
132-
export { MySearch as Search };
125+
export { Search, HomeLayout, NotFoundLayout };
133126
// 重导出其他部分
134127
export * from 'rspress/theme';
135128
```
@@ -172,12 +165,11 @@ function Layout() {
172165
// setup 函数,会在页面初始化时调用,一般用来做全局事件的监听,可为空函数
173166
const setup = () => {};
174167

175-
// 导出默认主题的所有内容,这样才能保证你的主题配置能够正常工作
176-
export * from 'rspress/theme';
177-
178168
// 导出 Layout 组件和 setup 函数
179169
// 两者必须导出
180-
export default { Layout, setup };
170+
export { Layout, setup };
171+
// 导出默认主题的所有内容,这样才能保证你的主题配置能够正常工作
172+
export * from 'rspress/theme';
181173
```
182174

183175
这个 Layout 组件会被用来渲染整个文档站点的布局,你可以在这个组件中引入你的自定义组件,比如:

packages/document/theme/index.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NavIcon } from '@rstack-dev/doc-ui/nav-icon';
2-
import Theme from 'rspress/theme';
2+
import { Layout as BasicLayout } from 'rspress/theme';
33
import { HomeLayout as BasicHomeLayout } from 'rspress/theme';
44
import { ToolStack } from './components/ToolStack';
55
import './index.css';
@@ -9,13 +9,8 @@ function HomeLayout() {
99
}
1010

1111
const Layout = () => {
12-
return <Theme.Layout beforeNavTitle={<NavIcon />} />;
13-
};
14-
15-
export default {
16-
...Theme,
17-
Layout,
18-
HomeLayout,
12+
return <BasicLayout beforeNavTitle={<NavIcon />} />;
1913
};
2014

15+
export { Layout, HomeLayout };
2116
export * from 'rspress/theme';

0 commit comments

Comments
 (0)