You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

337 lines
12 KiB

4 years ago
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. var _fs = require('fs');
  6. var _fs2 = _interopRequireDefault(_fs);
  7. var _path = require('path');
  8. var _path2 = _interopRequireDefault(_path);
  9. var _Chunk = require('webpack/lib/Chunk');
  10. var _Chunk2 = _interopRequireDefault(_Chunk);
  11. var _webpackSources = require('webpack-sources');
  12. var _async = require('async');
  13. var _async2 = _interopRequireDefault(_async);
  14. var _loaderUtils = require('loader-utils');
  15. var _loaderUtils2 = _interopRequireDefault(_loaderUtils);
  16. var _schemaUtils = require('schema-utils');
  17. var _schemaUtils2 = _interopRequireDefault(_schemaUtils);
  18. var _ExtractTextPluginCompilation = require('./lib/ExtractTextPluginCompilation');
  19. var _ExtractTextPluginCompilation2 = _interopRequireDefault(_ExtractTextPluginCompilation);
  20. var _OrderUndefinedError = require('./lib/OrderUndefinedError');
  21. var _OrderUndefinedError2 = _interopRequireDefault(_OrderUndefinedError);
  22. var _helpers = require('./lib/helpers');
  23. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  24. /* eslint-disable
  25. consistent-return,
  26. no-param-reassign,
  27. prefer-rest-params
  28. */
  29. const NS = _path2.default.dirname(_fs2.default.realpathSync(__filename));
  30. const plugin = { name: 'ExtractTextPlugin' };
  31. let nextId = 0;
  32. class ExtractTextPlugin {
  33. constructor(options) {
  34. if ((0, _helpers.isString)(options)) {
  35. options = { filename: options };
  36. } else {
  37. (0, _schemaUtils2.default)(_path2.default.resolve(__dirname, './plugin.json'), options, 'Extract Text Plugin');
  38. }
  39. this.filename = options.filename;
  40. this.id = options.id != null ? options.id : nextId += 1;
  41. this.options = {};
  42. (0, _helpers.mergeOptions)(this.options, options);
  43. delete this.options.filename;
  44. delete this.options.id;
  45. }
  46. static loader(options) {
  47. return { loader: require.resolve('./loader'), options };
  48. }
  49. static applyAdditionalInformation(source, info) {
  50. if (info) {
  51. return new _webpackSources.ConcatSource(`@media ${info[0]} {`, source, '}');
  52. }
  53. return source;
  54. }
  55. loader(options) {
  56. return ExtractTextPlugin.loader((0, _helpers.mergeOptions)({ id: this.id }, options));
  57. }
  58. mergeNonInitialChunks(chunk, intoChunk, checkedChunks) {
  59. if (!intoChunk) {
  60. const newCheckedChunks = [];
  61. for (const asyncChunk of chunk.getAllAsyncChunks()) {
  62. if (!asyncChunk.isOnlyInitial()) {
  63. this.mergeNonInitialChunks(asyncChunk, chunk, newCheckedChunks);
  64. }
  65. }
  66. } else if (!checkedChunks.includes(chunk)) {
  67. const newCheckedChunks = checkedChunks.concat(chunk);
  68. for (const chunkModule of chunk.modulesIterable) {
  69. intoChunk.addModule(chunkModule);
  70. chunkModule.addChunk(intoChunk);
  71. }
  72. for (const asyncChunk of chunk.getAllAsyncChunks()) {
  73. if (!asyncChunk.isOnlyInitial()) {
  74. this.mergeNonInitialChunks(asyncChunk, intoChunk, newCheckedChunks);
  75. }
  76. }
  77. }
  78. }
  79. static renderExtractedChunk(compilation, chunk) {
  80. const source = new _webpackSources.ConcatSource();
  81. for (const chunkModule of chunk.modulesIterable) {
  82. let moduleSource = chunkModule.source(compilation.dependencyTemplates, compilation.runtimeTemplate);
  83. // This module was concatenated by the ModuleConcatenationPlugin; because the pitching loader
  84. // only produces commonjs results, at least for now things we want to extract can't be in them.
  85. // NOTE: if ESM support is added, _this workaround will break_.
  86. if (moduleSource instanceof _webpackSources.ConcatSource) {
  87. moduleSource = null;
  88. }
  89. // Async imports (require.ensure(), import().then) are CachedSource module
  90. // instances caching a ReplaceSource instance, which breaks the plugin
  91. // because their .source() is the cached "// removed by ..." text.
  92. // The issue lies elsewhere, this is just a temporary fix that
  93. // creates a new RawSource with the extracted text. If it's
  94. // a CachedSource instance but there's no extracted text
  95. // it's "__webpack_require__();" statements. Skip it.
  96. if (moduleSource instanceof _webpackSources.CachedSource) {
  97. if (chunkModule[NS] && chunkModule[NS].content) {
  98. moduleSource = new _webpackSources.ConcatSource();
  99. if (chunkModule[NS].content.length > 1) {
  100. console.error(chunkModule[NS].content);
  101. }
  102. for (const content of chunkModule[NS].content) {
  103. moduleSource.add(new _webpackSources.RawSource(content[1]));
  104. }
  105. } else {
  106. moduleSource = null;
  107. }
  108. }
  109. if (moduleSource) {
  110. source.add(ExtractTextPlugin.applyAdditionalInformation(moduleSource, chunkModule.additionalInformation));
  111. }
  112. }
  113. return source;
  114. }
  115. extract(options) {
  116. if (Array.isArray(options) || (0, _helpers.isString)(options) || typeof options.options === 'object' || typeof options.query === 'object') {
  117. options = { use: options };
  118. } else {
  119. (0, _schemaUtils2.default)(_path2.default.resolve(__dirname, './loader.json'), options, 'Extract Text Plugin (Loader)');
  120. }
  121. let loader = options.use;
  122. let before = options.fallback || [];
  123. if ((0, _helpers.isString)(loader)) {
  124. loader = loader.split('!');
  125. }
  126. if ((0, _helpers.isString)(before)) {
  127. before = before.split('!');
  128. } else if (!Array.isArray(before)) {
  129. before = [before];
  130. }
  131. options = (0, _helpers.mergeOptions)({ omit: before.length, remove: true }, options);
  132. delete options.use;
  133. delete options.fallback;
  134. return [this.loader(options)].concat(before, loader).map(_helpers.getLoaderObject);
  135. }
  136. apply(compiler) {
  137. const { options, filename, id } = this;
  138. compiler.hooks.thisCompilation.tap(plugin, compilation => {
  139. const extractCompilation = new _ExtractTextPluginCompilation2.default();
  140. compilation.hooks.normalModuleLoader.tap(plugin, (loaderContext, module) => {
  141. loaderContext[NS] = (content, opt) => {
  142. if (options.disable) {
  143. return false;
  144. }
  145. if (!Array.isArray(content) && content != null) {
  146. throw new Error(`Exported value was not extracted as an array: ${JSON.stringify(content)}`);
  147. }
  148. module[NS] = {
  149. content,
  150. options: opt || {}
  151. };
  152. return options.allChunks || module[`${NS}/extract`]; // eslint-disable-line no-path-concat
  153. };
  154. });
  155. let extractedChunks;
  156. compilation.hooks.optimizeTree.tapAsync(plugin, (chunks, modules, callback) => {
  157. extractedChunks = chunks.map(() => new _Chunk2.default());
  158. chunks.forEach((chunk, i) => {
  159. const extractedChunk = extractedChunks[i];
  160. extractedChunk.index = i;
  161. extractedChunk.originalChunk = chunk;
  162. extractedChunk.name = chunk.name;
  163. // extractedChunk.entryModule = chunk.entryModule;
  164. for (const chunkGroup of chunk.groupsIterable) {
  165. extractedChunk.addGroup(chunkGroup);
  166. }
  167. });
  168. _async2.default.forEach(chunks, (chunk, chunkCallback) => {
  169. // eslint-disable-line no-shadow
  170. const extractedChunk = extractedChunks[chunks.indexOf(chunk)];
  171. const shouldExtract = !!(options.allChunks || (0, _helpers.isInitialOrHasNoParents)(chunk));
  172. _async2.default.forEach(Array.from(chunk.modulesIterable).sort(
  173. // NOTE: .index should be .index2 once ESM support is added
  174. (a, b) => a.index - b.index), (module, moduleCallback) => {
  175. // eslint-disable-line no-shadow
  176. let meta = module[NS];
  177. if (meta && (!meta.options.id || meta.options.id === id)) {
  178. const wasExtracted = Array.isArray(meta.content);
  179. // A stricter `shouldExtract !== wasExtracted` check to guard against cases where a previously extracted
  180. // module would be extracted twice. Happens when a module is a dependency of an initial and a non-initial
  181. // chunk. See issue #604
  182. if (shouldExtract && !wasExtracted) {
  183. module[`${NS}/extract`] = shouldExtract; // eslint-disable-line no-path-concat
  184. return compilation.rebuildModule(module, err => {
  185. if (err) {
  186. compilation.errors.push(err);
  187. return moduleCallback();
  188. }
  189. meta = module[NS];
  190. // Error out if content is not an array and is not null
  191. if (!Array.isArray(meta.content) && meta.content != null) {
  192. err = new Error(`${module.identifier()} doesn't export content`);
  193. compilation.errors.push(err);
  194. return moduleCallback();
  195. }
  196. if (meta.content) {
  197. extractCompilation.addResultToChunk(module.identifier(), meta.content, module, extractedChunk);
  198. }
  199. return moduleCallback();
  200. });
  201. } else if (meta.content) {
  202. extractCompilation.addResultToChunk(module.identifier(), meta.content, module, extractedChunk);
  203. }
  204. }
  205. return moduleCallback();
  206. }, err => {
  207. if (err) {
  208. return chunkCallback(err);
  209. }
  210. chunkCallback();
  211. });
  212. }, err => {
  213. if (err) {
  214. return callback(err);
  215. }
  216. extractedChunks.forEach(extractedChunk => {
  217. if ((0, _helpers.isInitialOrHasNoParents)(extractedChunk)) {
  218. this.mergeNonInitialChunks(extractedChunk);
  219. }
  220. });
  221. extractedChunks.forEach(extractedChunk => {
  222. if (!(0, _helpers.isInitialOrHasNoParents)(extractedChunk)) {
  223. for (const chunkModule of extractedChunk.modulesIterable) {
  224. extractedChunk.removeModule(chunkModule);
  225. }
  226. }
  227. });
  228. compilation.hooks.optimizeExtractedChunks.call(extractedChunks);
  229. callback();
  230. });
  231. });
  232. compilation.hooks.additionalAssets.tapAsync(plugin, assetCb => {
  233. extractedChunks.forEach(extractedChunk => {
  234. if (extractedChunk.getNumberOfModules()) {
  235. extractedChunk.sortModules((a, b) => {
  236. if (!options.ignoreOrder && (0, _helpers.isInvalidOrder)(a, b)) {
  237. compilation.errors.push(new _OrderUndefinedError2.default(a.getOriginalModule()));
  238. compilation.errors.push(new _OrderUndefinedError2.default(b.getOriginalModule()));
  239. }
  240. return (0, _helpers.getOrder)(a, b);
  241. });
  242. const chunk = extractedChunk.originalChunk;
  243. const source = ExtractTextPlugin.renderExtractedChunk(compilation, extractedChunk);
  244. const getPath = format => compilation.getPath(format, {
  245. chunk
  246. }).replace(/\[(?:(\w+):)?contenthash(?::([a-z]+\d*))?(?::(\d+))?\]/gi,
  247. // eslint-disable-next-line func-names
  248. function () {
  249. return _loaderUtils2.default.getHashDigest(source.source(), arguments[1], arguments[2], parseInt(arguments[3], 10));
  250. });
  251. const file = (0, _helpers.isFunction)(filename) ? filename(getPath) : getPath(filename);
  252. compilation.assets[file] = source;
  253. chunk.files.push(file);
  254. }
  255. }, this);
  256. assetCb();
  257. });
  258. });
  259. }
  260. }
  261. ExtractTextPlugin.extract = ExtractTextPlugin.prototype.extract.bind(ExtractTextPlugin);
  262. exports.default = ExtractTextPlugin;