Skip to content

feat: add static-style-extract #6713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bd25c13
docs: add ant-design-vue nuxt module
aibayanyu20 May 31, 2023
b045c82
Merge branch 'vueComponent:feat-v4' into feat-v4
aibayanyu20 Jun 7, 2023
bc25663
Merge branch 'vueComponent:feat-v4' into feat-v4
aibayanyu20 Jun 28, 2023
d360f0f
feat: add static-style-extract
aibayanyu20 Jul 1, 2023
981f713
docs: fix import defineProps and defineEmits
aibayanyu20 Jul 1, 2023
d37aaae
feat: add static-style extract test
aibayanyu20 Jul 1, 2023
3f25a6d
fix: add ignore component
aibayanyu20 Jul 1, 2023
6b0946b
Merge branch 'vueComponent:feat-v4' into feat-v4
aibayanyu20 Jul 4, 2023
db95029
feat: calendar select support info.source param (#6697)
aibayanyu20 Jul 3, 2023
8434369
docs: synchronous config-provider demo (#6706)
selicens Jul 3, 2023
1f1a8c7
revert: #6706
tangjinzhou Jul 3, 2023
e172c7b
feat: node error
aibayanyu20 Jul 4, 2023
f1e2d48
fix: Handle the output of styles in Server Side Rendering mode
aibayanyu20 Jul 4, 2023
92b51cb
fix: fix value required error
aibayanyu20 Jul 5, 2023
2bd5d85
chore: change tests dirname
aibayanyu20 Jul 5, 2023
d9aaa47
fix: auchor warning
aibayanyu20 Jul 5, 2023
1fd8deb
chore: add tests
aibayanyu20 Jul 5, 2023
d49c04c
chore: generate tests snap
aibayanyu20 Jul 5, 2023
0ec4eb1
docs: add ssr docs
aibayanyu20 Jul 5, 2023
5f9df5c
docs: synchronous config-provider demo (#6706)
selicens Jul 3, 2023
0ec68c8
revert: #6706
tangjinzhou Jul 3, 2023
50f9866
docs: add extract ssr doc
aibayanyu20 Jul 5, 2023
fb1e229
Merge branch 'feat-v4' into feat-extra-static-css
aibayanyu20 Jul 5, 2023
340b046
fix: Removing canUseDom does not have any effect.
aibayanyu20 Jul 5, 2023
f1c1432
fix: remove range picker in picker map
aibayanyu20 Jul 5, 2023
d9d6d8e
fix: watch source can only be a getter/effect function style warning
aibayanyu20 Jul 5, 2023
1a70c92
fix: styleContext props maybe ref
aibayanyu20 Jul 5, 2023
cfb298b
fix: remove black list component
aibayanyu20 Jul 6, 2023
592dea5
feat: add compChildNameMap
aibayanyu20 Jul 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/_util/PortalWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default defineComponent({
return true;
};
// attachToParent();
const defaultContainer = document.createElement('div');
const defaultContainer = canUseDom() && document.createElement('div');
const getContainer = () => {
if (!supportDom) {
return null;
Expand Down
2 changes: 1 addition & 1 deletion components/_util/cssinjs/StyleContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const useStyleProvider = (props: UseStyleProviderProps) => {
const parentContext = useStyleInject();
const context = shallowRef<Partial<StyleContextProps>>({ ...defaultStyleContext });
watch(
[props, parentContext],
[() => unref(props), parentContext],
() => {
const mergedContext: Partial<StyleContextProps> = {
...parentContext.value,
Expand Down
6 changes: 4 additions & 2 deletions components/_util/cssinjs/hooks/useStyleRegister.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ export default function useStyleRegister(
// ============================================================================
// == SSR ==
// ============================================================================
export function extractStyle(cache: Cache) {
export function extractStyle(cache: Cache, plain = false) {
// prefix with `style` is used for `useStyleRegister` to cache style context
const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith('style%'));

Expand All @@ -410,7 +410,9 @@ export function extractStyle(cache: Cache) {
styleKeys.forEach(key => {
const [styleStr, tokenKey, styleId]: [string, string, string] = cache.cache.get(key)![1];

styleText += `<style ${ATTR_TOKEN}="${tokenKey}" ${ATTR_MARK}="${styleId}">${styleStr}</style>`;
styleText += plain
? styleStr
: `<style ${ATTR_TOKEN}="${tokenKey}" ${ATTR_MARK}="${styleId}">${styleStr}</style>`;
});

return styleText;
Expand Down
4 changes: 4 additions & 0 deletions components/_util/hooks/useScrollLocker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Ref } from 'vue';
import { computed, watchEffect } from 'vue';
import { updateCSS, removeCSS } from '../../vc-util/Dom/dynamicCSS';
import getScrollBarSize from '../../_util/getScrollBarSize';
import canUseDom from '../../_util/canUseDom';

const UNIQUE_ID = `vc-util-locker-${Date.now()}`;

Expand All @@ -24,6 +25,9 @@ export default function useScrollLocker(lock?: Ref<boolean>) {

watchEffect(
onClear => {
if (!canUseDom()) {
return;
}
if (mergedLock.value) {
const scrollbarSize = getScrollBarSize();
const isOverflow = isBodyOverflowing();
Expand Down

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions components/_util/static-style-extract/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// import { StyleProvider } from '../../cssinjs';
import { extractStyle } from '../index';
import { ConfigProvider } from '../../../components';

const testGreenColor = '#008000';
describe('Static-Style-Extract', () => {
it('should extract static styles', () => {
const cssText = extractStyle();
expect(cssText).not.toContain(testGreenColor);
expect(cssText).toMatchSnapshot();
});
it('should extract static styles with customTheme', () => {
const cssText = extractStyle(node => {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: testGreenColor,
},
}}
>
{node}
</ConfigProvider>
);
});
expect(cssText).toContain(testGreenColor);
expect(cssText).toMatchSnapshot();
});
// it('with custom hashPriority', () => {
// const cssText = extractStyle(
// (node) => (
// <StyleProvider hashPriority='high'>
// <ConfigProvider
// theme={{
// token: {
// colorPrimary: testGreenColor,
// },
// }}
// >
// {node}
// </ConfigProvider>
// </StyleProvider>
// ),
// );
// expect(cssText).toContain(testGreenColor);
// expect(cssText).not.toContain(':where');
// expect(cssText).toMatchSnapshot();
//
// const cssText2 = extractStyle((node) => (
// <ConfigProvider
// theme={{
// token: {
// colorPrimary: testGreenColor,
// },
// }}
// >
// {node}
// </ConfigProvider>
// ));
// expect(cssText2).toContain(':where');
// });
});
90 changes: 90 additions & 0 deletions components/_util/static-style-extract/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { createCache, extractStyle as extStyle, StyleProvider } from '../cssinjs';
import * as antd from '../../components';
import { renderToString } from 'vue/server-renderer';
import type { CustomRender } from './interface';
const blackList: string[] = [
'ConfigProvider',
'Grid',
'Tour',
'SelectOptGroup',
'SelectOption',
'MentionsOption',
'TreeNode',
'TreeSelectNode',
'LocaleProvider',
];

const pickerMap = {
MonthPicker: 'month',
WeekPicker: 'week',
QuarterPicker: 'quarter',
};

const compChildNameMap = {
MenuDivider: 'Menu',
MenuItem: 'Menu',
MenuItemGroup: 'Menu',
SubMenu: 'Menu',
TableColumn: 'Table',
TableColumnGroup: 'Table',
TableSummary: 'Table',
TableSummaryRow: 'Table',
TableSummaryCell: 'Table',
TabPane: 'Tabs',
TimelineItem: 'Timeline',
};

const defaultNode = () => (
<>
{Object.keys(antd)
.filter(name => !blackList.includes(name) && name[0] === name[0].toUpperCase())
.map(compName => {
const Comp = antd[compName];
if (compName === 'Dropdown') {
return (
<Comp key={compName} menu={{ items: [] }}>
<div />
</Comp>
);
}
if (compName === 'Anchor') {
return <Comp key={compName} items={[]} />;
}
if (compName in pickerMap) {
const Comp = antd['DatePicker'];
const type = pickerMap[compName];
return <Comp key={compName} picker={type} />;
}
if (compName in compChildNameMap) {
const ParentComp = antd[compChildNameMap[compName]];
return (
<ParentComp>
<Comp />
</ParentComp>
);
}
if (compName === 'QRCode' || compName === 'Segmented') {
return (
<Comp key={compName} value={''}>
<div />
</Comp>
);
}
return <Comp key={compName} />;
})}
</>
);

export function extractStyle(customTheme?: CustomRender): string {
const cache = createCache();
renderToString(
<StyleProvider cache={cache}>
{customTheme ? customTheme(defaultNode()) : defaultNode()}
</StyleProvider>,
);

// Grab style from cache
const styleText = extStyle(cache, true);

return styleText;
}
3 changes: 3 additions & 0 deletions components/_util/static-style-extract/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { VueNode } from '../type';

export type CustomRender = (node: VueNode) => VueNode;
1 change: 0 additions & 1 deletion components/drawer/demo/descriptionItem/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
</div>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue';
interface Props {
title: string;
content?: string;
Expand Down
4 changes: 4 additions & 0 deletions components/float-button/FloatButtonGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useProvideFloatButtonGroupContext } from './context';
import { findDOMNode, initDefaultProps } from '../_util/props-util';
import { floatButtonGroupProps } from './interface';
import type { FloatButtonGroupProps } from './interface';
import canUseDom from '../_util/canUseDom';

// CSSINJS
import useStyle from './style';
Expand Down Expand Up @@ -74,6 +75,9 @@ const FloatButtonGroup = defineComponent({
watch(
computed(() => props.trigger),
value => {
if (!canUseDom()) {
return;
}
document.removeEventListener('click', onClick);
if (value === 'click') {
document.addEventListener('click', onClick);
Expand Down
1 change: 0 additions & 1 deletion components/form/demo/price-input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
</template>

<script lang="ts" setup>
import { defineProps, defineEmits } from 'vue';
import { Form } from 'ant-design-vue';

export type Currency = 'rmb' | 'dollar';
Expand Down
10 changes: 10 additions & 0 deletions site/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ const routes = [
meta: { enTitle: 'Customize Theme', title: '定制主题', category: 'docs' },
component: () => import('../vueDocs/customize-theme.en-US.md'),
},
{
path: 'vue/ssr-extract-ssr',
meta: { enTitle: 'SSR Static style export', title: 'SSR 静态样式导出', category: 'docs' },
component: () => import('../vueDocs/extract-ssr.en-US.md'),
},
{
path: 'vue/ssr-extract-ssr-cn',
meta: { enTitle: 'SSR Static style export', title: 'SSR 静态样式导出', category: 'docs' },
component: () => import('../vueDocs/extract-ssr.zh-CN.md'),
},
{
path: 'vue/replace-date-cn',
meta: { enTitle: 'Custom Date Library', title: '自定义时间库', category: 'docs' },
Expand Down
46 changes: 46 additions & 0 deletions site/src/vueDocs/extract-ssr.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# SSR Static style export

We are thinking about whether we can pre-bake the style of the component for front-end consumption like the v3 version, so we proposed [\[RFC\] Static Extract style](https://github.com/ant-design/ant-design/discussions/40985). Its idea is very simple that only need to render all the components once in advance to get the complete style from the cache, and then write it into the css file.

```tsx
const cache = createCache();

// HTML Content
renderToString(
<StyleProvider cache={cache}>
<Button />
<Switch />
<Input />
{/* Rest antd components */}
</StyleProvider>,
);

// Style Content
const styleText = extractStyle(cache);
```

Of course, this is a little cumbersome for developers. so we provide a method to fulfill this requirement:

```tsx
import { extractStyle } from 'ant-design-vue/lib/_util/static-style-extract';
import fs from 'fs';

// `extractStyle` containers all the antd component
// excludes popup like component which is no need in ssr: Modal, message, notification, etc.
const css = extractStyle();

fs.writeFile(...);
```

If developers use a hybrid theme, they can also implement the hybrid requirements by themselves:

```tsx
// `node` is the components set we prepared
const css = extractStyle(node => (
<>
<ConfigProvider theme={theme1}>{node}</ConfigProvider>
<ConfigProvider theme={theme2}>{node}</ConfigProvider>
<ConfigProvider theme={theme3}>{node}</ConfigProvider>
</>
));
```
46 changes: 46 additions & 0 deletions site/src/vueDocs/extract-ssr.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# SSR 静态样式导出

我们在思考是否可以如 v3 版本一样,预先烘焙组件的样式来使前端消费,所以提出了 [\[RFC\] Static Extract style](https://github.com/ant-design/ant-design/discussions/40985)。它的思路很简单,我们只需要提前将所有的组件进行一次渲染就可以从 cache 中获得完整的样式,然后将其写入到 css 文件中即可。

```tsx
const cache = createCache();

// HTML Content
renderToString(
<StyleProvider cache={cache}>
<Button />
<Switch />
<Input />
{/* Rest antd components */}
</StyleProvider>,
);

// Style Content
const styleText = extractStyle(cache);
```

当然,这对于开发者而言稍微有点麻烦。所以我们提供了一个方法来实现该需求:

```tsx
import { extractStyle } from 'ant-design-vue/lib/_util/static-style-extract';
import fs from 'fs';

// `extractStyle` containers all the antd component
// excludes popup like component which is no need in ssr: Modal, message, notification, etc.
const css = extractStyle();

fs.writeFile(...);
```

如果开发者使用了混合主题,也可以自行实现混合需求:

```tsx
// `node` is the components set we prepared
const css = extractStyle(node => (
<>
<ConfigProvider theme={theme1}>{node}</ConfigProvider>
<ConfigProvider theme={theme2}>{node}</ConfigProvider>
<ConfigProvider theme={theme3}>{node}</ConfigProvider>
</>
));
```