purgecss-webpack-plugin
Version:
PurgeCSS plugin for webpack - Remove unused css
163 lines (157 loc) • 5.64 kB
JavaScript
;
var fs = require('fs');
var path = require('path');
var purgecss = require('purgecss');
var webpack = require('webpack');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
const styleExtensions = [".css", ".scss", ".styl", ".sass", ".less"];
const pluginName = "PurgeCSS";
/**
* Get the filename without ?hash
*
* @param fileName - file name
*/
function getFormattedFilename(fileName) {
if (fileName.includes("?")) {
return fileName.split("?").slice(0, -1).join("");
}
return fileName;
}
/**
* Returns true if the filename is of types of one of the specified extensions
*
* @param filename - file name
* @param extensions - extensions
*/
function isFileOfTypes(filename, extensions) {
const extension = path__namespace.extname(getFormattedFilename(filename));
return extensions.includes(extension);
}
function getPurgeCSSOptions(pluginOptions, filesToSearch, asset, fileName, sourceMap) {
const options = {
...purgecss.defaultOptions,
...pluginOptions,
content: filesToSearch,
css: [
{
raw: asset.source().toString(),
},
],
};
if (typeof options.safelist === "function") {
options.safelist = options.safelist();
}
if (typeof options.blocklist === "function") {
options.blocklist = options.blocklist();
}
return {
content: options.content,
css: options.css,
defaultExtractor: options.defaultExtractor,
extractors: options.extractors,
fontFace: options.fontFace,
keyframes: options.keyframes,
output: options.output,
rejected: options.rejected,
variables: options.variables,
safelist: options.safelist,
blocklist: options.blocklist,
sourceMap: sourceMap ? { inline: false, to: fileName } : false,
};
}
/**
* Create the Source instance result of PurgeCSS
*
* @param name - asset name
* @param asset - webpack asset
* @param purgeResult - result of PurgeCSS purge method
* @param sourceMap - wether sourceMap is enabled
* @returns the new Source
*/
function createSource(name, asset, purgeResult, sourceMap) {
if (!sourceMap || !purgeResult.sourceMap) {
return new webpack.sources.RawSource(purgeResult.css);
}
const { source, map } = asset.sourceAndMap();
return new webpack.sources.SourceMapSource(purgeResult.css, name, purgeResult.sourceMap, source.toString(), map, false);
}
/**
* @public
*/
class PurgeCSSPlugin {
constructor(options) {
this.purgedStats = {};
this.options = options;
}
apply(compiler) {
compiler.hooks.compilation.tap(pluginName, this.initializePlugin.bind(this));
}
initializePlugin(compilation) {
compilation.hooks.additionalAssets.tapPromise(pluginName, async () => {
let configFileOptions;
try {
const t = path__namespace.resolve(process.cwd(), "purgecss.config.js");
configFileOptions = await import(t);
}
catch {
// no config file present
}
this.options = {
...(configFileOptions ? configFileOptions : {}),
...this.options,
};
const entryPaths = typeof this.options.paths === "function"
? this.options.paths()
: this.options.paths;
entryPaths.forEach((p) => {
if (!fs__namespace.existsSync(p))
throw new Error(`Path ${p} does not exist.`);
});
return this.runPluginHook(compilation, entryPaths);
});
}
async runPluginHook(compilation, entryPaths) {
const assetsFromCompilation = Object.entries(compilation.assets).filter(([name]) => {
return isFileOfTypes(name, [".css"]);
});
for (const chunk of compilation.chunks) {
const assetsToPurge = assetsFromCompilation.filter(([name]) => {
if (this.options.only) {
if (!this.options.only.some((only) => name.includes(only))) {
return false;
}
}
return chunk.files.has(name);
});
for (const [name, asset] of assetsToPurge) {
const filesToSearch = entryPaths.filter((v) => !styleExtensions.some((ext) => v.endsWith(ext)));
const sourceMapEnabled = !!compilation.compiler.options.devtool;
const purgeCSSOptions = getPurgeCSSOptions(this.options, filesToSearch, asset, name, sourceMapEnabled);
const purgecss$1 = await new purgecss.PurgeCSS().purge(purgeCSSOptions);
const purged = purgecss$1[0];
if (purged.rejected) {
this.purgedStats[name] = purged.rejected;
}
compilation.updateAsset(name, createSource(name, asset, purged, sourceMapEnabled));
}
}
}
}
exports.PurgeCSSPlugin = PurgeCSSPlugin;