UNPKG

purgecss-webpack-plugin

Version:

PurgeCSS plugin for webpack - Remove unused css

163 lines (157 loc) 5.64 kB
'use strict'; 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;