Skip to content

Commit

Permalink
feat: add source map support #526
Browse files Browse the repository at this point in the history
  • Loading branch information
Ffloriel committed Feb 22, 2022
1 parent 4e6f85c commit f2a9c5a
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 4 deletions.
26 changes: 26 additions & 0 deletions packages/purgecss/__tests__/sourcemap.test.ts
@@ -0,0 +1,26 @@
import { PurgeCSS } from '../src/'
import { ROOT_TEST_EXAMPLES } from './utils';

describe("source map option", () => {
it('contains the source map inlined in the CSS file', async () => {
const resultsPurge = await new PurgeCSS().purge({
content: [`${ROOT_TEST_EXAMPLES}others/remove_unused.js`],
css: [`${ROOT_TEST_EXAMPLES}others/remove_unused.css`],
sourceMap: true
});

expect(resultsPurge[0].css).toContain('sourceMappingURL=data:application/json;base64');
});

it('contains the source map separately when setting inline to false', async () => {
const resultsPurge = await new PurgeCSS().purge({
content: [`${ROOT_TEST_EXAMPLES}others/remove_unused.js`],
css: [`${ROOT_TEST_EXAMPLES}others/remove_unused.css`],
sourceMap: {
inline: false
}
});

expect(resultsPurge[0].sourceMap).toContain('sources":["__tests__/test_examples/others/remove_unused.css"]');
})
});
15 changes: 11 additions & 4 deletions packages/purgecss/src/index.ts
Expand Up @@ -18,7 +18,7 @@ import {
IGNORE_ANNOTATION_CURRENT,
IGNORE_ANNOTATION_END,
IGNORE_ANNOTATION_NEXT,
IGNORE_ANNOTATION_START,
IGNORE_ANNOTATION_START
} from "./constants";
import ExtractorResultSets from "./ExtractorResultSets";
import { CSS_SAFELIST } from "./internal-safelist";
Expand All @@ -36,7 +36,7 @@ import {
RawCSS,
ResultPurge,
UserDefinedOptions,
UserDefinedSafelist,
UserDefinedSafelist
} from "./types";
import { matchAll } from "./utils";
import VariablesStructure from "./VariablesStructure";
Expand Down Expand Up @@ -596,7 +596,8 @@ class PurgeCSS {
? option
: await asyncFs.readFile(option, "utf-8")
: option.raw;
const root = postcss.parse(cssContent);
const isFromFile = typeof option === "string" && !this.options.stdin
const root = postcss.parse(cssContent, { from: isFromFile ? option : undefined });

// purge unused selectors
this.walkThroughCSS(root, selectors);
Expand All @@ -605,11 +606,16 @@ class PurgeCSS {
if (this.options.keyframes) this.removeUnusedKeyframes();
if (this.options.variables) this.removeUnusedCSSVariables();

const postCSSResult = root.toResult({ map: this.options.sourceMap });
const result: ResultPurge = {
css: root.toString(),
css: postCSSResult.toString(),
file: typeof option === "string" ? option : option.name,
};

if (this.options.sourceMap) {
result.sourceMap = postCSSResult.map?.toString();
}

if (this.options.rejected) {
result.rejected = Array.from(this.selectorsRemoved);
this.selectorsRemoved.clear();
Expand All @@ -621,6 +627,7 @@ class PurgeCSS {
.toString();
}


sources.push(result);
}
return sources;
Expand Down
4 changes: 4 additions & 0 deletions packages/purgecss/src/options.ts
@@ -1,5 +1,8 @@
import { ExtractorResult, Options } from "./types/";

/**
* @public
*/
export const defaultOptions: Options = {
css: [],
content: [],
Expand All @@ -10,6 +13,7 @@ export const defaultOptions: Options = {
keyframes: false,
rejected: false,
rejectedCss: false,
sourceMap: false,
stdin: false,
stdout: false,
variables: false,
Expand Down
143 changes: 143 additions & 0 deletions packages/purgecss/src/types/index.ts
@@ -1,6 +1,13 @@
import * as postcss from "postcss";

/**
* @public
*/
export type PostCSSRoot = postcss.Root;

/**
* @internal
*/
export interface AtRules {
fontFace: Array<{
name: string;
Expand All @@ -10,16 +17,25 @@ export interface AtRules {
keyframes: postcss.AtRule[];
}

/**
* @public
*/
export interface RawContent<T = string> {
extension: string;
raw: T;
}

/**
* @public
*/
export interface RawCSS {
raw: string;
name?: string;
}

/**
* @public
*/
export interface ExtractorResultDetailed {
attributes: {
names: string[];
Expand All @@ -31,57 +47,158 @@ export interface ExtractorResultDetailed {
undetermined: string[];
}

/**
* @public
*/
export type ExtractorResult = ExtractorResultDetailed | string[];

/**
* @public
*/
export type ExtractorFunction<T = string> = (content: T) => ExtractorResult;

/**
* @public
*/
export interface Extractors {
extensions: string[];
extractor: ExtractorFunction;
}

/**
* @internal
*/
export type IgnoreType = "end" | "start" | "next";

/**
* @public
*/
export type StringRegExpArray = Array<RegExp | string>;

/**
* @public
*/
export type ComplexSafelist = {
standard?: StringRegExpArray;
/**
* You can safelist selectors and their children based on a regular
* expression with `safelist.deep`
*
* @example
*
* ```ts
* const purgecss = await new PurgeCSS().purge({
* content: [],
* css: [],
* safelist: {
* deep: [/red$/]
* }
* })
* ```
*
* In this example, selectors such as `.bg-red .child-of-bg` will be left
* in the final CSS, even if `child-of-bg` is not found.
*
*/
deep?: RegExp[];
greedy?: RegExp[];
variables?: StringRegExpArray;
keyframes?: StringRegExpArray;
};

/**
* @public
*/
export type UserDefinedSafelist = StringRegExpArray | ComplexSafelist;

/**
* Options used by PurgeCSS to remove unused CSS
*
* @public
*/
export interface UserDefinedOptions {
/** {@inheritDoc Options.content} */
content: Array<string | RawContent>;
/** {@inheritDoc Options.css} */
css: Array<string | RawCSS>;
/** {@inheritDoc Options.defaultExtractor} */
defaultExtractor?: ExtractorFunction;
/** {@inheritDoc Options.extractors} */
extractors?: Array<Extractors>;
/** {@inheritDoc Options.fontFace} */
fontFace?: boolean;
/** {@inheritDoc Options.keyframes} */
keyframes?: boolean;
/** {@inheritDoc Options.output} */
output?: string;
/** {@inheritDoc Options.rejected} */
rejected?: boolean;
/** {@inheritDoc Options.rejectedCss} */
rejectedCss?: boolean;
/** {@inheritDoc Options.sourceMap } */
sourceMap?: boolean | postcss.SourceMapOptions
/** {@inheritDoc Options.stdin} */
stdin?: boolean;
/** {@inheritDoc Options.stdout} */
stdout?: boolean;
/** {@inheritDoc Options.variables} */
variables?: boolean;
/** {@inheritDoc Options.safelist} */
safelist?: UserDefinedSafelist;
/** {@inheritDoc Options.blocklist} */
blocklist?: StringRegExpArray;
/** {@inheritDoc Options.skippedContentGlobs} */
skippedContentGlobs?: Array<string>;
/** {@inheritDoc Options.dynamicAttributes} */
dynamicAttributes?: string[];
}

/**
* Options used by PurgeCSS to remove unused CSS
* Those options are used internally
* @see {@link UserDefinedOptions} for the options defined by the user
*
* @public
*/
export interface Options {
/**
* You can specify content that should be analyzed by PurgeCSS with an
* array of filenames or globs. The files can be HTML, Pug, Blade, etc.
*
* @example
*
* ```ts
* await new PurgeCSS().purge({
* content: ['index.html', '*.js', '*.html', '*.vue'],
* css: ['css/app.css']
* })
* ```
*
* @example
* PurgeCSS also works with raw content. To do this, you need to pass an
* object with the `raw` property instead of a filename. To work properly
* with custom extractors you need to pass the `extension` property along
* with the raw content.
*
* ```ts
* await new PurgeCSS().purge({
* content: [
* {
* raw: '<html><body><div class="app"></div></body></html>',
* extension: 'html'
* },
* '*.js',
* '*.html',
* '*.vue'
* ],
* css: [
* {
* raw: 'body { margin: 0 }'
* },
* 'css/app.css'
* ]
* })
* ```
*/
content: Array<string | RawContent>;
/**
Expand All @@ -91,11 +208,28 @@ export interface Options {
css: Array<string | RawCSS>;
defaultExtractor: ExtractorFunction;
extractors: Array<Extractors>;
/**
* If there are any unused \@font-face rules in your css, you can remove
* them by setting the `fontFace` option to `true`.
*
* @defaultValue `false`
*
* @example
* ```ts
* await new PurgeCSS().purge({
* content: ['index.html', '*.js', '*.html', '*.vue'],
* css: ['css/app.css'],
* fontFace: true
* })
* ```
*/
fontFace: boolean;
keyframes: boolean;
output?: string;
rejected: boolean;
rejectedCss: boolean;
/** {@inheritDoc postcss#SourceMapOptions} */
sourceMap: boolean | postcss.SourceMapOptions
stdin: boolean;
stdout: boolean;
variables: boolean;
Expand Down Expand Up @@ -124,8 +258,17 @@ export interface Options {
dynamicAttributes: string[];
}

/**
* @public
*/
export interface ResultPurge {
css: string;
/**
* sourceMap property will be empty if
* {@link UserDefinedOptions.sourceMap} inline is not set to false, as the
* source map will be contained within the text of ResultPurge.css
*/
sourceMap?: string;
rejectedCss?: string;
file?: string;
rejected?: string[];
Expand Down

0 comments on commit f2a9c5a

Please sign in to comment.