extract-styles.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. "use strict";
  2. const htmlparser = require("htmlparser2");
  3. const buildSyntaxResolver = require("../syntax/build-syntax-resolver");
  4. const buildTemplateSyntax = require("../template/syntax");
  5. const JsxLikeTokenizer = require("./jsx-like-tokenizer");
  6. const AstroTokenizer = require("./astro-tokenizer");
  7. const { cssSafeSyntax } = require("../syntax/syntaxes");
  8. function iterateCode(source, { onStyleTag, onStyleAttribute, svelte, astro }) {
  9. const openTag = {};
  10. let disable, ignore, style;
  11. const parser = new htmlparser.Parser(
  12. {
  13. oncomment: (data) => {
  14. ignore = false;
  15. const match = /(?:^|\s+)postcss-(\w+)(?:\s+|$)/i.exec(data);
  16. if (!match) {
  17. return;
  18. }
  19. const directive = match[1].toLowerCase();
  20. if (directive === "enable") {
  21. disable = false;
  22. } else if (directive === "disable") {
  23. disable = true;
  24. } else if (directive === "ignore") {
  25. ignore = true;
  26. }
  27. },
  28. onopentag(name, attribute) {
  29. openTag[name] = true;
  30. const currIgnore = ignore;
  31. ignore = false;
  32. if (currIgnore) {
  33. // ignore
  34. return;
  35. }
  36. // Test if current tag is a valid <style> tag.
  37. if (!/^style$/i.test(name)) {
  38. return;
  39. }
  40. style = {
  41. inXsls: openTag["xsl:stylesheet"],
  42. inXslt: openTag["xsl:template"],
  43. inHtml: openTag.html,
  44. tagName: name,
  45. attribute,
  46. startIndex: parser.endIndex + 1,
  47. };
  48. },
  49. onclosetag(name) {
  50. openTag[name] = false;
  51. ignore = false;
  52. if (disable || !style || name !== style.tagName) {
  53. return;
  54. }
  55. let content = source.slice(style.startIndex, parser.startIndex);
  56. const firstNewLine = /^[\t ]*\r?\n/.exec(content);
  57. if (firstNewLine) {
  58. const offset = firstNewLine[0].length;
  59. style.startIndex += offset;
  60. content = content.slice(offset);
  61. }
  62. style.content = content.replace(/[\t ]*$/, "");
  63. onStyleTag(style);
  64. style = null;
  65. },
  66. onattribute(name, content) {
  67. if (disable || ignore || name !== "style") {
  68. return;
  69. }
  70. const endIndex = parser.endIndex;
  71. const startIndex = endIndex - content.length;
  72. if (
  73. source[startIndex - 1] !== source[endIndex] ||
  74. !/\S/.test(source[endIndex])
  75. ) {
  76. return;
  77. }
  78. onStyleAttribute({
  79. content,
  80. startIndex,
  81. inline: true,
  82. inTemplate: openTag.template,
  83. });
  84. },
  85. },
  86. {
  87. Tokenizer: svelte ? JsxLikeTokenizer : astro ? AstroTokenizer : undefined,
  88. }
  89. );
  90. parser.parseComplete(source);
  91. }
  92. function getSubString(str, regexp) {
  93. const subStr = str && regexp.exec(str);
  94. if (subStr) {
  95. return subStr[1].toLowerCase();
  96. }
  97. return undefined;
  98. }
  99. function getLang(attribute) {
  100. return (
  101. getSubString(attribute.type, /^\w+\/(?:x-)?(\w+)$/i) ||
  102. getSubString(attribute.lang, /^(\w+)(?:\?.+)?$/) ||
  103. "css"
  104. );
  105. }
  106. function extractStyles(source, opts) {
  107. const styles = [];
  108. const resolveSyntax = buildSyntaxResolver(opts.config);
  109. const standard =
  110. opts.from &&
  111. /\.(?:\w*html?|xht|xslt?|jsp|aspx?|ejs|php\d*|twig|liquid|m(?:ark)?d(?:ow)?n|mk?d)$/i.test(
  112. opts.from
  113. );
  114. const svelte = opts.from && /\.svelte$/i.test(opts.from);
  115. const astro = opts.from && /\.astro$/i.test(opts.from);
  116. function onStyleTag(style) {
  117. if (
  118. !(style.inHtml || style.inXsls || style.inXslt || standard) &&
  119. (style.attribute.src || style.attribute.href) &&
  120. !style.content.trim()
  121. ) {
  122. return;
  123. }
  124. style.lang = getLang(style.attribute);
  125. style.syntax = resolveSyntax(style.lang);
  126. if (style.syntax) styles.push(style);
  127. }
  128. function onStyleAttribute(style) {
  129. if (/\{[\s\S]*?\}/.test(style.content)) {
  130. style.syntax = buildTemplateSyntax(
  131. resolveSyntax("css", {
  132. defaultSyntax: svelte ? cssSafeSyntax : undefined,
  133. })
  134. );
  135. style.lang = "custom-template";
  136. } else {
  137. style.syntax = resolveSyntax();
  138. style.lang = "css";
  139. }
  140. styles.push(style);
  141. }
  142. iterateCode(source, {
  143. onStyleTag,
  144. onStyleAttribute,
  145. svelte,
  146. astro,
  147. });
  148. return styles;
  149. }
  150. module.exports = extractStyles;