JsonGenerator.js 7.2 KB

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