JsonGenerator.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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 RuntimeGlobals = require("../RuntimeGlobals");
  11. /** @typedef {import("webpack-sources").Source} Source */
  12. /** @typedef {import("../ExportsInfo")} ExportsInfo */
  13. /** @typedef {import("../Generator").GenerateContext} GenerateContext */
  14. /** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
  15. /** @typedef {import("../NormalModule")} NormalModule */
  16. /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
  17. const stringifySafe = data => {
  18. const stringified = JSON.stringify(data);
  19. if (!stringified) {
  20. return undefined; // Invalid JSON
  21. }
  22. return stringified.replace(/\u2028|\u2029/g, str =>
  23. str === "\u2029" ? "\\u2029" : "\\u2028"
  24. ); // invalid in JavaScript but valid JSON
  25. };
  26. /**
  27. * @param {Object} data data (always an object or array)
  28. * @param {ExportsInfo} exportsInfo exports info
  29. * @param {RuntimeSpec} runtime the runtime
  30. * @returns {Object} reduced data
  31. */
  32. const createObjectForExportsInfo = (data, exportsInfo, runtime) => {
  33. if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused)
  34. return data;
  35. const isArray = Array.isArray(data);
  36. const reducedData = isArray ? [] : {};
  37. for (const key of Object.keys(data)) {
  38. const exportInfo = exportsInfo.getReadOnlyExportInfo(key);
  39. const used = exportInfo.getUsed(runtime);
  40. if (used === UsageState.Unused) continue;
  41. let value;
  42. if (used === UsageState.OnlyPropertiesUsed && exportInfo.exportsInfo) {
  43. value = createObjectForExportsInfo(
  44. data[key],
  45. exportInfo.exportsInfo,
  46. runtime
  47. );
  48. } else {
  49. value = data[key];
  50. }
  51. const name = exportInfo.getUsedName(key, runtime);
  52. reducedData[name] = value;
  53. }
  54. if (isArray) {
  55. let arrayLengthWhenUsed =
  56. exportsInfo.getReadOnlyExportInfo("length").getUsed(runtime) !==
  57. UsageState.Unused
  58. ? data.length
  59. : undefined;
  60. let sizeObjectMinusArray = 0;
  61. for (let i = 0; i < reducedData.length; i++) {
  62. if (reducedData[i] === undefined) {
  63. sizeObjectMinusArray -= 2;
  64. } else {
  65. sizeObjectMinusArray += `${i}`.length + 3;
  66. }
  67. }
  68. if (arrayLengthWhenUsed !== undefined) {
  69. sizeObjectMinusArray +=
  70. `${arrayLengthWhenUsed}`.length +
  71. 8 -
  72. (arrayLengthWhenUsed - reducedData.length) * 2;
  73. }
  74. if (sizeObjectMinusArray < 0)
  75. return Object.assign(
  76. arrayLengthWhenUsed === undefined
  77. ? {}
  78. : { length: arrayLengthWhenUsed },
  79. reducedData
  80. );
  81. const generatedLength =
  82. arrayLengthWhenUsed !== undefined
  83. ? Math.max(arrayLengthWhenUsed, reducedData.length)
  84. : reducedData.length;
  85. for (let i = 0; i < generatedLength; i++) {
  86. if (reducedData[i] === undefined) {
  87. reducedData[i] = 0;
  88. }
  89. }
  90. }
  91. return reducedData;
  92. };
  93. const TYPES = new Set(["javascript"]);
  94. class JsonGenerator extends Generator {
  95. /**
  96. * @param {NormalModule} module fresh module
  97. * @returns {Set<string>} available types (do not mutate)
  98. */
  99. getTypes(module) {
  100. return TYPES;
  101. }
  102. /**
  103. * @param {NormalModule} module the module
  104. * @param {string=} type source type
  105. * @returns {number} estimate size of the module
  106. */
  107. getSize(module, type) {
  108. let data =
  109. module.buildInfo &&
  110. module.buildInfo.jsonData &&
  111. module.buildInfo.jsonData.get();
  112. if (!data) return 0;
  113. return stringifySafe(data).length + 10;
  114. }
  115. /**
  116. * @param {NormalModule} module module for which the bailout reason should be determined
  117. * @param {ConcatenationBailoutReasonContext} context context
  118. * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
  119. */
  120. getConcatenationBailoutReason(module, context) {
  121. return undefined;
  122. }
  123. /**
  124. * @param {NormalModule} module module for which the code should be generated
  125. * @param {GenerateContext} generateContext context for generate
  126. * @returns {Source} generated code
  127. */
  128. generate(
  129. module,
  130. {
  131. moduleGraph,
  132. runtimeTemplate,
  133. runtimeRequirements,
  134. runtime,
  135. concatenationScope
  136. }
  137. ) {
  138. const data =
  139. module.buildInfo &&
  140. module.buildInfo.jsonData &&
  141. module.buildInfo.jsonData.get();
  142. if (data === undefined) {
  143. return new RawSource(
  144. runtimeTemplate.missingModuleStatement({
  145. request: module.rawRequest
  146. })
  147. );
  148. }
  149. const exportsInfo = moduleGraph.getExportsInfo(module);
  150. let finalJson =
  151. typeof data === "object" &&
  152. data &&
  153. exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused
  154. ? createObjectForExportsInfo(data, exportsInfo, runtime)
  155. : data;
  156. // Use JSON because JSON.parse() is much faster than JavaScript evaluation
  157. const jsonStr = stringifySafe(finalJson);
  158. const jsonExpr =
  159. jsonStr.length > 20 && typeof finalJson === "object"
  160. ? `JSON.parse('${jsonStr.replace(/[\\']/g, "\\$&")}')`
  161. : jsonStr;
  162. let content;
  163. if (concatenationScope) {
  164. content = `${runtimeTemplate.supportsConst() ? "const" : "var"} ${
  165. ConcatenationScope.NAMESPACE_OBJECT_EXPORT
  166. } = ${jsonExpr};`;
  167. concatenationScope.registerNamespaceExport(
  168. ConcatenationScope.NAMESPACE_OBJECT_EXPORT
  169. );
  170. } else {
  171. runtimeRequirements.add(RuntimeGlobals.module);
  172. content = `${module.moduleArgument}.exports = ${jsonExpr};`;
  173. }
  174. return new RawSource(content);
  175. }
  176. }
  177. module.exports = JsonGenerator;