client.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. export const globalField = '__VITE_THEME__';
  2. export const styleTagId = '__VITE_PLUGIN_THEME__';
  3. export const darkStyleTagId = '__VITE_PLUGIN_DARK_THEME__';
  4. export const linkID = '__VITE_PLUGIN_THEME-ANTD_DARK_THEME_LINK__';
  5. const colorPluginOutputFileName = __COLOR_PLUGIN_OUTPUT_FILE_NAME__;
  6. const isProd = __PROD__;
  7. const colorPluginOptions = __COLOR_PLUGIN_OPTIONS__;
  8. const injectTo = colorPluginOptions.injectTo;
  9. const debounceThemeRender = debounce(200, renderTheme);
  10. export let darkCssIsReady = false;
  11. (() => {
  12. if (!window[globalField]) {
  13. window[globalField] = {
  14. styleIdMap: new Map(),
  15. styleRenderQueueMap: new Map(),
  16. };
  17. }
  18. setGlobalOptions('replaceStyleVariables', replaceStyleVariables);
  19. if (!getGlobalOptions('defaultOptions')) {
  20. // assign defines
  21. setGlobalOptions('defaultOptions', colorPluginOptions);
  22. }
  23. })();
  24. export function addCssToQueue(id, styleString) {
  25. const styleIdMap = getGlobalOptions('styleIdMap');
  26. if (!styleIdMap.get(id)) {
  27. window[globalField].styleRenderQueueMap.set(id, styleString);
  28. debounceThemeRender();
  29. }
  30. }
  31. function renderTheme() {
  32. const variables = getGlobalOptions('colorVariables');
  33. if (!variables) {
  34. return;
  35. }
  36. const styleRenderQueueMap = getGlobalOptions('styleRenderQueueMap');
  37. const styleDom = getStyleDom(styleTagId);
  38. let html = styleDom.innerHTML;
  39. for (const [id, css] of styleRenderQueueMap.entries()) {
  40. html += css;
  41. window[globalField].styleRenderQueueMap.delete(id);
  42. window[globalField].styleIdMap.set(id, css);
  43. }
  44. replaceCssColors(html, variables).then((processCss) => {
  45. appendCssToDom(styleDom, processCss, injectTo);
  46. });
  47. }
  48. export async function replaceStyleVariables({ colorVariables, customCssHandler, }) {
  49. setGlobalOptions('colorVariables', colorVariables);
  50. const styleIdMap = getGlobalOptions('styleIdMap');
  51. const styleRenderQueueMap = getGlobalOptions('styleRenderQueueMap');
  52. if (!isProd) {
  53. for (const [id, css] of styleIdMap.entries()) {
  54. styleRenderQueueMap.set(id, css);
  55. }
  56. renderTheme();
  57. }
  58. else {
  59. try {
  60. const cssText = await fetchCss(colorPluginOutputFileName);
  61. const styleDom = getStyleDom(styleTagId);
  62. const processCss = await replaceCssColors(cssText, colorVariables, customCssHandler);
  63. appendCssToDom(styleDom, processCss, injectTo);
  64. }
  65. catch (error) {
  66. throw new Error(error);
  67. }
  68. }
  69. }
  70. export async function loadDarkThemeCss() {
  71. const extractCss = __ANTD_DARK_PLUGIN_EXTRACT_CSS__;
  72. const isLoadLink = __ANTD_DARK_PLUGIN_LOAD_LINK__;
  73. if (darkCssIsReady || !extractCss) {
  74. return;
  75. }
  76. if (isLoadLink) {
  77. const linkTag = document.getElementById(linkID);
  78. if (linkTag) {
  79. linkTag.removeAttribute('disabled');
  80. linkTag.setAttribute('rel', 'stylesheet');
  81. }
  82. }
  83. else {
  84. const colorPluginOutputFileName = __ANTD_DARK_PLUGIN_OUTPUT_FILE_NAME__;
  85. const cssText = await fetchCss(colorPluginOutputFileName);
  86. const styleDom = getStyleDom(darkStyleTagId);
  87. appendCssToDom(styleDom, cssText, injectTo);
  88. }
  89. darkCssIsReady = true;
  90. }
  91. // Used to replace css color variables. Note that the order of the two arrays must be the same
  92. export async function replaceCssColors(css, colors, customCssHandler) {
  93. let retCss = css;
  94. const defaultOptions = getGlobalOptions('defaultOptions');
  95. const colorVariables = defaultOptions ? defaultOptions.colorVariables || [] : [];
  96. colorVariables.forEach(function (color, index) {
  97. const reg = new RegExp(color.replace(/,/g, ',\\s*').replace(/\s/g, '').replace('(', `\\(`).replace(')', `\\)`) +
  98. '([\\da-f]{2})?(\\b|\\)|,|\\s)?', 'ig');
  99. retCss = retCss.replace(reg, colors[index] + '$1$2').replace('$1$2', '');
  100. if (customCssHandler && typeof customCssHandler === 'function') {
  101. retCss = customCssHandler(retCss) || retCss;
  102. }
  103. });
  104. return retCss;
  105. }
  106. export function setGlobalOptions(key, value) {
  107. window[globalField][key] = value;
  108. }
  109. export function getGlobalOptions(key) {
  110. return window[globalField][key];
  111. }
  112. export function getStyleDom(id) {
  113. let style = document.getElementById(id);
  114. if (!style) {
  115. style = document.createElement('style');
  116. style.setAttribute('id', id);
  117. }
  118. return style;
  119. }
  120. export async function appendCssToDom(styleDom, cssText, appendTo = 'body') {
  121. styleDom.innerHTML = cssText;
  122. if (appendTo === 'head') {
  123. document.head.appendChild(styleDom);
  124. }
  125. else if (appendTo === 'body') {
  126. document.body.appendChild(styleDom);
  127. }
  128. else if (appendTo === 'body-prepend') {
  129. const firstChildren = document.body.firstChild;
  130. document.body.insertBefore(styleDom, firstChildren);
  131. }
  132. }
  133. function fetchCss(fileName) {
  134. return new Promise((resolve, reject) => {
  135. const append = getGlobalOptions('appended');
  136. if (append) {
  137. setGlobalOptions('appended', false);
  138. resolve('');
  139. return;
  140. }
  141. const xhr = new XMLHttpRequest();
  142. xhr.onload = function () {
  143. if (xhr.readyState === 4) {
  144. if (xhr.status === 200) {
  145. resolve(xhr.responseText);
  146. }
  147. else {
  148. reject(xhr.status);
  149. }
  150. }
  151. };
  152. xhr.onerror = function (e) {
  153. reject(e);
  154. };
  155. xhr.ontimeout = function (e) {
  156. reject(e);
  157. };
  158. xhr.open('GET', fileName, true);
  159. xhr.send();
  160. });
  161. }
  162. function debounce(delay, fn) {
  163. let timer;
  164. return function (...args) {
  165. // @ts-ignore
  166. // eslint-disable-next-line @typescript-eslint/no-this-alias
  167. const ctx = this;
  168. clearTimeout(timer);
  169. timer = setTimeout(function () {
  170. fn.apply(ctx, args);
  171. }, delay);
  172. };
  173. }