/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const identifierUtils = require("./util/identifier"); /** @typedef {import("./Compiler")} Compiler */ /** @typedef {import("./Chunk")} Chunk */ /** @typedef {import("./Module")} Module */ /** * @typedef {Object} RecordsChunks * @property {Record<string, number>=} byName * @property {Record<string, number>=} bySource * @property {number[]=} usedIds */ /** * @typedef {Object} RecordsModules * @property {Record<string, number>=} byIdentifier * @property {Record<string, number>=} bySource * @property {Record<number, number>=} usedIds */ /** * @typedef {Object} Records * @property {RecordsChunks=} chunks * @property {RecordsModules=} modules */ class RecordIdsPlugin { /** * @param {Object} options Options object * @param {boolean=} options.portableIds true, when ids need to be portable */ constructor(options) { this.options = options || {}; } /** * @param {Compiler} compiler the Compiler * @returns {void} */ apply(compiler) { const portableIds = this.options.portableIds; compiler.hooks.compilation.tap("RecordIdsPlugin", compilation => { compilation.hooks.recordModules.tap( "RecordIdsPlugin", /** * @param {Module[]} modules the modules array * @param {Records} records the records object * @returns {void} */ (modules, records) => { if (!records.modules) records.modules = {}; if (!records.modules.byIdentifier) records.modules.byIdentifier = {}; if (!records.modules.usedIds) records.modules.usedIds = {}; for (const module of modules) { if (typeof module.id !== "number") continue; const identifier = portableIds ? identifierUtils.makePathsRelative( compiler.context, module.identifier(), compilation.cache ) : module.identifier(); records.modules.byIdentifier[identifier] = module.id; records.modules.usedIds[module.id] = module.id; } } ); compilation.hooks.reviveModules.tap( "RecordIdsPlugin", /** * @param {Module[]} modules the modules array * @param {Records} records the records object * @returns {void} */ (modules, records) => { if (!records.modules) return; if (records.modules.byIdentifier) { /** @type {Set<number>} */ const usedIds = new Set(); for (const module of modules) { if (module.id !== null) continue; const identifier = portableIds ? identifierUtils.makePathsRelative( compiler.context, module.identifier(), compilation.cache ) : module.identifier(); const id = records.modules.byIdentifier[identifier]; if (id === undefined) continue; if (usedIds.has(id)) continue; usedIds.add(id); module.id = id; } } if (Array.isArray(records.modules.usedIds)) { compilation.usedModuleIds = new Set(records.modules.usedIds); } } ); /** * @param {Module} module the module * @returns {string} the (portable) identifier */ const getModuleIdentifier = module => { if (portableIds) { return identifierUtils.makePathsRelative( compiler.context, module.identifier(), compilation.cache ); } return module.identifier(); }; /** * @param {Chunk} chunk the chunk * @returns {string[]} sources of the chunk */ const getChunkSources = chunk => { /** @type {string[]} */ const sources = []; for (const chunkGroup of chunk.groupsIterable) { const index = chunkGroup.chunks.indexOf(chunk); if (chunkGroup.name) { sources.push(`${index} ${chunkGroup.name}`); } else { for (const origin of chunkGroup.origins) { if (origin.module) { if (origin.request) { sources.push( `${index} ${getModuleIdentifier(origin.module)} ${ origin.request }` ); } else if (typeof origin.loc === "string") { sources.push( `${index} ${getModuleIdentifier(origin.module)} ${ origin.loc }` ); } else if ( origin.loc && typeof origin.loc === "object" && origin.loc.start ) { sources.push( `${index} ${getModuleIdentifier( origin.module )} ${JSON.stringify(origin.loc.start)}` ); } } } } } return sources; }; compilation.hooks.recordChunks.tap( "RecordIdsPlugin", /** * @param {Chunk[]} chunks the chunks array * @param {Records} records the records object * @returns {void} */ (chunks, records) => { if (!records.chunks) records.chunks = {}; if (!records.chunks.byName) records.chunks.byName = {}; if (!records.chunks.bySource) records.chunks.bySource = {}; /** @type {Set<number>} */ const usedIds = new Set(); for (const chunk of chunks) { if (typeof chunk.id !== "number") continue; const name = chunk.name; if (name) records.chunks.byName[name] = chunk.id; const sources = getChunkSources(chunk); for (const source of sources) { records.chunks.bySource[source] = chunk.id; } usedIds.add(chunk.id); } records.chunks.usedIds = Array.from(usedIds).sort(); } ); compilation.hooks.reviveChunks.tap( "RecordIdsPlugin", /** * @param {Chunk[]} chunks the chunks array * @param {Records} records the records object * @returns {void} */ (chunks, records) => { if (!records.chunks) return; /** @type {Set<number>} */ const usedIds = new Set(); if (records.chunks.byName) { for (const chunk of chunks) { if (chunk.id !== null) continue; if (!chunk.name) continue; const id = records.chunks.byName[chunk.name]; if (id === undefined) continue; if (usedIds.has(id)) continue; usedIds.add(id); chunk.id = id; } } if (records.chunks.bySource) { for (const chunk of chunks) { const sources = getChunkSources(chunk); for (const source of sources) { const id = records.chunks.bySource[source]; if (id === undefined) continue; if (usedIds.has(id)) continue; usedIds.add(id); chunk.id = id; break; } } } if (Array.isArray(records.chunks.usedIds)) { compilation.usedChunkIds = new Set(records.chunks.usedIds); } } ); }); } } module.exports = RecordIdsPlugin;