expandTailwindAtRules.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. Object.defineProperty(exports, "default", {
  6. enumerable: true,
  7. get: function() {
  8. return expandTailwindAtRules;
  9. }
  10. });
  11. const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
  12. const _quicklru = /*#__PURE__*/ _interop_require_default(require("@alloc/quick-lru"));
  13. const _sharedState = /*#__PURE__*/ _interop_require_wildcard(require("./sharedState"));
  14. const _generateRules = require("./generateRules");
  15. const _log = /*#__PURE__*/ _interop_require_default(require("../util/log"));
  16. const _cloneNodes = /*#__PURE__*/ _interop_require_default(require("../util/cloneNodes"));
  17. const _defaultExtractor = require("./defaultExtractor");
  18. function _interop_require_default(obj) {
  19. return obj && obj.__esModule ? obj : {
  20. default: obj
  21. };
  22. }
  23. function _getRequireWildcardCache(nodeInterop) {
  24. if (typeof WeakMap !== "function") return null;
  25. var cacheBabelInterop = new WeakMap();
  26. var cacheNodeInterop = new WeakMap();
  27. return (_getRequireWildcardCache = function(nodeInterop) {
  28. return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
  29. })(nodeInterop);
  30. }
  31. function _interop_require_wildcard(obj, nodeInterop) {
  32. if (!nodeInterop && obj && obj.__esModule) {
  33. return obj;
  34. }
  35. if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
  36. return {
  37. default: obj
  38. };
  39. }
  40. var cache = _getRequireWildcardCache(nodeInterop);
  41. if (cache && cache.has(obj)) {
  42. return cache.get(obj);
  43. }
  44. var newObj = {};
  45. var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
  46. for(var key in obj){
  47. if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
  48. var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
  49. if (desc && (desc.get || desc.set)) {
  50. Object.defineProperty(newObj, key, desc);
  51. } else {
  52. newObj[key] = obj[key];
  53. }
  54. }
  55. }
  56. newObj.default = obj;
  57. if (cache) {
  58. cache.set(obj, newObj);
  59. }
  60. return newObj;
  61. }
  62. let env = _sharedState.env;
  63. const builtInExtractors = {
  64. DEFAULT: _defaultExtractor.defaultExtractor
  65. };
  66. const builtInTransformers = {
  67. DEFAULT: (content)=>content,
  68. svelte: (content)=>content.replace(/(?:^|\s)class:/g, " ")
  69. };
  70. function getExtractor(context, fileExtension) {
  71. let extractors = context.tailwindConfig.content.extract;
  72. return extractors[fileExtension] || extractors.DEFAULT || builtInExtractors[fileExtension] || builtInExtractors.DEFAULT(context);
  73. }
  74. function getTransformer(tailwindConfig, fileExtension) {
  75. let transformers = tailwindConfig.content.transform;
  76. return transformers[fileExtension] || transformers.DEFAULT || builtInTransformers[fileExtension] || builtInTransformers.DEFAULT;
  77. }
  78. let extractorCache = new WeakMap();
  79. // Scans template contents for possible classes. This is a hot path on initial build but
  80. // not too important for subsequent builds. The faster the better though — if we can speed
  81. // up these regexes by 50% that could cut initial build time by like 20%.
  82. function getClassCandidates(content, extractor, candidates, seen) {
  83. if (!extractorCache.has(extractor)) {
  84. extractorCache.set(extractor, new _quicklru.default({
  85. maxSize: 25000
  86. }));
  87. }
  88. for (let line of content.split("\n")){
  89. line = line.trim();
  90. if (seen.has(line)) {
  91. continue;
  92. }
  93. seen.add(line);
  94. if (extractorCache.get(extractor).has(line)) {
  95. for (let match of extractorCache.get(extractor).get(line)){
  96. candidates.add(match);
  97. }
  98. } else {
  99. let extractorMatches = extractor(line).filter((s)=>s !== "!*");
  100. let lineMatchesSet = new Set(extractorMatches);
  101. for (let match of lineMatchesSet){
  102. candidates.add(match);
  103. }
  104. extractorCache.get(extractor).set(line, lineMatchesSet);
  105. }
  106. }
  107. }
  108. /**
  109. *
  110. * @param {[import('./offsets.js').RuleOffset, import('postcss').Node][]} rules
  111. * @param {*} context
  112. */ function buildStylesheet(rules, context) {
  113. let sortedRules = context.offsets.sort(rules);
  114. let returnValue = {
  115. base: new Set(),
  116. defaults: new Set(),
  117. components: new Set(),
  118. utilities: new Set(),
  119. variants: new Set()
  120. };
  121. for (let [sort, rule] of sortedRules){
  122. returnValue[sort.layer].add(rule);
  123. }
  124. return returnValue;
  125. }
  126. function expandTailwindAtRules(context) {
  127. return async (root)=>{
  128. let layerNodes = {
  129. base: null,
  130. components: null,
  131. utilities: null,
  132. variants: null
  133. };
  134. root.walkAtRules((rule)=>{
  135. // Make sure this file contains Tailwind directives. If not, we can save
  136. // a lot of work and bail early. Also we don't have to register our touch
  137. // file as a dependency since the output of this CSS does not depend on
  138. // the source of any templates. Think Vue <style> blocks for example.
  139. if (rule.name === "tailwind") {
  140. if (Object.keys(layerNodes).includes(rule.params)) {
  141. layerNodes[rule.params] = rule;
  142. }
  143. }
  144. });
  145. if (Object.values(layerNodes).every((n)=>n === null)) {
  146. return root;
  147. }
  148. var _context_candidates;
  149. // ---
  150. // Find potential rules in changed files
  151. let candidates = new Set([
  152. ...(_context_candidates = context.candidates) !== null && _context_candidates !== void 0 ? _context_candidates : [],
  153. _sharedState.NOT_ON_DEMAND
  154. ]);
  155. let seen = new Set();
  156. env.DEBUG && console.time("Reading changed files");
  157. /** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */ let regexParserContent = [];
  158. for (let item of context.changedContent){
  159. let transformer = getTransformer(context.tailwindConfig, item.extension);
  160. let extractor = getExtractor(context, item.extension);
  161. regexParserContent.push([
  162. item,
  163. {
  164. transformer,
  165. extractor
  166. }
  167. ]);
  168. }
  169. const BATCH_SIZE = 500;
  170. for(let i = 0; i < regexParserContent.length; i += BATCH_SIZE){
  171. let batch = regexParserContent.slice(i, i + BATCH_SIZE);
  172. await Promise.all(batch.map(async ([{ file , content }, { transformer , extractor }])=>{
  173. content = file ? await _fs.default.promises.readFile(file, "utf8") : content;
  174. getClassCandidates(transformer(content), extractor, candidates, seen);
  175. }));
  176. }
  177. env.DEBUG && console.timeEnd("Reading changed files");
  178. // ---
  179. // Generate the actual CSS
  180. let classCacheCount = context.classCache.size;
  181. env.DEBUG && console.time("Generate rules");
  182. env.DEBUG && console.time("Sorting candidates");
  183. let sortedCandidates = new Set([
  184. ...candidates
  185. ].sort((a, z)=>{
  186. if (a === z) return 0;
  187. if (a < z) return -1;
  188. return 1;
  189. }));
  190. env.DEBUG && console.timeEnd("Sorting candidates");
  191. (0, _generateRules.generateRules)(sortedCandidates, context);
  192. env.DEBUG && console.timeEnd("Generate rules");
  193. // We only ever add to the classCache, so if it didn't grow, there is nothing new.
  194. env.DEBUG && console.time("Build stylesheet");
  195. if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) {
  196. context.stylesheetCache = buildStylesheet([
  197. ...context.ruleCache
  198. ], context);
  199. }
  200. env.DEBUG && console.timeEnd("Build stylesheet");
  201. let { defaults: defaultNodes , base: baseNodes , components: componentNodes , utilities: utilityNodes , variants: screenNodes } = context.stylesheetCache;
  202. // ---
  203. // Replace any Tailwind directives with generated CSS
  204. if (layerNodes.base) {
  205. layerNodes.base.before((0, _cloneNodes.default)([
  206. ...defaultNodes,
  207. ...baseNodes
  208. ], layerNodes.base.source, {
  209. layer: "base"
  210. }));
  211. layerNodes.base.remove();
  212. }
  213. if (layerNodes.components) {
  214. layerNodes.components.before((0, _cloneNodes.default)([
  215. ...componentNodes
  216. ], layerNodes.components.source, {
  217. layer: "components"
  218. }));
  219. layerNodes.components.remove();
  220. }
  221. if (layerNodes.utilities) {
  222. layerNodes.utilities.before((0, _cloneNodes.default)([
  223. ...utilityNodes
  224. ], layerNodes.utilities.source, {
  225. layer: "utilities"
  226. }));
  227. layerNodes.utilities.remove();
  228. }
  229. // We do post-filtering to not alter the emitted order of the variants
  230. const variantNodes = Array.from(screenNodes).filter((node)=>{
  231. var _node_raws_tailwind;
  232. const parentLayer = (_node_raws_tailwind = node.raws.tailwind) === null || _node_raws_tailwind === void 0 ? void 0 : _node_raws_tailwind.parentLayer;
  233. if (parentLayer === "components") {
  234. return layerNodes.components !== null;
  235. }
  236. if (parentLayer === "utilities") {
  237. return layerNodes.utilities !== null;
  238. }
  239. return true;
  240. });
  241. if (layerNodes.variants) {
  242. layerNodes.variants.before((0, _cloneNodes.default)(variantNodes, layerNodes.variants.source, {
  243. layer: "variants"
  244. }));
  245. layerNodes.variants.remove();
  246. } else if (variantNodes.length > 0) {
  247. root.append((0, _cloneNodes.default)(variantNodes, root.source, {
  248. layer: "variants"
  249. }));
  250. }
  251. var _root_source_end;
  252. // TODO: Why is the root node having no source location for `end` possible?
  253. root.source.end = (_root_source_end = root.source.end) !== null && _root_source_end !== void 0 ? _root_source_end : root.source.start;
  254. // If we've got a utility layer and no utilities are generated there's likely something wrong
  255. const hasUtilityVariants = variantNodes.some((node)=>{
  256. var _node_raws_tailwind;
  257. return ((_node_raws_tailwind = node.raws.tailwind) === null || _node_raws_tailwind === void 0 ? void 0 : _node_raws_tailwind.parentLayer) === "utilities";
  258. });
  259. if (layerNodes.utilities && utilityNodes.size === 0 && !hasUtilityVariants) {
  260. _log.default.warn("content-problems", [
  261. "No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.",
  262. "https://tailwindcss.com/docs/content-configuration"
  263. ]);
  264. }
  265. // ---
  266. if (env.DEBUG) {
  267. console.log("Potential classes: ", candidates.size);
  268. console.log("Active contexts: ", _sharedState.contextSourcesMap.size);
  269. }
  270. // Clear the cache for the changed files
  271. context.changedContent = [];
  272. // Cleanup any leftover @layer atrules
  273. root.walkAtRules("layer", (rule)=>{
  274. if (Object.keys(layerNodes).includes(rule.params)) {
  275. rule.remove();
  276. }
  277. });
  278. };
  279. }