From b3f73ed5293fa1d840aff98b5796f635343d48ec Mon Sep 17 00:00:00 2001 From: Floriel Date: Wed, 23 Feb 2022 17:11:49 +0000 Subject: [PATCH] fix(purgecss-webpack-plugin): add sourcemap support #409 BREAKING CHANGE: drop webpack 4 support --- package-lock.json | 7 +- packages/postcss-purgecss/src/types/index.ts | 3 +- .../webpack.config.js | 1 + .../simple-with-exclusion/webpack.config.js | 1 + .../cases/simple/expected/styles.css | 3 + .../__tests__/cases/simple/webpack.config.js | 1 + packages/purgecss-webpack-plugin/package.json | 9 +- packages/purgecss-webpack-plugin/src/index.ts | 138 ++++++++++++------ .../src/types/index.ts | 2 +- packages/purgecss/src/index.ts | 5 +- packages/purgecss/src/types/index.ts | 4 +- 11 files changed, 117 insertions(+), 57 deletions(-) diff --git a/package-lock.json b/package-lock.json index 67c84507..77dd0443 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,11 +32,11 @@ "acorn-jsx-walk": "^2.0.0", "acorn-walk": "^8.1.1", "commander": "^8.3.0", - "css-loader": "^6.2.0", + "css-loader": "^6.6.0", "event-stream": "^4.0.1", "glob": "^7.2.0", "grunt": "~1.4.1", - "mini-css-extract-plugin": "^2.1.0", + "mini-css-extract-plugin": "^2.5.0", "parse5": "^6.0.0", "parse5-htmlparser2-tree-adapter": "^6.0.0", "plugin-error": "^1.0.1", @@ -48,8 +48,7 @@ "through2": "^4.0.1", "twing": "^5.1.0", "vinyl": "^2.2.0", - "webpack": "*", - "webpack-sources": "^3.2.0" + "webpack": ">=5.0.0" }, "devDependencies": { "@microsoft/api-documenter": "^7.15.3", diff --git a/packages/postcss-purgecss/src/types/index.ts b/packages/postcss-purgecss/src/types/index.ts index fdcd01ee..d330fe78 100644 --- a/packages/postcss-purgecss/src/types/index.ts +++ b/packages/postcss-purgecss/src/types/index.ts @@ -9,6 +9,7 @@ import { * * @public */ -export interface UserDefinedOptions extends Omit { +export interface UserDefinedOptions extends Omit { + content?: PurgeCSSUserDefinedOptions['content']; contentFunction?: (sourceFile: string) => Array; } diff --git a/packages/purgecss-webpack-plugin/__tests__/cases/path-and-safelist-functions/webpack.config.js b/packages/purgecss-webpack-plugin/__tests__/cases/path-and-safelist-functions/webpack.config.js index 3f76f96c..75129898 100644 --- a/packages/purgecss-webpack-plugin/__tests__/cases/path-and-safelist-functions/webpack.config.js +++ b/packages/purgecss-webpack-plugin/__tests__/cases/path-and-safelist-functions/webpack.config.js @@ -11,6 +11,7 @@ const PATHS = { module.exports = { mode: "development", + devtool: false, entry: "./src/index.js", context: path.resolve(__dirname), optimization: { diff --git a/packages/purgecss-webpack-plugin/__tests__/cases/simple-with-exclusion/webpack.config.js b/packages/purgecss-webpack-plugin/__tests__/cases/simple-with-exclusion/webpack.config.js index 7276be9e..7f0fa1c5 100644 --- a/packages/purgecss-webpack-plugin/__tests__/cases/simple-with-exclusion/webpack.config.js +++ b/packages/purgecss-webpack-plugin/__tests__/cases/simple-with-exclusion/webpack.config.js @@ -11,6 +11,7 @@ const PATHS = { module.exports = { mode: "development", + devtool: false, entry: { bundle: "./src/index.js", legacy: "./src/legacy.js", diff --git a/packages/purgecss-webpack-plugin/__tests__/cases/simple/expected/styles.css b/packages/purgecss-webpack-plugin/__tests__/cases/simple/expected/styles.css index 9931e065..ef82036c 100644 --- a/packages/purgecss-webpack-plugin/__tests__/cases/simple/expected/styles.css +++ b/packages/purgecss-webpack-plugin/__tests__/cases/simple/expected/styles.css @@ -13,3 +13,6 @@ md\:w-2\/3 { color: red; } + +/*# sourceMappingURL=styles.css.map */ +/*# sourceMappingURL=styles.css.map*/ \ No newline at end of file diff --git a/packages/purgecss-webpack-plugin/__tests__/cases/simple/webpack.config.js b/packages/purgecss-webpack-plugin/__tests__/cases/simple/webpack.config.js index 989f1c10..fbf654c3 100644 --- a/packages/purgecss-webpack-plugin/__tests__/cases/simple/webpack.config.js +++ b/packages/purgecss-webpack-plugin/__tests__/cases/simple/webpack.config.js @@ -14,6 +14,7 @@ const PATHS = { module.exports = { mode: "development", + devtool: "source-map", optimization: { splitChunks: { cacheGroups: { diff --git a/packages/purgecss-webpack-plugin/package.json b/packages/purgecss-webpack-plugin/package.json index 37acc4c0..aa77827e 100644 --- a/packages/purgecss-webpack-plugin/package.json +++ b/packages/purgecss-webpack-plugin/package.json @@ -38,19 +38,18 @@ }, "dependencies": { "purgecss": "^4.1.3", - "webpack": "*", - "webpack-sources": "^3.2.0" + "webpack": ">=5.0.0" }, "bugs": { "url": "https://github.com/FullHuman/purgecss/issues" }, "devDependencies": { "@types/webpack-sources": "^3.2.0", - "css-loader": "^6.2.0", - "mini-css-extract-plugin": "^2.1.0" + "css-loader": "^6.6.0", + "mini-css-extract-plugin": "^2.5.0" }, "peerDependencies": { - "webpack": "*" + "webpack": ">=5.0.0" }, "publishConfig": { "access": "public", diff --git a/packages/purgecss-webpack-plugin/src/index.ts b/packages/purgecss-webpack-plugin/src/index.ts index 19227e69..e4fca315 100644 --- a/packages/purgecss-webpack-plugin/src/index.ts +++ b/packages/purgecss-webpack-plugin/src/index.ts @@ -1,8 +1,12 @@ import * as fs from "fs"; import * as path from "path"; -import { PurgeCSS, defaultOptions } from "purgecss"; -import { Compilation, Compiler } from "webpack"; -import { ConcatSource } from "webpack-sources"; +import { + PurgeCSS, + defaultOptions, + ResultPurge, + UserDefinedOptions as PurgeCSSUserDefinedOptions, +} from "purgecss"; +import { Compilation, Compiler, sources } from "webpack"; import { PurgedStats, UserDefinedOptions } from "./types"; export * from "./types"; @@ -12,6 +16,7 @@ const pluginName = "PurgeCSS"; /** * Get the filename without ?hash + * * @param fileName - file name */ function getFormattedFilename(fileName: string): string { @@ -23,6 +28,7 @@ function getFormattedFilename(fileName: string): string { /** * Returns true if the filename is of types of one of the specified extensions + * * @param filename - file name * @param extensions - extensions */ @@ -31,6 +37,78 @@ function isFileOfTypes(filename: string, extensions: string[]): boolean { return extensions.includes(extension); } +function getPurgeCSSOptions( + pluginOptions: UserDefinedOptions, + filesToSearch: string[], + asset: sources.Source, + fileName: string, + sourceMap: boolean +): PurgeCSSUserDefinedOptions { + const options = { + ...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: string, + asset: sources.Source, + purgeResult: ResultPurge, + sourceMap: boolean +): sources.Source { + if (!sourceMap || !purgeResult.sourceMap) { + return new sources.RawSource(purgeResult.css); + } + const { source, map } = asset.sourceAndMap(); + + return new sources.SourceMapSource( + purgeResult.css, + name, + purgeResult.sourceMap, + source.toString(), + map, + false + ); +} + /** * @public */ @@ -80,9 +158,7 @@ export class PurgeCSSPlugin { return this.options.only.some((only) => name.includes(only)); } - return Array.isArray(chunk.files) - ? chunk.files.includes(name) - : chunk.files.has(name); + return chunk.files.has(name); }); for (const [name, asset] of assetsToPurge) { @@ -90,50 +166,26 @@ export class PurgeCSSPlugin { (v) => !styleExtensions.some((ext) => v.endsWith(ext)) ); - // Compile through Purgecss and attach to output. - // This loses sourcemaps should there be any! - const options = { - ...defaultOptions, - ...this.options, - content: filesToSearch, - css: [ - { - raw: asset.source().toString(), - }, - ], - }; - - if (typeof options.safelist === "function") { - options.safelist - options.safelist = options.safelist(); - } - - if (typeof options.blocklist === "function") { - options.blocklist = options.blocklist(); - } + const sourceMapEnabled = !!compilation.compiler.options.devtool; + const purgeCSSOptions = getPurgeCSSOptions( + this.options, + filesToSearch, + asset, + name, + sourceMapEnabled + ); - const purgecss = await new PurgeCSS().purge({ - 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, - }); + const purgecss = await new PurgeCSS().purge(purgeCSSOptions); const purged = purgecss[0]; if (purged.rejected) { this.purgedStats[name] = purged.rejected; } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - compilation.updateAsset(name, new ConcatSource(purged.css)); + compilation.updateAsset( + name, + createSource(name, asset, purged, sourceMapEnabled) + ); } } } diff --git a/packages/purgecss-webpack-plugin/src/types/index.ts b/packages/purgecss-webpack-plugin/src/types/index.ts index a315aec4..aa22add1 100644 --- a/packages/purgecss-webpack-plugin/src/types/index.ts +++ b/packages/purgecss-webpack-plugin/src/types/index.ts @@ -28,7 +28,7 @@ export type PurgedStats = { /** * @public */ -export type UserDefinedOptions = Omit & { +export type UserDefinedOptions = Omit & { paths: string[] | PathFunction; moduleExtensions?: string[]; verbose?: boolean; diff --git a/packages/purgecss/src/index.ts b/packages/purgecss/src/index.ts index 7d037f37..9a188837 100644 --- a/packages/purgecss/src/index.ts +++ b/packages/purgecss/src/index.ts @@ -606,7 +606,10 @@ class PurgeCSS { if (this.options.keyframes) this.removeUnusedKeyframes(); if (this.options.variables) this.removeUnusedCSSVariables(); - const postCSSResult = root.toResult({ map: this.options.sourceMap }); + const postCSSResult = root.toResult({ + map: this.options.sourceMap, + to: typeof this.options.sourceMap === 'object' ? this.options.sourceMap.to : undefined + }); const result: ResultPurge = { css: postCSSResult.toString(), file: typeof option === "string" ? option : option.name, diff --git a/packages/purgecss/src/types/index.ts b/packages/purgecss/src/types/index.ts index 375fbce7..5081ee5f 100644 --- a/packages/purgecss/src/types/index.ts +++ b/packages/purgecss/src/types/index.ts @@ -136,7 +136,7 @@ export interface UserDefinedOptions { /** {@inheritDoc Options.rejectedCss} */ rejectedCss?: boolean; /** {@inheritDoc Options.sourceMap } */ - sourceMap?: boolean | postcss.SourceMapOptions + sourceMap?: boolean | postcss.SourceMapOptions & { to?: string } /** {@inheritDoc Options.stdin} */ stdin?: boolean; /** {@inheritDoc Options.stdout} */ @@ -229,7 +229,7 @@ export interface Options { rejected: boolean; rejectedCss: boolean; /** {@inheritDoc postcss#SourceMapOptions} */ - sourceMap: boolean | postcss.SourceMapOptions + sourceMap: boolean | postcss.SourceMapOptions & { to?: string } stdin: boolean; stdout: boolean; variables: boolean;