UNPKG

5.41 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { RawSource } = require("webpack-sources");
9const ConcatenationScope = require("../ConcatenationScope");
10const { UsageState } = require("../ExportsInfo");
11const Generator = require("../Generator");
12const RuntimeGlobals = require("../RuntimeGlobals");
13
14/** @typedef {import("webpack-sources").Source} Source */
15/** @typedef {import("../ExportsInfo")} ExportsInfo */
16/** @typedef {import("../Generator").GenerateContext} GenerateContext */
17/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
18/** @typedef {import("../NormalModule")} NormalModule */
19/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
20
21const stringifySafe = data => {
22 const stringified = JSON.stringify(data);
23 if (!stringified) {
24 return undefined; // Invalid JSON
25 }
26
27 return stringified.replace(/\u2028|\u2029/g, str =>
28 str === "\u2029" ? "\\u2029" : "\\u2028"
29 ); // invalid in JavaScript but valid JSON
30};
31
32/**
33 * @param {Object} data data (always an object or array)
34 * @param {ExportsInfo} exportsInfo exports info
35 * @param {RuntimeSpec} runtime the runtime
36 * @returns {Object} reduced data
37 */
38const createObjectForExportsInfo = (data, exportsInfo, runtime) => {
39 if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused)
40 return data;
41 const isArray = Array.isArray(data);
42 const reducedData = isArray ? [] : {};
43 for (const key of Object.keys(data)) {
44 const exportInfo = exportsInfo.getReadOnlyExportInfo(key);
45 const used = exportInfo.getUsed(runtime);
46 if (used === UsageState.Unused) continue;
47
48 let value;
49 if (used === UsageState.OnlyPropertiesUsed && exportInfo.exportsInfo) {
50 value = createObjectForExportsInfo(
51 data[key],
52 exportInfo.exportsInfo,
53 runtime
54 );
55 } else {
56 value = data[key];
57 }
58 const name = exportInfo.getUsedName(key, runtime);
59 reducedData[name] = value;
60 }
61 if (isArray) {
62 let arrayLengthWhenUsed =
63 exportsInfo.getReadOnlyExportInfo("length").getUsed(runtime) !==
64 UsageState.Unused
65 ? data.length
66 : undefined;
67
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 const generatedLength =
90 arrayLengthWhenUsed !== undefined
91 ? Math.max(arrayLengthWhenUsed, reducedData.length)
92 : reducedData.length;
93 for (let i = 0; i < generatedLength; i++) {
94 if (reducedData[i] === undefined) {
95 reducedData[i] = 0;
96 }
97 }
98 }
99 return reducedData;
100};
101
102const TYPES = new Set(["javascript"]);
103
104class JsonGenerator extends Generator {
105 /**
106 * @param {NormalModule} module fresh module
107 * @returns {Set<string>} available types (do not mutate)
108 */
109 getTypes(module) {
110 return TYPES;
111 }
112
113 /**
114 * @param {NormalModule} module the module
115 * @param {string=} type source type
116 * @returns {number} estimate size of the module
117 */
118 getSize(module, type) {
119 let data = module.buildInfo.jsonData;
120 if (!data) return 0;
121 return stringifySafe(data).length + 10;
122 }
123
124 /**
125 * @param {NormalModule} module module for which the bailout reason should be determined
126 * @param {ConcatenationBailoutReasonContext} context context
127 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
128 */
129 getConcatenationBailoutReason(module, context) {
130 return undefined;
131 }
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 const data = module.buildInfo.jsonData;
149 if (data === undefined) {
150 return new RawSource(
151 runtimeTemplate.missingModuleStatement({
152 request: module.rawRequest
153 })
154 );
155 }
156 const exportsInfo = moduleGraph.getExportsInfo(module);
157 let 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 = stringifySafe(finalJson);
165 const jsonExpr =
166 jsonStr.length > 20 && typeof finalJson === "object"
167 ? `JSON.parse('${jsonStr.replace(/[\\']/g, "\\$&")}')`
168 : jsonStr;
169 let content;
170 if (concatenationScope) {
171 content = `${runtimeTemplate.supportsConst() ? "const" : "var"} ${
172 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
173 } = ${jsonExpr};`;
174 concatenationScope.registerNamespaceExport(
175 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
176 );
177 } else {
178 runtimeRequirements.add(RuntimeGlobals.module);
179 content = `${module.moduleArgument}.exports = ${jsonExpr};`;
180 }
181 return new RawSource(content);
182 }
183}
184
185module.exports = JsonGenerator;