You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

229 lines
5.7 KiB

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