JsonGenerator.js 6.0 KB

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