Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fresh-tools-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@compiled/webpack-loader': minor
---

**BREAKING** Compiled now takes control of the entire CSS pipeline. This means any CSS declared inside or outside of Compiled will be sorted and potentially hoisted if unsafe.
5 changes: 5 additions & 0 deletions .changeset/large-pans-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@compiled/css': patch
---

Sort now can remove unstable atomic rules with the `removeUnstableRules` option set to `true`.
5 changes: 5 additions & 0 deletions .changeset/silent-planets-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@compiled/webpack-loader': patch
---

Compiled now supports async chunked CSS. When components are code split and have unique styles only used in that chunk, its styles will be in their own style sheet.
5 changes: 5 additions & 0 deletions .changeset/tame-trains-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@compiled/css': minor
---

**BREAKING** Sort now returns an object with `css` and any found `unstableRules` when the `removeUnstableRules` option is `true`.
24 changes: 24 additions & 0 deletions packages/css/src/plugins/remove-unstable-rules.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { plugin } from 'postcss';

/**
* Removes unstable atomic rules from the style sheet.
*/
export const removeUnstableRules = plugin<{ callback: (sheet: string) => void }>(
'remove-unstable-rules',
(opts) => {
return (root) => {
root.each((node) => {
switch (node.type) {
case 'rule':
if (node.selector.includes(':')) {
opts?.callback(node.toString());
node.remove();
}

default:
break;
}
});
};
}
);
23 changes: 20 additions & 3 deletions packages/css/src/sort.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
import postcss from 'postcss';
import { toBoolean } from '@compiled/utils';
import { sortAtomicStyleSheet } from './plugins/sort-atomic-style-sheet';
import { mergeDuplicateAtRules } from './plugins/merge-duplicate-at-rules';
import { removeUnstableRules } from './plugins/remove-unstable-rules';

interface SortOpts {
removeUnstableRules?: boolean;
}

/**
* Sorts an atomic style sheet.
*
* @param stylesheet
* @returns
*/
export function sort(stylesheet: string): string {
const result = postcss([mergeDuplicateAtRules(), sortAtomicStyleSheet()]).process(stylesheet, {
export function sort(
stylesheet: string,
opts: SortOpts = { removeUnstableRules: false }
): { css: string; unstableRules: string[] } {
const unstableRules: string[] = [];
const result = postcss(
[
opts.removeUnstableRules &&
removeUnstableRules({ callback: (sheet) => unstableRules.push(sheet) }),
mergeDuplicateAtRules(),
sortAtomicStyleSheet(),
].filter(toBoolean)
).process(stylesheet, {
from: undefined,
});

return result.css;
return { css: result.css, unstableRules };
}
96 changes: 64 additions & 32 deletions packages/webpack-loader/src/__tests__/extract-plugin.test.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,76 @@
import { bundle } from './utils/webpack';

describe('CompiledExtractPlugin', () => {
const assetName = 'static/compiled-css.css';
const getCSSAssets = (assets: Record<string, string>) => {
return Object.keys(assets)
.filter((name) => name.endsWith('.css'))
.reduce(
(acc, name) =>
Object.assign(acc, {
[name]: assets[name],
}),
{}
);
};

it('should extract styles from a single file into a style sheet', async () => {
const actual = await bundle(require.resolve('./fixtures/single.js'));

expect(actual[assetName]).toMatchInlineSnapshot(`
"._1wyb1fwx{font-size:12px}
"
expect(getCSSAssets(actual)).toMatchInlineSnapshot(`
Object {
"static/main.css": "._1wyb1fwx{font-size:12px}
",
}
`);
});

it('should extract styles from multiple files into a style sheet', async () => {
const actual = await bundle(require.resolve('./fixtures/multiple.js'));

expect(actual[assetName]).toMatchInlineSnapshot(`
"
expect(getCSSAssets(actual)).toMatchInlineSnapshot(`
Object {
"static/main.css": "
._syaz5scu{color:red}
._syazmu8g{color:blueviolet}
._19itgh5a{border:2px solid orange}
._syazruxl{color:orange}
._f8pjruxl:focus{color:orange}
._f8pj1cnh:focus{color:purple}._30l31gy6:hover{color:yellow}
._syazruxl{color:orange}._f8pjruxl:focus{color:orange}
._f8pj1cnh:focus{color:purple}
._30l31gy6:hover{color:yellow}
._30l313q2:hover{color:blue}
"
@media screen{._43475scu{color:red}}
",
}
`);
});

it('should extract styles from an async chunk', async () => {
it('should chunk safe style declarations', async () => {
const actual = await bundle(require.resolve('./fixtures/async.js'));

// Only generate one CSS bundle
expect(Object.keys(actual)).toMatchInlineSnapshot(`
Array [
"bundle.js",
"298.bundle.js",
"static/compiled-css.css",
"298.bundle.js.LICENSE.txt",
]
`);
// Extract the styles into said bundle
expect(actual[assetName]).toMatchInlineSnapshot(`
"._19itgh5a{border:2px solid orange}
expect(getCSSAssets(actual)).toMatchInlineSnapshot(`
Object {
"static/696.css": "._19itgh5a{border:2px solid orange}
._syazruxl{color:orange}
",
"static/main.css": "._syazmu8g{color:blueviolet}
",
}
`);
});

it('should hoist and sort chunked style declaration', async () => {
const actual = await bundle(require.resolve('./fixtures/async-sort.js'));

expect(getCSSAssets(actual)).toMatchInlineSnapshot(`
Object {
"static/569.css": "._syaz5scu{color:red}
._19itgh5a{border:2px solid orange}
._syazruxl{color:orange}
"
@media screen{._43475scu{color:red}}
",
"static/main.css": "._syazmu8g{color:blueviolet}
._f8pjruxl:focus{color:orange}._f8pj1cnh:focus{color:purple}._30l31gy6:hover{color:yellow}._30l313q2:hover{color:blue}",
}
`);
});

Expand All @@ -61,24 +87,27 @@ describe('CompiledExtractPlugin', () => {
it('should extract from a pre-built babel files', async () => {
const actual = await bundle(require.resolve('./fixtures/babel.js'));

expect(actual[assetName]).toMatchInlineSnapshot(`
"._19pk1ul9{margin-top:30px}
expect(getCSSAssets(actual)).toMatchInlineSnapshot(`
Object {
"static/main.css": "._19pk1ul9{margin-top:30px}
._19bvftgi{padding-left:8px}
._n3tdftgi{padding-bottom:8px}
._u5f3ftgi{padding-right:8px}
._ca0qftgi{padding-top:8px}
._19itlf8h{border:2px solid blue}
._1wyb1ul9{font-size:30px}
._syaz13q2{color:blue}
"
",
}
`);
});

it('should find bindings', async () => {
const actual = await bundle(require.resolve('./fixtures/binding-not-found.tsx'));

expect(actual[assetName]).toMatchInlineSnapshot(`
"._syaz1r31{color:currentColor}
expect(getCSSAssets(actual)).toMatchInlineSnapshot(`
Object {
"static/main.css": "._syaz1r31{color:currentColor}
._ajmmnqa1{-webkit-text-decoration-style:solid;text-decoration-style:solid}
._1hmsglyw{-webkit-text-decoration-line:none;text-decoration-line:none}
._4bfu1r31{-webkit-text-decoration-color:currentColor;text-decoration-color:currentColor}
Expand All @@ -101,17 +130,20 @@ describe('CompiledExtractPlugin', () => {
._4cvr1h6o{align-items:center}
._1e0c1txw{display:flex}
._4t3i1jdh{height:9rem}
"
",
}
`);
});

it('should extract important', async () => {
const actual = await bundle(require.resolve('./fixtures/important-styles.js'));

expect(actual[assetName]).toMatchInlineSnapshot(`
"._syaz13q2{color:blue}
expect(getCSSAssets(actual)).toMatchInlineSnapshot(`
Object {
"static/main.css": "._syaz13q2{color:blue}
._1wybc038{font-size:12!important}
"
",
}
`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import '@compiled/react';
import { blueviolet } from './imports/colors';

import('./multiple');

const Component = () => <div css={{ color: blueviolet }} />;

export default Component;
7 changes: 7 additions & 0 deletions packages/webpack-loader/src/__tests__/fixtures/async.js
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
import '@compiled/react';
import { blueviolet } from './imports/colors';

const Component = () => <div css={{ color: blueviolet }} />;

import('./imports/css-prop');

export default Component;
12 changes: 8 additions & 4 deletions packages/webpack-loader/src/__tests__/fixtures/multiple.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { blueviolet, blue, orange, purple, red, yellow } from './imports/colors'
import { Orange } from './imports/css-prop';

export const Blue = styled.span`
@media screen {
color: red;
}

color: ${blueviolet};

:focus {
Expand All @@ -18,11 +22,11 @@ export const Blue = styled.span`
export const Red = styled.span`
color: ${red};

:focus {
color: ${orange};
}

:hover {
color: ${yellow};
}

:focus {
color: ${orange};
}
`;
2 changes: 1 addition & 1 deletion packages/webpack-loader/src/__tests__/utils/webpack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function bundle(
},
},
{
loader: '@compiled/webpack-loader',
loader: require.resolve('../../compiled-loader'),
options: {
importReact: false,
extract,
Expand Down
Loading