Projekt

Obecné

Profil

Stáhnout (7.96 KB) Statistiky
| Větev: | Revize:
1
/*
2
	MIT License http://www.opensource.org/licenses/mit-license.php
3
	Author Tobias Koppers @sokra
4
*/
5
/** @typedef {import("./Module")} Module */
6
/** @typedef {import("./Chunk")} Chunk */
7
/** @typedef {import("./ModuleTemplate")} ModuleTemplate */
8
/** @typedef {import("webpack-sources").ConcatSource} ConcatSource */
9

    
10
const { ConcatSource } = require("webpack-sources");
11
const HotUpdateChunk = require("./HotUpdateChunk");
12

    
13
const START_LOWERCASE_ALPHABET_CODE = "a".charCodeAt(0);
14
const START_UPPERCASE_ALPHABET_CODE = "A".charCodeAt(0);
15
const DELTA_A_TO_Z = "z".charCodeAt(0) - START_LOWERCASE_ALPHABET_CODE + 1;
16
const FUNCTION_CONTENT_REGEX = /^function\s?\(\)\s?\{\r?\n?|\r?\n?\}$/g;
17
const INDENT_MULTILINE_REGEX = /^\t/gm;
18
const LINE_SEPARATOR_REGEX = /\r?\n/g;
19
const IDENTIFIER_NAME_REPLACE_REGEX = /^([^a-zA-Z$_])/;
20
const IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX = /[^a-zA-Z0-9$]+/g;
21
const COMMENT_END_REGEX = /\*\//g;
22
const PATH_NAME_NORMALIZE_REPLACE_REGEX = /[^a-zA-Z0-9_!§$()=\-]+/g;
23
const MATCH_PADDED_HYPHENS_REPLACE_REGEX = /^-|-$/g;
24

    
25
/** @typedef {import("webpack-sources").Source} Source */
26

    
27
/**
28
 * @typedef {Object} HasId
29
 * @property {number | string} id
30
 */
31

    
32
/**
33
 * @typedef {function(Module, number): boolean} ModuleFilterPredicate
34
 */
35

    
36
/**
37
 * @param {HasId} a first id object to be sorted
38
 * @param {HasId} b second id object to be sorted against
39
 * @returns {-1|0|1} the sort value
40
 */
41
const stringifyIdSortPredicate = (a, b) => {
42
	const aId = a.id + "";
43
	const bId = b.id + "";
44
	if (aId < bId) return -1;
45
	if (aId > bId) return 1;
46
	return 0;
47
};
48

    
49
class Template {
50
	/**
51
	 *
52
	 * @param {Function} fn a runtime function (.runtime.js) "template"
53
	 * @returns {string} the updated and normalized function string
54
	 */
55
	static getFunctionContent(fn) {
56
		return fn
57
			.toString()
58
			.replace(FUNCTION_CONTENT_REGEX, "")
59
			.replace(INDENT_MULTILINE_REGEX, "")
60
			.replace(LINE_SEPARATOR_REGEX, "\n");
61
	}
62

    
63
	/**
64
	 * @param {string} str the string converted to identifier
65
	 * @returns {string} created identifier
66
	 */
67
	static toIdentifier(str) {
68
		if (typeof str !== "string") return "";
69
		return str
70
			.replace(IDENTIFIER_NAME_REPLACE_REGEX, "_$1")
71
			.replace(IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX, "_");
72
	}
73
	/**
74
	 *
75
	 * @param {string} str string to be converted to commented in bundle code
76
	 * @returns {string} returns a commented version of string
77
	 */
78
	static toComment(str) {
79
		if (!str) return "";
80
		return `/*! ${str.replace(COMMENT_END_REGEX, "* /")} */`;
81
	}
82

    
83
	/**
84
	 *
85
	 * @param {string} str string to be converted to "normal comment"
86
	 * @returns {string} returns a commented version of string
87
	 */
88
	static toNormalComment(str) {
89
		if (!str) return "";
90
		return `/* ${str.replace(COMMENT_END_REGEX, "* /")} */`;
91
	}
92

    
93
	/**
94
	 * @param {string} str string path to be normalized
95
	 * @returns {string} normalized bundle-safe path
96
	 */
97
	static toPath(str) {
98
		if (typeof str !== "string") return "";
99
		return str
100
			.replace(PATH_NAME_NORMALIZE_REPLACE_REGEX, "-")
101
			.replace(MATCH_PADDED_HYPHENS_REPLACE_REGEX, "");
102
	}
103

    
104
	// map number to a single character a-z, A-Z or <_ + number> if number is too big
105
	/**
106
	 *
107
	 * @param {number} n number to convert to ident
108
	 * @returns {string} returns single character ident
109
	 */
110
	static numberToIdentifer(n) {
111
		// lower case
112
		if (n < DELTA_A_TO_Z) {
113
			return String.fromCharCode(START_LOWERCASE_ALPHABET_CODE + n);
114
		}
115

    
116
		// upper case
117
		if (n < DELTA_A_TO_Z * 2) {
118
			return String.fromCharCode(
119
				START_UPPERCASE_ALPHABET_CODE + n - DELTA_A_TO_Z
120
			);
121
		}
122

    
123
		// use multiple letters
124
		return (
125
			Template.numberToIdentifer(n % (2 * DELTA_A_TO_Z)) +
126
			Template.numberToIdentifer(Math.floor(n / (2 * DELTA_A_TO_Z)))
127
		);
128
	}
129

    
130
	/**
131
	 *
132
	 * @param {string | string[]} s string to convert to identity
133
	 * @returns {string} converted identity
134
	 */
135
	static indent(s) {
136
		if (Array.isArray(s)) {
137
			return s.map(Template.indent).join("\n");
138
		} else {
139
			const str = s.trimRight();
140
			if (!str) return "";
141
			const ind = str[0] === "\n" ? "" : "\t";
142
			return ind + str.replace(/\n([^\n])/g, "\n\t$1");
143
		}
144
	}
145

    
146
	/**
147
	 *
148
	 * @param {string|string[]} s string to create prefix for
149
	 * @param {string} prefix prefix to compose
150
	 * @returns {string} returns new prefix string
151
	 */
152
	static prefix(s, prefix) {
153
		const str = Template.asString(s).trim();
154
		if (!str) return "";
155
		const ind = str[0] === "\n" ? "" : prefix;
156
		return ind + str.replace(/\n([^\n])/g, "\n" + prefix + "$1");
157
	}
158

    
159
	/**
160
	 *
161
	 * @param {string|string[]} str string or string collection
162
	 * @returns {string} returns a single string from array
163
	 */
164
	static asString(str) {
165
		if (Array.isArray(str)) {
166
			return str.join("\n");
167
		}
168
		return str;
169
	}
170

    
171
	/**
172
	 * @typedef {Object} WithId
173
	 * @property {string|number} id
174
	 */
175

    
176
	/**
177
	 * @param {WithId[]} modules a collection of modules to get array bounds for
178
	 * @returns {[number, number] | false} returns the upper and lower array bounds
179
	 * or false if not every module has a number based id
180
	 */
181
	static getModulesArrayBounds(modules) {
182
		let maxId = -Infinity;
183
		let minId = Infinity;
184
		for (const module of modules) {
185
			if (typeof module.id !== "number") return false;
186
			if (maxId < module.id) maxId = /** @type {number} */ (module.id);
187
			if (minId > module.id) minId = /** @type {number} */ (module.id);
188
		}
189
		if (minId < 16 + ("" + minId).length) {
190
			// add minId x ',' instead of 'Array(minId).concat(…)'
191
			minId = 0;
192
		}
193
		const objectOverhead = modules
194
			.map(module => (module.id + "").length + 2)
195
			.reduce((a, b) => a + b, -1);
196
		const arrayOverhead =
197
			minId === 0 ? maxId : 16 + ("" + minId).length + maxId;
198
		return arrayOverhead < objectOverhead ? [minId, maxId] : false;
199
	}
200

    
201
	/**
202
	 * @param {Chunk} chunk chunk whose modules will be rendered
203
	 * @param {ModuleFilterPredicate} filterFn function used to filter modules from chunk to render
204
	 * @param {ModuleTemplate} moduleTemplate ModuleTemplate instance used to render modules
205
	 * @param {TODO | TODO[]} dependencyTemplates templates needed for each module to render dependencies
206
	 * @param {string=} prefix applying prefix strings
207
	 * @returns {ConcatSource} rendered chunk modules in a Source object
208
	 */
209
	static renderChunkModules(
210
		chunk,
211
		filterFn,
212
		moduleTemplate,
213
		dependencyTemplates,
214
		prefix = ""
215
	) {
216
		const source = new ConcatSource();
217
		const modules = chunk.getModules().filter(filterFn);
218
		let removedModules;
219
		if (chunk instanceof HotUpdateChunk) {
220
			removedModules = chunk.removedModules;
221
		}
222
		if (
223
			modules.length === 0 &&
224
			(!removedModules || removedModules.length === 0)
225
		) {
226
			source.add("[]");
227
			return source;
228
		}
229
		/** @type {{id: string|number, source: Source|string}[]} */
230
		const allModules = modules.map(module => {
231
			return {
232
				id: module.id,
233
				source: moduleTemplate.render(module, dependencyTemplates, {
234
					chunk
235
				})
236
			};
237
		});
238
		if (removedModules && removedModules.length > 0) {
239
			for (const id of removedModules) {
240
				allModules.push({
241
					id,
242
					source: "false"
243
				});
244
			}
245
		}
246
		const bounds = Template.getModulesArrayBounds(allModules);
247
		if (bounds) {
248
			// Render a spare array
249
			const minId = bounds[0];
250
			const maxId = bounds[1];
251
			if (minId !== 0) {
252
				source.add(`Array(${minId}).concat(`);
253
			}
254
			source.add("[\n");
255
			/** @type {Map<string|number, {id: string|number, source: Source|string}>} */
256
			const modules = new Map();
257
			for (const module of allModules) {
258
				modules.set(module.id, module);
259
			}
260
			for (let idx = minId; idx <= maxId; idx++) {
261
				const module = modules.get(idx);
262
				if (idx !== minId) {
263
					source.add(",\n");
264
				}
265
				source.add(`/* ${idx} */`);
266
				if (module) {
267
					source.add("\n");
268
					source.add(module.source);
269
				}
270
			}
271
			source.add("\n" + prefix + "]");
272
			if (minId !== 0) {
273
				source.add(")");
274
			}
275
		} else {
276
			// Render an object
277
			source.add("{\n");
278
			allModules.sort(stringifyIdSortPredicate).forEach((module, idx) => {
279
				if (idx !== 0) {
280
					source.add(",\n");
281
				}
282
				source.add(`\n/***/ ${JSON.stringify(module.id)}:\n`);
283
				source.add(module.source);
284
			});
285
			source.add(`\n\n${prefix}}`);
286
		}
287
		return source;
288
	}
289
}
290

    
291
module.exports = Template;
(126-126/144)