From 53ee8d69011100867b717157924af46b9983fd81 Mon Sep 17 00:00:00 2001 From: madou Date: Sun, 11 Apr 2021 11:54:25 +1000 Subject: [PATCH 1/6] feat: remove concat of style sheets --- .../src/__tests__/extract-plugin.test.tsx | 73 +++++++++++-------- .../src/__tests__/fixtures/async.js | 6 ++ .../webpack-loader/src/extract-plugin.tsx | 43 +---------- 3 files changed, 52 insertions(+), 70 deletions(-) diff --git a/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx b/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx index b24ee0ee0..58db6d38c 100644 --- a/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx +++ b/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx @@ -1,22 +1,35 @@ import { bundle } from './utils/webpack'; describe('CompiledExtractPlugin', () => { - const assetName = 'static/compiled-css.css'; + const getCSSAssets = (assets: Record) => { + 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} @@ -24,27 +37,23 @@ describe('CompiledExtractPlugin', () => { ._f8pjruxl:focus{color:orange} ._f8pj1cnh:focus{color:purple}._30l31gy6:hover{color:yellow} ._30l313q2:hover{color:blue} - " + ", + } `); }); - 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/298.css": "._19itgh5a{border:2px solid orange} ._syazruxl{color:orange} - " + ", + "static/main.css": "._1wyb1fwx{font-size:12px} + ", + } `); }); @@ -61,8 +70,9 @@ 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} @@ -70,15 +80,17 @@ describe('CompiledExtractPlugin', () => { ._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} @@ -101,17 +113,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} - " + ", + } `); }); }); diff --git a/packages/webpack-loader/src/__tests__/fixtures/async.js b/packages/webpack-loader/src/__tests__/fixtures/async.js index 5143d3197..0d6c4146b 100644 --- a/packages/webpack-loader/src/__tests__/fixtures/async.js +++ b/packages/webpack-loader/src/__tests__/fixtures/async.js @@ -1 +1,7 @@ +import '@compiled/react'; + +const Component = () =>
; + import('./imports/css-prop'); + +export default Component; diff --git a/packages/webpack-loader/src/extract-plugin.tsx b/packages/webpack-loader/src/extract-plugin.tsx index 9449a6242..7a5b0a6a5 100644 --- a/packages/webpack-loader/src/extract-plugin.tsx +++ b/packages/webpack-loader/src/extract-plugin.tsx @@ -13,56 +13,18 @@ export const pluginName = 'CompiledExtractPlugin'; export const styleSheetName = 'compiled-css'; /** - * Returns CSS Assets that we're interested in. + * Returns CSS Assets from the current compilation. * - * @param options * @param assets - * @returns */ const getCSSAssets = (assets: Compilation['assets']) => { return Object.keys(assets) .filter((assetName) => { - return assetName.endsWith(`${styleSheetName}.css`); + return assetName.endsWith('.css'); }) .map((assetName) => ({ name: assetName, source: assets[assetName], info: {} })); }; -/** - * Set a cache group to force all CompiledCSS found to be in a single style sheet. - * We do this to simplify the sorting story for now. Later on we can investigate - * hoisting only unstable styles into the parent style sheet from async chunks. - * - * @param compiler - */ -const forceCSSIntoOneStyleSheet = (compiler: Compiler) => { - const cacheGroup = { - compiledCSS: { - name: styleSheetName, - type: 'css/mini-extract', - chunks: 'all', - // We merge only CSS from Compiled. - test: /css-loader\/extract\.css$/, - enforce: true, - }, - }; - - if (!compiler.options.optimization) { - compiler.options.optimization = {}; - } - - if (!compiler.options.optimization.splitChunks) { - compiler.options.optimization.splitChunks = { - cacheGroups: {}, - }; - } - - if (!compiler.options.optimization.splitChunks.cacheGroups) { - compiler.options.optimization.splitChunks.cacheGroups = {}; - } - - Object.assign(compiler.options.optimization.splitChunks.cacheGroups, cacheGroup); -}; - /** * Pushes a new loader onto the compiler. * The loader will be applied to all JS files found in node modules that import `@compiled/react`. @@ -108,7 +70,6 @@ export class CompiledExtractPlugin { const { RawSource } = getSources(compiler); pushNodeModulesExtractLoader(compiler, this.#options); - forceCSSIntoOneStyleSheet(compiler); compiler.hooks.compilation.tap(pluginName, (compilation) => { getNormalModuleHook(compiler, compilation).tap(pluginName, (loaderContext) => { From b086ea7add64000db60cca44b045d3fa3faf3338 Mon Sep 17 00:00:00 2001 From: madou Date: Sun, 11 Apr 2021 12:16:06 +1000 Subject: [PATCH 2/6] chore: adds tests --- .../src/__tests__/extract-plugin.test.tsx | 26 ++++++++++++++++--- .../src/__tests__/fixtures/async-sort.js | 1 + .../src/__tests__/fixtures/multiple.js | 12 ++++++--- .../webpack-loader/src/extract-plugin.tsx | 6 ++--- packages/webpack-loader/src/types.tsx | 14 +++++----- 5 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 packages/webpack-loader/src/__tests__/fixtures/async-sort.js diff --git a/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx b/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx index 58db6d38c..3728fd02b 100644 --- a/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx +++ b/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx @@ -33,10 +33,11 @@ describe('CompiledExtractPlugin', () => { ._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}} ", } `); @@ -57,6 +58,25 @@ describe('CompiledExtractPlugin', () => { `); }); + it('should sort chunk style declaration', async () => { + const actual = await bundle(require.resolve('./fixtures/async-sort.js')); + + expect(getCSSAssets(actual)).toMatchInlineSnapshot(` + Object { + "static/354.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} + ._30l313q2:hover{color:blue} + @media screen{._43475scu{color:red}} + ", + } + `); + }); + it('should throw when plugin is not configured', async () => { const error: Error = await bundle(require.resolve('./fixtures/single.js'), { disablePlugins: true, diff --git a/packages/webpack-loader/src/__tests__/fixtures/async-sort.js b/packages/webpack-loader/src/__tests__/fixtures/async-sort.js new file mode 100644 index 000000000..0b0d9b297 --- /dev/null +++ b/packages/webpack-loader/src/__tests__/fixtures/async-sort.js @@ -0,0 +1 @@ +import('./multiple'); diff --git a/packages/webpack-loader/src/__tests__/fixtures/multiple.js b/packages/webpack-loader/src/__tests__/fixtures/multiple.js index ddfc328d6..25027ad2e 100644 --- a/packages/webpack-loader/src/__tests__/fixtures/multiple.js +++ b/packages/webpack-loader/src/__tests__/fixtures/multiple.js @@ -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 { @@ -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}; + } `; diff --git a/packages/webpack-loader/src/extract-plugin.tsx b/packages/webpack-loader/src/extract-plugin.tsx index 7a5b0a6a5..71ed24d3f 100644 --- a/packages/webpack-loader/src/extract-plugin.tsx +++ b/packages/webpack-loader/src/extract-plugin.tsx @@ -1,7 +1,7 @@ import { sort } from '@compiled/css'; import { toBoolean, createError } from '@compiled/utils'; import type { Compiler, Compilation } from 'webpack'; -import type { CompiledExtractPluginOptions } from './types'; +import type { CompiledExtractPluginOptions, LoaderOpts } from './types'; import { getAssetSourceContents, getNormalModuleHook, @@ -72,10 +72,10 @@ export class CompiledExtractPlugin { pushNodeModulesExtractLoader(compiler, this.#options); compiler.hooks.compilation.tap(pluginName, (compilation) => { - getNormalModuleHook(compiler, compilation).tap(pluginName, (loaderContext) => { + getNormalModuleHook(compiler, compilation).tap(pluginName, (loaderContext: LoaderOpts) => { // We add some information here to tell loaders that the plugin has been configured. // Bundling will throw if this is missing (i.e. consumers did not setup correctly). - (loaderContext as any)[pluginName] = true; + loaderContext[pluginName] = true; }); getOptimizeAssetsHook(compiler, compilation).tap(pluginName, (assets) => { diff --git a/packages/webpack-loader/src/types.tsx b/packages/webpack-loader/src/types.tsx index dad8afba2..bec0962ba 100644 --- a/packages/webpack-loader/src/types.tsx +++ b/packages/webpack-loader/src/types.tsx @@ -27,7 +27,14 @@ export interface CompiledLoaderOptions { nonce?: string; } -export interface LoaderThis { +export interface LoaderOpts { + /** + * When set confirms that the extract plugin has been configured. + */ + [pluginName]?: true; +} + +export interface LoaderThis extends LoaderOpts { /** * Query param passed to the loader. * @@ -81,11 +88,6 @@ export interface LoaderThis { * @param error */ emitError(error: Error): void; - - /** - * When set confirms that the extract plugin has been configured. - */ - [pluginName]?: true; } export interface CompiledExtractPluginOptions { From d4cd8d1b55ffd9c95aad7260e090ce339ca2dfd6 Mon Sep 17 00:00:00 2001 From: madou Date: Sun, 11 Apr 2021 12:34:44 +1000 Subject: [PATCH 3/6] fix: sort CSS chunks --- .../src/__tests__/extract-plugin.test.tsx | 5 ++-- .../src/__tests__/fixtures/async-sort.js | 7 +++++ .../src/__tests__/fixtures/async.js | 3 ++- .../webpack-loader/src/extract-plugin.tsx | 21 ++++++++++----- packages/webpack-loader/src/utils/webpack.tsx | 26 +++++++++++++++++++ 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx b/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx index 3728fd02b..1b3a9cc26 100644 --- a/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx +++ b/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx @@ -52,7 +52,7 @@ describe('CompiledExtractPlugin', () => { "static/298.css": "._19itgh5a{border:2px solid orange} ._syazruxl{color:orange} ", - "static/main.css": "._1wyb1fwx{font-size:12px} + "static/main.css": "._syazmu8g{color:blueviolet} ", } `); @@ -65,13 +65,14 @@ describe('CompiledExtractPlugin', () => { Object { "static/354.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} ._30l313q2:hover{color:blue} @media screen{._43475scu{color:red}} + ", + "static/main.css": "._syazmu8g{color:blueviolet} ", } `); diff --git a/packages/webpack-loader/src/__tests__/fixtures/async-sort.js b/packages/webpack-loader/src/__tests__/fixtures/async-sort.js index 0b0d9b297..80f6673d2 100644 --- a/packages/webpack-loader/src/__tests__/fixtures/async-sort.js +++ b/packages/webpack-loader/src/__tests__/fixtures/async-sort.js @@ -1 +1,8 @@ +import '@compiled/react'; +import { blueviolet } from './imports/colors'; + import('./multiple'); + +const Component = () =>
; + +export default Component; diff --git a/packages/webpack-loader/src/__tests__/fixtures/async.js b/packages/webpack-loader/src/__tests__/fixtures/async.js index 0d6c4146b..2a4fcdc2f 100644 --- a/packages/webpack-loader/src/__tests__/fixtures/async.js +++ b/packages/webpack-loader/src/__tests__/fixtures/async.js @@ -1,6 +1,7 @@ import '@compiled/react'; +import { blueviolet } from './imports/colors'; -const Component = () =>
; +const Component = () =>
; import('./imports/css-prop'); diff --git a/packages/webpack-loader/src/extract-plugin.tsx b/packages/webpack-loader/src/extract-plugin.tsx index 71ed24d3f..b9c8691f0 100644 --- a/packages/webpack-loader/src/extract-plugin.tsx +++ b/packages/webpack-loader/src/extract-plugin.tsx @@ -7,6 +7,7 @@ import { getNormalModuleHook, getOptimizeAssetsHook, getSources, + getMergeAssetsHook, } from './utils/webpack'; export const pluginName = 'CompiledExtractPlugin'; @@ -79,16 +80,24 @@ export class CompiledExtractPlugin { }); getOptimizeAssetsHook(compiler, compilation).tap(pluginName, (assets) => { - const cssAssets = getCSSAssets(assets); - if (cssAssets.length === 0) { + const CSSAssets = getCSSAssets(assets); + if (CSSAssets.length === 0) { return; } - const [asset] = cssAssets; - const contents = getAssetSourceContents(asset.source); - const newSource = new RawSource(sort(contents)); + CSSAssets.forEach((asset) => { + const contents = getAssetSourceContents(asset.source); + const newSource = new RawSource(sort(contents)); - compilation.updateAsset(asset.name, newSource, asset.info); + compilation.updateAsset(asset.name, newSource, asset.info); + }); + }); + + getMergeAssetsHook(compiler, compilation).tap(pluginName, (assets) => { + const CSSAssets = getCSSAssets(assets); + if (CSSAssets.length === 0) { + return; + } }); }); } diff --git a/packages/webpack-loader/src/utils/webpack.tsx b/packages/webpack-loader/src/utils/webpack.tsx index 393c153fe..7ca3d1a88 100644 --- a/packages/webpack-loader/src/utils/webpack.tsx +++ b/packages/webpack-loader/src/utils/webpack.tsx @@ -77,6 +77,32 @@ export const getOptimizeAssetsHook = ( }; }; +/** + * Returns webpack 4 & 5 compatible merge assets hook. + * @param compiler + * @param compilation + * @returns + */ +export const getMergeAssetsHook = ( + compiler: Compiler, + compilation: CompilationType +): { tap: CompilationType['hooks']['processAssets']['tap'] } => { + const { Compilation } = compiler.webpack; + const processAssets = compilation.hooks.processAssets; + + return { + tap: (pluginName: string, callback: (assets: CompilationType['assets']) => void) => { + processAssets.tap( + { + name: pluginName, + stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT, + }, + callback + ); + }, + }; +}; + /** * Returns webpack 4 & 5 compatible sources. * @returns From 8f556d2591df7260a133ed1384aa244729e5d263 Mon Sep 17 00:00:00 2001 From: madou Date: Sun, 11 Apr 2021 13:33:46 +1000 Subject: [PATCH 4/6] chore: use source file --- .../webpack-loader/src/__tests__/extract-plugin.test.tsx | 6 +++--- packages/webpack-loader/src/__tests__/utils/webpack.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx b/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx index 1b3a9cc26..ba1111d09 100644 --- a/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx +++ b/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx @@ -49,7 +49,7 @@ describe('CompiledExtractPlugin', () => { // Extract the styles into said bundle expect(getCSSAssets(actual)).toMatchInlineSnapshot(` Object { - "static/298.css": "._19itgh5a{border:2px solid orange} + "static/696.css": "._19itgh5a{border:2px solid orange} ._syazruxl{color:orange} ", "static/main.css": "._syazmu8g{color:blueviolet} @@ -58,12 +58,12 @@ describe('CompiledExtractPlugin', () => { `); }); - it('should sort chunk style declaration', async () => { + 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/354.css": " + "static/569.css": " ._syaz5scu{color:red} ._19itgh5a{border:2px solid orange} ._syazruxl{color:orange}._f8pjruxl:focus{color:orange} diff --git a/packages/webpack-loader/src/__tests__/utils/webpack.tsx b/packages/webpack-loader/src/__tests__/utils/webpack.tsx index e3ab64f62..de7d646f8 100644 --- a/packages/webpack-loader/src/__tests__/utils/webpack.tsx +++ b/packages/webpack-loader/src/__tests__/utils/webpack.tsx @@ -37,7 +37,7 @@ export function bundle( }, }, { - loader: '@compiled/webpack-loader', + loader: require.resolve('../../compiled-loader'), options: { importReact: false, extract, From 5195108a6d0edb0c8e9f0f23ca57b5b53b2e0e56 Mon Sep 17 00:00:00 2001 From: madou Date: Sun, 11 Apr 2021 16:38:52 +1000 Subject: [PATCH 5/6] feat: hoist unstable styles from chunks --- .../css/src/plugins/remove-unstable-rules.tsx | 24 +++++++++++++++++ packages/css/src/sort.tsx | 23 +++++++++++++--- .../src/__tests__/extract-plugin.test.tsx | 10 +++---- .../webpack-loader/src/extract-plugin.tsx | 20 +++++++------- packages/webpack-loader/src/utils/webpack.tsx | 26 ------------------- 5 files changed, 58 insertions(+), 45 deletions(-) create mode 100644 packages/css/src/plugins/remove-unstable-rules.tsx diff --git a/packages/css/src/plugins/remove-unstable-rules.tsx b/packages/css/src/plugins/remove-unstable-rules.tsx new file mode 100644 index 000000000..86de2b5c6 --- /dev/null +++ b/packages/css/src/plugins/remove-unstable-rules.tsx @@ -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; + } + }); + }; + } +); diff --git a/packages/css/src/sort.tsx b/packages/css/src/sort.tsx index 17123d29e..cb7343a69 100644 --- a/packages/css/src/sort.tsx +++ b/packages/css/src/sort.tsx @@ -1,6 +1,12 @@ 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. @@ -8,10 +14,21 @@ import { mergeDuplicateAtRules } from './plugins/merge-duplicate-at-rules'; * @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 }; } diff --git a/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx b/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx index ba1111d09..d99bd4e71 100644 --- a/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx +++ b/packages/webpack-loader/src/__tests__/extract-plugin.test.tsx @@ -63,17 +63,13 @@ describe('CompiledExtractPlugin', () => { expect(getCSSAssets(actual)).toMatchInlineSnapshot(` Object { - "static/569.css": " - ._syaz5scu{color:red} + "static/569.css": "._syaz5scu{color:red} ._19itgh5a{border:2px solid orange} - ._syazruxl{color:orange}._f8pjruxl:focus{color:orange} - ._f8pj1cnh:focus{color:purple} - ._30l31gy6:hover{color:yellow} - ._30l313q2:hover{color:blue} + ._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}", } `); }); diff --git a/packages/webpack-loader/src/extract-plugin.tsx b/packages/webpack-loader/src/extract-plugin.tsx index b9c8691f0..aa4e56754 100644 --- a/packages/webpack-loader/src/extract-plugin.tsx +++ b/packages/webpack-loader/src/extract-plugin.tsx @@ -7,7 +7,6 @@ import { getNormalModuleHook, getOptimizeAssetsHook, getSources, - getMergeAssetsHook, } from './utils/webpack'; export const pluginName = 'CompiledExtractPlugin'; @@ -85,19 +84,22 @@ export class CompiledExtractPlugin { return; } - CSSAssets.forEach((asset) => { + const hoistedStyles: string[] = []; + const [entry, ...chunks] = CSSAssets; + + chunks.forEach((asset) => { const contents = getAssetSourceContents(asset.source); - const newSource = new RawSource(sort(contents)); + const { css, unstableRules } = sort(contents, { removeUnstableRules: true }); + const newSource = new RawSource(css); + hoistedStyles.push(...unstableRules); compilation.updateAsset(asset.name, newSource, asset.info); }); - }); - getMergeAssetsHook(compiler, compilation).tap(pluginName, (assets) => { - const CSSAssets = getCSSAssets(assets); - if (CSSAssets.length === 0) { - return; - } + const contents = getAssetSourceContents(entry.source); + const { css } = sort(contents + hoistedStyles.join('')); + const newSource = new RawSource(css); + compilation.updateAsset(entry.name, newSource, entry.info); }); }); } diff --git a/packages/webpack-loader/src/utils/webpack.tsx b/packages/webpack-loader/src/utils/webpack.tsx index 7ca3d1a88..393c153fe 100644 --- a/packages/webpack-loader/src/utils/webpack.tsx +++ b/packages/webpack-loader/src/utils/webpack.tsx @@ -77,32 +77,6 @@ export const getOptimizeAssetsHook = ( }; }; -/** - * Returns webpack 4 & 5 compatible merge assets hook. - * @param compiler - * @param compilation - * @returns - */ -export const getMergeAssetsHook = ( - compiler: Compiler, - compilation: CompilationType -): { tap: CompilationType['hooks']['processAssets']['tap'] } => { - const { Compilation } = compiler.webpack; - const processAssets = compilation.hooks.processAssets; - - return { - tap: (pluginName: string, callback: (assets: CompilationType['assets']) => void) => { - processAssets.tap( - { - name: pluginName, - stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT, - }, - callback - ); - }, - }; -}; - /** * Returns webpack 4 & 5 compatible sources. * @returns From 0768f8edb896616214a37ff5c90f5aa7a7e99820 Mon Sep 17 00:00:00 2001 From: madou Date: Sun, 11 Apr 2021 16:52:02 +1000 Subject: [PATCH 6/6] chore: adds changesets --- .changeset/fresh-tools-refuse.md | 5 +++++ .changeset/large-pans-retire.md | 5 +++++ .changeset/silent-planets-invent.md | 5 +++++ .changeset/tame-trains-draw.md | 5 +++++ 4 files changed, 20 insertions(+) create mode 100644 .changeset/fresh-tools-refuse.md create mode 100644 .changeset/large-pans-retire.md create mode 100644 .changeset/silent-planets-invent.md create mode 100644 .changeset/tame-trains-draw.md diff --git a/.changeset/fresh-tools-refuse.md b/.changeset/fresh-tools-refuse.md new file mode 100644 index 000000000..c539ba79b --- /dev/null +++ b/.changeset/fresh-tools-refuse.md @@ -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. diff --git a/.changeset/large-pans-retire.md b/.changeset/large-pans-retire.md new file mode 100644 index 000000000..66eeec127 --- /dev/null +++ b/.changeset/large-pans-retire.md @@ -0,0 +1,5 @@ +--- +'@compiled/css': patch +--- + +Sort now can remove unstable atomic rules with the `removeUnstableRules` option set to `true`. diff --git a/.changeset/silent-planets-invent.md b/.changeset/silent-planets-invent.md new file mode 100644 index 000000000..1ffaba55c --- /dev/null +++ b/.changeset/silent-planets-invent.md @@ -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. diff --git a/.changeset/tame-trains-draw.md b/.changeset/tame-trains-draw.md new file mode 100644 index 000000000..daaabe64d --- /dev/null +++ b/.changeset/tame-trains-draw.md @@ -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`.