webpack-sources
Version:
Source code handling classes for webpack
404 lines (383 loc) • 10.4 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
;
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;