| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Tobias Koppers @sokra
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | const { RawSource } = require("webpack-sources");
|
|---|
| 9 | const ConcatenationScope = require("../ConcatenationScope");
|
|---|
| 10 | const { UsageState } = require("../ExportsInfo");
|
|---|
| 11 | const Generator = require("../Generator");
|
|---|
| 12 | const { JAVASCRIPT_TYPES } = require("../ModuleSourceTypeConstants");
|
|---|
| 13 | const RuntimeGlobals = require("../RuntimeGlobals");
|
|---|
| 14 |
|
|---|
| 15 | /** @typedef {import("webpack-sources").Source} Source */
|
|---|
| 16 | /** @typedef {import("../../declarations/WebpackOptions").JsonGeneratorOptions} JsonGeneratorOptions */
|
|---|
| 17 | /** @typedef {import("../ExportsInfo")} ExportsInfo */
|
|---|
| 18 | /** @typedef {import("../Generator").GenerateContext} GenerateContext */
|
|---|
| 19 | /** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
|
|---|
| 20 | /** @typedef {import("../util/Hash")} Hash */
|
|---|
| 21 | /** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
|
|---|
| 22 | /** @typedef {import("../Module").SourceType} SourceType */
|
|---|
| 23 | /** @typedef {import("../Module").SourceTypes} SourceTypes */
|
|---|
| 24 | /** @typedef {import("../NormalModule")} NormalModule */
|
|---|
| 25 | /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
|
|---|
| 26 | /** @typedef {import("../util/fs").JsonArray} JsonArray */
|
|---|
| 27 | /** @typedef {import("../util/fs").JsonObject} JsonObject */
|
|---|
| 28 | /** @typedef {import("../util/fs").JsonValue} JsonValue */
|
|---|
| 29 |
|
|---|
| 30 | /**
|
|---|
| 31 | * Returns stringified data.
|
|---|
| 32 | * @param {JsonValue} data Raw JSON data
|
|---|
| 33 | * @returns {undefined | string} stringified data
|
|---|
| 34 | */
|
|---|
| 35 | const stringifySafe = (data) => {
|
|---|
| 36 | const stringified = JSON.stringify(data);
|
|---|
| 37 | if (!stringified) {
|
|---|
| 38 | return; // Invalid JSON
|
|---|
| 39 | }
|
|---|
| 40 |
|
|---|
| 41 | return stringified.replace(/\u2028|\u2029/g, (str) =>
|
|---|
| 42 | str === "\u2029" ? "\\u2029" : "\\u2028"
|
|---|
| 43 | ); // invalid in JavaScript but valid JSON
|
|---|
| 44 | };
|
|---|
| 45 |
|
|---|
| 46 | /**
|
|---|
| 47 | * Creates an object for exports info.
|
|---|
| 48 | * @param {JsonObject | JsonArray} data Raw JSON data (always an object or array)
|
|---|
| 49 | * @param {ExportsInfo} exportsInfo exports info
|
|---|
| 50 | * @param {RuntimeSpec} runtime the runtime
|
|---|
| 51 | * @returns {JsonObject | JsonArray} reduced data
|
|---|
| 52 | */
|
|---|
| 53 | const createObjectForExportsInfo = (data, exportsInfo, runtime) => {
|
|---|
| 54 | if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) {
|
|---|
| 55 | return data;
|
|---|
| 56 | }
|
|---|
| 57 | const isArray = Array.isArray(data);
|
|---|
| 58 | /** @type {JsonObject | JsonArray} */
|
|---|
| 59 | const reducedData = isArray ? [] : {};
|
|---|
| 60 | for (const key of Object.keys(data)) {
|
|---|
| 61 | const exportInfo = exportsInfo.getReadOnlyExportInfo(key);
|
|---|
| 62 | const used = exportInfo.getUsed(runtime);
|
|---|
| 63 | if (used === UsageState.Unused) continue;
|
|---|
| 64 |
|
|---|
| 65 | // The real type is `JsonObject | JsonArray`, but typescript doesn't work `Object.keys(['string', 'other-string', 'etc'])` properly
|
|---|
| 66 | const newData = /** @type {JsonObject} */ (data)[key];
|
|---|
| 67 | const value =
|
|---|
| 68 | used === UsageState.OnlyPropertiesUsed &&
|
|---|
| 69 | exportInfo.exportsInfo &&
|
|---|
| 70 | typeof newData === "object" &&
|
|---|
| 71 | newData
|
|---|
| 72 | ? createObjectForExportsInfo(newData, exportInfo.exportsInfo, runtime)
|
|---|
| 73 | : newData;
|
|---|
| 74 |
|
|---|
| 75 | const name = /** @type {string} */ (exportInfo.getUsedName(key, runtime));
|
|---|
| 76 | /** @type {JsonObject} */
|
|---|
| 77 | (reducedData)[name] = value;
|
|---|
| 78 | }
|
|---|
| 79 | if (isArray) {
|
|---|
| 80 | const arrayLengthWhenUsed =
|
|---|
| 81 | exportsInfo.getReadOnlyExportInfo("length").getUsed(runtime) !==
|
|---|
| 82 | UsageState.Unused
|
|---|
| 83 | ? data.length
|
|---|
| 84 | : undefined;
|
|---|
| 85 |
|
|---|
| 86 | let sizeObjectMinusArray = 0;
|
|---|
| 87 | const reducedDataLength =
|
|---|
| 88 | /** @type {JsonArray} */
|
|---|
| 89 | (reducedData).length;
|
|---|
| 90 | for (let i = 0; i < reducedDataLength; i++) {
|
|---|
| 91 | if (/** @type {JsonArray} */ (reducedData)[i] === undefined) {
|
|---|
| 92 | sizeObjectMinusArray -= 2;
|
|---|
| 93 | } else {
|
|---|
| 94 | sizeObjectMinusArray += `${i}`.length + 3;
|
|---|
| 95 | }
|
|---|
| 96 | }
|
|---|
| 97 | if (arrayLengthWhenUsed !== undefined) {
|
|---|
| 98 | sizeObjectMinusArray +=
|
|---|
| 99 | `${arrayLengthWhenUsed}`.length +
|
|---|
| 100 | 8 -
|
|---|
| 101 | (arrayLengthWhenUsed - reducedDataLength) * 2;
|
|---|
| 102 | }
|
|---|
| 103 | if (sizeObjectMinusArray < 0) {
|
|---|
| 104 | return Object.assign(
|
|---|
| 105 | arrayLengthWhenUsed === undefined
|
|---|
| 106 | ? {}
|
|---|
| 107 | : { length: arrayLengthWhenUsed },
|
|---|
| 108 | reducedData
|
|---|
| 109 | );
|
|---|
| 110 | }
|
|---|
| 111 | /** @type {number} */
|
|---|
| 112 | const generatedLength =
|
|---|
| 113 | arrayLengthWhenUsed !== undefined
|
|---|
| 114 | ? Math.max(arrayLengthWhenUsed, reducedDataLength)
|
|---|
| 115 | : reducedDataLength;
|
|---|
| 116 | for (let i = 0; i < generatedLength; i++) {
|
|---|
| 117 | if (/** @type {JsonArray} */ (reducedData)[i] === undefined) {
|
|---|
| 118 | /** @type {JsonArray} */
|
|---|
| 119 | (reducedData)[i] = 0;
|
|---|
| 120 | }
|
|---|
| 121 | }
|
|---|
| 122 | }
|
|---|
| 123 | return reducedData;
|
|---|
| 124 | };
|
|---|
| 125 |
|
|---|
| 126 | class JsonGenerator extends Generator {
|
|---|
| 127 | /**
|
|---|
| 128 | * Creates an instance of JsonGenerator.
|
|---|
| 129 | * @param {JsonGeneratorOptions} options options
|
|---|
| 130 | */
|
|---|
| 131 | constructor(options) {
|
|---|
| 132 | super();
|
|---|
| 133 | /** @type {JsonGeneratorOptions} */
|
|---|
| 134 | this.options = options;
|
|---|
| 135 | }
|
|---|
| 136 |
|
|---|
| 137 | /**
|
|---|
| 138 | * Returns the source types available for this module.
|
|---|
| 139 | * @param {NormalModule} module fresh module
|
|---|
| 140 | * @returns {SourceTypes} available types (do not mutate)
|
|---|
| 141 | */
|
|---|
| 142 | getTypes(module) {
|
|---|
| 143 | return JAVASCRIPT_TYPES;
|
|---|
| 144 | }
|
|---|
| 145 |
|
|---|
| 146 | /**
|
|---|
| 147 | * Returns the estimated size for the requested source type.
|
|---|
| 148 | * @param {NormalModule} module the module
|
|---|
| 149 | * @param {SourceType=} type source type
|
|---|
| 150 | * @returns {number} estimate size of the module
|
|---|
| 151 | */
|
|---|
| 152 | getSize(module, type) {
|
|---|
| 153 | /** @type {JsonValue | undefined} */
|
|---|
| 154 | const data =
|
|---|
| 155 | module.buildInfo &&
|
|---|
| 156 | module.buildInfo.jsonData &&
|
|---|
| 157 | module.buildInfo.jsonData.get();
|
|---|
| 158 | if (!data) return 0;
|
|---|
| 159 | return /** @type {string} */ (stringifySafe(data)).length + 10;
|
|---|
| 160 | }
|
|---|
| 161 |
|
|---|
| 162 | /**
|
|---|
| 163 | * Returns the reason this module cannot be concatenated, when one exists.
|
|---|
| 164 | * @param {NormalModule} module module for which the bailout reason should be determined
|
|---|
| 165 | * @param {ConcatenationBailoutReasonContext} context context
|
|---|
| 166 | * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
|
|---|
| 167 | */
|
|---|
| 168 | getConcatenationBailoutReason(module, context) {
|
|---|
| 169 | return undefined;
|
|---|
| 170 | }
|
|---|
| 171 |
|
|---|
| 172 | /**
|
|---|
| 173 | * Generates generated code for this runtime module.
|
|---|
| 174 | * @param {NormalModule} module module for which the code should be generated
|
|---|
| 175 | * @param {GenerateContext} generateContext context for generate
|
|---|
| 176 | * @returns {Source | null} generated code
|
|---|
| 177 | */
|
|---|
| 178 | generate(
|
|---|
| 179 | module,
|
|---|
| 180 | {
|
|---|
| 181 | moduleGraph,
|
|---|
| 182 | runtimeTemplate,
|
|---|
| 183 | runtimeRequirements,
|
|---|
| 184 | runtime,
|
|---|
| 185 | concatenationScope
|
|---|
| 186 | }
|
|---|
| 187 | ) {
|
|---|
| 188 | /** @type {JsonValue | undefined} */
|
|---|
| 189 | const data =
|
|---|
| 190 | module.buildInfo &&
|
|---|
| 191 | module.buildInfo.jsonData &&
|
|---|
| 192 | module.buildInfo.jsonData.get();
|
|---|
| 193 | if (data === undefined) {
|
|---|
| 194 | return new RawSource(
|
|---|
| 195 | runtimeTemplate.missingModuleStatement({
|
|---|
| 196 | request: module.rawRequest
|
|---|
| 197 | })
|
|---|
| 198 | );
|
|---|
| 199 | }
|
|---|
| 200 | const exportsInfo = moduleGraph.getExportsInfo(module);
|
|---|
| 201 | /** @type {JsonValue} */
|
|---|
| 202 | const finalJson =
|
|---|
| 203 | typeof data === "object" &&
|
|---|
| 204 | data &&
|
|---|
| 205 | exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused
|
|---|
| 206 | ? createObjectForExportsInfo(data, exportsInfo, runtime)
|
|---|
| 207 | : data;
|
|---|
| 208 | // Use JSON because JSON.parse() is much faster than JavaScript evaluation
|
|---|
| 209 | const jsonStr = /** @type {string} */ (stringifySafe(finalJson));
|
|---|
| 210 | const jsonExpr =
|
|---|
| 211 | this.options.JSONParse &&
|
|---|
| 212 | jsonStr.length > 20 &&
|
|---|
| 213 | typeof finalJson === "object"
|
|---|
| 214 | ? `/*#__PURE__*/JSON.parse('${jsonStr.replace(/[\\']/g, "\\$&")}')`
|
|---|
| 215 | : jsonStr.replace(/"__proto__":/g, '["__proto__"]:');
|
|---|
| 216 | /** @type {string} */
|
|---|
| 217 | let content;
|
|---|
| 218 | if (concatenationScope) {
|
|---|
| 219 | content = `${runtimeTemplate.renderConst()} ${
|
|---|
| 220 | ConcatenationScope.NAMESPACE_OBJECT_EXPORT
|
|---|
| 221 | } = ${jsonExpr};`;
|
|---|
| 222 | concatenationScope.registerNamespaceExport(
|
|---|
| 223 | ConcatenationScope.NAMESPACE_OBJECT_EXPORT
|
|---|
| 224 | );
|
|---|
| 225 | } else {
|
|---|
| 226 | runtimeRequirements.add(RuntimeGlobals.module);
|
|---|
| 227 | content = `${module.moduleArgument}.exports = ${jsonExpr};`;
|
|---|
| 228 | }
|
|---|
| 229 | return new RawSource(content);
|
|---|
| 230 | }
|
|---|
| 231 |
|
|---|
| 232 | /**
|
|---|
| 233 | * Generates fallback output for the provided error condition.
|
|---|
| 234 | * @param {Error} error the error
|
|---|
| 235 | * @param {NormalModule} module module for which the code should be generated
|
|---|
| 236 | * @param {GenerateContext} generateContext context for generate
|
|---|
| 237 | * @returns {Source | null} generated code
|
|---|
| 238 | */
|
|---|
| 239 | generateError(error, module, generateContext) {
|
|---|
| 240 | return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
|
|---|
| 241 | }
|
|---|
| 242 |
|
|---|
| 243 | /**
|
|---|
| 244 | * Updates the hash with the data contributed by this instance.
|
|---|
| 245 | * @param {Hash} hash hash that will be modified
|
|---|
| 246 | * @param {UpdateHashContext} updateHashContext context for updating hash
|
|---|
| 247 | */
|
|---|
| 248 | updateHash(hash, updateHashContext) {
|
|---|
| 249 | if (this.options.JSONParse) {
|
|---|
| 250 | hash.update("json-parse");
|
|---|
| 251 | }
|
|---|
| 252 | }
|
|---|
| 253 | }
|
|---|
| 254 |
|
|---|
| 255 | module.exports = JsonGenerator;
|
|---|