mirror of
https://gitee.com/ant-design-vue/ant-design-vue.git
synced 2024-11-29 18:48:32 +08:00
feat: add static-style-extract (#6713)
* docs: add ant-design-vue nuxt module * feat: add static-style-extract * docs: fix import defineProps and defineEmits * feat: add static-style extract test * fix: add ignore component * feat: calendar select support info.source param (#6697) * docs: add ant-design-vue nuxt module * feat: calendar select support info.source param * docs: synchronous config-provider demo (#6706) * revert: #6706 * feat: node error * fix: Handle the output of styles in Server Side Rendering mode * fix: fix value required error * chore: change tests dirname * fix: auchor warning * chore: add tests * chore: generate tests snap * docs: add ssr docs * docs: synchronous config-provider demo (#6706) * revert: #6706 * docs: add extract ssr doc * fix: Removing canUseDom does not have any effect. * fix: remove range picker in picker map * fix: watch source can only be a getter/effect function style warning * fix: styleContext props maybe ref * fix: remove black list component * feat: add compChildNameMap --------- Co-authored-by: selicens <1244620067@qq.com> Co-authored-by: tangjinzhou <415800467@qq.com>
This commit is contained in:
parent
531ae16553
commit
53dc5daaaf
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -413,7 +413,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%'));
|
||||
|
||||
@ -424,7 +424,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;
|
||||
|
@ -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()}`;
|
||||
|
||||
@ -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();
|
||||
|
File diff suppressed because one or more lines are too long
@ -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
components/_util/static-style-extract/index.tsx
Normal file
90
components/_util/static-style-extract/index.tsx
Normal file
@ -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
components/_util/static-style-extract/interface.ts
Normal file
3
components/_util/static-style-extract/interface.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import type { VueNode } from '../type';
|
||||
|
||||
export type CustomRender = (node: VueNode) => VueNode;
|
@ -10,7 +10,6 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { defineProps } from 'vue';
|
||||
interface Props {
|
||||
title: string;
|
||||
content?: string;
|
||||
|
@ -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';
|
||||
@ -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);
|
||||
|
@ -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';
|
||||
|
@ -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' },
|
||||
|
46
site/src/vueDocs/extract-ssr.en-US.md
Normal file
46
site/src/vueDocs/extract-ssr.en-US.md
Normal file
@ -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
site/src/vueDocs/extract-ssr.zh-CN.md
Normal file
46
site/src/vueDocs/extract-ssr.zh-CN.md
Normal file
@ -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>
|
||||
</>
|
||||
));
|
||||
```
|
Loading…
Reference in New Issue
Block a user