index.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _webpack = _interopRequireDefault(require("webpack"));
  7. var _webpackSources = _interopRequireDefault(require("webpack-sources"));
  8. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  9. /* eslint-disable class-methods-use-this */
  10. const {
  11. ConcatSource,
  12. SourceMapSource,
  13. OriginalSource
  14. } = _webpackSources.default;
  15. const {
  16. Template,
  17. util: {
  18. createHash
  19. }
  20. } = _webpack.default;
  21. const MODULE_TYPE = 'css/mini-extract';
  22. const pluginName = 'mini-css-extract-plugin';
  23. const REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/i;
  24. const REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/i;
  25. const REGEXP_NAME = /\[name\]/i;
  26. const REGEXP_PLACEHOLDERS = /\[(name|id|chunkhash)\]/g;
  27. const DEFAULT_FILENAME = '[name].css';
  28. class CssDependency extends _webpack.default.Dependency {
  29. constructor({
  30. identifier,
  31. content,
  32. media,
  33. sourceMap
  34. }, context, identifierIndex) {
  35. super();
  36. this.identifier = identifier;
  37. this.identifierIndex = identifierIndex;
  38. this.content = content;
  39. this.media = media;
  40. this.sourceMap = sourceMap;
  41. this.context = context;
  42. }
  43. getResourceIdentifier() {
  44. return `css-module-${this.identifier}-${this.identifierIndex}`;
  45. }
  46. }
  47. class CssDependencyTemplate {
  48. apply() {}
  49. }
  50. class CssModule extends _webpack.default.Module {
  51. constructor(dependency) {
  52. super(MODULE_TYPE, dependency.context);
  53. this.id = '';
  54. this._identifier = dependency.identifier;
  55. this._identifierIndex = dependency.identifierIndex;
  56. this.content = dependency.content;
  57. this.media = dependency.media;
  58. this.sourceMap = dependency.sourceMap;
  59. } // no source() so webpack doesn't do add stuff to the bundle
  60. size() {
  61. return this.content.length;
  62. }
  63. identifier() {
  64. return `css ${this._identifier} ${this._identifierIndex}`;
  65. }
  66. readableIdentifier(requestShortener) {
  67. return `css ${requestShortener.shorten(this._identifier)}${this._identifierIndex ? ` (${this._identifierIndex})` : ''}`;
  68. }
  69. nameForCondition() {
  70. const resource = this._identifier.split('!').pop();
  71. const idx = resource.indexOf('?');
  72. if (idx >= 0) {
  73. return resource.substring(0, idx);
  74. }
  75. return resource;
  76. }
  77. updateCacheModule(module) {
  78. this.content = module.content;
  79. this.media = module.media;
  80. this.sourceMap = module.sourceMap;
  81. }
  82. needRebuild() {
  83. return true;
  84. }
  85. build(options, compilation, resolver, fileSystem, callback) {
  86. this.buildInfo = {};
  87. this.buildMeta = {};
  88. callback();
  89. }
  90. updateHash(hash) {
  91. super.updateHash(hash);
  92. hash.update(this.content);
  93. hash.update(this.media || '');
  94. hash.update(this.sourceMap ? JSON.stringify(this.sourceMap) : '');
  95. }
  96. }
  97. class CssModuleFactory {
  98. create({
  99. dependencies: [dependency]
  100. }, callback) {
  101. callback(null, new CssModule(dependency));
  102. }
  103. }
  104. class MiniCssExtractPlugin {
  105. constructor(options = {}) {
  106. this.options = Object.assign({
  107. filename: DEFAULT_FILENAME,
  108. moduleFilename: () => options.filename || DEFAULT_FILENAME,
  109. ignoreOrder: false
  110. }, options);
  111. if (!this.options.chunkFilename) {
  112. const {
  113. filename
  114. } = this.options; // Anything changing depending on chunk is fine
  115. if (filename.match(REGEXP_PLACEHOLDERS)) {
  116. this.options.chunkFilename = filename;
  117. } else {
  118. // Elsewise prefix '[id].' in front of the basename to make it changing
  119. this.options.chunkFilename = filename.replace(/(^|\/)([^/]*(?:\?|$))/, '$1[id].$2');
  120. }
  121. }
  122. }
  123. apply(compiler) {
  124. compiler.hooks.thisCompilation.tap(pluginName, compilation => {
  125. compilation.hooks.normalModuleLoader.tap(pluginName, (lc, m) => {
  126. const loaderContext = lc;
  127. const module = m;
  128. loaderContext[MODULE_TYPE] = content => {
  129. if (!Array.isArray(content) && content != null) {
  130. throw new Error(`Exported value was not extracted as an array: ${JSON.stringify(content)}`);
  131. }
  132. const identifierCountMap = new Map();
  133. for (const line of content) {
  134. const count = identifierCountMap.get(line.identifier) || 0;
  135. module.addDependency(new CssDependency(line, m.context, count));
  136. identifierCountMap.set(line.identifier, count + 1);
  137. }
  138. };
  139. });
  140. compilation.dependencyFactories.set(CssDependency, new CssModuleFactory());
  141. compilation.dependencyTemplates.set(CssDependency, new CssDependencyTemplate());
  142. compilation.mainTemplate.hooks.renderManifest.tap(pluginName, (result, {
  143. chunk
  144. }) => {
  145. const renderedModules = Array.from(chunk.modulesIterable).filter(module => module.type === MODULE_TYPE);
  146. if (renderedModules.length > 0) {
  147. result.push({
  148. render: () => this.renderContentAsset(compilation, chunk, renderedModules, compilation.runtimeTemplate.requestShortener),
  149. filenameTemplate: ({
  150. chunk: chunkData
  151. }) => this.options.moduleFilename(chunkData),
  152. pathOptions: {
  153. chunk,
  154. contentHashType: MODULE_TYPE
  155. },
  156. identifier: `${pluginName}.${chunk.id}`,
  157. hash: chunk.contentHash[MODULE_TYPE]
  158. });
  159. }
  160. });
  161. compilation.chunkTemplate.hooks.renderManifest.tap(pluginName, (result, {
  162. chunk
  163. }) => {
  164. const renderedModules = Array.from(chunk.modulesIterable).filter(module => module.type === MODULE_TYPE);
  165. if (renderedModules.length > 0) {
  166. result.push({
  167. render: () => this.renderContentAsset(compilation, chunk, renderedModules, compilation.runtimeTemplate.requestShortener),
  168. filenameTemplate: this.options.chunkFilename,
  169. pathOptions: {
  170. chunk,
  171. contentHashType: MODULE_TYPE
  172. },
  173. identifier: `${pluginName}.${chunk.id}`,
  174. hash: chunk.contentHash[MODULE_TYPE]
  175. });
  176. }
  177. });
  178. compilation.mainTemplate.hooks.hashForChunk.tap(pluginName, (hash, chunk) => {
  179. const {
  180. chunkFilename
  181. } = this.options;
  182. if (REGEXP_CHUNKHASH.test(chunkFilename)) {
  183. hash.update(JSON.stringify(chunk.getChunkMaps(true).hash));
  184. }
  185. if (REGEXP_CONTENTHASH.test(chunkFilename)) {
  186. hash.update(JSON.stringify(chunk.getChunkMaps(true).contentHash[MODULE_TYPE] || {}));
  187. }
  188. if (REGEXP_NAME.test(chunkFilename)) {
  189. hash.update(JSON.stringify(chunk.getChunkMaps(true).name));
  190. }
  191. });
  192. compilation.hooks.contentHash.tap(pluginName, chunk => {
  193. const {
  194. outputOptions
  195. } = compilation;
  196. const {
  197. hashFunction,
  198. hashDigest,
  199. hashDigestLength
  200. } = outputOptions;
  201. const hash = createHash(hashFunction);
  202. for (const m of chunk.modulesIterable) {
  203. if (m.type === MODULE_TYPE) {
  204. m.updateHash(hash);
  205. }
  206. }
  207. const {
  208. contentHash
  209. } = chunk;
  210. contentHash[MODULE_TYPE] = hash.digest(hashDigest).substring(0, hashDigestLength);
  211. });
  212. const {
  213. mainTemplate
  214. } = compilation;
  215. mainTemplate.hooks.localVars.tap(pluginName, (source, chunk) => {
  216. const chunkMap = this.getCssChunkObject(chunk);
  217. if (Object.keys(chunkMap).length > 0) {
  218. return Template.asString([source, '', '// object to store loaded CSS chunks', 'var installedCssChunks = {', Template.indent(chunk.ids.map(id => `${JSON.stringify(id)}: 0`).join(',\n')), '}']);
  219. }
  220. return source;
  221. });
  222. mainTemplate.hooks.requireEnsure.tap(pluginName, (source, chunk, hash) => {
  223. const chunkMap = this.getCssChunkObject(chunk);
  224. if (Object.keys(chunkMap).length > 0) {
  225. const chunkMaps = chunk.getChunkMaps();
  226. const {
  227. crossOriginLoading
  228. } = mainTemplate.outputOptions;
  229. const linkHrefPath = mainTemplate.getAssetPath(JSON.stringify(this.options.chunkFilename), {
  230. hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
  231. hashWithLength: length => `" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,
  232. chunk: {
  233. id: '" + chunkId + "',
  234. hash: `" + ${JSON.stringify(chunkMaps.hash)}[chunkId] + "`,
  235. hashWithLength(length) {
  236. const shortChunkHashMap = Object.create(null);
  237. for (const chunkId of Object.keys(chunkMaps.hash)) {
  238. if (typeof chunkMaps.hash[chunkId] === 'string') {
  239. shortChunkHashMap[chunkId] = chunkMaps.hash[chunkId].substring(0, length);
  240. }
  241. }
  242. return `" + ${JSON.stringify(shortChunkHashMap)}[chunkId] + "`;
  243. },
  244. contentHash: {
  245. [MODULE_TYPE]: `" + ${JSON.stringify(chunkMaps.contentHash[MODULE_TYPE])}[chunkId] + "`
  246. },
  247. contentHashWithLength: {
  248. [MODULE_TYPE]: length => {
  249. const shortContentHashMap = {};
  250. const contentHash = chunkMaps.contentHash[MODULE_TYPE];
  251. for (const chunkId of Object.keys(contentHash)) {
  252. if (typeof contentHash[chunkId] === 'string') {
  253. shortContentHashMap[chunkId] = contentHash[chunkId].substring(0, length);
  254. }
  255. }
  256. return `" + ${JSON.stringify(shortContentHashMap)}[chunkId] + "`;
  257. }
  258. },
  259. name: `" + (${JSON.stringify(chunkMaps.name)}[chunkId]||chunkId) + "`
  260. },
  261. contentHashType: MODULE_TYPE
  262. });
  263. return Template.asString([source, '', `// ${pluginName} CSS loading`, `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', Template.indent([`var href = ${linkHrefPath};`, `var fullhref = ${mainTemplate.requireFn}.p + href;`, 'var existingLinkTags = document.getElementsByTagName("link");', 'for(var i = 0; i < existingLinkTags.length; i++) {', Template.indent(['var tag = existingLinkTags[i];', 'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");', 'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();']), '}', 'var existingStyleTags = document.getElementsByTagName("style");', 'for(var i = 0; i < existingStyleTags.length; i++) {', Template.indent(['var tag = existingStyleTags[i];', 'var dataHref = tag.getAttribute("data-href");', 'if(dataHref === href || dataHref === fullhref) return resolve();']), '}', 'var linkTag = document.createElement("link");', 'linkTag.rel = "stylesheet";', 'linkTag.type = "text/css";', 'linkTag.onload = resolve;', 'linkTag.onerror = function(event) {', Template.indent(['var request = event && event.target && event.target.src || fullhref;', 'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");', 'err.code = "CSS_CHUNK_LOAD_FAILED";', 'err.request = request;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['installedCssChunks[chunkId] = 0;']), '}));']), '}']);
  264. }
  265. return source;
  266. });
  267. });
  268. }
  269. getCssChunkObject(mainChunk) {
  270. const obj = {};
  271. for (const chunk of mainChunk.getAllAsyncChunks()) {
  272. for (const module of chunk.modulesIterable) {
  273. if (module.type === MODULE_TYPE) {
  274. obj[chunk.id] = 1;
  275. break;
  276. }
  277. }
  278. }
  279. return obj;
  280. }
  281. renderContentAsset(compilation, chunk, modules, requestShortener) {
  282. let usedModules;
  283. const [chunkGroup] = chunk.groupsIterable;
  284. if (typeof chunkGroup.getModuleIndex2 === 'function') {
  285. // Store dependencies for modules
  286. const moduleDependencies = new Map(modules.map(m => [m, new Set()])); // Get ordered list of modules per chunk group
  287. // This loop also gathers dependencies from the ordered lists
  288. // Lists are in reverse order to allow to use Array.pop()
  289. const modulesByChunkGroup = Array.from(chunk.groupsIterable, cg => {
  290. const sortedModules = modules.map(m => {
  291. return {
  292. module: m,
  293. index: cg.getModuleIndex2(m)
  294. };
  295. }) // eslint-disable-next-line no-undefined
  296. .filter(item => item.index !== undefined).sort((a, b) => b.index - a.index).map(item => item.module);
  297. for (let i = 0; i < sortedModules.length; i++) {
  298. const set = moduleDependencies.get(sortedModules[i]);
  299. for (let j = i + 1; j < sortedModules.length; j++) {
  300. set.add(sortedModules[j]);
  301. }
  302. }
  303. return sortedModules;
  304. }); // set with already included modules in correct order
  305. usedModules = new Set();
  306. const unusedModulesFilter = m => !usedModules.has(m);
  307. while (usedModules.size < modules.length) {
  308. let success = false;
  309. let bestMatch;
  310. let bestMatchDeps; // get first module where dependencies are fulfilled
  311. for (const list of modulesByChunkGroup) {
  312. // skip and remove already added modules
  313. while (list.length > 0 && usedModules.has(list[list.length - 1])) {
  314. list.pop();
  315. } // skip empty lists
  316. if (list.length !== 0) {
  317. const module = list[list.length - 1];
  318. const deps = moduleDependencies.get(module); // determine dependencies that are not yet included
  319. const failedDeps = Array.from(deps).filter(unusedModulesFilter); // store best match for fallback behavior
  320. if (!bestMatchDeps || bestMatchDeps.length > failedDeps.length) {
  321. bestMatch = list;
  322. bestMatchDeps = failedDeps;
  323. }
  324. if (failedDeps.length === 0) {
  325. // use this module and remove it from list
  326. usedModules.add(list.pop());
  327. success = true;
  328. break;
  329. }
  330. }
  331. }
  332. if (!success) {
  333. // no module found => there is a conflict
  334. // use list with fewest failed deps
  335. // and emit a warning
  336. const fallbackModule = bestMatch.pop();
  337. if (!this.options.ignoreOrder) {
  338. compilation.warnings.push(new Error(`chunk ${chunk.name || chunk.id} [${pluginName}]\n` + 'Conflicting order between:\n' + ` * ${fallbackModule.readableIdentifier(requestShortener)}\n` + `${bestMatchDeps.map(m => ` * ${m.readableIdentifier(requestShortener)}`).join('\n')}`));
  339. }
  340. usedModules.add(fallbackModule);
  341. }
  342. }
  343. } else {
  344. // fallback for older webpack versions
  345. // (to avoid a breaking change)
  346. // TODO remove this in next mayor version
  347. // and increase minimum webpack version to 4.12.0
  348. modules.sort((a, b) => a.index2 - b.index2);
  349. usedModules = modules;
  350. }
  351. const source = new ConcatSource();
  352. const externalsSource = new ConcatSource();
  353. for (const m of usedModules) {
  354. if (/^@import url/.test(m.content)) {
  355. // HACK for IE
  356. // http://stackoverflow.com/a/14676665/1458162
  357. let {
  358. content
  359. } = m;
  360. if (m.media) {
  361. // insert media into the @import
  362. // this is rar
  363. // TODO improve this and parse the CSS to support multiple medias
  364. content = content.replace(/;|\s*$/, m.media);
  365. }
  366. externalsSource.add(content);
  367. externalsSource.add('\n');
  368. } else {
  369. if (m.media) {
  370. source.add(`@media ${m.media} {\n`);
  371. }
  372. if (m.sourceMap) {
  373. source.add(new SourceMapSource(m.content, m.readableIdentifier(requestShortener), m.sourceMap));
  374. } else {
  375. source.add(new OriginalSource(m.content, m.readableIdentifier(requestShortener)));
  376. }
  377. source.add('\n');
  378. if (m.media) {
  379. source.add('}\n');
  380. }
  381. }
  382. }
  383. return new ConcatSource(externalsSource, source);
  384. }
  385. }
  386. MiniCssExtractPlugin.loader = require.resolve('./loader');
  387. var _default = MiniCssExtractPlugin;
  388. exports.default = _default;