'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _Chunk = require('webpack/lib/Chunk'); var _Chunk2 = _interopRequireDefault(_Chunk); var _webpackSources = require('webpack-sources'); var _async = require('async'); var _async2 = _interopRequireDefault(_async); var _loaderUtils = require('loader-utils'); var _loaderUtils2 = _interopRequireDefault(_loaderUtils); var _schemaUtils = require('schema-utils'); var _schemaUtils2 = _interopRequireDefault(_schemaUtils); var _ExtractTextPluginCompilation = require('./lib/ExtractTextPluginCompilation'); var _ExtractTextPluginCompilation2 = _interopRequireDefault(_ExtractTextPluginCompilation); var _OrderUndefinedError = require('./lib/OrderUndefinedError'); var _OrderUndefinedError2 = _interopRequireDefault(_OrderUndefinedError); var _helpers = require('./lib/helpers'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* eslint-disable consistent-return, no-param-reassign, prefer-rest-params */ const NS = _path2.default.dirname(_fs2.default.realpathSync(__filename)); const plugin = { name: 'ExtractTextPlugin' }; let nextId = 0; class ExtractTextPlugin { constructor(options) { if ((0, _helpers.isString)(options)) { options = { filename: options }; } else { (0, _schemaUtils2.default)(_path2.default.resolve(__dirname, './plugin.json'), options, 'Extract Text Plugin'); } this.filename = options.filename; this.id = options.id != null ? options.id : nextId += 1; this.options = {}; (0, _helpers.mergeOptions)(this.options, options); delete this.options.filename; delete this.options.id; } static loader(options) { return { loader: require.resolve('./loader'), options }; } static applyAdditionalInformation(source, info) { if (info) { return new _webpackSources.ConcatSource(`@media ${info[0]} {`, source, '}'); } return source; } loader(options) { return ExtractTextPlugin.loader((0, _helpers.mergeOptions)({ id: this.id }, options)); } mergeNonInitialChunks(chunk, intoChunk, checkedChunks) { if (!intoChunk) { const newCheckedChunks = []; for (const asyncChunk of chunk.getAllAsyncChunks()) { if (!asyncChunk.isOnlyInitial()) { this.mergeNonInitialChunks(asyncChunk, chunk, newCheckedChunks); } } } else if (!checkedChunks.includes(chunk)) { const newCheckedChunks = checkedChunks.concat(chunk); for (const chunkModule of chunk.modulesIterable) { intoChunk.addModule(chunkModule); chunkModule.addChunk(intoChunk); } for (const asyncChunk of chunk.getAllAsyncChunks()) { if (!asyncChunk.isOnlyInitial()) { this.mergeNonInitialChunks(asyncChunk, intoChunk, newCheckedChunks); } } } } static renderExtractedChunk(compilation, chunk) { const source = new _webpackSources.ConcatSource(); for (const chunkModule of chunk.modulesIterable) { let moduleSource = chunkModule.source(compilation.dependencyTemplates, compilation.runtimeTemplate); // This module was concatenated by the ModuleConcatenationPlugin; because the pitching loader // only produces commonjs results, at least for now things we want to extract can't be in them. // NOTE: if ESM support is added, _this workaround will break_. if (moduleSource instanceof _webpackSources.ConcatSource) { moduleSource = null; } // Async imports (require.ensure(), import().then) are CachedSource module // instances caching a ReplaceSource instance, which breaks the plugin // because their .source() is the cached "// removed by ..." text. // The issue lies elsewhere, this is just a temporary fix that // creates a new RawSource with the extracted text. If it's // a CachedSource instance but there's no extracted text // it's "__webpack_require__();" statements. Skip it. if (moduleSource instanceof _webpackSources.CachedSource) { if (chunkModule[NS] && chunkModule[NS].content) { moduleSource = new _webpackSources.ConcatSource(); if (chunkModule[NS].content.length > 1) { console.error(chunkModule[NS].content); } for (const content of chunkModule[NS].content) { moduleSource.add(new _webpackSources.RawSource(content[1])); } } else { moduleSource = null; } } if (moduleSource) { source.add(ExtractTextPlugin.applyAdditionalInformation(moduleSource, chunkModule.additionalInformation)); } } return source; } extract(options) { if (Array.isArray(options) || (0, _helpers.isString)(options) || typeof options.options === 'object' || typeof options.query === 'object') { options = { use: options }; } else { (0, _schemaUtils2.default)(_path2.default.resolve(__dirname, './loader.json'), options, 'Extract Text Plugin (Loader)'); } let loader = options.use; let before = options.fallback || []; if ((0, _helpers.isString)(loader)) { loader = loader.split('!'); } if ((0, _helpers.isString)(before)) { before = before.split('!'); } else if (!Array.isArray(before)) { before = [before]; } options = (0, _helpers.mergeOptions)({ omit: before.length, remove: true }, options); delete options.use; delete options.fallback; return [this.loader(options)].concat(before, loader).map(_helpers.getLoaderObject); } apply(compiler) { const { options, filename, id } = this; compiler.hooks.thisCompilation.tap(plugin, compilation => { const extractCompilation = new _ExtractTextPluginCompilation2.default(); compilation.hooks.normalModuleLoader.tap(plugin, (loaderContext, module) => { loaderContext[NS] = (content, opt) => { if (options.disable) { return false; } if (!Array.isArray(content) && content != null) { throw new Error(`Exported value was not extracted as an array: ${JSON.stringify(content)}`); } module[NS] = { content, options: opt || {} }; return options.allChunks || module[`${NS}/extract`]; // eslint-disable-line no-path-concat }; }); let extractedChunks; compilation.hooks.optimizeTree.tapAsync(plugin, (chunks, modules, callback) => { extractedChunks = chunks.map(() => new _Chunk2.default()); chunks.forEach((chunk, i) => { const extractedChunk = extractedChunks[i]; extractedChunk.index = i; extractedChunk.originalChunk = chunk; extractedChunk.name = chunk.name; // extractedChunk.entryModule = chunk.entryModule; for (const chunkGroup of chunk.groupsIterable) { extractedChunk.addGroup(chunkGroup); } }); _async2.default.forEach(chunks, (chunk, chunkCallback) => { // eslint-disable-line no-shadow const extractedChunk = extractedChunks[chunks.indexOf(chunk)]; const shouldExtract = !!(options.allChunks || (0, _helpers.isInitialOrHasNoParents)(chunk)); _async2.default.forEach(Array.from(chunk.modulesIterable).sort( // NOTE: .index should be .index2 once ESM support is added (a, b) => a.index - b.index), (module, moduleCallback) => { // eslint-disable-line no-shadow let meta = module[NS]; if (meta && (!meta.options.id || meta.options.id === id)) { const wasExtracted = Array.isArray(meta.content); // A stricter `shouldExtract !== wasExtracted` check to guard against cases where a previously extracted // module would be extracted twice. Happens when a module is a dependency of an initial and a non-initial // chunk. See issue #604 if (shouldExtract && !wasExtracted) { module[`${NS}/extract`] = shouldExtract; // eslint-disable-line no-path-concat return compilation.rebuildModule(module, err => { if (err) { compilation.errors.push(err); return moduleCallback(); } meta = module[NS]; // Error out if content is not an array and is not null if (!Array.isArray(meta.content) && meta.content != null) { err = new Error(`${module.identifier()} doesn't export content`); compilation.errors.push(err); return moduleCallback(); } if (meta.content) { extractCompilation.addResultToChunk(module.identifier(), meta.content, module, extractedChunk); } return moduleCallback(); }); } else if (meta.content) { extractCompilation.addResultToChunk(module.identifier(), meta.content, module, extractedChunk); } } return moduleCallback(); }, err => { if (err) { return chunkCallback(err); } chunkCallback(); }); }, err => { if (err) { return callback(err); } extractedChunks.forEach(extractedChunk => { if ((0, _helpers.isInitialOrHasNoParents)(extractedChunk)) { this.mergeNonInitialChunks(extractedChunk); } }); extractedChunks.forEach(extractedChunk => { if (!(0, _helpers.isInitialOrHasNoParents)(extractedChunk)) { for (const chunkModule of extractedChunk.modulesIterable) { extractedChunk.removeModule(chunkModule); } } }); compilation.hooks.optimizeExtractedChunks.call(extractedChunks); callback(); }); }); compilation.hooks.additionalAssets.tapAsync(plugin, assetCb => { extractedChunks.forEach(extractedChunk => { if (extractedChunk.getNumberOfModules()) { extractedChunk.sortModules((a, b) => { if (!options.ignoreOrder && (0, _helpers.isInvalidOrder)(a, b)) { compilation.errors.push(new _OrderUndefinedError2.default(a.getOriginalModule())); compilation.errors.push(new _OrderUndefinedError2.default(b.getOriginalModule())); } return (0, _helpers.getOrder)(a, b); }); const chunk = extractedChunk.originalChunk; const source = ExtractTextPlugin.renderExtractedChunk(compilation, extractedChunk); const getPath = format => compilation.getPath(format, { chunk }).replace(/\[(?:(\w+):)?contenthash(?::([a-z]+\d*))?(?::(\d+))?\]/gi, // eslint-disable-next-line func-names function () { return _loaderUtils2.default.getHashDigest(source.source(), arguments[1], arguments[2], parseInt(arguments[3], 10)); }); const file = (0, _helpers.isFunction)(filename) ? filename(getPath) : getPath(filename); compilation.assets[file] = source; chunk.files.push(file); } }, this); assetCb(); }); }); } } ExtractTextPlugin.extract = ExtractTextPlugin.prototype.extract.bind(ExtractTextPlugin); exports.default = ExtractTextPlugin;