219 lines
6.5 KiB
Diff
219 lines
6.5 KiB
Diff
|
diff --git a/lib/RawSource.js b/lib/RawSource.js
|
||
|
index 098d317..06e6b8f 100644
|
||
|
--- a/lib/RawSource.js
|
||
|
+++ b/lib/RawSource.js
|
||
|
@@ -6,6 +6,10 @@
|
||
|
"use strict";
|
||
|
|
||
|
const streamChunksOfRawSource = require("./helpers/streamChunksOfRawSource");
|
||
|
+const {
|
||
|
+ internString,
|
||
|
+ isDualStringBufferCachingEnabled
|
||
|
+} = require("./helpers/stringBufferUtils");
|
||
|
const Source = require("./Source");
|
||
|
|
||
|
class RawSource extends Source {
|
||
|
@@ -17,8 +21,13 @@ class RawSource extends Source {
|
||
|
}
|
||
|
this._valueIsBuffer = !convertToString && isBuffer;
|
||
|
- this._value = convertToString && isBuffer ? undefined : value;
|
||
|
+ this._value =
|
||
|
+ convertToString && isBuffer
|
||
|
+ ? undefined
|
||
|
+ : typeof value === "string"
|
||
|
+ ? internString(value)
|
||
|
+ : value;
|
||
|
this._valueAsBuffer = isBuffer ? value : undefined;
|
||
|
- this._valueAsString = isBuffer ? undefined : value;
|
||
|
+ this._valueAsString = isBuffer ? undefined : internString(value);
|
||
|
}
|
||
|
|
||
|
isBuffer() {
|
||
|
@@ -27,14 +36,22 @@ class RawSource extends Source {
|
||
|
|
||
|
source() {
|
||
|
if (this._value === undefined) {
|
||
|
- this._value = this._valueAsBuffer.toString("utf-8");
|
||
|
+ const value = internString(this._valueAsBuffer.toString("utf-8"));
|
||
|
+ if (isDualStringBufferCachingEnabled()) {
|
||
|
+ this._value = value;
|
||
|
+ }
|
||
|
+ return value;
|
||
|
}
|
||
|
return this._value;
|
||
|
}
|
||
|
|
||
|
buffer() {
|
||
|
if (this._valueAsBuffer === undefined) {
|
||
|
- this._valueAsBuffer = Buffer.from(this._value, "utf-8");
|
||
|
+ const value = Buffer.from(this._value, "utf-8");
|
||
|
+ if (isDualStringBufferCachingEnabled()) {
|
||
|
+ this._valueAsBuffer = value;
|
||
|
+ }
|
||
|
+ return value;
|
||
|
}
|
||
|
return this._valueAsBuffer;
|
||
|
}
|
||
|
@@ -51,17 +68,21 @@ class RawSource extends Source {
|
||
|
* @returns {void}
|
||
|
*/
|
||
|
streamChunks(options, onChunk, onSource, onName) {
|
||
|
- if (this._value === undefined) {
|
||
|
+ if (this._value === undefined && isDualStringBufferCachingEnabled()) {
|
||
|
this._value = Buffer.from(this._valueAsBuffer, "utf-8");
|
||
|
}
|
||
|
- if (this._valueAsString === undefined) {
|
||
|
- this._valueAsString =
|
||
|
+ let strValue = this._valueAsString;
|
||
|
+ if (strValue === undefined) {
|
||
|
+ strValue =
|
||
|
typeof this._value === "string"
|
||
|
? this._value
|
||
|
- : this._value.toString("utf-8");
|
||
|
+ : internString(this._value.toString("utf-8"));
|
||
|
+ if (isDualStringBufferCachingEnabled()) {
|
||
|
+ this._valueAsString = strValue;
|
||
|
+ }
|
||
|
}
|
||
|
return streamChunksOfRawSource(
|
||
|
- this._valueAsString,
|
||
|
+ strValue,
|
||
|
onChunk,
|
||
|
onSource,
|
||
|
onName,
|
||
|
@@ -70,11 +91,8 @@ class RawSource extends Source {
|
||
|
}
|
||
|
|
||
|
updateHash(hash) {
|
||
|
- if (this._valueAsBuffer === undefined) {
|
||
|
- this._valueAsBuffer = Buffer.from(this._value, "utf-8");
|
||
|
- }
|
||
|
hash.update("RawSource");
|
||
|
- hash.update(this._valueAsBuffer);
|
||
|
+ hash.update(this.buffer());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
diff --git a/lib/helpers/stringBufferUtils.js b/lib/helpers/stringBufferUtils.js
|
||
|
new file mode 100644
|
||
|
index 0000000..3b210f1
|
||
|
--- /dev/null
|
||
|
+++ b/lib/helpers/stringBufferUtils.js
|
||
|
@@ -0,0 +1,107 @@
|
||
|
+/*
|
||
|
+ MIT License http://www.opensource.org/licenses/mit-license.php
|
||
|
+ Author Mark Knichel @mknichel
|
||
|
+*/
|
||
|
+
|
||
|
+"use strict";
|
||
|
+
|
||
|
+let dualStringBufferCaching = true;
|
||
|
+
|
||
|
+/**
|
||
|
+ * @returns {boolean} Whether the optimization to cache copies of both the
|
||
|
+ * string and buffer version of source content is enabled. This is enabled by
|
||
|
+ * default to improve performance but can consume more memory since values are
|
||
|
+ * stored twice.
|
||
|
+ */
|
||
|
+function isDualStringBufferCachingEnabled() {
|
||
|
+ return dualStringBufferCaching;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * Enables an optimization to save both string and buffer in memory to avoid
|
||
|
+ * repeat conversions between the two formats when they are requested. This
|
||
|
+ * is enabled by default. This option can improve performance but can consume
|
||
|
+ * additional memory since values are stored twice.
|
||
|
+ *
|
||
|
+ * @returns {void}
|
||
|
+ */
|
||
|
+function enableDualStringBufferCaching() {
|
||
|
+ dualStringBufferCaching = true;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * Disables the optimization to save both string and buffer in memory. This
|
||
|
+ * may increase performance but should reduce memory usage in the Webpack
|
||
|
+ * compiler.
|
||
|
+ *
|
||
|
+ * @returns {void}
|
||
|
+ */
|
||
|
+function disableDualStringBufferCaching() {
|
||
|
+ dualStringBufferCaching = false;
|
||
|
+}
|
||
|
+
|
||
|
+const interningStringMap = new Map();
|
||
|
+
|
||
|
+/**
|
||
|
+ * Saves the string in a map to ensure that only one copy of the string exists
|
||
|
+ * in memory at a given time. This is controlled by {@link enableStringInterning}
|
||
|
+ * and {@link disableStringInterning}. Callers are expect to manage the memory
|
||
|
+ * of the interned strings by calling {@link disableStringInterning} after the
|
||
|
+ * compiler no longer needs to save the interned memory.
|
||
|
+ *
|
||
|
+ * @param {string} str A string to be interned.
|
||
|
+ * @returns {string} The original string or a reference to an existing string
|
||
|
+ * of the same value if it has already been interned.
|
||
|
+ */
|
||
|
+function internString(str) {
|
||
|
+ if (!isStringInterningEnabled() || !str || typeof str !== "string") {
|
||
|
+ return str;
|
||
|
+ }
|
||
|
+ let internedString = interningStringMap.get(str);
|
||
|
+ if (internedString === undefined) {
|
||
|
+ internedString = str;
|
||
|
+ interningStringMap.set(str, internedString);
|
||
|
+ }
|
||
|
+ return internedString;
|
||
|
+}
|
||
|
+
|
||
|
+let enableStringInterningRefCount = 0;
|
||
|
+
|
||
|
+function isStringInterningEnabled() {
|
||
|
+ return enableStringInterningRefCount > 0;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * Enables a memory optimization to avoid repeat copies of the same string in
|
||
|
+ * memory by caching a single reference to the string. This can reduce memory
|
||
|
+ * usage if the same string is repeated many times in the compiler, such as
|
||
|
+ * when Webpack layers are used with the same files.
|
||
|
+ *
|
||
|
+ * @returns {void}
|
||
|
+ */
|
||
|
+function enableStringInterning() {
|
||
|
+ enableStringInterningRefCount++;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * Disables string interning. This should be called to free the memory used by
|
||
|
+ * the interned strings after the compiler no longer needs to reuse the
|
||
|
+ * interned strings such as at the end of the compilation.
|
||
|
+ *
|
||
|
+ * @returns {void}
|
||
|
+ */
|
||
|
+function disableStringInterning() {
|
||
|
+ if (--enableStringInterningRefCount <= 0) {
|
||
|
+ interningStringMap.clear();
|
||
|
+ enableStringInterningRefCount = 0;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+module.exports = {
|
||
|
+ disableDualStringBufferCaching,
|
||
|
+ disableStringInterning,
|
||
|
+ enableDualStringBufferCaching,
|
||
|
+ enableStringInterning,
|
||
|
+ internString,
|
||
|
+ isDualStringBufferCachingEnabled
|
||
|
+};
|
||
|
diff --git a/lib/index.js b/lib/index.js
|
||
|
index 0c11c2f..86a7234 100644
|
||
|
--- a/lib/index.js
|
||
|
+++ b/lib/index.js
|
||
|
@@ -28,3 +28,4 @@ defineExport("ReplaceSource", () => require("./ReplaceSource"));
|
||
|
defineExport("PrefixSource", () => require("./PrefixSource"));
|
||
|
defineExport("SizeOnlySource", () => require("./SizeOnlySource"));
|
||
|
defineExport("CompatSource", () => require("./CompatSource"));
|
||
|
+defineExport("stringBufferUtils", () => require("./helpers/stringBufferUtils"));
|