UNPKG

10up-toolkit

Version:

Collection of reusable scripts for 10up development.

162 lines (129 loc) 4.87 kB
/** * Webpack 5 plugin to remove empty scripts generated by usage only styles in webpack entry. * * This is a fork of https://github.com/webdiscus/webpack-remove-empty-scripts to remove ansis. */ const path = require('path'); const outToConsole = (...args) => process.stdout.write(`${args.join(' ')}\n`); const pluginName = 'remove-empty-scripts'; const defaultOptions = { enabled: true, verbose: false, extensions: ['css', 'scss', 'sass', 'less', 'styl'], ignore: [], remove: /\.(js|mjs)$/, }; // Save unique id in dependency object as marker of 'analysed module' // to avoid the infinite recursion by collect of resources. let dependencyId = 1; function getEntryResources(compilation, module, cache) { const { moduleGraph } = compilation; const index = moduleGraph.getPreOrderIndex(module); const propNameDependencyId = '__webpackRemoveEmptyScriptsUniqueId'; const resources = []; // the index can be null if (index == null) return resources; // index of module is unique per compilation // module.id can be null, not used here if (cache[index] !== undefined) return cache[index]; if (typeof module.resource === 'string') { const resources = [module.resource]; cache[index] = resources; return resources; } if (module.dependencies) { module.dependencies.forEach((dependency) => { const module = moduleGraph.getModule(dependency); const originModule = moduleGraph.getParentModule(dependency); const nextModule = module || originModule; let useNextModule = false; if (!Object.prototype.hasOwnProperty.call(dependency, propNameDependencyId)) { dependency[propNameDependencyId] = dependencyId++; useNextModule = true; } if (nextModule && useNextModule) { const dependencyResources = getEntryResources(compilation, nextModule, cache); for (let i = 0, { length } = dependencyResources; i !== length; i++) { const file = dependencyResources[i]; if (resources.indexOf(file) < 0) resources.push(file); } } }); } if (resources.length > 0) cache[index] = resources; return resources; } class WebpackRemoveEmptyScriptsPlugin { outputPath = ''; trash = []; constructor(options) { this.apply = this.apply.bind(this); this.options = { ...defaultOptions, ...options }; this.enabled = this.options.enabled !== false; this.verbose = this.options.verbose; // if by assigned option the `ignore` was not array, then set as array if (!Array.isArray(this.options.ignore)) { this.options.ignore = [this.options.ignore]; } if (Array.isArray(this.options.extensions)) { const pattern = this.options.extensions .map((etx) => (etx[0] === '.' ? etx.substring(1) : etx)) .join('|'); // note: the pattern must match a resource with a query, e.g.: style.css?key=val this.options.extensions = new RegExp(`.(${pattern})([?].*)?$`); } } apply(compiler) { if (!this.enabled) return; // clear cache for webpack dev server this.trash = []; this.outputPath = compiler.options.output.path; compiler.hooks.thisCompilation.tap(pluginName, (compilation) => { const resourcesCache = []; compilation.hooks.chunkAsset.tap(pluginName, (chunk, filename) => { const { remove: removeAssets, ignore: ignoreEntryResource, extensions: styleExtensionRegexp, } = this.options; if (!removeAssets.test(filename)) return; const { chunkGraph } = compilation; let entryResources = []; for (const module of chunkGraph.getChunkEntryModulesIterable(chunk)) { if (!compilation.modules.has(module)) { throw new Error( `\n${`[${pluginName}]`} entry module in chunk but not in compilation ${ chunk.debugId } ${module.debugId}`, ); } const moduleResources = getEntryResources(compilation, module, resourcesCache); entryResources = entryResources.concat(moduleResources); } const resources = ignoreEntryResource.length > 0 ? entryResources.filter((res) => ignoreEntryResource.every((item) => !res.match(item)), ) : entryResources; const isEmptyScript = resources.length > 0 && resources.every((resource) => styleExtensionRegexp.test(resource)); if (isEmptyScript) { if (this.verbose) { const outputFile = path.join(this.outputPath, filename); outToConsole(`${`[${pluginName}]`} remove ${outputFile}\n`); } // note: do not delete here compilation empty assets, do it in 'afterProcessAssets' only this.trash.push(filename); } }); // Delete empty scripts only after processing all plugins, // otherwise, by usage some plugins, the necessary files may be not created. compilation.hooks.afterProcessAssets.tap(pluginName, () => { this.trash.forEach((file) => compilation.deleteAsset(file)); }); }); } } module.exports = WebpackRemoveEmptyScriptsPlugin;