Skip to content

Commit

Permalink
Merge pull request #15801 from webpack/refactor-json-modules
Browse files Browse the repository at this point in the history
refactor json modules
  • Loading branch information
sokra committed Jun 2, 2022
2 parents b8748cf + aa76e82 commit 096efc3
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 26 deletions.
38 changes: 17 additions & 21 deletions lib/dependencies/JsonExportsDependency.js
Expand Up @@ -13,18 +13,21 @@ const NullDependency = require("./NullDependency");
/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../json/JsonData")} JsonData */
/** @typedef {import("../util/Hash")} Hash */

const getExportsFromData = data => {
if (data && typeof data === "object") {
if (Array.isArray(data)) {
return data.map((item, idx) => {
return {
name: `${idx}`,
canMangle: true,
exports: getExportsFromData(item)
};
});
return data.length < 100
? data.map((item, idx) => {
return {
name: `${idx}`,
canMangle: true,
exports: getExportsFromData(item)
};
})
: undefined;
} else {
const exports = [];
for (const key of Object.keys(data)) {
Expand All @@ -42,12 +45,11 @@ const getExportsFromData = data => {

class JsonExportsDependency extends NullDependency {
/**
* @param {(string | ExportSpec)[]} exports json exports
* @param {JsonData=} data json data
*/
constructor(exports) {
constructor(data) {
super();
this.exports = exports;
this._hashUpdate = undefined;
this.data = data;
}

get type() {
Expand All @@ -61,7 +63,7 @@ class JsonExportsDependency extends NullDependency {
*/
getExports(moduleGraph) {
return {
exports: this.exports,
exports: getExportsFromData(this.data && this.data.get()),
dependencies: undefined
};
}
Expand All @@ -73,23 +75,18 @@ class JsonExportsDependency extends NullDependency {
* @returns {void}
*/
updateHash(hash, context) {
if (this._hashUpdate === undefined) {
this._hashUpdate = this.exports
? JSON.stringify(this.exports)
: "undefined";
}
hash.update(this._hashUpdate);
this.data.updateHash(hash);
}

serialize(context) {
const { write } = context;
write(this.exports);
write(this.data);
super.serialize(context);
}

deserialize(context) {
const { read } = context;
this.exports = read();
this.data = read();
super.deserialize(context);
}
}
Expand All @@ -100,4 +97,3 @@ makeSerializable(
);

module.exports = JsonExportsDependency;
module.exports.getExportsFromData = getExportsFromData;
8 changes: 8 additions & 0 deletions lib/json/JsonData.js
Expand Up @@ -24,6 +24,14 @@ class JsonData {
}
return this._data;
}

updateHash(hash) {
if (this._buffer === undefined && this._data !== undefined) {
this._buffer = Buffer.from(JSON.stringify(this._data));
}

if (this._buffer) return hash.update(this._buffer);
}
}

register(JsonData, "webpack/lib/json/JsonData", null, {
Expand Down
8 changes: 3 additions & 5 deletions lib/json/JsonParser.js
Expand Up @@ -41,15 +41,13 @@ class JsonParser extends Parser {
typeof source === "object"
? source
: parseFn(source[0] === "\ufeff" ? source.slice(1) : source);

state.module.buildInfo.jsonData = new JsonData(data);
const jsonData = new JsonData(data);
state.module.buildInfo.jsonData = jsonData;
state.module.buildInfo.strict = true;
state.module.buildMeta.exportsType = "default";
state.module.buildMeta.defaultObject =
typeof data === "object" ? "redirect-warn" : false;
state.module.addDependency(
new JsonExportsDependency(JsonExportsDependency.getExportsFromData(data))
);
state.module.addDependency(new JsonExportsDependency(jsonData));
return state;
}
}
Expand Down
131 changes: 131 additions & 0 deletions test/MemoryLimitTestCases.test.js
@@ -0,0 +1,131 @@
"use strict";

require("./helpers/warmup-webpack");
const path = require("path");
const fs = require("graceful-fs");
const rimraf = require("rimraf");
const captureStdio = require("./helpers/captureStdio");
const webpack = require("..");

const toMiB = bytes => `${Math.round(bytes / 1024 / 1024)}MiB`;
const base = path.join(__dirname, "memoryLimitCases");
const outputBase = path.join(__dirname, "js", "memoryLimit");
const tests = fs
.readdirSync(base)
.filter(
testName =>
fs.existsSync(path.join(base, testName, "index.js")) ||
fs.existsSync(path.join(base, testName, "webpack.config.js"))
)
.filter(testName => {
const testDirectory = path.join(base, testName);
const filterPath = path.join(testDirectory, "test.filter.js");
if (fs.existsSync(filterPath) && !require(filterPath)()) {
describe.skip(testName, () => it("filtered"));
return false;
}
return true;
});

describe("MemoryLimitTestCases", () => {
jest.setTimeout(40000);
let stderr;
beforeEach(() => {
stderr = captureStdio(process.stderr, true);
if (global.gc) {
global.gc();
global.gc();
}
});
afterEach(() => {
stderr.restore();
});
tests.forEach(testName => {
let testConfig = {
heapSizeLimitBytes: 250 * 1024 * 1024
};
try {
// try to load a test file
testConfig = Object.assign(
testConfig,
require(path.join(base, testName, "test.config.js"))
);
} catch (e) {
// ignored
}
it(`should build ${JSON.stringify(testName)} with heap limit of ${toMiB(
testConfig.heapSizeLimitBytes
)}`, done => {
const outputDirectory = path.join(outputBase, testName);
rimraf.sync(outputDirectory);
fs.mkdirSync(outputDirectory, { recursive: true });
let options = {
mode: "development",
entry: "./index",
output: {
filename: "bundle.js"
}
};
if (fs.existsSync(path.join(base, testName, "webpack.config.js"))) {
options = require(path.join(base, testName, "webpack.config.js"));
}

(Array.isArray(options) ? options : [options]).forEach(options => {
if (!options.context) options.context = path.join(base, testName);
if (!options.output) options.output = options.output || {};
if (!options.output.path) options.output.path = outputDirectory;
if (!options.plugins) options.plugins = [];
if (!options.optimization) options.optimization = {};
if (options.optimization.minimize === undefined)
options.optimization.minimize = false;
});
const heapSizeStart = process.memoryUsage().heapUsed;
const c = webpack(options);
const compilers = c.compilers ? c.compilers : [c];
compilers.forEach(c => {
const ifs = c.inputFileSystem;
c.inputFileSystem = Object.create(ifs);
c.inputFileSystem.readFile = function () {
const args = Array.prototype.slice.call(arguments);
const callback = args.pop();
ifs.readFile.apply(
ifs,
args.concat([
(err, result) => {
if (err) return callback(err);
if (!/\.(js|json|txt)$/.test(args[0]))
return callback(null, result);
callback(null, result.toString("utf-8").replace(/\r/g, ""));
}
])
);
};
});
c.run((err, stats) => {
if (err) return done(err);
if (/error$/.test(testName)) {
expect(stats.hasErrors()).toBe(true);
} else if (stats.hasErrors()) {
return done(
new Error(
stats.toString({
all: false,
errors: true,
errorStack: true,
errorDetails: true
})
)
);
}
const heapUsed = process.memoryUsage().heapUsed - heapSizeStart;
if (heapUsed > testConfig.heapSizeLimitBytes) {
return done(
new Error(`Out of memory limit with ${toMiB(heapUsed)} heap used`)
);
}
if (testConfig.validate) testConfig.validate(stats, stderr.toString());
done();
});
});
});
});
1 change: 1 addition & 0 deletions test/memoryLimitCases/json/index.js
@@ -0,0 +1 @@
const ctx = require.context("./src", false, /\.json$/);
1 change: 1 addition & 0 deletions test/memoryLimitCases/json/src/1.json
@@ -0,0 +1 @@
{"type":"Topology","box":[-73.9958013,45.3984821,-73.4742952,45.7047897],"transform":{"scale":[0.0005225512024048059,0.00030692144288576825],"translate":[-73.9958013,45.3984821]},"objects":{"boundary":{"type":"Polygon","arcs":[[0]],"id":"relation/8508277","properties":{"admin_level":"6","alt_name:1":"Montréal","boundary":"administrative","name":"Agglomération de Montréal","name:en":"Urban agglomeration of Montreal","name:fr":"Agglomération de Montréal","type":"boundary","wikidata":"Q2826806","wikipedia":"fr:Agglomération de Montréal","id":"relation/8508277"}}},"arcs":[[[992,804],[-2,23],[-15,31],[-3,32],[4,45],[12,24],[2,14],[5,9],[3,8],[-4,7],[-23,-3],[-4,4],[-8,-1],[-5,-2],[-22,-7],[-18,-7],[-10,-1],[-8,-25],[-5,-18],[-6,-11],[-11,-9],[-18,-14],[-29,-31],[-25,-20],[-6,-5],[-53,-44],[-17,-21],[-14,-17],[-17,-22],[-3,-9],[-6,-16],[-5,-24],[-2,-6],[-6,-22],[-13,-25],[-11,-21],[-5,-11],[-2,-3],[-12,-28],[-1,-3],[-1,-25],[-11,-22],[-2,-3],[-1,-4],[-3,-8],[0,-2],[-4,-6],[-6,-6],[-23,-7],[-7,-3],[-6,-3],[-14,-11],[-6,-11],[-11,-7],[-7,-3],[-3,-1],[-16,-17],[-11,-8],[-8,-5],[-3,-5],[-9,-22],[-11,-3],[-11,-8],[-5,-10],[-5,-5],[-4,-1],[-10,-3],[-27,3],[-20,4],[-11,9],[-8,0],[-10,7],[-15,-5],[-5,0],[-21,8],[-20,0],[-2,-2],[-3,-1],[-3,-4],[-7,-12],[-3,-3],[-1,-1],[-2,-1],[-2,1],[-6,12],[-8,4],[-3,5],[-1,5],[-7,1],[-14,1],[-7,0],[-8,3],[-11,6],[-7,5],[-7,6],[-11,-4],[-11,-9],[-6,-7],[-7,-12],[-8,-11],[-7,-9],[-21,-21],[-19,-13],[-14,-19],[-10,-19],[-5,-16],[-7,-13],[-11,-26],[-14,-17],[-15,-20],[-10,-6],[-12,-4],[-4,0],[5,-17],[0,-3],[1,-2],[1,-6],[3,-10],[2,-12],[2,-9],[2,-9],[2,-5],[2,-19],[0,-25],[10,-13],[17,-16],[14,-14],[5,-6],[6,-7],[2,-2],[1,0],[1,-1],[1,0],[11,-5],[6,-3],[2,-1],[6,0],[16,1],[21,2],[12,5],[13,3],[3,2],[6,3],[2,2],[8,7],[12,5],[5,2],[3,0],[4,0],[6,-2],[18,-9],[13,-5],[25,-6],[36,-6],[29,-3],[9,-2],[22,-5],[7,11],[4,7],[5,7],[5,4],[1,1],[3,4],[7,5],[7,5],[8,4],[9,6],[8,5],[12,8],[49,5],[14,1],[5,1],[13,2],[45,1],[12,1],[12,0],[4,0],[8,-1],[11,-2],[8,-3],[9,-3],[12,-5],[3,-2],[6,-3],[18,-10],[10,-6],[9,-3],[5,-1],[1,0],[7,-1],[2,0],[12,1],[13,1],[16,1],[6,1],[7,1],[36,4],[24,4],[15,4],[20,7],[13,8],[7,5],[4,3],[14,14],[9,15],[7,21],[2,7],[3,26],[1,14],[-1,23],[0,5],[0,5],[0,21],[-2,7],[-2,7],[-5,16],[-2,23],[-1,6],[-4,10],[7,5],[2,1],[1,3],[1,1],[2,2],[3,4],[-1,5],[2,2],[-2,10],[-3,16],[-8,45],[-2,7],[0,3],[-2,9],[0,3],[-4,29],[-2,10],[19,25],[14,32],[10,25],[14,35],[1,4],[0,17],[3,18],[-4,33],[-2,25],[3,20],[4,12],[17,40],[9,21],[4,11],[10,33]]]}
1 change: 1 addition & 0 deletions test/memoryLimitCases/json/src/2.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/memoryLimitCases/json/src/3.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/memoryLimitCases/json/src/4.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/memoryLimitCases/json/src/5.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/memoryLimitCases/json/src/6.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/memoryLimitCases/json/src/7.json

Large diffs are not rendered by default.

0 comments on commit 096efc3

Please sign in to comment.