JsonGenerator.js 6.0 KB

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