Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
322 lines
9.6 KiB
Diff
322 lines
9.6 KiB
Diff
diff --git a/lib/RawSource.js b/lib/RawSource.js
|
|
index 71f1341c43dd45dc82b633eabe0a1cfe99bbe25d..b56ea1a17ba81295826dc1c820d462cca8643bc1 100644
|
|
--- a/lib/RawSource.js
|
|
+++ b/lib/RawSource.js
|
|
@@ -6,76 +6,91 @@
|
|
"use strict";
|
|
|
|
const streamChunksOfRawSource = require("./helpers/streamChunksOfRawSource");
|
|
+const {
|
|
+ internString,
|
|
+ isDualStringBufferCachingEnabled,
|
|
+} = require("./helpers/stringBufferUtils");
|
|
const Source = require("./Source");
|
|
|
|
class RawSource extends Source {
|
|
- constructor(value, convertToString = false) {
|
|
- super();
|
|
- const isBuffer = Buffer.isBuffer(value);
|
|
- if (!isBuffer && typeof value !== "string") {
|
|
- throw new TypeError("argument 'value' must be either string of Buffer");
|
|
- }
|
|
- this._valueIsBuffer = !convertToString && isBuffer;
|
|
- this._value = convertToString && isBuffer ? undefined : value;
|
|
- this._valueAsBuffer = isBuffer ? value : undefined;
|
|
- this._valueAsString = isBuffer ? undefined : value;
|
|
- }
|
|
+ constructor(value, convertToString = false) {
|
|
+ super();
|
|
+ const isBuffer = Buffer.isBuffer(value);
|
|
+ if (!isBuffer && typeof value !== "string") {
|
|
+ throw new TypeError("argument 'value' must be either string or Buffer");
|
|
+ }
|
|
+ this._valueIsBuffer = !convertToString && isBuffer;
|
|
+ const internedString =
|
|
+ typeof value === "string" ? internString(value) : undefined;
|
|
+ this._value =
|
|
+ convertToString && isBuffer
|
|
+ ? undefined
|
|
+ : typeof value === "string"
|
|
+ ? internedString
|
|
+ : value;
|
|
+ this._valueAsBuffer = isBuffer ? value : undefined;
|
|
+ this._valueAsString = isBuffer ? undefined : internedString;
|
|
+ }
|
|
|
|
- isBuffer() {
|
|
- return this._valueIsBuffer;
|
|
- }
|
|
+ isBuffer() {
|
|
+ return this._valueIsBuffer;
|
|
+ }
|
|
|
|
- source() {
|
|
- if (this._value === undefined) {
|
|
- this._value = this._valueAsBuffer.toString("utf-8");
|
|
- }
|
|
- return this._value;
|
|
- }
|
|
+ source() {
|
|
+ if (this._value === undefined) {
|
|
+ const value = this._valueAsBuffer.toString("utf-8");
|
|
+ if (isDualStringBufferCachingEnabled()) {
|
|
+ this._value = internString(value);
|
|
+ }
|
|
+ return value;
|
|
+ }
|
|
+ return this._value;
|
|
+ }
|
|
|
|
- buffer() {
|
|
- if (this._valueAsBuffer === undefined) {
|
|
- this._valueAsBuffer = Buffer.from(this._value, "utf-8");
|
|
- }
|
|
- return this._valueAsBuffer;
|
|
- }
|
|
+ buffer() {
|
|
+ if (this._valueAsBuffer === undefined) {
|
|
+ const value = Buffer.from(this._value, "utf-8");
|
|
+ if (isDualStringBufferCachingEnabled()) {
|
|
+ this._valueAsBuffer = value;
|
|
+ }
|
|
+ return value;
|
|
+ }
|
|
+ return this._valueAsBuffer;
|
|
+ }
|
|
|
|
- map(options) {
|
|
- return null;
|
|
- }
|
|
+ map(options) {
|
|
+ return null;
|
|
+ }
|
|
|
|
- /**
|
|
- * @param {object} options options
|
|
- * @param {function(string, number, number, number, number, number, number): void} onChunk called for each chunk of code
|
|
- * @param {function(number, string, string)} onSource called for each source
|
|
- * @param {function(number, string)} onName called for each name
|
|
- * @returns {void}
|
|
- */
|
|
- streamChunks(options, onChunk, onSource, onName) {
|
|
- if (this._value === undefined) {
|
|
- this._value = Buffer.from(this._valueAsBuffer, "utf-8");
|
|
- }
|
|
- if (this._valueAsString === undefined) {
|
|
- this._valueAsString =
|
|
- typeof this._value === "string"
|
|
- ? this._value
|
|
- : this._value.toString("utf-8");
|
|
- }
|
|
- return streamChunksOfRawSource(
|
|
- this._valueAsString,
|
|
- onChunk,
|
|
- onSource,
|
|
- onName,
|
|
- !!(options && options.finalSource)
|
|
- );
|
|
- }
|
|
+ /**
|
|
+ * @param {object} options options
|
|
+ * @param {function(string, number, number, number, number, number, number): void} onChunk called for each chunk of code
|
|
+ * @param {function(number, string, string)} onSource called for each source
|
|
+ * @param {function(number, string)} onName called for each name
|
|
+ * @returns {void}
|
|
+ */
|
|
+ streamChunks(options, onChunk, onSource, onName) {
|
|
+ let strValue = this._valueAsString;
|
|
+ if (strValue === undefined) {
|
|
+ const value = this.source();
|
|
+ strValue = typeof value === "string" ? value : value.toString("utf-8");
|
|
+ if (isDualStringBufferCachingEnabled()) {
|
|
+ this._valueAsString = internString(strValue);
|
|
+ }
|
|
+ }
|
|
+ return streamChunksOfRawSource(
|
|
+ strValue,
|
|
+ onChunk,
|
|
+ onSource,
|
|
+ onName,
|
|
+ !!(options && options.finalSource)
|
|
+ );
|
|
+ }
|
|
|
|
- updateHash(hash) {
|
|
- if (this._valueAsBuffer === undefined) {
|
|
- this._valueAsBuffer = Buffer.from(this._value, "utf-8");
|
|
- }
|
|
- hash.update("RawSource");
|
|
- hash.update(this._valueAsBuffer);
|
|
- }
|
|
+ updateHash(hash) {
|
|
+ hash.update("RawSource");
|
|
+ hash.update(this.buffer());
|
|
+ }
|
|
}
|
|
|
|
module.exports = RawSource;
|
|
diff --git a/lib/helpers/stringBufferUtils.js b/lib/helpers/stringBufferUtils.js
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..3d8b85e61643412796e12e2aac6905c5e3a8d5b9
|
|
--- /dev/null
|
|
+++ b/lib/helpers/stringBufferUtils.js
|
|
@@ -0,0 +1,120 @@
|
|
+/*
|
|
+ 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 ||
|
|
+ str.length < 128 ||
|
|
+ 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;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Starts 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.
|
|
+ *
|
|
+ * {@link exitStringInterningRange} should be called when string interning is
|
|
+ * no longer necessary to free up the memory used by the interned strings. If
|
|
+ * {@link enterStringInterningRange} has been called multiple times, then
|
|
+ * this method may not immediately free all the memory until
|
|
+ * {@link exitStringInterningRange} has been called to end all string
|
|
+ * interning ranges.
|
|
+ *
|
|
+ * @returns {void}
|
|
+ */
|
|
+function enterStringInterningRange() {
|
|
+ enableStringInterningRefCount++;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Stops the current string interning range. Once all string interning ranges
|
|
+ * have been exited, this method will free all the memory used by the interned
|
|
+ * strings. This method should be called once for each time that
|
|
+ * {@link enterStringInterningRange} was called.
|
|
+ *
|
|
+ * @returns {void}
|
|
+ */
|
|
+function exitStringInterningRange() {
|
|
+ if (--enableStringInterningRefCount <= 0) {
|
|
+ interningStringMap.clear();
|
|
+ enableStringInterningRefCount = 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+module.exports = {
|
|
+ disableDualStringBufferCaching,
|
|
+ enableDualStringBufferCaching,
|
|
+ internString,
|
|
+ isDualStringBufferCachingEnabled,
|
|
+ enterStringInterningRange,
|
|
+ exitStringInterningRange,
|
|
+};
|
|
diff --git a/lib/index.js b/lib/index.js
|
|
index 0c11c2f4cf5387e249cf12fa0fcb746dfcfec539..9ca1b100d8250d4f3c41e5931f710efbbca21611 100644
|
|
--- a/lib/index.js
|
|
+++ b/lib/index.js
|
|
@@ -4,17 +4,17 @@
|
|
*/
|
|
|
|
const defineExport = (name, fn) => {
|
|
- let value;
|
|
- Object.defineProperty(exports, name, {
|
|
- get: () => {
|
|
- if (fn !== undefined) {
|
|
- value = fn();
|
|
- fn = undefined;
|
|
- }
|
|
- return value;
|
|
- },
|
|
- configurable: true
|
|
- });
|
|
+ let value;
|
|
+ Object.defineProperty(exports, name, {
|
|
+ get: () => {
|
|
+ if (fn !== undefined) {
|
|
+ value = fn();
|
|
+ fn = undefined;
|
|
+ }
|
|
+ return value;
|
|
+ },
|
|
+ configurable: true,
|
|
+ });
|
|
};
|
|
|
|
defineExport("Source", () => require("./Source"));
|
|
@@ -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"));
|