UNPKG

webpack-sources

Version:

Source code handling classes for webpack

404 lines (383 loc) 10.4 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const RawSource = require("./RawSource"); const Source = require("./Source"); const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks"); const streamChunks = require("./helpers/streamChunks"); /** @typedef {import("./CompatSource").SourceLike} SourceLike */ /** @typedef {import("./Source").HashLike} HashLike */ /** @typedef {import("./Source").MapOptions} MapOptions */ /** @typedef {import("./Source").RawSourceMap} RawSourceMap */ /** @typedef {import("./Source").SourceAndMap} SourceAndMap */ /** @typedef {import("./Source").SourceValue} SourceValue */ /** @typedef {import("./helpers/getGeneratedSourceInfo").GeneratedSourceInfo} GeneratedSourceInfo */ /** @typedef {import("./helpers/streamChunks").OnChunk} OnChunk */ /** @typedef {import("./helpers/streamChunks").OnName} OnName */ /** @typedef {import("./helpers/streamChunks").OnSource} OnSource */ /** @typedef {import("./helpers/streamChunks").Options} Options */ /** @typedef {string | Source | SourceLike} Child */ const stringsAsRawSources = new WeakSet(); class ConcatSource extends Source { /** * * @param {Child[]} args children */ constructor(...args) { super(); /** * @private * @type {Child[]} */ this._children = []; for (let i = 0; i < args.length; i++) { const item = args[i]; if (item instanceof ConcatSource) { for (const child of item._children) { this._children.push(child); } } else { this._children.push(item); } } this._isOptimized = args.length === 0; } /** * @returns {Source[]} children */ getChildren() { if (!this._isOptimized) this._optimize(); return /** @type {Source[]} */ (this._children); } /** * @param {Child} item item * @returns {void} */ add(item) { if (item instanceof ConcatSource) { for (const child of item._children) { this._children.push(child); } } else { this._children.push(item); } this._isOptimized = false; } /** * @param {Child[]} items items * @returns {void} */ addAllSkipOptimizing(items) { for (const item of items) { this._children.push(item); } } buffer() { if (!this._isOptimized) this._optimize(); /** @type {Buffer[]} */ const buffers = []; for (const child of /** @type {SourceLike[]} */ (this._children)) { if (typeof child.buffer === "function") { buffers.push(child.buffer()); } else { const bufferOrString = child.source(); if (Buffer.isBuffer(bufferOrString)) { buffers.push(bufferOrString); } else { // This will not happen buffers.push(Buffer.from(bufferOrString, "utf-8")); } } } return Buffer.concat(buffers); } /** * @returns {SourceValue} source */ source() { if (!this._isOptimized) this._optimize(); let source = ""; for (const child of this._children) { source += /** @type {Source} */ (child).source(); } return source; } size() { if (!this._isOptimized) this._optimize(); let size = 0; for (const child of this._children) { size += /** @type {Source} */ (child).size(); } return size; } /** * @param {MapOptions=} options map options * @returns {RawSourceMap | null} map */ map(options) { return getMap(this, options); } /** * @param {MapOptions=} options map options * @returns {SourceAndMap} source and map */ sourceAndMap(options) { return getSourceAndMap(this, options); } /** * @param {Options} options options * @param {OnChunk} onChunk called for each chunk of code * @param {OnSource} onSource called for each source * @param {OnName} onName called for each name * @returns {GeneratedSourceInfo} generated source info */ streamChunks(options, onChunk, onSource, onName) { if (!this._isOptimized) this._optimize(); if (this._children.length === 1) { return /** @type {ConcatSource[]} */ (this._children)[0].streamChunks( options, onChunk, onSource, onName ); } let currentLineOffset = 0; let currentColumnOffset = 0; let sourceMapping = new Map(); let nameMapping = new Map(); const finalSource = !!(options && options.finalSource); let code = ""; let needToCloseMapping = false; for (const item of /** @type {Source[]} */ (this._children)) { /** @type {number[]} */ const sourceIndexMapping = []; /** @type {number[]} */ const nameIndexMapping = []; let lastMappingLine = 0; const { generatedLine, generatedColumn, source } = streamChunks( item, options, // eslint-disable-next-line no-loop-func ( chunk, generatedLine, generatedColumn, sourceIndex, originalLine, originalColumn, nameIndex ) => { const line = generatedLine + currentLineOffset; const column = generatedLine === 1 ? generatedColumn + currentColumnOffset : generatedColumn; if (needToCloseMapping) { if (generatedLine !== 1 || generatedColumn !== 0) { onChunk( undefined, currentLineOffset + 1, currentColumnOffset, -1, -1, -1, -1 ); } needToCloseMapping = false; } const resultSourceIndex = sourceIndex < 0 || sourceIndex >= sourceIndexMapping.length ? -1 : sourceIndexMapping[sourceIndex]; const resultNameIndex = nameIndex < 0 || nameIndex >= nameIndexMapping.length ? -1 : nameIndexMapping[nameIndex]; lastMappingLine = resultSourceIndex < 0 ? 0 : generatedLine; if (finalSource) { if (chunk !== undefined) code += chunk; if (resultSourceIndex >= 0) { onChunk( undefined, line, column, resultSourceIndex, originalLine, originalColumn, resultNameIndex ); } } else { if (resultSourceIndex < 0) { onChunk(chunk, line, column, -1, -1, -1, -1); } else { onChunk( chunk, line, column, resultSourceIndex, originalLine, originalColumn, resultNameIndex ); } } }, (i, source, sourceContent) => { let globalIndex = sourceMapping.get(source); if (globalIndex === undefined) { sourceMapping.set(source, (globalIndex = sourceMapping.size)); onSource(globalIndex, source, sourceContent); } sourceIndexMapping[i] = globalIndex; }, (i, name) => { let globalIndex = nameMapping.get(name); if (globalIndex === undefined) { nameMapping.set(name, (globalIndex = nameMapping.size)); onName(globalIndex, name); } nameIndexMapping[i] = globalIndex; } ); if (source !== undefined) code += source; if (needToCloseMapping) { if (generatedLine !== 1 || generatedColumn !== 0) { onChunk( undefined, currentLineOffset + 1, currentColumnOffset, -1, -1, -1, -1 ); needToCloseMapping = false; } } if (/** @type {number} */ (generatedLine) > 1) { currentColumnOffset = /** @type {number} */ (generatedColumn); } else { currentColumnOffset += /** @type {number} */ (generatedColumn); } needToCloseMapping = needToCloseMapping || (finalSource && lastMappingLine === generatedLine); currentLineOffset += /** @type {number} */ (generatedLine) - 1; } return { generatedLine: currentLineOffset + 1, generatedColumn: currentColumnOffset, source: finalSource ? code : undefined }; } /** * @param {HashLike} hash hash * @returns {void} */ updateHash(hash) { if (!this._isOptimized) this._optimize(); hash.update("ConcatSource"); for (const item of this._children) { /** @type {Source} */ (item).updateHash(hash); } } _optimize() { const newChildren = []; let currentString = undefined; /** @type {undefined | string | [string, string] | SourceLike} */ let currentRawSources = undefined; /** * @param {string} string string * @returns {void} */ const addStringToRawSources = (string) => { if (currentRawSources === undefined) { currentRawSources = string; } else if (Array.isArray(currentRawSources)) { currentRawSources.push(string); } else { currentRawSources = [ typeof currentRawSources === "string" ? currentRawSources : /** @type {string} */ (currentRawSources.source()), string ]; } }; /** * @param {SourceLike} source source * @returns {void} */ const addSourceToRawSources = (source) => { if (currentRawSources === undefined) { currentRawSources = source; } else if (Array.isArray(currentRawSources)) { currentRawSources.push( /** @type {string} */ (source.source()) ); } else { currentRawSources = [ typeof currentRawSources === "string" ? currentRawSources : /** @type {string} */ (currentRawSources.source()), /** @type {string} */ (source.source()) ]; } }; const mergeRawSources = () => { if (Array.isArray(currentRawSources)) { const rawSource = new RawSource(currentRawSources.join("")); stringsAsRawSources.add(rawSource); newChildren.push(rawSource); } else if (typeof currentRawSources === "string") { const rawSource = new RawSource(currentRawSources); stringsAsRawSources.add(rawSource); newChildren.push(rawSource); } else { newChildren.push(currentRawSources); } }; for (const child of this._children) { if (typeof child === "string") { if (currentString === undefined) { currentString = child; } else { currentString += child; } } else { if (currentString !== undefined) { addStringToRawSources(currentString); currentString = undefined; } if (stringsAsRawSources.has(child)) { addSourceToRawSources( /** @type {SourceLike} */ (child) ); } else { if (currentRawSources !== undefined) { mergeRawSources(); currentRawSources = undefined; } newChildren.push(child); } } } if (currentString !== undefined) { addStringToRawSources(currentString); } if (currentRawSources !== undefined) { mergeRawSources(); } this._children = newChildren; this._isOptimized = true; } } module.exports = ConcatSource;