extract-styles.js 3.6 KB

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