You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
5.4 KiB
191 lines
5.4 KiB
/* |
|
MIT License http://www.opensource.org/licenses/mit-license.php |
|
Author Tobias Koppers @sokra |
|
*/ |
|
|
|
"use strict"; |
|
|
|
const { RawSource } = require("webpack-sources"); |
|
const ConcatenationScope = require("../ConcatenationScope"); |
|
const { UsageState } = require("../ExportsInfo"); |
|
const Generator = require("../Generator"); |
|
const RuntimeGlobals = require("../RuntimeGlobals"); |
|
|
|
/** @typedef {import("webpack-sources").Source} Source */ |
|
/** @typedef {import("../ExportsInfo")} ExportsInfo */ |
|
/** @typedef {import("../Generator").GenerateContext} GenerateContext */ |
|
/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ |
|
/** @typedef {import("../NormalModule")} NormalModule */ |
|
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ |
|
|
|
const stringifySafe = data => { |
|
const stringified = JSON.stringify(data); |
|
if (!stringified) { |
|
return undefined; // Invalid JSON |
|
} |
|
|
|
return stringified.replace(/\u2028|\u2029/g, str => |
|
str === "\u2029" ? "\\u2029" : "\\u2028" |
|
); // invalid in JavaScript but valid JSON |
|
}; |
|
|
|
/** |
|
* @param {Object} data data (always an object or array) |
|
* @param {ExportsInfo} exportsInfo exports info |
|
* @param {RuntimeSpec} runtime the runtime |
|
* @returns {Object} reduced data |
|
*/ |
|
const createObjectForExportsInfo = (data, exportsInfo, runtime) => { |
|
if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) |
|
return data; |
|
const isArray = Array.isArray(data); |
|
const reducedData = isArray ? [] : {}; |
|
for (const key of Object.keys(data)) { |
|
const exportInfo = exportsInfo.getReadOnlyExportInfo(key); |
|
const used = exportInfo.getUsed(runtime); |
|
if (used === UsageState.Unused) continue; |
|
|
|
let value; |
|
if (used === UsageState.OnlyPropertiesUsed && exportInfo.exportsInfo) { |
|
value = createObjectForExportsInfo( |
|
data[key], |
|
exportInfo.exportsInfo, |
|
runtime |
|
); |
|
} else { |
|
value = data[key]; |
|
} |
|
const name = exportInfo.getUsedName(key, runtime); |
|
reducedData[name] = value; |
|
} |
|
if (isArray) { |
|
let arrayLengthWhenUsed = |
|
exportsInfo.getReadOnlyExportInfo("length").getUsed(runtime) !== |
|
UsageState.Unused |
|
? data.length |
|
: undefined; |
|
|
|
let sizeObjectMinusArray = 0; |
|
for (let i = 0; i < reducedData.length; i++) { |
|
if (reducedData[i] === undefined) { |
|
sizeObjectMinusArray -= 2; |
|
} else { |
|
sizeObjectMinusArray += `${i}`.length + 3; |
|
} |
|
} |
|
if (arrayLengthWhenUsed !== undefined) { |
|
sizeObjectMinusArray += |
|
`${arrayLengthWhenUsed}`.length + |
|
8 - |
|
(arrayLengthWhenUsed - reducedData.length) * 2; |
|
} |
|
if (sizeObjectMinusArray < 0) |
|
return Object.assign( |
|
arrayLengthWhenUsed === undefined |
|
? {} |
|
: { length: arrayLengthWhenUsed }, |
|
reducedData |
|
); |
|
const generatedLength = |
|
arrayLengthWhenUsed !== undefined |
|
? Math.max(arrayLengthWhenUsed, reducedData.length) |
|
: reducedData.length; |
|
for (let i = 0; i < generatedLength; i++) { |
|
if (reducedData[i] === undefined) { |
|
reducedData[i] = 0; |
|
} |
|
} |
|
} |
|
return reducedData; |
|
}; |
|
|
|
const TYPES = new Set(["javascript"]); |
|
|
|
class JsonGenerator extends Generator { |
|
/** |
|
* @param {NormalModule} module fresh module |
|
* @returns {Set<string>} available types (do not mutate) |
|
*/ |
|
getTypes(module) { |
|
return TYPES; |
|
} |
|
|
|
/** |
|
* @param {NormalModule} module the module |
|
* @param {string=} type source type |
|
* @returns {number} estimate size of the module |
|
*/ |
|
getSize(module, type) { |
|
let data = |
|
module.buildInfo && |
|
module.buildInfo.jsonData && |
|
module.buildInfo.jsonData.get(); |
|
if (!data) return 0; |
|
return stringifySafe(data).length + 10; |
|
} |
|
|
|
/** |
|
* @param {NormalModule} module module for which the bailout reason should be determined |
|
* @param {ConcatenationBailoutReasonContext} context context |
|
* @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated |
|
*/ |
|
getConcatenationBailoutReason(module, context) { |
|
return undefined; |
|
} |
|
|
|
/** |
|
* @param {NormalModule} module module for which the code should be generated |
|
* @param {GenerateContext} generateContext context for generate |
|
* @returns {Source} generated code |
|
*/ |
|
generate( |
|
module, |
|
{ |
|
moduleGraph, |
|
runtimeTemplate, |
|
runtimeRequirements, |
|
runtime, |
|
concatenationScope |
|
} |
|
) { |
|
const data = |
|
module.buildInfo && |
|
module.buildInfo.jsonData && |
|
module.buildInfo.jsonData.get(); |
|
if (data === undefined) { |
|
return new RawSource( |
|
runtimeTemplate.missingModuleStatement({ |
|
request: module.rawRequest |
|
}) |
|
); |
|
} |
|
const exportsInfo = moduleGraph.getExportsInfo(module); |
|
let finalJson = |
|
typeof data === "object" && |
|
data && |
|
exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused |
|
? createObjectForExportsInfo(data, exportsInfo, runtime) |
|
: data; |
|
// Use JSON because JSON.parse() is much faster than JavaScript evaluation |
|
const jsonStr = stringifySafe(finalJson); |
|
const jsonExpr = |
|
jsonStr.length > 20 && typeof finalJson === "object" |
|
? `JSON.parse('${jsonStr.replace(/[\\']/g, "\\$&")}')` |
|
: jsonStr; |
|
let content; |
|
if (concatenationScope) { |
|
content = `${runtimeTemplate.supportsConst() ? "const" : "var"} ${ |
|
ConcatenationScope.NAMESPACE_OBJECT_EXPORT |
|
} = ${jsonExpr};`; |
|
concatenationScope.registerNamespaceExport( |
|
ConcatenationScope.NAMESPACE_OBJECT_EXPORT |
|
); |
|
} else { |
|
runtimeRequirements.add(RuntimeGlobals.module); |
|
content = `${module.moduleArgument}.exports = ${jsonExpr};`; |
|
} |
|
return new RawSource(content); |
|
} |
|
} |
|
|
|
module.exports = JsonGenerator;
|
|
|