Skip to content

Commit b3f73ed

Browse files
committed
fix(purgecss-webpack-plugin): add sourcemap support #409
BREAKING CHANGE: drop webpack 4 support
1 parent b2b92fc commit b3f73ed

File tree

11 files changed

+117
-57
lines changed

11 files changed

+117
-57
lines changed

package-lock.json

Lines changed: 3 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/postcss-purgecss/src/types/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
*
1010
* @public
1111
*/
12-
export interface UserDefinedOptions extends Omit<PurgeCSSUserDefinedOptions, "css"> {
12+
export interface UserDefinedOptions extends Omit<PurgeCSSUserDefinedOptions,"content" | "css"> {
13+
content?: PurgeCSSUserDefinedOptions['content'];
1314
contentFunction?: (sourceFile: string) => Array<string | RawContent>;
1415
}

packages/purgecss-webpack-plugin/__tests__/cases/path-and-safelist-functions/webpack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const PATHS = {
1111

1212
module.exports = {
1313
mode: "development",
14+
devtool: false,
1415
entry: "./src/index.js",
1516
context: path.resolve(__dirname),
1617
optimization: {

packages/purgecss-webpack-plugin/__tests__/cases/simple-with-exclusion/webpack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const PATHS = {
1111

1212
module.exports = {
1313
mode: "development",
14+
devtool: false,
1415
entry: {
1516
bundle: "./src/index.js",
1617
legacy: "./src/legacy.js",

packages/purgecss-webpack-plugin/__tests__/cases/simple/expected/styles.css

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/purgecss-webpack-plugin/__tests__/cases/simple/webpack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const PATHS = {
1414

1515
module.exports = {
1616
mode: "development",
17+
devtool: "source-map",
1718
optimization: {
1819
splitChunks: {
1920
cacheGroups: {

packages/purgecss-webpack-plugin/package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,18 @@
3838
},
3939
"dependencies": {
4040
"purgecss": "^4.1.3",
41-
"webpack": "*",
42-
"webpack-sources": "^3.2.0"
41+
"webpack": ">=5.0.0"
4342
},
4443
"bugs": {
4544
"url": "https://github.com/FullHuman/purgecss/issues"
4645
},
4746
"devDependencies": {
4847
"@types/webpack-sources": "^3.2.0",
49-
"css-loader": "^6.2.0",
50-
"mini-css-extract-plugin": "^2.1.0"
48+
"css-loader": "^6.6.0",
49+
"mini-css-extract-plugin": "^2.5.0"
5150
},
5251
"peerDependencies": {
53-
"webpack": "*"
52+
"webpack": ">=5.0.0"
5453
},
5554
"publishConfig": {
5655
"access": "public",

packages/purgecss-webpack-plugin/src/index.ts

Lines changed: 95 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import * as fs from "fs";
22
import * as path from "path";
3-
import { PurgeCSS, defaultOptions } from "purgecss";
4-
import { Compilation, Compiler } from "webpack";
5-
import { ConcatSource } from "webpack-sources";
3+
import {
4+
PurgeCSS,
5+
defaultOptions,
6+
ResultPurge,
7+
UserDefinedOptions as PurgeCSSUserDefinedOptions,
8+
} from "purgecss";
9+
import { Compilation, Compiler, sources } from "webpack";
610
import { PurgedStats, UserDefinedOptions } from "./types";
711

812
export * from "./types";
@@ -12,6 +16,7 @@ const pluginName = "PurgeCSS";
1216

1317
/**
1418
* Get the filename without ?hash
19+
*
1520
* @param fileName - file name
1621
*/
1722
function getFormattedFilename(fileName: string): string {
@@ -23,6 +28,7 @@ function getFormattedFilename(fileName: string): string {
2328

2429
/**
2530
* Returns true if the filename is of types of one of the specified extensions
31+
*
2632
* @param filename - file name
2733
* @param extensions - extensions
2834
*/
@@ -31,6 +37,78 @@ function isFileOfTypes(filename: string, extensions: string[]): boolean {
3137
return extensions.includes(extension);
3238
}
3339

40+
function getPurgeCSSOptions(
41+
pluginOptions: UserDefinedOptions,
42+
filesToSearch: string[],
43+
asset: sources.Source,
44+
fileName: string,
45+
sourceMap: boolean
46+
): PurgeCSSUserDefinedOptions {
47+
const options = {
48+
...defaultOptions,
49+
...pluginOptions,
50+
content: filesToSearch,
51+
css: [
52+
{
53+
raw: asset.source().toString(),
54+
},
55+
],
56+
};
57+
58+
if (typeof options.safelist === "function") {
59+
options.safelist = options.safelist();
60+
}
61+
62+
if (typeof options.blocklist === "function") {
63+
options.blocklist = options.blocklist();
64+
}
65+
66+
return {
67+
content: options.content,
68+
css: options.css,
69+
defaultExtractor: options.defaultExtractor,
70+
extractors: options.extractors,
71+
fontFace: options.fontFace,
72+
keyframes: options.keyframes,
73+
output: options.output,
74+
rejected: options.rejected,
75+
variables: options.variables,
76+
safelist: options.safelist,
77+
blocklist: options.blocklist,
78+
sourceMap: sourceMap ? { inline: false, to: fileName } : false,
79+
};
80+
}
81+
82+
/**
83+
* Create the Source instance result of PurgeCSS
84+
*
85+
* @param name - asset name
86+
* @param asset - webpack asset
87+
* @param purgeResult - result of PurgeCSS purge method
88+
* @param sourceMap - wether sourceMap is enabled
89+
* @returns the new Source
90+
*/
91+
function createSource(
92+
name: string,
93+
asset: sources.Source,
94+
purgeResult: ResultPurge,
95+
sourceMap: boolean
96+
): sources.Source {
97+
if (!sourceMap || !purgeResult.sourceMap) {
98+
return new sources.RawSource(purgeResult.css);
99+
}
100+
const { source, map } = asset.sourceAndMap();
101+
102+
return new sources.SourceMapSource(
103+
purgeResult.css,
104+
name,
105+
purgeResult.sourceMap,
106+
source.toString(),
107+
map,
108+
false
109+
);
110+
}
111+
34112
/**
35113
* @public
36114
*/
@@ -80,60 +158,34 @@ export class PurgeCSSPlugin {
80158
return this.options.only.some((only) => name.includes(only));
81159
}
82160

83-
return Array.isArray(chunk.files)
84-
? chunk.files.includes(name)
85-
: chunk.files.has(name);
161+
return chunk.files.has(name);
86162
});
87163

88164
for (const [name, asset] of assetsToPurge) {
89165
const filesToSearch = entryPaths.filter(
90166
(v) => !styleExtensions.some((ext) => v.endsWith(ext))
91167
);
92168

93-
// Compile through Purgecss and attach to output.
94-
// This loses sourcemaps should there be any!
95-
const options = {
96-
...defaultOptions,
97-
...this.options,
98-
content: filesToSearch,
99-
css: [
100-
{
101-
raw: asset.source().toString(),
102-
},
103-
],
104-
};
105-
106-
if (typeof options.safelist === "function") {
107-
options.safelist
108-
options.safelist = options.safelist();
109-
}
110-
111-
if (typeof options.blocklist === "function") {
112-
options.blocklist = options.blocklist();
113-
}
169+
const sourceMapEnabled = !!compilation.compiler.options.devtool;
170+
const purgeCSSOptions = getPurgeCSSOptions(
171+
this.options,
172+
filesToSearch,
173+
asset,
174+
name,
175+
sourceMapEnabled
176+
);
114177

115-
const purgecss = await new PurgeCSS().purge({
116-
content: options.content,
117-
css: options.css,
118-
defaultExtractor: options.defaultExtractor,
119-
extractors: options.extractors,
120-
fontFace: options.fontFace,
121-
keyframes: options.keyframes,
122-
output: options.output,
123-
rejected: options.rejected,
124-
variables: options.variables,
125-
safelist: options.safelist,
126-
blocklist: options.blocklist,
127-
});
178+
const purgecss = await new PurgeCSS().purge(purgeCSSOptions);
128179
const purged = purgecss[0];
129180

130181
if (purged.rejected) {
131182
this.purgedStats[name] = purged.rejected;
132183
}
133184

134-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
135-
// @ts-ignore
136-
compilation.updateAsset(name, new ConcatSource(purged.css));
185+
compilation.updateAsset(
186+
name,
187+
createSource(name, asset, purged, sourceMapEnabled)
188+
);
137189
}
138190
}
139191
}

packages/purgecss-webpack-plugin/src/types/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export type PurgedStats = {
2828
/**
2929
* @public
3030
*/
31-
export type UserDefinedOptions = Omit<PurgeCSSUserDefinedOptions, "css" | "content" | "safelist" | "blocklist"> & {
31+
export type UserDefinedOptions = Omit<PurgeCSSUserDefinedOptions, "css" | "content" | "safelist" | "blocklist" | "sourceMap"> & {
3232
paths: string[] | PathFunction;
3333
moduleExtensions?: string[];
3434
verbose?: boolean;

packages/purgecss/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,10 @@ class PurgeCSS {
606606
if (this.options.keyframes) this.removeUnusedKeyframes();
607607
if (this.options.variables) this.removeUnusedCSSVariables();
608608

609-
const postCSSResult = root.toResult({ map: this.options.sourceMap });
609+
const postCSSResult = root.toResult({
610+
map: this.options.sourceMap,
611+
to: typeof this.options.sourceMap === 'object' ? this.options.sourceMap.to : undefined
612+
});
610613
const result: ResultPurge = {
611614
css: postCSSResult.toString(),
612615
file: typeof option === "string" ? option : option.name,

0 commit comments

Comments
 (0)