Skip to content

Commit

Permalink
Extract CSS for webpack4 (#658)
Browse files Browse the repository at this point in the history
* chore: adds webpack 4 example

* feat: adds webpack4 support

* chore: delete wp4 example

* chore: cleanup code

* docs(changeset): Support for webpack 4 has been added, follow the [extraction guide](https://compiledcssinjs.com/docs/css-extraction-webpack) to get started.

* chore: remove wp4 scripts

* fix: use better error
  • Loading branch information
Madou authored Mar 23, 2021
1 parent c93fec2 commit 660309a
Show file tree
Hide file tree
Showing 6 changed files with 338 additions and 76 deletions.
5 changes: 5 additions & 0 deletions .changeset/neat-shrimps-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@compiled/webpack-loader': patch
---

Support for webpack 4 has been added, follow the [extraction guide](https://compiledcssinjs.com/docs/css-extraction-webpack) to get started.
3 changes: 2 additions & 1 deletion packages/webpack-loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"@compiled/babel-plugin": "0.6.9",
"@compiled/css": "0.6.9",
"@compiled/utils": "0.6.9",
"loader-utils": "^2.0.0"
"loader-utils": "^2.0.0",
"webpack-sources": "^1.4.2"
},
"peerDependencies": {
"webpack": ">= 4.46.0"
Expand Down
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 @@ -69,7 +69,7 @@ export function bundle(
return;
}

if (stats?.hasErrors && stats.compilation.errors.length) {
if (stats?.hasErrors() && stats.compilation.errors.length) {
rej(stats.compilation.errors);
return;
}
Expand Down
82 changes: 31 additions & 51 deletions packages/webpack-loader/src/extract-plugin.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { sort } from '@compiled/css';
import { toBoolean } from '@compiled/utils';
import type { Compiler, Compilation, sources } from 'webpack';
import { toBoolean, createError } from '@compiled/utils';
import type { Compiler, Compilation } from 'webpack';
import type { CompiledExtractPluginOptions } from './types';
import {
getAssetSourceContents,
getNormalModuleHook,
getOptimizeAssetsHook,
getSources,
} from './utils/webpack';

export const pluginName = 'CompiledExtractPlugin';
export const styleSheetName = 'compiled-css';
Expand All @@ -21,21 +27,6 @@ const getCSSAssets = (assets: Compilation['assets']) => {
.map((assetName) => ({ name: assetName, source: assets[assetName], info: {} }));
};

/**
* Returns the string representation of an assets source.
*
* @param source
* @returns
*/
const getAssetSourceContents = (assetSource: sources.Source) => {
const source = assetSource.source();
if (typeof source === 'string') {
return source;
}

return source.toString();
};

/**
* 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
Expand All @@ -55,6 +46,10 @@ const forceCSSIntoOneStyleSheet = (compiler: Compiler) => {
},
};

if (!compiler.options.optimization) {
compiler.options.optimization = {};
}

if (!compiler.options.optimization.splitChunks) {
compiler.options.optimization.splitChunks = {
cacheGroups: {},
Expand All @@ -74,10 +69,14 @@ const forceCSSIntoOneStyleSheet = (compiler: Compiler) => {
*
* @param compiler
*/
const applyExtractFromNodeModule = (
const pushNodeModulesExtractLoader = (
compiler: Compiler,
options: CompiledExtractPluginOptions
): void => {
if (!compiler.options.module) {
throw createError('webpack-loader')('module options not defined');
}

compiler.options.module.rules.push({
test: { and: [/node_modules.+\.js$/, options.nodeModulesTest].filter(toBoolean) },
include: options.nodeModulesInclude,
Expand Down Expand Up @@ -106,49 +105,30 @@ export class CompiledExtractPlugin {
}

apply(compiler: Compiler): void {
const { NormalModule, Compilation, sources } = compiler.webpack;
const { RawSource } = getSources(compiler);

applyExtractFromNodeModule(compiler, this.#options);
pushNodeModulesExtractLoader(compiler, this.#options);
forceCSSIntoOneStyleSheet(compiler);

compiler.hooks.compilation.tap(pluginName, (compilation) => {
const normalModuleHook =
typeof NormalModule.getCompilationHooks !== 'undefined'
? // Webpack 5 flow
NormalModule.getCompilationHooks(compilation).loader
: // Webpack 4 flow
compilation.hooks.normalModuleLoader;

normalModuleHook.tap(pluginName, (loaderContext) => {
getNormalModuleHook(compiler, compilation).tap(pluginName, (loaderContext) => {
// 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;
});

const processAssetsAfterOptimize =
// Webpack 5 flow
compilation.hooks.processAssets ||
// Webpack 4 flow
compilation.hooks.afterOptimizeChunkAssets;

processAssetsAfterOptimize.tap(
{
name: pluginName,
stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
},
(assets) => {
const cssAssets = getCSSAssets(assets);
if (cssAssets.length === 0) {
return;
}

const [asset] = cssAssets;
const contents = getAssetSourceContents(asset.source);
const newSource = new sources.RawSource(sort(contents));

compilation.updateAsset(asset.name, newSource, asset.info);
getOptimizeAssetsHook(compiler, compilation).tap(pluginName, (assets) => {
const cssAssets = getCSSAssets(assets);
if (cssAssets.length === 0) {
return;
}
);

const [asset] = cssAssets;
const contents = getAssetSourceContents(asset.source);
const newSource = new RawSource(sort(contents));

compilation.updateAsset(asset.name, newSource, asset.info);
});
});
}
}
95 changes: 95 additions & 0 deletions packages/webpack-loader/src/utils/webpack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { Compiler, Compilation as CompilationType, sources } from 'webpack';

/**
* Gets the normal module hook for webpack 4 & webpack 5.
*
* @returns
*/
export const getNormalModuleHook = (
compiler: Compiler,
compilation: CompilationType
): CompilationType['hooks']['normalModuleLoader'] => {
const { NormalModule } =
// Webpack 5 flow
compiler.webpack ||
// Webpack 4 flow
require('webpack');

const normalModuleHook =
NormalModule && typeof NormalModule.getCompilationHooks !== 'undefined'
? // Webpack 5 flow
NormalModule.getCompilationHooks(compilation).loader
: // Webpack 4 flow
compilation.hooks.normalModuleLoader;

return normalModuleHook;
};

/**
* Returns the string representation of an assets source.
*
* @param source
* @returns
*/
export const getAssetSourceContents = (assetSource: sources.Source): string => {
const source = assetSource.source();
if (typeof source === 'string') {
return source;
}

return source.toString();
};

/**
* Returns a webpack 4 & 5 compatible hook for optimizing assets.
*
* @param compilation
* @returns
*/
export const getOptimizeAssetsHook = (
compiler: Compiler,
compilation: CompilationType
): { tap: CompilationType['hooks']['processAssets']['tap'] } => {
const { Compilation, version } =
// Webpack 5 flow
compiler.webpack ||
// Webpack 4 flow
require('webpack');
const isWebpack4 = version.startsWith('4.');
const optimizeAssets =
// Webpack 5 flow
compilation.hooks.processAssets ||
// Webpack 4 flow
compilation.hooks.optimizeAssets;

return {
tap: (pluginName: string, callback: (assets: CompilationType['assets']) => void) => {
optimizeAssets.tap(
isWebpack4
? pluginName
: {
name: pluginName,
stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
},
callback
);
},
};
};

/**
* Returns webpack 4 & 5 compatible sources.
* @returns
*/
export const getSources = (compiler: Compiler): typeof sources => {
const { sources } =
// Webpack 5 flow
compiler.webpack;

return (
// Webpack 5 flow
sources ||
// Webpack 4 flow
require('webpack-sources')
);
};
Loading

0 comments on commit 660309a

Please sign in to comment.