index.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. import _extends from "@babel/runtime/helpers/esm/extends";
  2. import hash from '@emotion/hash';
  3. // @ts-ignore
  4. import unitless from '@emotion/unitless';
  5. import { compile, serialize, stringify } from 'stylis';
  6. import { contentQuotesLinter, hashedAnimationLinter } from '../../linters';
  7. import { useStyleInject, ATTR_CACHE_PATH, ATTR_MARK, ATTR_TOKEN, CSS_IN_JS_INSTANCE } from '../../StyleContext';
  8. import { supportLayer } from '../../util';
  9. import useGlobalCache from '../useGlobalCache';
  10. import { removeCSS, updateCSS } from '../../../../vc-util/Dom/dynamicCSS';
  11. import { computed } from 'vue';
  12. import canUseDom from '../../../../_util/canUseDom';
  13. import { ATTR_CACHE_MAP, existPath, getStyleAndHash, serialize as serializeCacheMap } from './cacheMapUtil';
  14. const isClientSide = canUseDom();
  15. const SKIP_CHECK = '_skip_check_';
  16. const MULTI_VALUE = '_multi_value_';
  17. // ============================================================================
  18. // == Parser ==
  19. // ============================================================================
  20. // Preprocessor style content to browser support one
  21. export function normalizeStyle(styleStr) {
  22. const serialized = serialize(compile(styleStr), stringify);
  23. return serialized.replace(/\{%%%\:[^;];}/g, ';');
  24. }
  25. function isCompoundCSSProperty(value) {
  26. return typeof value === 'object' && value && (SKIP_CHECK in value || MULTI_VALUE in value);
  27. }
  28. // 注入 hash 值
  29. function injectSelectorHash(key, hashId, hashPriority) {
  30. if (!hashId) {
  31. return key;
  32. }
  33. const hashClassName = `.${hashId}`;
  34. const hashSelector = hashPriority === 'low' ? `:where(${hashClassName})` : hashClassName;
  35. // 注入 hashId
  36. const keys = key.split(',').map(k => {
  37. var _a;
  38. const fullPath = k.trim().split(/\s+/);
  39. // 如果 Selector 第一个是 HTML Element,那我们就插到它的后面。反之,就插到最前面。
  40. let firstPath = fullPath[0] || '';
  41. const htmlElement = ((_a = firstPath.match(/^\w+/)) === null || _a === void 0 ? void 0 : _a[0]) || '';
  42. firstPath = `${htmlElement}${hashSelector}${firstPath.slice(htmlElement.length)}`;
  43. return [firstPath, ...fullPath.slice(1)].join(' ');
  44. });
  45. return keys.join(',');
  46. }
  47. // Global effect style will mount once and not removed
  48. // The effect will not save in SSR cache (e.g. keyframes)
  49. const globalEffectStyleKeys = new Set();
  50. /**
  51. * @private Test only. Clear the global effect style keys.
  52. */
  53. export const _cf = process.env.NODE_ENV !== 'production' ? () => globalEffectStyleKeys.clear() : undefined;
  54. // Parse CSSObject to style content
  55. export const parseStyle = function (interpolation) {
  56. let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  57. let {
  58. root,
  59. injectHash,
  60. parentSelectors
  61. } = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
  62. root: true,
  63. parentSelectors: []
  64. };
  65. const {
  66. hashId,
  67. layer,
  68. path,
  69. hashPriority,
  70. transformers = [],
  71. linters = []
  72. } = config;
  73. let styleStr = '';
  74. let effectStyle = {};
  75. function parseKeyframes(keyframes) {
  76. const animationName = keyframes.getName(hashId);
  77. if (!effectStyle[animationName]) {
  78. const [parsedStr] = parseStyle(keyframes.style, config, {
  79. root: false,
  80. parentSelectors
  81. });
  82. effectStyle[animationName] = `@keyframes ${keyframes.getName(hashId)}${parsedStr}`;
  83. }
  84. }
  85. function flattenList(list) {
  86. let fullList = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
  87. list.forEach(item => {
  88. if (Array.isArray(item)) {
  89. flattenList(item, fullList);
  90. } else if (item) {
  91. fullList.push(item);
  92. }
  93. });
  94. return fullList;
  95. }
  96. const flattenStyleList = flattenList(Array.isArray(interpolation) ? interpolation : [interpolation]);
  97. flattenStyleList.forEach(originStyle => {
  98. // Only root level can use raw string
  99. const style = typeof originStyle === 'string' && !root ? {} : originStyle;
  100. if (typeof style === 'string') {
  101. styleStr += `${style}\n`;
  102. } else if (style._keyframe) {
  103. // Keyframe
  104. parseKeyframes(style);
  105. } else {
  106. const mergedStyle = transformers.reduce((prev, trans) => {
  107. var _a;
  108. return ((_a = trans === null || trans === void 0 ? void 0 : trans.visit) === null || _a === void 0 ? void 0 : _a.call(trans, prev)) || prev;
  109. }, style);
  110. // Normal CSSObject
  111. Object.keys(mergedStyle).forEach(key => {
  112. var _a;
  113. const value = mergedStyle[key];
  114. if (typeof value === 'object' && value && (key !== 'animationName' || !value._keyframe) && !isCompoundCSSProperty(value)) {
  115. let subInjectHash = false;
  116. // 当成嵌套对象来处理
  117. let mergedKey = key.trim();
  118. // Whether treat child as root. In most case it is false.
  119. let nextRoot = false;
  120. // 拆分多个选择器
  121. if ((root || injectHash) && hashId) {
  122. if (mergedKey.startsWith('@')) {
  123. // 略过媒体查询,交给子节点继续插入 hashId
  124. subInjectHash = true;
  125. } else {
  126. // 注入 hashId
  127. mergedKey = injectSelectorHash(key, hashId, hashPriority);
  128. }
  129. } else if (root && !hashId && (mergedKey === '&' || mergedKey === '')) {
  130. // In case of `{ '&': { a: { color: 'red' } } }` or `{ '': { a: { color: 'red' } } }` without hashId,
  131. // we will get `&{a:{color:red;}}` or `{a:{color:red;}}` string for stylis to compile.
  132. // But it does not conform to stylis syntax,
  133. // and finally we will get `{color:red;}` as css, which is wrong.
  134. // So we need to remove key in root, and treat child `{ a: { color: 'red' } }` as root.
  135. mergedKey = '';
  136. nextRoot = true;
  137. }
  138. const [parsedStr, childEffectStyle] = parseStyle(value, config, {
  139. root: nextRoot,
  140. injectHash: subInjectHash,
  141. parentSelectors: [...parentSelectors, mergedKey]
  142. });
  143. effectStyle = _extends(_extends({}, effectStyle), childEffectStyle);
  144. styleStr += `${mergedKey}${parsedStr}`;
  145. } else {
  146. function appendStyle(cssKey, cssValue) {
  147. if (process.env.NODE_ENV !== 'production' && (typeof value !== 'object' || !(value === null || value === void 0 ? void 0 : value[SKIP_CHECK]))) {
  148. [contentQuotesLinter, hashedAnimationLinter, ...linters].forEach(linter => linter(cssKey, cssValue, {
  149. path,
  150. hashId,
  151. parentSelectors
  152. }));
  153. }
  154. // 如果是样式则直接插入
  155. const styleName = cssKey.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
  156. // Auto suffix with px
  157. let formatValue = cssValue;
  158. if (!unitless[cssKey] && typeof formatValue === 'number' && formatValue !== 0) {
  159. formatValue = `${formatValue}px`;
  160. }
  161. // handle animationName & Keyframe value
  162. if (cssKey === 'animationName' && (cssValue === null || cssValue === void 0 ? void 0 : cssValue._keyframe)) {
  163. parseKeyframes(cssValue);
  164. formatValue = cssValue.getName(hashId);
  165. }
  166. styleStr += `${styleName}:${formatValue};`;
  167. }
  168. const actualValue = (_a = value === null || value === void 0 ? void 0 : value.value) !== null && _a !== void 0 ? _a : value;
  169. if (typeof value === 'object' && (value === null || value === void 0 ? void 0 : value[MULTI_VALUE]) && Array.isArray(actualValue)) {
  170. actualValue.forEach(item => {
  171. appendStyle(key, item);
  172. });
  173. } else {
  174. appendStyle(key, actualValue);
  175. }
  176. }
  177. });
  178. }
  179. });
  180. if (!root) {
  181. styleStr = `{${styleStr}}`;
  182. } else if (layer && supportLayer()) {
  183. const layerCells = layer.split(',');
  184. const layerName = layerCells[layerCells.length - 1].trim();
  185. styleStr = `@layer ${layerName} {${styleStr}}`;
  186. // Order of layer if needed
  187. if (layerCells.length > 1) {
  188. // zombieJ: stylis do not support layer order, so we need to handle it manually.
  189. styleStr = `@layer ${layer}{%%%:%}${styleStr}`;
  190. }
  191. }
  192. return [styleStr, effectStyle];
  193. };
  194. // ============================================================================
  195. // == Register ==
  196. // ============================================================================
  197. function uniqueHash(path, styleStr) {
  198. return hash(`${path.join('%')}${styleStr}`);
  199. }
  200. // function Empty() {
  201. // return null;
  202. // }
  203. /**
  204. * Register a style to the global style sheet.
  205. */
  206. export default function useStyleRegister(info, styleFn) {
  207. const styleContext = useStyleInject();
  208. const tokenKey = computed(() => info.value.token._tokenKey);
  209. const fullPath = computed(() => [tokenKey.value, ...info.value.path]);
  210. // Check if need insert style
  211. let isMergedClientSide = isClientSide;
  212. if (process.env.NODE_ENV !== 'production' && styleContext.value.mock !== undefined) {
  213. isMergedClientSide = styleContext.value.mock === 'client';
  214. }
  215. // const [cacheStyle[0], cacheStyle[1], cacheStyle[2]]
  216. useGlobalCache('style', fullPath,
  217. // Create cache if needed
  218. () => {
  219. const {
  220. path,
  221. hashId,
  222. layer,
  223. nonce,
  224. clientOnly,
  225. order = 0
  226. } = info.value;
  227. const cachePath = fullPath.value.join('|');
  228. // Get style from SSR inline style directly
  229. if (existPath(cachePath)) {
  230. const [inlineCacheStyleStr, styleHash] = getStyleAndHash(cachePath);
  231. if (inlineCacheStyleStr) {
  232. return [inlineCacheStyleStr, tokenKey.value, styleHash, {}, clientOnly, order];
  233. }
  234. }
  235. const styleObj = styleFn();
  236. const {
  237. hashPriority,
  238. container,
  239. transformers,
  240. linters,
  241. cache
  242. } = styleContext.value;
  243. const [parsedStyle, effectStyle] = parseStyle(styleObj, {
  244. hashId,
  245. hashPriority,
  246. layer,
  247. path: path.join('-'),
  248. transformers,
  249. linters
  250. });
  251. const styleStr = normalizeStyle(parsedStyle);
  252. const styleId = uniqueHash(fullPath.value, styleStr);
  253. if (isMergedClientSide) {
  254. const mergedCSSConfig = {
  255. mark: ATTR_MARK,
  256. prepend: 'queue',
  257. attachTo: container,
  258. priority: order
  259. };
  260. const nonceStr = typeof nonce === 'function' ? nonce() : nonce;
  261. if (nonceStr) {
  262. mergedCSSConfig.csp = {
  263. nonce: nonceStr
  264. };
  265. }
  266. const style = updateCSS(styleStr, styleId, mergedCSSConfig);
  267. style[CSS_IN_JS_INSTANCE] = cache.instanceId;
  268. // Used for `useCacheToken` to remove on batch when token removed
  269. style.setAttribute(ATTR_TOKEN, tokenKey.value);
  270. // Dev usage to find which cache path made this easily
  271. if (process.env.NODE_ENV !== 'production') {
  272. style.setAttribute(ATTR_CACHE_PATH, fullPath.value.join('|'));
  273. }
  274. // Inject client side effect style
  275. Object.keys(effectStyle).forEach(effectKey => {
  276. if (!globalEffectStyleKeys.has(effectKey)) {
  277. globalEffectStyleKeys.add(effectKey);
  278. // Inject
  279. updateCSS(normalizeStyle(effectStyle[effectKey]), `_effect-${effectKey}`, {
  280. mark: ATTR_MARK,
  281. prepend: 'queue',
  282. attachTo: container
  283. });
  284. }
  285. });
  286. }
  287. return [styleStr, tokenKey.value, styleId, effectStyle, clientOnly, order];
  288. },
  289. // Remove cache if no need
  290. (_ref, fromHMR) => {
  291. let [,, styleId] = _ref;
  292. if ((fromHMR || styleContext.value.autoClear) && isClientSide) {
  293. removeCSS(styleId, {
  294. mark: ATTR_MARK
  295. });
  296. }
  297. });
  298. return node => {
  299. return node;
  300. // let styleNode: VueNode;
  301. // if (!styleContext.ssrInline || isMergedClientSide || !styleContext.defaultCache) {
  302. // styleNode = <Empty />;
  303. // } else {
  304. // styleNode = (
  305. // <style
  306. // {...{
  307. // [ATTR_TOKEN]: cacheStyle.value[1],
  308. // [ATTR_MARK]: cacheStyle.value[2],
  309. // }}
  310. // innerHTML={cacheStyle.value[0]}
  311. // />
  312. // );
  313. // }
  314. // return (
  315. // <>
  316. // {styleNode}
  317. // {node}
  318. // </>
  319. // );
  320. };
  321. }
  322. // ============================================================================
  323. // == SSR ==
  324. // ============================================================================
  325. export function extractStyle(cache) {
  326. let plain = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  327. const matchPrefix = `style%`;
  328. // prefix with `style` is used for `useStyleRegister` to cache style context
  329. const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith(matchPrefix));
  330. // Common effect styles like animation
  331. const effectStyles = {};
  332. // Mapping of cachePath to style hash
  333. const cachePathMap = {};
  334. let styleText = '';
  335. function toStyleStr(style, tokenKey, styleId) {
  336. let customizeAttrs = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  337. const attrs = _extends(_extends({}, customizeAttrs), {
  338. [ATTR_TOKEN]: tokenKey,
  339. [ATTR_MARK]: styleId
  340. });
  341. const attrStr = Object.keys(attrs).map(attr => {
  342. const val = attrs[attr];
  343. return val ? `${attr}="${val}"` : null;
  344. }).filter(v => v).join(' ');
  345. return plain ? style : `<style ${attrStr}>${style}</style>`;
  346. }
  347. const orderStyles = styleKeys.map(key => {
  348. const cachePath = key.slice(matchPrefix.length).replace(/%/g, '|');
  349. const [styleStr, tokenKey, styleId, effectStyle, clientOnly, order] = cache.cache.get(key)[1];
  350. // Skip client only style
  351. if (clientOnly) {
  352. return null;
  353. }
  354. // ====================== Style ======================
  355. // Used for vc-util
  356. const sharedAttrs = {
  357. 'data-vc-order': 'prependQueue',
  358. 'data-vc-priority': `${order}`
  359. };
  360. let keyStyleText = toStyleStr(styleStr, tokenKey, styleId, sharedAttrs);
  361. // Save cache path with hash mapping
  362. cachePathMap[cachePath] = styleId;
  363. // =============== Create effect style ===============
  364. if (effectStyle) {
  365. Object.keys(effectStyle).forEach(effectKey => {
  366. // Effect style can be reused
  367. if (!effectStyles[effectKey]) {
  368. effectStyles[effectKey] = true;
  369. keyStyleText += toStyleStr(normalizeStyle(effectStyle[effectKey]), tokenKey, `_effect-${effectKey}`, sharedAttrs);
  370. }
  371. });
  372. }
  373. const ret = [order, keyStyleText];
  374. return ret;
  375. }).filter(o => o);
  376. orderStyles.sort((o1, o2) => o1[0] - o2[0]).forEach(_ref2 => {
  377. let [, style] = _ref2;
  378. styleText += style;
  379. });
  380. // ==================== Fill Cache Path ====================
  381. styleText += toStyleStr(`.${ATTR_CACHE_MAP}{content:"${serializeCacheMap(cachePathMap)}";}`, undefined, undefined, {
  382. [ATTR_CACHE_MAP]: ATTR_CACHE_MAP
  383. });
  384. return styleText;
  385. }