@regru/webpack-babel-multi-target-plugin
Version:
A Webpack plugin that works with Babel to allow deployment of ES2015 builds targeted to modern browsers, with an ES5 fallback for legacy browsers.
140 lines • 6.13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const babel_target_1 = require("./babel-target");
const plugin_name_1 = require("./plugin.name");
// While CSS modules aren't duplicated by targeting the way code modules are, since they are referenced by targeted
// modules, they end up getting duplicated. Without intervention, we'd end up with one CSS file per target, which each
// file containing the exact same content. To fix this, we remove CSS modules from the targeted and move them into their
// own (non-targeted) chunks.
/**
* @internalapi
*/
class NormalizeCssChunksPlugin {
constructor(targets) {
this.targets = targets;
}
apply(compiler) {
compiler.hooks.compilation.tap(plugin_name_1.PLUGIN_NAME, (compilation) => {
if (compilation.name) {
return;
}
compilation.hooks.optimizeChunksBasic.tap(plugin_name_1.PLUGIN_NAME, this.extractCssChunks.bind(this, compilation));
compilation.hooks.optimizeChunkAssets.tap(plugin_name_1.PLUGIN_NAME, this.cleanCssChunks.bind(this, compilation));
});
}
extractCssChunks(compilation, chunks) {
const cssModules = {};
let hasUntaggedTarget = false;
// first, find the CSS modules and remove them from their targeted chunks
chunks.forEach(chunk => {
// if `isGeneratedForBabelTargets` is present, we've already processed this chunk
// the `optimizeChunksBasic` hook can get called more than once
if (chunk.isGeneratedForBabelTargets) {
return;
}
const target = babel_target_1.BabelTarget.findTarget(chunk);
if (!target) {
// can probably skip these? maybe?
}
// don't mess with a chunk if it's not tagged with the target key
if (target && !target.tagAssetsWithKey) {
hasUntaggedTarget = true;
return;
}
// get the original (untagged) name of the entry module so we can correctly
// attribute any contained CSS modules to the entry
const name = this.findEntryName(chunk);
// track the original entry names to use later
if (!cssModules[name]) {
cssModules[name] = new Set();
}
chunk.modulesIterable.forEach(module => {
if (module.constructor.name !== 'CssModule') {
return;
}
chunk.removeModule(module);
// don't duplicate modules - we should only have one per imported/required CSS/SCSS/etc file
cssModules[name].add(module);
});
});
if (hasUntaggedTarget) {
// untagged targets keep their CSS modules, so we don't need to create a fake one below
return;
}
// create chunks for the extracted modules
Object.keys(cssModules).forEach(name => {
const modules = cssModules[name];
const cssGroup = compilation.addChunkInGroup(name);
const cssChunk = cssGroup.chunks[cssGroup.chunks.length - 1];
// HACK ALERT! fool HtmlWebpackPlugin into thinking this is an actual Entrypoint chunk so it
// will include its assets by default (assuming the user hasn't filtered the name of the chunk)
// somewhat relevant: BabelMultiTargetHtmlUpdater.mapChunkNames
cssGroup.isInitial = () => true;
cssChunk.hasRuntime = () => false;
cssChunk.isInitial = () => true;
cssChunk.isGeneratedForBabelTargets = true;
modules.forEach(module => cssChunk.addModule(module));
});
}
findEntryName(chunk) {
const entry = this.findEntryModule(chunk);
if (entry) {
return entry.reasons[0].dependency.originalName;
}
throw new Error(`Could not determine entry module for chunk ${chunk.name}`);
}
findEntryModule(chunk) {
if (chunk.entryModule) {
return chunk.entryModule;
}
// sure, fine, make me work for it...
for (const group of chunk.groupsIterable) {
for (const groupParent of group.parentsIterable) {
for (const chunk of groupParent.chunks) {
if (chunk.hasEntryModule()) {
return chunk.entryModule;
}
}
}
}
// sure, fine, make me REALLY work for it...
for (const module of chunk.modulesIterable) {
const entry = this.getEntryFromModule(module);
if (entry) {
return entry;
}
}
}
getEntryFromModule(module) {
for (const reason of module.reasons) {
const babelTarget = reason.dependency.babelTarget || babel_target_1.BabelTarget.getTargetFromTag(reason.dependency.request, this.targets);
if (babelTarget) {
return module;
}
const depEntry = this.getEntryFromModule(reason.dependency.originModule || reason.dependency.module);
if (depEntry) {
return depEntry;
}
}
}
// The extract process in extractCssChunks causes a small JavaScript loader file to get generated. Since the file
// gets loaded by HtmlWebpackPlugin, we don't want this file cluttering up the assets, so it gets removed.
cleanCssChunks(compilation, chunks) {
chunks.forEach(chunk => {
if (!chunk.isGeneratedForBabelTargets) {
return;
}
chunk.files = chunk.files.reduce((result, file) => {
if (file.endsWith('.js')) {
delete compilation.assets[file];
}
else {
result.push(file);
}
return result;
}, []);
});
}
}
exports.NormalizeCssChunksPlugin = NormalizeCssChunksPlugin;
//# sourceMappingURL=normalize.css.chunks.plugin.js.map