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.
230 lines
5.7 KiB
230 lines
5.7 KiB
4 years ago
|
/*
|
||
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
||
|
Author Tobias Koppers @sokra
|
||
|
*/
|
||
|
"use strict";
|
||
|
|
||
|
const { RawSource, ReplaceSource } = require("webpack-sources");
|
||
|
|
||
|
// TODO: clean up this file
|
||
|
// replace with newer constructs
|
||
|
|
||
|
// TODO: remove DependencyVariables and replace them with something better
|
||
|
|
||
|
class JavascriptGenerator {
|
||
|
generate(module, dependencyTemplates, runtimeTemplate) {
|
||
|
const originalSource = module.originalSource();
|
||
|
if (!originalSource) {
|
||
|
return new RawSource("throw new Error('No source available');");
|
||
|
}
|
||
|
|
||
|
const source = new ReplaceSource(originalSource);
|
||
|
|
||
|
this.sourceBlock(
|
||
|
module,
|
||
|
module,
|
||
|
[],
|
||
|
dependencyTemplates,
|
||
|
source,
|
||
|
runtimeTemplate
|
||
|
);
|
||
|
|
||
|
return source;
|
||
|
}
|
||
|
|
||
|
sourceBlock(
|
||
|
module,
|
||
|
block,
|
||
|
availableVars,
|
||
|
dependencyTemplates,
|
||
|
source,
|
||
|
runtimeTemplate
|
||
|
) {
|
||
|
for (const dependency of block.dependencies) {
|
||
|
this.sourceDependency(
|
||
|
dependency,
|
||
|
dependencyTemplates,
|
||
|
source,
|
||
|
runtimeTemplate
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the variables of all blocks that we need to inject.
|
||
|
* These will contain the variable name and its expression.
|
||
|
* The name will be added as a parameter in a IIFE the expression as its value.
|
||
|
*/
|
||
|
const vars = block.variables.reduce((result, value) => {
|
||
|
const variable = this.sourceVariables(
|
||
|
value,
|
||
|
availableVars,
|
||
|
dependencyTemplates,
|
||
|
runtimeTemplate
|
||
|
);
|
||
|
|
||
|
if (variable) {
|
||
|
result.push(variable);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}, []);
|
||
|
|
||
|
/**
|
||
|
* if we actually have variables
|
||
|
* this is important as how #splitVariablesInUniqueNamedChunks works
|
||
|
* it will always return an array in an array which would lead to a IIFE wrapper around
|
||
|
* a module if we do this with an empty vars array.
|
||
|
*/
|
||
|
if (vars.length > 0) {
|
||
|
/**
|
||
|
* Split all variables up into chunks of unique names.
|
||
|
* e.g. imagine you have the following variable names that need to be injected:
|
||
|
* [foo, bar, baz, foo, some, more]
|
||
|
* we can not inject "foo" twice, therefore we just make two IIFEs like so:
|
||
|
* (function(foo, bar, baz){
|
||
|
* (function(foo, some, more){
|
||
|
* …
|
||
|
* }(…));
|
||
|
* }(…));
|
||
|
*
|
||
|
* "splitVariablesInUniqueNamedChunks" splits the variables shown above up to this:
|
||
|
* [[foo, bar, baz], [foo, some, more]]
|
||
|
*/
|
||
|
const injectionVariableChunks = this.splitVariablesInUniqueNamedChunks(
|
||
|
vars
|
||
|
);
|
||
|
|
||
|
// create all the beginnings of IIFEs
|
||
|
const functionWrapperStarts = injectionVariableChunks.map(
|
||
|
variableChunk => {
|
||
|
return this.variableInjectionFunctionWrapperStartCode(
|
||
|
variableChunk.map(variable => variable.name)
|
||
|
);
|
||
|
}
|
||
|
);
|
||
|
|
||
|
// and all the ends
|
||
|
const functionWrapperEnds = injectionVariableChunks.map(variableChunk => {
|
||
|
return this.variableInjectionFunctionWrapperEndCode(
|
||
|
module,
|
||
|
variableChunk.map(variable => variable.expression),
|
||
|
block
|
||
|
);
|
||
|
});
|
||
|
|
||
|
// join them to one big string
|
||
|
const varStartCode = functionWrapperStarts.join("");
|
||
|
|
||
|
// reverse the ends first before joining them, as the last added must be the inner most
|
||
|
const varEndCode = functionWrapperEnds.reverse().join("");
|
||
|
|
||
|
// if we have anything, add it to the source
|
||
|
if (varStartCode && varEndCode) {
|
||
|
const start = block.range ? block.range[0] : -10;
|
||
|
const end = block.range
|
||
|
? block.range[1]
|
||
|
: module.originalSource().size() + 1;
|
||
|
source.insert(start + 0.5, varStartCode);
|
||
|
source.insert(end + 0.5, "\n/* WEBPACK VAR INJECTION */" + varEndCode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (const childBlock of block.blocks) {
|
||
|
this.sourceBlock(
|
||
|
module,
|
||
|
childBlock,
|
||
|
availableVars.concat(vars),
|
||
|
dependencyTemplates,
|
||
|
source,
|
||
|
runtimeTemplate
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sourceDependency(dependency, dependencyTemplates, source, runtimeTemplate) {
|
||
|
const template = dependencyTemplates.get(dependency.constructor);
|
||
|
if (!template) {
|
||
|
throw new Error(
|
||
|
"No template for dependency: " + dependency.constructor.name
|
||
|
);
|
||
|
}
|
||
|
template.apply(dependency, source, runtimeTemplate, dependencyTemplates);
|
||
|
}
|
||
|
|
||
|
sourceVariables(
|
||
|
variable,
|
||
|
availableVars,
|
||
|
dependencyTemplates,
|
||
|
runtimeTemplate
|
||
|
) {
|
||
|
const name = variable.name;
|
||
|
const expr = variable.expressionSource(
|
||
|
dependencyTemplates,
|
||
|
runtimeTemplate
|
||
|
);
|
||
|
|
||
|
if (
|
||
|
availableVars.some(
|
||
|
v => v.name === name && v.expression.source() === expr.source()
|
||
|
)
|
||
|
) {
|
||
|
return;
|
||
|
}
|
||
|
return {
|
||
|
name: name,
|
||
|
expression: expr
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* creates the start part of a IIFE around the module to inject a variable name
|
||
|
* (function(…){ <- this part
|
||
|
* }.call(…))
|
||
|
*/
|
||
|
variableInjectionFunctionWrapperStartCode(varNames) {
|
||
|
const args = varNames.join(", ");
|
||
|
return `/* WEBPACK VAR INJECTION */(function(${args}) {`;
|
||
|
}
|
||
|
|
||
|
contextArgument(module, block) {
|
||
|
if (this === block) {
|
||
|
return module.exportsArgument;
|
||
|
}
|
||
|
return "this";
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* creates the end part of a IIFE around the module to inject a variable name
|
||
|
* (function(…){
|
||
|
* }.call(…)) <- this part
|
||
|
*/
|
||
|
variableInjectionFunctionWrapperEndCode(module, varExpressions, block) {
|
||
|
const firstParam = this.contextArgument(module, block);
|
||
|
const furtherParams = varExpressions.map(e => e.source()).join(", ");
|
||
|
return `}.call(${firstParam}, ${furtherParams}))`;
|
||
|
}
|
||
|
|
||
|
splitVariablesInUniqueNamedChunks(vars) {
|
||
|
const startState = [[]];
|
||
|
return vars.reduce((chunks, variable) => {
|
||
|
const current = chunks[chunks.length - 1];
|
||
|
// check if variable with same name exists already
|
||
|
// if so create a new chunk of variables.
|
||
|
const variableNameAlreadyExists = current.some(
|
||
|
v => v.name === variable.name
|
||
|
);
|
||
|
|
||
|
if (variableNameAlreadyExists) {
|
||
|
// start new chunk with current variable
|
||
|
chunks.push([variable]);
|
||
|
} else {
|
||
|
// else add it to current chunk
|
||
|
current.push(variable);
|
||
|
}
|
||
|
return chunks;
|
||
|
}, startState);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = JavascriptGenerator;
|