webpack/lib/JavascriptGenerator.js

284 lines
7.1 KiB
JavaScript
Raw Normal View History

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
2018-07-30 23:08:51 +08:00
"use strict";
const { ConcatSource, RawSource, ReplaceSource } = require("webpack-sources");
// TODO: clean up this file
// replace with newer constructs
// TODO: remove DependencyVariables and replace them with something better
const extractFragmentIndex = (fragment, index) => [fragment, index];
const sortByFragmentIndex = ([a, i], [b, j]) => {
const x = a.order - b.order;
return x !== 0 ? x : i - j;
};
class JavascriptGenerator {
generate(module, dependencyTemplates, runtimeTemplate) {
const originalSource = module.originalSource();
2018-02-25 09:00:20 +08:00
if (!originalSource) {
return new RawSource("throw new Error('No source available');");
}
const source = new ReplaceSource(originalSource);
const dependencyFragments = [];
2018-02-25 09:00:20 +08:00
this.sourceBlock(
module,
module,
[],
dependencyTemplates,
dependencyFragments,
2018-02-25 09:00:20 +08:00
source,
runtimeTemplate
);
if (dependencyFragments.length > 0) {
2018-07-25 01:56:35 +08:00
// Sort fragments by priority. If 2 fragments have the same priority,
// use their index.
const sortedFragments = dependencyFragments
.map(extractFragmentIndex)
.sort(sortByFragmentIndex);
2018-07-25 01:56:35 +08:00
// Deduplicate fragments. If a fragment has no key, it is always included.
const keyedFragments = new Map();
for (const [fragment] of sortedFragments) {
2018-07-25 01:56:35 +08:00
keyedFragments.set(fragment.key || Symbol(), fragment);
}
const concatSource = new ConcatSource();
for (const fragment of keyedFragments.values()) {
concatSource.add(fragment.content);
}
concatSource.add(source);
return concatSource;
} else {
return source;
}
}
2018-02-25 09:00:20 +08:00
sourceBlock(
module,
block,
availableVars,
dependencyTemplates,
dependencyFragments,
2018-02-25 09:00:20 +08:00
source,
runtimeTemplate
) {
for (const dependency of block.dependencies) {
this.sourceDependency(
dependency,
dependencyTemplates,
dependencyFragments,
2018-02-25 09:00:20 +08:00
source,
runtimeTemplate
);
}
/**
* Get the variables of all blocks that we need to inject.
* These will contain the variable name and its expression.
2018-02-26 10:38:26 +08:00
* 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(
2018-02-25 09:00:20 +08:00
value,
availableVars,
dependencyTemplates,
runtimeTemplate
);
2018-02-25 09:00:20 +08:00
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.
*/
2018-02-25 09:00:20 +08:00
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){
2018-03-22 17:54:18 +08:00
*
* }());
* }());
*
* "splitVariablesInUniqueNamedChunks" splits the variables shown above up to this:
* [[foo, bar, baz], [foo, some, more]]
*/
2018-02-25 09:00:20 +08:00
const injectionVariableChunks = this.splitVariablesInUniqueNamedChunks(
vars
);
// create all the beginnings of IIFEs
2018-02-25 09:00:20 +08:00
const functionWrapperStarts = injectionVariableChunks.map(
variableChunk => {
return this.variableInjectionFunctionWrapperStartCode(
variableChunk.map(variable => variable.name)
);
}
);
// and all the ends
2018-02-25 09:00:20 +08:00
const functionWrapperEnds = injectionVariableChunks.map(variableChunk => {
return this.variableInjectionFunctionWrapperEndCode(
module,
2018-02-25 09:00:20 +08:00
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
2018-02-25 09:00:20 +08:00
if (varStartCode && varEndCode) {
const start = block.range ? block.range[0] : -10;
2018-02-25 09:00:20 +08:00
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);
}
}
2018-02-25 09:00:20 +08:00
for (const childBlock of block.blocks) {
this.sourceBlock(
module,
childBlock,
availableVars.concat(vars),
dependencyTemplates,
dependencyFragments,
source,
runtimeTemplate
);
}
}
sourceDependency(
dependency,
dependencyTemplates,
dependencyFragments,
source,
runtimeTemplate
) {
const template = dependencyTemplates.get(dependency.constructor);
if (!template) {
2018-02-25 09:00:20 +08:00
throw new Error(
"No template for dependency: " + dependency.constructor.name
);
}
template.apply(dependency, source, runtimeTemplate, dependencyTemplates);
if (typeof template.getInitFragments === "function") {
const fragments = template.getInitFragments(
dependency,
source, // TODO remove this argument
runtimeTemplate,
dependencyTemplates
);
for (const fragment of fragments) {
dependencyFragments.push(fragment);
}
}
}
2018-02-25 09:00:20 +08:00
sourceVariables(
variable,
availableVars,
dependencyTemplates,
runtimeTemplate
) {
const name = variable.name;
2018-02-25 09:00:20 +08:00
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
2018-03-22 17:54:18 +08:00
* (function(){ <- this part
* }.call())
*/
variableInjectionFunctionWrapperStartCode(varNames) {
const args = varNames.join(", ");
return `/* WEBPACK VAR INJECTION */(function(${args}) {`;
}
contextArgument(module, block) {
2018-02-25 09:00:20 +08:00
if (this === block) {
return module.exportsArgument;
}
return "this";
}
/*
* creates the end part of a IIFE around the module to inject a variable name
2018-03-22 17:54:18 +08:00
* (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) {
2018-02-25 09:00:20 +08:00
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.
2018-02-25 09:00:20 +08:00
const variableNameAlreadyExists = current.some(
v => v.name === variable.name
);
2018-02-25 09:00:20 +08:00
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;