250 lines
6.9 KiB
JavaScript
250 lines
6.9 KiB
JavaScript
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
Author Tobias Koppers @sokra
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const util = require("util");
|
|
const { ConcatSource, RawSource, ReplaceSource } = require("webpack-sources");
|
|
const Generator = require("../Generator");
|
|
|
|
/** @typedef {import("webpack-sources").Source} Source */
|
|
/** @typedef {import("../DependenciesBlock")} DependenciesBlock */
|
|
/** @typedef {import("../Dependency")} Dependency */
|
|
/** @typedef {import("../DependencyTemplates")} DependencyTemplates */
|
|
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
|
|
/** @typedef {import("../InitFragment")} InitFragment */
|
|
/** @typedef {import("../Module")} Module */
|
|
/** @typedef {import("../NormalModule")} NormalModule */
|
|
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
|
|
|
|
// TODO: clean up this file
|
|
// replace with newer constructs
|
|
|
|
const deprecatedGetInitFragments = util.deprecate(
|
|
(template, dependency, templateContext) =>
|
|
template.getInitFragments(dependency, templateContext),
|
|
"DependencyTemplate.getInitFragment is deprecated (use apply(dep, source, { initFragments }) instead)"
|
|
);
|
|
|
|
/**
|
|
* @param {InitFragment} fragment the init fragment
|
|
* @param {number} index index
|
|
* @returns {[InitFragment, number]} tuple with both
|
|
*/
|
|
const extractFragmentIndex = (fragment, index) => [fragment, index];
|
|
|
|
/**
|
|
* @param {[InitFragment, number]} a first pair
|
|
* @param {[InitFragment, number]} b second pair
|
|
* @returns {number} sort value
|
|
*/
|
|
const sortFragmentWithIndex = ([a, i], [b, j]) => {
|
|
const stageCmp = a.stage - b.stage;
|
|
if (stageCmp !== 0) return stageCmp;
|
|
const positionCmp = a.position - b.position;
|
|
if (positionCmp !== 0) return positionCmp;
|
|
return i - j;
|
|
};
|
|
|
|
const TYPES = new Set(["javascript"]);
|
|
|
|
class JavascriptGenerator extends Generator {
|
|
/**
|
|
* @returns {Set<string>} available types (do not mutate)
|
|
*/
|
|
getTypes() {
|
|
return TYPES;
|
|
}
|
|
|
|
/**
|
|
* @param {NormalModule} module the module
|
|
* @param {string=} type source type
|
|
* @returns {number} estimate size of the module
|
|
*/
|
|
getSize(module, type) {
|
|
const originalSource = module.originalSource();
|
|
if (!originalSource) {
|
|
return 39;
|
|
}
|
|
return originalSource.size();
|
|
}
|
|
|
|
/**
|
|
* @param {NormalModule} module module for which the code should be generated
|
|
* @param {GenerateContext} generateContext context for generate
|
|
* @returns {Source} generated code
|
|
*/
|
|
generate(module, generateContext) {
|
|
const originalSource = module.originalSource();
|
|
if (!originalSource) {
|
|
return new RawSource("throw new Error('No source available');");
|
|
}
|
|
|
|
const source = new ReplaceSource(originalSource);
|
|
const initFragments = [];
|
|
|
|
this.sourceModule(module, initFragments, source, generateContext);
|
|
|
|
if (initFragments.length > 0) {
|
|
// Sort fragments by position. If 2 fragments have the same position,
|
|
// use their index.
|
|
const sortedFragments = initFragments
|
|
.map(extractFragmentIndex)
|
|
.sort(sortFragmentWithIndex);
|
|
|
|
// Deduplicate fragments. If a fragment has no key, it is always included.
|
|
const keyedFragments = new Map();
|
|
for (const [fragment] of sortedFragments) {
|
|
if (typeof fragment.merge === "function") {
|
|
const oldValue = keyedFragments.get(fragment.key);
|
|
if (oldValue !== undefined) {
|
|
keyedFragments.set(
|
|
fragment.key || Symbol(),
|
|
fragment.merge(oldValue)
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
keyedFragments.set(fragment.key || Symbol(), fragment);
|
|
}
|
|
|
|
const concatSource = new ConcatSource();
|
|
const endContents = [];
|
|
for (const fragment of keyedFragments.values()) {
|
|
concatSource.add(fragment.getContent(generateContext));
|
|
const endContent = fragment.getEndContent(generateContext);
|
|
if (endContent) {
|
|
endContents.push(endContent);
|
|
}
|
|
}
|
|
|
|
concatSource.add(source);
|
|
for (const content of endContents.reverse()) {
|
|
concatSource.add(content);
|
|
}
|
|
return concatSource;
|
|
} else {
|
|
return source;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Module} module the module to generate
|
|
* @param {InitFragment[]} initFragments mutable list of init fragments
|
|
* @param {ReplaceSource} source the current replace source which can be modified
|
|
* @param {GenerateContext} generateContext the generateContext
|
|
* @returns {void}
|
|
*/
|
|
sourceModule(module, initFragments, source, generateContext) {
|
|
for (const dependency of module.dependencies) {
|
|
this.sourceDependency(
|
|
module,
|
|
dependency,
|
|
initFragments,
|
|
source,
|
|
generateContext
|
|
);
|
|
}
|
|
|
|
for (const dependency of module.presentationalDependencies) {
|
|
this.sourceDependency(
|
|
module,
|
|
dependency,
|
|
initFragments,
|
|
source,
|
|
generateContext
|
|
);
|
|
}
|
|
|
|
for (const childBlock of module.blocks) {
|
|
this.sourceBlock(
|
|
module,
|
|
childBlock,
|
|
initFragments,
|
|
source,
|
|
generateContext
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Module} module the module to generate
|
|
* @param {DependenciesBlock} block the dependencies block which will be processed
|
|
* @param {InitFragment[]} initFragments mutable list of init fragments
|
|
* @param {ReplaceSource} source the current replace source which can be modified
|
|
* @param {GenerateContext} generateContext the generateContext
|
|
* @returns {void}
|
|
*/
|
|
sourceBlock(module, block, initFragments, source, generateContext) {
|
|
for (const dependency of block.dependencies) {
|
|
this.sourceDependency(
|
|
module,
|
|
dependency,
|
|
initFragments,
|
|
source,
|
|
generateContext
|
|
);
|
|
}
|
|
|
|
for (const childBlock of block.blocks) {
|
|
this.sourceBlock(
|
|
module,
|
|
childBlock,
|
|
initFragments,
|
|
source,
|
|
generateContext
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Module} module the current module
|
|
* @param {Dependency} dependency the dependency to generate
|
|
* @param {InitFragment[]} initFragments mutable list of init fragments
|
|
* @param {ReplaceSource} source the current replace source which can be modified
|
|
* @param {GenerateContext} generateContext the render context
|
|
* @returns {void}
|
|
*/
|
|
sourceDependency(module, dependency, initFragments, source, generateContext) {
|
|
const constructor =
|
|
/** @type {new (...args: any[]) => Dependency} */ (dependency.constructor);
|
|
const template = generateContext.dependencyTemplates.get(constructor);
|
|
if (!template) {
|
|
throw new Error(
|
|
"No template for dependency: " + dependency.constructor.name
|
|
);
|
|
}
|
|
|
|
const templateContext = {
|
|
runtimeTemplate: generateContext.runtimeTemplate,
|
|
dependencyTemplates: generateContext.dependencyTemplates,
|
|
moduleGraph: generateContext.moduleGraph,
|
|
chunkGraph: generateContext.chunkGraph,
|
|
module,
|
|
runtimeRequirements: generateContext.runtimeRequirements,
|
|
initFragments
|
|
};
|
|
|
|
template.apply(dependency, source, templateContext);
|
|
|
|
// TODO remove in webpack 6
|
|
if ("getInitFragments" in template) {
|
|
const fragments = deprecatedGetInitFragments(
|
|
template,
|
|
dependency,
|
|
templateContext
|
|
);
|
|
|
|
if (fragments) {
|
|
for (const fragment of fragments) {
|
|
initFragments.push(fragment);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = JavascriptGenerator;
|