Skip to content

Commit

Permalink
[Memory] Dedupe strings across modules in multiple layers
Browse files Browse the repository at this point in the history
  • Loading branch information
mknichel committed Apr 19, 2024
1 parent bdf23f9 commit a35031b
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 29 deletions.
4 changes: 3 additions & 1 deletion lib/ModuleLayerCache.js
Expand Up @@ -70,7 +70,9 @@ class ModuleLayerCache {
* @param {string | Buffer} value The value to store in the cache.
*/
set(key, value) {
const weakMapKey = new WeakMapKey(key);
const weakMapKey = this.weakMapKeys.has(key)
? this.weakMapKeys.get(key)
: new WeakMapKey(key);
this.weakMapKeys.set(key, weakMapKey);
this.cachedValues.set(weakMapKey, value);
}
Expand Down
47 changes: 27 additions & 20 deletions lib/NormalModule.js
Expand Up @@ -232,8 +232,8 @@ makeSerializable(
/** @type {WeakMap<Compilation, NormalModuleCompilationHooks>} */
const compilationHooksMap = new WeakMap();

/** @type {ModuleLayerCache} */
const moduleLayerCache = new ModuleLayerCache();
/** @type {WeakMap<Object, ModuleLayerCache>} */
const cachedModuleLayerCaches = new WeakMap();

class NormalModule extends Module {
/**
Expand Down Expand Up @@ -784,6 +784,31 @@ class NormalModule extends Module {
* @returns {Source} the created source
*/
createSource(context, content, sourceMap, associatedObjectForCache) {
// If this module exists in a layer, try to reuse the value for the
// source string so it is not duplicated in multiple modules.
if (this.layer && this.userRequest) {
let moduleLayerCache = cachedModuleLayerCaches.get(
associatedObjectForCache
);
if (moduleLayerCache) {
moduleLayerCache = new ModuleLayerCache();
cachedModuleLayerCaches.set(associatedObjectForCache, moduleLayerCache);
}
if (moduleLayerCache.has(this.userRequest)) {
const cachedSource = moduleLayerCache.get(this.userRequest);
if (
(Buffer.isBuffer(content) &&
Buffer.isBuffer(cachedSource) &&
content.equals(cachedSource)) ||
content === cachedSource
) {
content = cachedSource;
}
} else {
moduleLayerCache.set(this.userRequest, content);
}
}

if (Buffer.isBuffer(content)) {
return new RawSource(content);
}
Expand Down Expand Up @@ -871,24 +896,6 @@ class NormalModule extends Module {
return callback(error);
}

// If this module exists in a layer, try to reuse the value for the
// source string so it is not duplicated in multiple modules.
if (this.layer && this.request) {
if (moduleLayerCache.has(this.request)) {
const cachedSource = moduleLayerCache.get(this.request);
if (
(Buffer.isBuffer(source) &&
Buffer.isBuffer(cachedSource) &&
source.equals(cachedSource)) ||
source === cachedSource
) {
source = cachedSource;
}
} else {
moduleLayerCache.set(this.request, source);
}
}

this._source = this.createSource(
/** @type {string} */ (options.context),
this.binary ? asBuffer(source) : asString(source),
Expand Down
2 changes: 1 addition & 1 deletion lib/json/JsonModulesPlugin.js
Expand Up @@ -44,7 +44,7 @@ class JsonModulesPlugin {
.tap(PLUGIN_NAME, parserOptions => {
validate(parserOptions);

return new JsonParser(parserOptions);
return new JsonParser(parserOptions, compilation);
});
normalModuleFactory.hooks.createGenerator
.for(JSON_MODULE_TYPE)
Expand Down
26 changes: 19 additions & 7 deletions lib/json/JsonParser.js
Expand Up @@ -20,16 +20,18 @@ const JsonData = require("./JsonData");

const getParseJson = memoize(() => require("json-parse-even-better-errors"));

/** @type {ModuleLayerCache} */
const moduleLayerCache = new ModuleLayerCache();
/** @type {WeakMap<Object, ModuleLayerCache>} */
const cachedModuleLayerCaches = new WeakMap();

class JsonParser extends Parser {
/**
* @param {JsonModulesPluginParserOptions} options parser options
* @param {Object} associatedObjectForCache An object to associate cached data with.
*/
constructor(options) {
constructor(options, associatedObjectForCache) {
super();
this.options = options || {};
this.associatedObjectForCache = associatedObjectForCache;
}

/**
Expand Down Expand Up @@ -61,14 +63,24 @@ class JsonParser extends Parser {
// If the module is associated with a layer, try to reuse cached data instead
// of duplicating the data multiple times.
const module = state.module;
if (module && module.request && module.layer && Buffer.isBuffer(data)) {
if (moduleLayerCache.has(module.request)) {
const cachedData = moduleLayerCache.get(module.request);
if (module && module.userRequest && module.layer && Buffer.isBuffer(data)) {
let moduleLayerCache = cachedModuleLayerCaches.get(
this.associatedObjectForCache
);
if (moduleLayerCache) {
moduleLayerCache = new ModuleLayerCache();
cachedModuleLayerCaches.set(
this.associatedObjectForCache,
moduleLayerCache
);
}
if (moduleLayerCache.has(module.userRequest)) {
const cachedData = moduleLayerCache.get(module.userRequest);
if (Buffer.isBuffer(cachedData) && data.equals(cachedData)) {
data = cachedData;
}
} else {
moduleLayerCache.set(module.request, data);
moduleLayerCache.set(module.userRequest, data);
}
}

Expand Down

0 comments on commit a35031b

Please sign in to comment.