CssUrlDependency.js 5.3 KB

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