CssUrlDependency.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Ivan Kopeykin @vankop
  4. */
  5. "use strict";
  6. const RawDataUrlModule = require("../asset/RawDataUrlModule");
  7. const makeSerializable = require("../util/makeSerializable");
  8. const memoize = require("../util/memoize");
  9. const ModuleDependency = require("./ModuleDependency");
  10. /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
  11. /** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
  12. /** @typedef {import("../Dependency")} Dependency */
  13. /** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
  14. /** @typedef {import("../Module")} Module */
  15. /** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
  16. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  17. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
  18. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
  19. /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
  20. const getIgnoredRawDataUrlModule = memoize(
  21. () => new RawDataUrlModule("data:,", "ignored-asset", "(ignored asset)")
  22. );
  23. class CssUrlDependency extends ModuleDependency {
  24. /**
  25. * @param {string} request request
  26. * @param {Range} range range of the argument
  27. * @param {"string" | "url" | "src"} urlType dependency type e.g. url() or string
  28. */
  29. constructor(request, range, urlType) {
  30. super(request);
  31. this.range = range;
  32. this.urlType = urlType;
  33. }
  34. get type() {
  35. return "css url()";
  36. }
  37. get category() {
  38. return "url";
  39. }
  40. /**
  41. * @param {string} context context directory
  42. * @returns {Module} ignored module
  43. */
  44. createIgnoredModule(context) {
  45. return getIgnoredRawDataUrlModule();
  46. }
  47. /**
  48. * @param {ObjectSerializerContext} context context
  49. */
  50. serialize(context) {
  51. const { write } = context;
  52. write(this.urlType);
  53. super.serialize(context);
  54. }
  55. /**
  56. * @param {ObjectDeserializerContext} context context
  57. */
  58. deserialize(context) {
  59. const { read } = context;
  60. this.urlType = read();
  61. super.deserialize(context);
  62. }
  63. }
  64. /**
  65. * @param {string} str string
  66. * @returns {string} string in quotes if needed
  67. */
  68. const cssEscapeString = (str) => {
  69. let countWhiteOrBracket = 0;
  70. let countQuotation = 0;
  71. let countApostrophe = 0;
  72. for (let i = 0; i < str.length; i++) {
  73. const cc = str.charCodeAt(i);
  74. switch (cc) {
  75. case 9: // tab
  76. case 10: // nl
  77. case 32: // space
  78. case 40: // (
  79. case 41: // )
  80. countWhiteOrBracket++;
  81. break;
  82. case 34:
  83. countQuotation++;
  84. break;
  85. case 39:
  86. countApostrophe++;
  87. break;
  88. }
  89. }
  90. if (countWhiteOrBracket < 2) {
  91. return str.replace(/[\n\t ()'"\\]/g, (m) => `\\${m}`);
  92. } else if (countQuotation <= countApostrophe) {
  93. return `"${str.replace(/[\n"\\]/g, (m) => `\\${m}`)}"`;
  94. }
  95. return `'${str.replace(/[\n'\\]/g, (m) => `\\${m}`)}'`;
  96. };
  97. CssUrlDependency.Template = class CssUrlDependencyTemplate extends (
  98. ModuleDependency.Template
  99. ) {
  100. /**
  101. * @param {Dependency} dependency the dependency for which the template should be applied
  102. * @param {ReplaceSource} source the current replace source which can be modified
  103. * @param {DependencyTemplateContext} templateContext the context object
  104. * @returns {void}
  105. */
  106. apply(
  107. dependency,
  108. source,
  109. { type, moduleGraph, runtimeTemplate, codeGenerationResults }
  110. ) {
  111. if (type === "javascript") return;
  112. const dep = /** @type {CssUrlDependency} */ (dependency);
  113. const module = /** @type {Module} */ (moduleGraph.getModule(dep));
  114. /** @type {string | undefined} */
  115. let newValue;
  116. switch (dep.urlType) {
  117. case "string":
  118. newValue = cssEscapeString(
  119. this.assetUrl({
  120. module,
  121. codeGenerationResults
  122. })
  123. );
  124. break;
  125. case "url":
  126. newValue = `url(${cssEscapeString(
  127. this.assetUrl({
  128. module,
  129. codeGenerationResults
  130. })
  131. )})`;
  132. break;
  133. case "src":
  134. newValue = `src(${cssEscapeString(
  135. this.assetUrl({
  136. module,
  137. codeGenerationResults
  138. })
  139. )})`;
  140. break;
  141. }
  142. source.replace(
  143. dep.range[0],
  144. dep.range[1] - 1,
  145. /** @type {string} */ (newValue)
  146. );
  147. }
  148. /**
  149. * @param {object} options options object
  150. * @param {Module} options.module the module
  151. * @param {RuntimeSpec=} options.runtime runtime
  152. * @param {CodeGenerationResults} options.codeGenerationResults the code generation results
  153. * @returns {string} the url of the asset
  154. */
  155. assetUrl({ runtime, module, codeGenerationResults }) {
  156. if (!module) {
  157. return "data:,";
  158. }
  159. const codeGen = codeGenerationResults.get(module, runtime);
  160. const data =
  161. /** @type {NonNullable<CodeGenerationResult["data"]>} */
  162. (codeGen.data);
  163. if (!data) return "data:,";
  164. const url = data.get("url");
  165. if (!url || !url["css-url"]) return "data:,";
  166. return url["css-url"];
  167. }
  168. };
  169. makeSerializable(CssUrlDependency, "webpack/lib/dependencies/CssUrlDependency");
  170. CssUrlDependency.PUBLIC_PATH_AUTO = "__WEBPACK_CSS_PUBLIC_PATH_AUTO__";
  171. module.exports = CssUrlDependency;