Skip to content

Instantly share code, notes, and snippets.

@PutziSan
Created August 8, 2019 15:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PutziSan/89711654c0c012ba9ea6882370c2cdf4 to your computer and use it in GitHub Desktop.
Save PutziSan/89711654c0c012ba9ea6882370c2cdf4 to your computer and use it in GitHub Desktop.
PurgeCSS via Webpack without glob
const esprima = require("esprima");
const Purgecss = require("purgecss");
const LastCallWebpackPlugin = require("last-call-webpack-plugin");
class Emitter extends require("events") {}
// by https://github.com/FullHuman/purgecss-from-js/blob/master/index.js
class PurgeFromJS {
static extract(content) {
const tokens = esprima.tokenize(content);
const selectors = tokens
.filter(token => {
return (
token.type === "Identifier" ||
token.type === "Template" ||
token.type === "String"
);
})
.reduce((acc, token) => {
if (token.type === "String") {
// cut single/double quotes from the string
// because esprima wraps string to a string
const unwrappedString = token.value.slice(1, token.value.length - 1);
return acc.concat(unwrappedString.split(" ")); // in case if string contains a list of classes
} else if (token.type === "Template") {
// cut backticks from the template
const len = token.value.length;
const isOpenedTemplate = token.value[0] === "`";
const isClosedTemplate = token.value[len - 1] === "`";
const unwrappedTemplate = token.value.slice(
isOpenedTemplate ? 1 : 0,
isClosedTemplate ? len - 1 : len
);
return acc.concat(unwrappedTemplate.split(" "));
}
return acc.concat(token.value);
}, [])
// clear selectors from empty strings
.filter(Boolean);
return [...new Set(selectors)]; // remove duplicates
}
}
// use it in plugins like:
// optimization: {
// minimizer: [
// new TerserPlugin({ sourceMap: true }),
// require("./purgecss-webpack-plugin.js")()
// ]
// }
module.exports = function newProcessor() {
let timeoutId;
const resolvedEvt = new Emitter();
const _srcs = [];
function newSource(s) {
_srcs.push(s);
const tid = setTimeout(() => {
if (tid === timeoutId) {
resolvedEvt.emit("resolved", _srcs);
}
}, 100);
timeoutId = tid;
}
let allJsFilesP = new Promise(resolve => {
resolvedEvt.once("resolved", jsSources => {
resolve(jsSources);
});
});
return new LastCallWebpackPlugin({
assetProcessors: [
{
phase: LastCallWebpackPlugin.PHASES.OPTIMIZE_CHUNK_ASSETS,
regExp: /\.js$/,
processor: (assetName, asset) => {
const s = asset.source();
newSource(s);
return Promise.resolve(s);
}
},
{
phase: LastCallWebpackPlugin.PHASES.EMIT,
regExp: /\.css$/,
processor: async (assetName, asset, assets) => {
const jsSources = await allJsFilesP;
var purgecss = new Purgecss({
extractors: [
{
extractor: PurgeFromJS,
extensions: ["js"]
}
],
content: jsSources.map(s => ({ raw: s, extension: "js" })),
css: [
{
raw: asset.source()
}
]
});
const purged = purgecss.purge()[0];
if (purged.rejected) {
throw new Error(JSON.stringify(purged.rejected));
}
return purged.css;
}
}
],
canPrint: true
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment