UNPKG

5.87 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5"use strict";
6
7const { RawSource, ReplaceSource } = require("webpack-sources");
8
9// TODO: clean up this file
10// replace with newer constructs
11
12// TODO: remove DependencyVariables and replace them with something better
13
14class JavascriptGenerator {
15 generate(module, dependencyTemplates, runtimeTemplate) {
16 const originalSource = module.originalSource();
17 if (!originalSource) {
18 return new RawSource("throw new Error('No source available');");
19 }
20
21 const source = new ReplaceSource(originalSource);
22
23 this.sourceBlock(
24 module,
25 module,
26 [],
27 dependencyTemplates,
28 source,
29 runtimeTemplate
30 );
31
32 return source;
33 }
34
35 sourceBlock(
36 module,
37 block,
38 availableVars,
39 dependencyTemplates,
40 source,
41 runtimeTemplate
42 ) {
43 for (const dependency of block.dependencies) {
44 this.sourceDependency(
45 dependency,
46 dependencyTemplates,
47 source,
48 runtimeTemplate
49 );
50 }
51
52 /**
53 * Get the variables of all blocks that we need to inject.
54 * These will contain the variable name and its expression.
55 * The name will be added as a parameter in a IIFE the expression as its value.
56 */
57 const vars = block.variables.reduce((result, value) => {
58 const variable = this.sourceVariables(
59 value,
60 availableVars,
61 dependencyTemplates,
62 runtimeTemplate
63 );
64
65 if (variable) {
66 result.push(variable);
67 }
68
69 return result;
70 }, []);
71
72 /**
73 * if we actually have variables
74 * this is important as how #splitVariablesInUniqueNamedChunks works
75 * it will always return an array in an array which would lead to a IIFE wrapper around
76 * a module if we do this with an empty vars array.
77 */
78 if (vars.length > 0) {
79 /**
80 * Split all variables up into chunks of unique names.
81 * e.g. imagine you have the following variable names that need to be injected:
82 * [foo, bar, baz, foo, some, more]
83 * we can not inject "foo" twice, therefore we just make two IIFEs like so:
84 * (function(foo, bar, baz){
85 * (function(foo, some, more){
86 * …
87 * }(…));
88 * }(…));
89 *
90 * "splitVariablesInUniqueNamedChunks" splits the variables shown above up to this:
91 * [[foo, bar, baz], [foo, some, more]]
92 */
93 const injectionVariableChunks = this.splitVariablesInUniqueNamedChunks(
94 vars
95 );
96
97 // create all the beginnings of IIFEs
98 const functionWrapperStarts = injectionVariableChunks.map(
99 variableChunk => {
100 return this.variableInjectionFunctionWrapperStartCode(
101 variableChunk.map(variable => variable.name)
102 );
103 }
104 );
105
106 // and all the ends
107 const functionWrapperEnds = injectionVariableChunks.map(variableChunk => {
108 return this.variableInjectionFunctionWrapperEndCode(
109 module,
110 variableChunk.map(variable => variable.expression),
111 block
112 );
113 });
114
115 // join them to one big string
116 const varStartCode = functionWrapperStarts.join("");
117
118 // reverse the ends first before joining them, as the last added must be the inner most
119 const varEndCode = functionWrapperEnds.reverse().join("");
120
121 // if we have anything, add it to the source
122 if (varStartCode && varEndCode) {
123 const start = block.range ? block.range[0] : -10;
124 const end = block.range
125 ? block.range[1]
126 : module.originalSource().size() + 1;
127 source.insert(start + 0.5, varStartCode);
128 source.insert(end + 0.5, "\n/* WEBPACK VAR INJECTION */" + varEndCode);
129 }
130 }
131
132 for (const childBlock of block.blocks) {
133 this.sourceBlock(
134 module,
135 childBlock,
136 availableVars.concat(vars),
137 dependencyTemplates,
138 source,
139 runtimeTemplate
140 );
141 }
142 }
143
144 sourceDependency(dependency, dependencyTemplates, source, runtimeTemplate) {
145 const template = dependencyTemplates.get(dependency.constructor);
146 if (!template) {
147 throw new Error(
148 "No template for dependency: " + dependency.constructor.name
149 );
150 }
151 template.apply(dependency, source, runtimeTemplate, dependencyTemplates);
152 }
153
154 sourceVariables(
155 variable,
156 availableVars,
157 dependencyTemplates,
158 runtimeTemplate
159 ) {
160 const name = variable.name;
161 const expr = variable.expressionSource(
162 dependencyTemplates,
163 runtimeTemplate
164 );
165
166 if (
167 availableVars.some(
168 v => v.name === name && v.expression.source() === expr.source()
169 )
170 ) {
171 return;
172 }
173 return {
174 name: name,
175 expression: expr
176 };
177 }
178
179 /*
180 * creates the start part of a IIFE around the module to inject a variable name
181 * (function(…){ <- this part
182 * }.call(…))
183 */
184 variableInjectionFunctionWrapperStartCode(varNames) {
185 const args = varNames.join(", ");
186 return `/* WEBPACK VAR INJECTION */(function(${args}) {`;
187 }
188
189 contextArgument(module, block) {
190 if (this === block) {
191 return module.exportsArgument;
192 }
193 return "this";
194 }
195
196 /*
197 * creates the end part of a IIFE around the module to inject a variable name
198 * (function(…){
199 * }.call(…)) <- this part
200 */
201 variableInjectionFunctionWrapperEndCode(module, varExpressions, block) {
202 const firstParam = this.contextArgument(module, block);
203 const furtherParams = varExpressions.map(e => e.source()).join(", ");
204 return `}.call(${firstParam}, ${furtherParams}))`;
205 }
206
207 splitVariablesInUniqueNamedChunks(vars) {
208 const startState = [[]];
209 return vars.reduce((chunks, variable) => {
210 const current = chunks[chunks.length - 1];
211 // check if variable with same name exists already
212 // if so create a new chunk of variables.
213 const variableNameAlreadyExists = current.some(
214 v => v.name === variable.name
215 );
216
217 if (variableNameAlreadyExists) {
218 // start new chunk with current variable
219 chunks.push([variable]);
220 } else {
221 // else add it to current chunk
222 current.push(variable);
223 }
224 return chunks;
225 }, startState);
226 }
227}
228
229module.exports = JavascriptGenerator;