UNPKG

webpack-sources

Version:

Source code handling classes for webpack

228 lines (212 loc) 6.28 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const Source = require("./Source"); const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks"); const getGeneratedSourceInfo = require("./helpers/getGeneratedSourceInfo"); const splitIntoPotentialTokens = require("./helpers/splitIntoPotentialTokens"); const { isDualStringBufferCachingEnabled, } = require("./helpers/stringBufferUtils"); /** @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 */ class OriginalSource extends Source { /** * @param {string | Buffer} value value * @param {string} name name */ constructor(value, name) { super(); const isBuffer = Buffer.isBuffer(value); /** * @private * @type {undefined | string} */ this._value = isBuffer ? undefined : value; /** * @private * @type {undefined | Buffer} */ this._valueAsBuffer = isBuffer ? value : undefined; /** * @private * @type {string} */ this._name = name; } getName() { return this._name; } /** * @returns {SourceValue} source */ source() { if (this._value === undefined) { const value = /** @type {Buffer} */ (this._valueAsBuffer).toString("utf8"); if (isDualStringBufferCachingEnabled()) { this._value = value; } return value; } return this._value; } buffer() { if (this._valueAsBuffer === undefined) { const value = Buffer.from(/** @type {string} */ (this._value), "utf8"); if (isDualStringBufferCachingEnabled()) { this._valueAsBuffer = value; } return value; } return this._valueAsBuffer; } /** * Override Source.prototype.size (= `this.buffer().length`) to avoid * allocating a Buffer just to read the byte length, and memoize the * result so repeated calls don't re-scan the string. * @returns {number} byte length */ size() { if (this._cachedSize !== undefined) return this._cachedSize; if (this._valueAsBuffer !== undefined) { return (this._cachedSize = this._valueAsBuffer.length); } return (this._cachedSize = Buffer.byteLength( /** @type {string} */ (this._value), "utf8", )); } /** * @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._value === undefined) { this._value = /** @type {Buffer} */ (this._valueAsBuffer).toString("utf8"); } onSource(0, this._name, this._value); const finalSource = Boolean(options && options.finalSource); if (!options || options.columns !== false) { // With column info we need to read all lines and split them const matches = splitIntoPotentialTokens(this._value); let line = 1; let column = 0; if (matches !== null) { for (const match of matches) { const isEndOfLine = match.endsWith("\n"); if (isEndOfLine && match.length === 1) { if (!finalSource) onChunk(match, line, column, -1, -1, -1, -1); } else { const chunk = finalSource ? undefined : match; onChunk(chunk, line, column, 0, line, column, -1); } if (isEndOfLine) { line++; column = 0; } else { column += match.length; } } } return { generatedLine: line, generatedColumn: column, source: finalSource ? this._value : undefined, }; } else if (finalSource) { // Without column info and with final source we only // need meta info to generate mapping const result = getGeneratedSourceInfo(this._value); const { generatedLine, generatedColumn } = result; if (generatedColumn === 0) { for ( let line = 1; line < /** @type {number} */ (generatedLine); line++ ) { onChunk(undefined, line, 0, 0, line, 0, -1); } } else { for ( let line = 1; line <= /** @type {number} */ (generatedLine); line++ ) { onChunk(undefined, line, 0, 0, line, 0, -1); } } return result; } // Without column info, but also without final source. // We only get here when (options.columns === false && !finalSource), // so the source field is always undefined and the chunk arg is always // the line text. Single-pass scan over newlines avoids the // splitIntoLines array allocation. const value = this._value; const len = value.length; if (len === 0) { return { generatedLine: 1, generatedColumn: 0, source: undefined }; } let line = 1; let i = 0; while (i < len) { const n = value.indexOf("\n", i); if (n === -1) { const lastLine = i === 0 ? value : value.slice(i); onChunk(lastLine, line, 0, 0, line, 0, -1); return { generatedLine: line, generatedColumn: lastLine.length, source: undefined, }; } const chunk = n === i ? "\n" : value.slice(i, n + 1); onChunk(chunk, line, 0, 0, line, 0, -1); line++; i = n + 1; } // Source ended with a newline. return { generatedLine: line, generatedColumn: 0, source: undefined }; } /** * @param {HashLike} hash hash * @returns {void} */ updateHash(hash) { hash.update("OriginalSource"); hash.update(this.buffer()); hash.update(this._name || ""); } } module.exports = OriginalSource;