index.js 15 KB

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