CssLocalIdentifierDependency.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Ivan Kopeykin @vankop
  4. */
  5. "use strict";
  6. const { cssExportConvention } = require("../util/conventions");
  7. const createHash = require("../util/createHash");
  8. const { makePathsRelative } = require("../util/identifier");
  9. const makeSerializable = require("../util/makeSerializable");
  10. const NullDependency = require("./NullDependency");
  11. /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
  12. /** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */
  13. /** @typedef {import("../../declarations/WebpackOptions").CssGeneratorLocalIdentName} CssGeneratorLocalIdentName */
  14. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  15. /** @typedef {import("../CssModule")} CssModule */
  16. /** @typedef {import("../Dependency")} Dependency */
  17. /** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
  18. /** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
  19. /** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
  20. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  21. /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
  22. /** @typedef {import("../css/CssExportsGenerator")} CssExportsGenerator */
  23. /** @typedef {import("../css/CssGenerator")} CssGenerator */
  24. /** @typedef {import("../css/CssParser").Range} Range */
  25. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
  26. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
  27. /** @typedef {import("../util/Hash")} Hash */
  28. /**
  29. * @param {string} local css local
  30. * @param {CssModule} module module
  31. * @param {ChunkGraph} chunkGraph chunk graph
  32. * @param {RuntimeTemplate} runtimeTemplate runtime template
  33. * @returns {string} local ident
  34. */
  35. const getLocalIdent = (local, module, chunkGraph, runtimeTemplate) => {
  36. const localIdentName =
  37. /** @type {CssGenerator | CssExportsGenerator} */
  38. (module.generator).localIdentName;
  39. const relativeResourcePath = makePathsRelative(
  40. /** @type {string} */ (module.context),
  41. module.resourceResolveData.path
  42. );
  43. const { hashFunction, hashDigest, hashDigestLength, hashSalt, uniqueName } =
  44. runtimeTemplate.outputOptions;
  45. const hash = createHash(hashFunction);
  46. if (hashSalt) {
  47. hash.update(hashSalt);
  48. }
  49. hash.update(relativeResourcePath);
  50. if (!/\[local\]/.test(localIdentName)) {
  51. hash.update(local);
  52. }
  53. const localIdentHash = /** @type {string} */ (hash.digest(hashDigest))
  54. // Remove all leading digits
  55. .replace(/^\d+/, "")
  56. // Replace all slashes with underscores (same as in base64url)
  57. .replace(/\//g, "_")
  58. // Remove everything that is not an alphanumeric or underscore
  59. .replace(/[^A-Za-z0-9_]+/g, "_")
  60. .slice(0, hashDigestLength);
  61. return runtimeTemplate.compilation
  62. .getPath(localIdentName, {
  63. filename: relativeResourcePath,
  64. hash: localIdentHash,
  65. contentHash: localIdentHash,
  66. chunkGraph,
  67. module
  68. })
  69. .replace(/\[local\]/g, local)
  70. .replace(/\[uniqueName\]/g, uniqueName);
  71. };
  72. class CssLocalIdentifierDependency extends NullDependency {
  73. /**
  74. * @param {string} name name
  75. * @param {Range} range range
  76. * @param {string=} prefix prefix
  77. */
  78. constructor(name, range, prefix = "") {
  79. super();
  80. this.name = name;
  81. this.range = range;
  82. this.prefix = prefix;
  83. }
  84. get type() {
  85. return "css local identifier";
  86. }
  87. /**
  88. * @param {string} name export name
  89. * @param {CssGeneratorExportsConvention} convention convention of the export name
  90. * @returns {string[]} convention results
  91. */
  92. getExportsConventionNames(name, convention) {
  93. if (this._conventionNames) {
  94. return this._conventionNames;
  95. }
  96. this._conventionNames = cssExportConvention(this.name, convention);
  97. return this._conventionNames;
  98. }
  99. /**
  100. * Returns the exported names
  101. * @param {ModuleGraph} moduleGraph module graph
  102. * @returns {ExportsSpec | undefined} export names
  103. */
  104. getExports(moduleGraph) {
  105. const module = /** @type {CssModule} */ (moduleGraph.getParentModule(this));
  106. const convention = /** @type {CssGenerator | CssExportsGenerator} */ (
  107. module.generator
  108. ).convention;
  109. const names = this.getExportsConventionNames(this.name, convention);
  110. return {
  111. exports: names.map(name => ({
  112. name,
  113. canMangle: true
  114. })),
  115. dependencies: undefined
  116. };
  117. }
  118. /**
  119. * Update the hash
  120. * @param {Hash} hash hash to be updated
  121. * @param {UpdateHashContext} context context
  122. * @returns {void}
  123. */
  124. updateHash(hash, { chunkGraph }) {
  125. const module = /** @type {CssModule} */ (
  126. chunkGraph.moduleGraph.getParentModule(this)
  127. );
  128. const generator = /** @type {CssGenerator | CssExportsGenerator} */ (
  129. module.generator
  130. );
  131. const names = this.getExportsConventionNames(
  132. this.name,
  133. generator.convention
  134. );
  135. hash.update(`exportsConvention`);
  136. hash.update(JSON.stringify(names));
  137. hash.update(`localIdentName`);
  138. hash.update(generator.localIdentName);
  139. }
  140. /**
  141. * @param {ObjectSerializerContext} context context
  142. */
  143. serialize(context) {
  144. const { write } = context;
  145. write(this.name);
  146. write(this.range);
  147. write(this.prefix);
  148. super.serialize(context);
  149. }
  150. /**
  151. * @param {ObjectDeserializerContext} context context
  152. */
  153. deserialize(context) {
  154. const { read } = context;
  155. this.name = read();
  156. this.range = read();
  157. this.prefix = read();
  158. super.deserialize(context);
  159. }
  160. }
  161. /**
  162. * @param {string} str string
  163. * @param {string | boolean} omitUnderscore true if you need to omit underscore
  164. * @returns {string} escaped css identifier
  165. */
  166. const escapeCssIdentifier = (str, omitUnderscore) => {
  167. const escaped = `${str}`.replace(
  168. // cspell:word uffff
  169. /[^a-zA-Z0-9_\u0081-\uffff-]/g,
  170. s => `\\${s}`
  171. );
  172. return !omitUnderscore && /^(?!--)[0-9-]/.test(escaped)
  173. ? `_${escaped}`
  174. : escaped;
  175. };
  176. CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTemplate extends (
  177. NullDependency.Template
  178. ) {
  179. /**
  180. * @param {Dependency} dependency the dependency for which the template should be applied
  181. * @param {ReplaceSource} source the current replace source which can be modified
  182. * @param {DependencyTemplateContext} templateContext the context object
  183. * @returns {void}
  184. */
  185. apply(
  186. dependency,
  187. source,
  188. {
  189. module: m,
  190. moduleGraph,
  191. chunkGraph,
  192. runtime,
  193. runtimeTemplate,
  194. cssExportsData
  195. }
  196. ) {
  197. const dep = /** @type {CssLocalIdentifierDependency} */ (dependency);
  198. const module = /** @type {CssModule} */ (m);
  199. const convention = /** @type {CssGenerator | CssExportsGenerator} */ (
  200. module.generator
  201. ).convention;
  202. const names = dep.getExportsConventionNames(dep.name, convention);
  203. const usedNames = /** @type {string[]} */ (
  204. names
  205. .map(name =>
  206. moduleGraph.getExportInfo(module, name).getUsedName(name, runtime)
  207. )
  208. .filter(Boolean)
  209. );
  210. if (usedNames.length === 0) return;
  211. // use the first usedName to generate localIdent, it's shorter when mangle exports enabled
  212. const localIdent =
  213. dep.prefix +
  214. getLocalIdent(usedNames[0], module, chunkGraph, runtimeTemplate);
  215. source.replace(
  216. dep.range[0],
  217. dep.range[1] - 1,
  218. escapeCssIdentifier(localIdent, dep.prefix)
  219. );
  220. for (const used of usedNames) {
  221. cssExportsData.exports.set(used, localIdent);
  222. }
  223. }
  224. };
  225. makeSerializable(
  226. CssLocalIdentifierDependency,
  227. "webpack/lib/dependencies/CssLocalIdentifierDependency"
  228. );
  229. module.exports = CssLocalIdentifierDependency;