@griffel/webpack-plugin 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Change Log - @griffel/webpack-plugin
2
+
3
+ This log was last generated on Fri, 06 Mar 2026 08:15:28 GMT and should not be manually modified.
4
+
5
+ <!-- Start content -->
6
+
7
+ ## 1.1.0
8
+
9
+ Fri, 06 Mar 2026 08:15:28 GMT
10
+
11
+ ### Minor changes
12
+
13
+ - feat: initial release of @griffel/webpack-plugin (olfedias@microsoft.com)
14
+ - feat: add makeStaticStyles AOT/CSS extraction support (olfedias@microsoft.com)
15
+ - feat: add createFluentOxcResolverFactory for FluentUI ESM resolution (olfedias@microsoft.com)
16
+ - feat: create @griffel/webpack-plugin package with boilerplate (copilot@microsoft.com)
17
+ - Bump @griffel/transform to v1.2.0
18
+ - Bump @griffel/core to v1.20.0
19
+
20
+ ### Patches
21
+
22
+ - chore: bump oxc-resolver to ^11.19.1 (olfedias@microsoft.com)
23
+ - perf: merge triple sort into single comparator, use Buffer.indexOf, cache JSON.stringify (olfedias@microsoft.com)
24
+ - fix: resolve tagged asset paths before CSS generation (olfedias@microsoft.com)
@@ -0,0 +1,18 @@
1
+ import { GriffelRenderer } from '@griffel/core';
2
+ import { Compilation, Compiler } from 'webpack';
3
+ import { TransformResolverFactory } from './resolver/types.mjs';
4
+ type EntryPoint = Compilation['entrypoints'] extends Map<unknown, infer I> ? I : never;
5
+ export type GriffelCSSExtractionPluginOptions = {
6
+ collectStats?: boolean;
7
+ compareMediaQueries?: GriffelRenderer['compareMediaQueries'];
8
+ /** Allows to override resolver used to resolve imports inside evaluated modules. */
9
+ resolverFactory?: TransformResolverFactory;
10
+ /** Specifies if the CSS extracted from Griffel calls should be attached to a specific chunk with an entrypoint. */
11
+ unstable_attachToEntryPoint?: string | ((chunk: EntryPoint) => boolean);
12
+ };
13
+ export declare class GriffelPlugin {
14
+ #private;
15
+ constructor(options?: GriffelCSSExtractionPluginOptions);
16
+ apply(compiler: Compiler): void;
17
+ }
18
+ export {};
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # Webpack plugin to perform CSS extraction in Griffel
2
+
3
+ A plugin for Webpack 5 that performs CSS extraction for [`@griffel/react`](../react).
4
+
5
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
6
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
7
+
8
+ - [Install](#install)
9
+ - [When to use it?](#when-to-use-it)
10
+ - [Usage](#usage)
11
+ - [Options](#options)
12
+
13
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ yarn add --dev @griffel/webpack-plugin
19
+ # or
20
+ npm install --save-dev @griffel/webpack-plugin
21
+ ```
22
+
23
+ ## When to use it?
24
+
25
+ This is a replacement for `@griffel/webpack-loader` + `@griffel/webpack-extraction-plugin`. It combines both into a single plugin that handles CSS extraction without needing a separate loader setup.
26
+
27
+ ## Usage
28
+
29
+ Webpack documentation:
30
+
31
+ - [Plugins](https://webpack.js.org/concepts/plugins/)
32
+ - [Loaders](https://webpack.js.org/loaders/)
33
+
34
+ Within your Webpack configuration, add the plugin along with `mini-css-extract-plugin`:
35
+
36
+ ```js
37
+ import { GriffelPlugin } from '@griffel/webpack-plugin';
38
+ import MiniCssExtractPlugin from 'mini-css-extract-plugin';
39
+
40
+ export default {
41
+ module: {
42
+ rules: [
43
+ {
44
+ test: /\.(js|ts|tsx)$/,
45
+ // Apply "exclude" only if your dependencies **do not use** Griffel
46
+ // exclude: /node_modules/,
47
+ use: {
48
+ loader: '@griffel/webpack-plugin/loader',
49
+ },
50
+ },
51
+ // "css-loader" and "mini-css-extract-plugin" are required to handle CSS assets produced by Griffel
52
+ {
53
+ test: /\.css$/,
54
+ use: [MiniCssExtractPlugin.loader, 'css-loader'],
55
+ },
56
+ ],
57
+ },
58
+ plugins: [new MiniCssExtractPlugin(), new GriffelPlugin()],
59
+ };
60
+ ```
61
+
62
+ The plugin automatically:
63
+
64
+ - Transforms `makeStyles()`, `makeResetStyles()`, and `makeStaticStyles()` calls at build time
65
+ - Extracts CSS into a dedicated chunk (named `griffel`) via `mini-css-extract-plugin`
66
+ - Sorts CSS rules by specificity buckets and media queries
67
+
68
+ ## Options
69
+
70
+ ```js
71
+ new GriffelPlugin({
72
+ // Compare function for sorting media queries (default: @griffel/core's defaultCompareMediaQueries)
73
+ compareMediaQueries: myCompareFunction,
74
+
75
+ // Override the resolver used to resolve imports inside evaluated modules
76
+ resolverFactory: myResolverFactory,
77
+
78
+ // Attach extracted CSS to a specific entry point chunk
79
+ unstable_attachToEntryPoint: 'main',
80
+
81
+ // Collect and log timing stats
82
+ collectStats: false,
83
+ });
84
+ ```
@@ -0,0 +1,6 @@
1
+ const PLUGIN_NAME = "GriffelExtractPlugin";
2
+ const GriffelCssLoaderContextKey = /* @__PURE__ */ Symbol.for(`${PLUGIN_NAME}/GriffelCssLoaderContextKey`);
3
+ export {
4
+ GriffelCssLoaderContextKey as G,
5
+ PLUGIN_NAME as P
6
+ };
@@ -0,0 +1,20 @@
1
+ import { LoaderContext } from 'webpack';
2
+ import { TransformResolver } from './resolver/types.mjs';
3
+ export declare const PLUGIN_NAME = "GriffelExtractPlugin";
4
+ export declare const GriffelCssLoaderContextKey: unique symbol;
5
+ export interface GriffelLoaderContextSupplement {
6
+ resolveModule: TransformResolver;
7
+ registerExtractedCss(css: string): void;
8
+ getExtractedCss(): string;
9
+ runWithTimer<T>(cb: () => {
10
+ result: T;
11
+ meta: {
12
+ filename: string;
13
+ step: 'transform';
14
+ evaluationMode: 'ast' | 'vm';
15
+ };
16
+ }): T;
17
+ }
18
+ export type SupplementedLoaderContext<Options = unknown> = LoaderContext<Options> & {
19
+ [GriffelCssLoaderContextKey]?: GriffelLoaderContextSupplement;
20
+ };
package/index.d.mts ADDED
@@ -0,0 +1,4 @@
1
+ export { GriffelPlugin, type GriffelCSSExtractionPluginOptions } from './GriffelPlugin.mjs';
2
+ export { createEnhancedResolverFactory } from './resolver/createEnhancedResolverFactory.mjs';
3
+ export { createOxcResolverFactory } from './resolver/createOxcResolverFactory.mjs';
4
+ export { createFluentOxcResolverFactory, type FluentOxcResolverOptions, } from './resolver/createFluentOxcResolverFactory.mjs';
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@griffel/webpack-plugin",
3
+ "version": "1.1.0",
4
+ "description": "Webpack plugin that performs CSS extraction for Griffel",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/microsoft/griffel"
9
+ },
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "node": "./webpack-plugin.js",
14
+ "types": "./index.d.mts"
15
+ },
16
+ "./loader": {
17
+ "node": "./webpack-loader.js",
18
+ "types": "./webpackLoader.d.mts"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "dependencies": {
23
+ "@griffel/transform": "^1.2.0",
24
+ "@griffel/core": "^1.20.0",
25
+ "enhanced-resolve": "^5.15.0",
26
+ "oxc-resolver": "^11.19.1",
27
+ "stylis": "^4.2.0"
28
+ },
29
+ "peerDependencies": {
30
+ "webpack": "^5"
31
+ }
32
+ }
@@ -0,0 +1,6 @@
1
+ import { Configuration } from 'webpack';
2
+ import { TransformResolverFactory } from './types.mjs';
3
+ export declare function createEnhancedResolverFactory(resolveOptions?: {
4
+ inheritResolveOptions?: ('alias' | 'modules' | 'plugins' | 'conditionNames' | 'extensions')[];
5
+ webpackResolveOptions?: Pick<Required<Configuration>['resolve'], 'alias' | 'modules' | 'plugins' | 'conditionNames' | 'extensions'>;
6
+ }): TransformResolverFactory;
@@ -0,0 +1,7 @@
1
+ import { NapiResolveOptions } from 'oxc-resolver';
2
+ import { TransformResolverFactory } from './types.mjs';
3
+ export type FluentOxcResolverOptions = Pick<NapiResolveOptions, 'conditionNames' | 'extensions' | 'alias' | 'mainFields' | 'modules'> & {
4
+ /** Predicate to determine if a module specifier should be resolved with ESM conditions. Defaults to matching `@fluentui/` prefixed packages. */
5
+ isFluentPackage?: (id: string) => boolean;
6
+ };
7
+ export declare function createFluentOxcResolverFactory(resolveOptions?: FluentOxcResolverOptions): TransformResolverFactory;
@@ -0,0 +1,2 @@
1
+ import { TransformResolverFactory } from './types.mjs';
2
+ export declare function createOxcResolverFactory(): TransformResolverFactory;
@@ -0,0 +1,4 @@
1
+ import { Module } from '@griffel/transform';
2
+ import { Compilation } from 'webpack';
3
+ export type TransformResolver = (typeof Module)['_resolveFilename'];
4
+ export type TransformResolverFactory = (compilation: Compilation) => TransformResolver;
@@ -0,0 +1,2 @@
1
+ import { CSSRulesByBucket } from '@griffel/core';
2
+ export declare function generateCSSRules(cssRulesByBucket: CSSRulesByBucket): string;
@@ -0,0 +1,5 @@
1
+ import { CSSRulesByBucket } from '@griffel/core';
2
+ export declare function parseCSSRules(css: string): {
3
+ cssRulesByBucket: Required<CSSRulesByBucket>;
4
+ remainingCSS: string;
5
+ };
@@ -0,0 +1,5 @@
1
+ import { CSSRulesByBucket } from '@griffel/core';
2
+ /**
3
+ * Walks `CSSRulesByBucket` and replaces all tagged asset paths with paths relative to `sourceFile`.
4
+ */
5
+ export declare function resolveAssetPathsInCSSRules(cssRulesByBucket: CSSRulesByBucket, sourceFile: string): CSSRulesByBucket;
@@ -0,0 +1,10 @@
1
+ import { GriffelRenderer, StyleBucketName, CSSRulesByBucket } from '@griffel/core';
2
+ type RuleEntry = {
3
+ styleBucketName: StyleBucketName;
4
+ cssRule: string;
5
+ priority: number;
6
+ media: string;
7
+ };
8
+ export declare function getUniqueRulesFromSets(setOfCSSRules: CSSRulesByBucket[]): RuleEntry[];
9
+ export declare function sortCSSRules(setOfCSSRules: CSSRulesByBucket[], compareMediaQueries: GriffelRenderer['compareMediaQueries']): string;
10
+ export {};
@@ -0,0 +1 @@
1
+ /** A fake CSS file, used for Rspack compat */
@@ -0,0 +1,20 @@
1
+ const { URLSearchParams } = require('url');
2
+
3
+ const GriffelCssLoaderContextKey = Symbol.for(`GriffelExtractPlugin/GriffelCssLoaderContextKey`);
4
+
5
+ /**
6
+ * @this {import("../src/constants").SupplementedLoaderContext}
7
+ * @return {String}
8
+ */
9
+ function virtualLoader() {
10
+ if (this.webpack) {
11
+ return this[GriffelCssLoaderContextKey]?.getExtractedCss() ?? '';
12
+ }
13
+
14
+ const query = new URLSearchParams(this.resourceQuery);
15
+ const style = query.get('style');
16
+
17
+ return style ?? '';
18
+ }
19
+
20
+ module.exports = virtualLoader;
@@ -0,0 +1,160 @@
1
+ import { ASSET_TAG_OPEN, ASSET_TAG_CLOSE, EvalCache, Module, transformSync } from "@griffel/transform";
2
+ import * as path from "node:path";
3
+ import { G as GriffelCssLoaderContextKey } from "./constants-aY3k4vvW.js";
4
+ import { normalizeCSSBucketEntry } from "@griffel/core";
5
+ function generateCSSRules(cssRulesByBucket) {
6
+ const entries = Object.entries(cssRulesByBucket);
7
+ if (entries.length === 0) {
8
+ return "";
9
+ }
10
+ const cssLines = [];
11
+ let lastEntryKey = "";
12
+ for (const [cssBucketName, cssBucketEntries] of entries) {
13
+ for (const bucketEntry of cssBucketEntries) {
14
+ const [cssRule, metadata] = normalizeCSSBucketEntry(bucketEntry);
15
+ const metadataAsJSON = JSON.stringify(metadata ?? null);
16
+ const entryKey = `${cssBucketName}-${metadataAsJSON}`;
17
+ if (lastEntryKey !== entryKey) {
18
+ if (lastEntryKey !== "") {
19
+ cssLines.push("/** @griffel:css-end **/");
20
+ }
21
+ cssLines.push(`/** @griffel:css-start [${cssBucketName}] ${metadataAsJSON} **/`);
22
+ lastEntryKey = entryKey;
23
+ }
24
+ cssLines.push(cssRule);
25
+ }
26
+ }
27
+ if (cssLines.length > 0) {
28
+ cssLines.push("/** @griffel:css-end **/");
29
+ }
30
+ return cssLines.join("\n");
31
+ }
32
+ function resolveAssetPathsInString(cssRule, sourceDir) {
33
+ let result = "";
34
+ let searchFrom = 0;
35
+ while (searchFrom < cssRule.length) {
36
+ const openIdx = cssRule.indexOf(ASSET_TAG_OPEN, searchFrom);
37
+ if (openIdx === -1) {
38
+ result += cssRule.slice(searchFrom);
39
+ break;
40
+ }
41
+ result += cssRule.slice(searchFrom, openIdx);
42
+ const contentStart = openIdx + ASSET_TAG_OPEN.length;
43
+ const closeIdx = cssRule.indexOf(ASSET_TAG_CLOSE, contentStart);
44
+ if (closeIdx === -1) {
45
+ result += cssRule.slice(openIdx);
46
+ break;
47
+ }
48
+ const absolutePath = cssRule.slice(contentStart, closeIdx);
49
+ const relativePath = path.relative(sourceDir, absolutePath);
50
+ result += relativePath;
51
+ searchFrom = closeIdx + ASSET_TAG_CLOSE.length;
52
+ }
53
+ return result;
54
+ }
55
+ function resolveEntry(entry, sourceDir) {
56
+ if (typeof entry === "string") {
57
+ return resolveAssetPathsInString(entry, sourceDir);
58
+ }
59
+ return [resolveAssetPathsInString(entry[0], sourceDir), entry[1]];
60
+ }
61
+ function resolveAssetPathsInCSSRules(cssRulesByBucket, sourceFile) {
62
+ const sourceDir = path.dirname(sourceFile);
63
+ const resolved = {};
64
+ for (const bucketName in cssRulesByBucket) {
65
+ const entries = cssRulesByBucket[bucketName];
66
+ resolved[bucketName] = entries.map((entry) => resolveEntry(entry, sourceDir));
67
+ }
68
+ return resolved;
69
+ }
70
+ const __dirname$1 = path.dirname(new URL(import.meta.url).pathname);
71
+ const virtualLoaderPath = path.resolve(__dirname$1, "virtual-loader", "index.cjs");
72
+ const virtualCSSFilePath = path.resolve(__dirname$1, "virtual-loader", "griffel.css");
73
+ function toURIComponent(rule) {
74
+ return encodeURIComponent(rule).replace(/!/g, "%21");
75
+ }
76
+ function webpackLoader(sourceCode, inputSourceMap) {
77
+ this.async();
78
+ this.cacheable();
79
+ if (sourceCode.indexOf("makeStyles") === -1 && sourceCode.indexOf("makeResetStyles") === -1 && sourceCode.indexOf("makeStaticStyles") === -1) {
80
+ this.callback(null, sourceCode, inputSourceMap);
81
+ return;
82
+ }
83
+ const IS_RSPACK = !this.webpack;
84
+ if (!IS_RSPACK) {
85
+ if (!this[GriffelCssLoaderContextKey]) {
86
+ throw new Error("GriffelCSSExtractionPlugin is not configured, please check your webpack config");
87
+ }
88
+ }
89
+ const { classNameHashSalt, modules, evaluationRules, babelOptions } = this.getOptions();
90
+ this[GriffelCssLoaderContextKey].runWithTimer(() => {
91
+ EvalCache.clearForFile(this.resourcePath);
92
+ const originalResolveFilename = Module._resolveFilename;
93
+ let result = null;
94
+ let error = null;
95
+ try {
96
+ Module._resolveFilename = (id, params) => {
97
+ const resolvedPath = this[GriffelCssLoaderContextKey].resolveModule(id, params);
98
+ this.addDependency(resolvedPath);
99
+ return resolvedPath;
100
+ };
101
+ result = transformSync(sourceCode, {
102
+ filename: this.resourcePath,
103
+ classNameHashSalt,
104
+ modules,
105
+ evaluationRules,
106
+ babelOptions
107
+ });
108
+ } catch (err) {
109
+ error = err;
110
+ } finally {
111
+ Module._resolveFilename = originalResolveFilename;
112
+ }
113
+ if (result) {
114
+ const { code, cssRulesByBucket, usedVMForEvaluation } = result;
115
+ const meta = {
116
+ filename: this.resourcePath,
117
+ step: "transform",
118
+ evaluationMode: usedVMForEvaluation ? "vm" : "ast"
119
+ };
120
+ if (cssRulesByBucket) {
121
+ const resolvedCssRulesByBucket = resolveAssetPathsInCSSRules(cssRulesByBucket, this.resourcePath);
122
+ const css = generateCSSRules(resolvedCssRulesByBucket);
123
+ if (css.length === 0) {
124
+ this.callback(null, code);
125
+ return { result: void 0, meta };
126
+ }
127
+ if (IS_RSPACK) {
128
+ const request2 = `griffel.css!=!${virtualLoaderPath}!${virtualCSSFilePath}?style=${toURIComponent(css)}`;
129
+ const stringifiedRequest2 = JSON.stringify(this.utils.contextify(this.context || this.rootContext, request2));
130
+ this.callback(null, `${result.code}
131
+
132
+ import ${stringifiedRequest2};`);
133
+ return { result: void 0, meta };
134
+ }
135
+ this[GriffelCssLoaderContextKey].registerExtractedCss(css);
136
+ const outputFileName = this.resourcePath.replace(/\.[^.]+$/, ".griffel.css");
137
+ const request = `${outputFileName}!=!${virtualLoaderPath}!${this.resourcePath}`;
138
+ const stringifiedRequest = JSON.stringify(this.utils.contextify(this.context || this.rootContext, request));
139
+ this.callback(null, `${result.code}
140
+
141
+ import ${stringifiedRequest};`);
142
+ return { result: void 0, meta };
143
+ }
144
+ this.callback(null, code);
145
+ return { result: void 0, meta };
146
+ }
147
+ this.callback(error);
148
+ return {
149
+ result: void 0,
150
+ meta: {
151
+ filename: this.resourcePath,
152
+ step: "transform",
153
+ evaluationMode: "ast"
154
+ }
155
+ };
156
+ });
157
+ }
158
+ export {
159
+ webpackLoader as default
160
+ };
@@ -0,0 +1,368 @@
1
+ import { styleBucketOrdering, normalizeCSSBucketEntry, defaultCompareMediaQueries } from "@griffel/core";
2
+ import { P as PLUGIN_NAME, G as GriffelCssLoaderContextKey } from "./constants-aY3k4vvW.js";
3
+ import enhancedResolve from "enhanced-resolve";
4
+ import * as path from "node:path";
5
+ import { compile, COMMENT, serialize, stringify } from "stylis";
6
+ import { ResolverFactory } from "oxc-resolver";
7
+ const RESOLVE_OPTIONS_DEFAULTS$2 = {
8
+ conditionNames: ["require"],
9
+ extensions: [".js", ".jsx", ".cjs", ".mjs", ".ts", ".tsx", ".json"]
10
+ };
11
+ function createEnhancedResolverFactory(resolveOptions = {}) {
12
+ const { inheritResolveOptions = ["alias", "modules", "plugins"], webpackResolveOptions } = resolveOptions;
13
+ return function(compilation) {
14
+ const resolveOptionsFromWebpackConfig = compilation?.options.resolve ?? {};
15
+ const resolveSync = enhancedResolve.create.sync({
16
+ ...RESOLVE_OPTIONS_DEFAULTS$2,
17
+ ...Object.fromEntries(
18
+ inheritResolveOptions.map((resolveOptionKey) => [
19
+ resolveOptionKey,
20
+ resolveOptionsFromWebpackConfig[resolveOptionKey]
21
+ ])
22
+ ),
23
+ ...webpackResolveOptions
24
+ });
25
+ return function resolveModule(id, { filename }) {
26
+ const resolvedPath = resolveSync(path.dirname(filename), id);
27
+ if (!resolvedPath) {
28
+ throw new Error(`enhanced-resolve: Failed to resolve module "${id}"`);
29
+ }
30
+ return resolvedPath;
31
+ };
32
+ };
33
+ }
34
+ function parseCSSRules(css) {
35
+ const cssRulesByBucket = styleBucketOrdering.reduce((acc, styleBucketName) => {
36
+ acc[styleBucketName] = [];
37
+ return acc;
38
+ }, {});
39
+ const elements = compile(css);
40
+ const unrelatedElements = [];
41
+ let cssBucketName = null;
42
+ let cssMeta = null;
43
+ for (const element of elements) {
44
+ if (element.type === COMMENT) {
45
+ if (element.value.indexOf("/** @griffel:css-start") === 0) {
46
+ cssBucketName = element.value.charAt(24);
47
+ cssMeta = JSON.parse(element.value.slice(27, -4));
48
+ continue;
49
+ }
50
+ if (element.value.indexOf("/** @griffel:css-end") === 0) {
51
+ cssBucketName = null;
52
+ cssMeta = null;
53
+ continue;
54
+ }
55
+ }
56
+ if (cssBucketName) {
57
+ const cssRule = serialize([element], stringify);
58
+ const bucketEntry = cssMeta ? [cssRule, cssMeta] : cssRule;
59
+ cssRulesByBucket[cssBucketName].push(bucketEntry);
60
+ continue;
61
+ }
62
+ unrelatedElements.push(element);
63
+ }
64
+ return { cssRulesByBucket, remainingCSS: serialize(unrelatedElements, stringify) };
65
+ }
66
+ const styleBucketOrderingMap = styleBucketOrdering.reduce((acc, cur, j) => {
67
+ acc[cur] = j;
68
+ return acc;
69
+ }, {});
70
+ function getUniqueRulesFromSets(setOfCSSRules) {
71
+ const uniqueCSSRules = /* @__PURE__ */ new Map();
72
+ for (const cssRulesByBucket of setOfCSSRules) {
73
+ for (const _styleBucketName in cssRulesByBucket) {
74
+ const styleBucketName = _styleBucketName;
75
+ for (const bucketEntry of cssRulesByBucket[styleBucketName]) {
76
+ const [cssRule, meta] = normalizeCSSBucketEntry(bucketEntry);
77
+ const priority = meta?.["p"] ?? 0;
78
+ const media = meta?.["m"] ?? "";
79
+ uniqueCSSRules.set(cssRule, { styleBucketName, cssRule, priority, media });
80
+ }
81
+ }
82
+ }
83
+ return Array.from(uniqueCSSRules.values());
84
+ }
85
+ function compareCSSRules(a, b, compareMediaQueries) {
86
+ return compareMediaQueries(a.media, b.media) || styleBucketOrderingMap[a.styleBucketName] - styleBucketOrderingMap[b.styleBucketName] || a.priority - b.priority;
87
+ }
88
+ function sortCSSRules(setOfCSSRules, compareMediaQueries) {
89
+ const entries = getUniqueRulesFromSets(setOfCSSRules).sort((a, b) => compareCSSRules(a, b, compareMediaQueries));
90
+ let result = "";
91
+ for (let i = 0; i < entries.length; i++) {
92
+ result += entries[i].cssRule;
93
+ }
94
+ return result;
95
+ }
96
+ const OPTIMIZE_CHUNKS_STAGE_ADVANCED = 10;
97
+ function attachGriffelChunkToAnotherChunk(compilation, griffelChunk, attachToEntryPoint) {
98
+ const entryPoints = Array.from(compilation.entrypoints.values());
99
+ if (entryPoints.length === 0) {
100
+ return;
101
+ }
102
+ const searchFn = typeof attachToEntryPoint === "string" ? (chunk) => chunk.name === attachToEntryPoint : attachToEntryPoint;
103
+ const mainEntryPoint = entryPoints.find(searchFn) ?? entryPoints[0];
104
+ const targetChunk = mainEntryPoint.getEntrypointChunk();
105
+ targetChunk.split(griffelChunk);
106
+ }
107
+ function getAssetSourceContents(assetSource) {
108
+ const source = assetSource.source();
109
+ if (typeof source === "string") {
110
+ return source;
111
+ }
112
+ return source.toString();
113
+ }
114
+ function isCSSModule(module) {
115
+ return module.type === "css/mini-extract";
116
+ }
117
+ function isGriffelCSSModule(module) {
118
+ if (isCSSModule(module)) {
119
+ if (Buffer.isBuffer(module.content)) {
120
+ return module.content.indexOf("/** @griffel:css-start") !== -1;
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+ function moveCSSModulesToGriffelChunk(compilation) {
126
+ let griffelChunk = compilation.namedChunks.get("griffel");
127
+ if (!griffelChunk) {
128
+ griffelChunk = compilation.addChunk("griffel");
129
+ }
130
+ const matchingChunks = /* @__PURE__ */ new Set();
131
+ let moduleIndex = 0;
132
+ for (const module of compilation.modules) {
133
+ if (isGriffelCSSModule(module)) {
134
+ const moduleChunks = compilation.chunkGraph.getModuleChunksIterable(module);
135
+ for (const chunk of moduleChunks) {
136
+ compilation.chunkGraph.disconnectChunkAndModule(chunk, module);
137
+ for (const group of chunk.groupsIterable) {
138
+ group.setModulePostOrderIndex(module, moduleIndex++);
139
+ }
140
+ matchingChunks.add(chunk);
141
+ }
142
+ compilation.chunkGraph.connectChunkAndModule(griffelChunk, module);
143
+ }
144
+ }
145
+ for (const chunk of matchingChunks) {
146
+ chunk.split(griffelChunk);
147
+ }
148
+ }
149
+ class GriffelPlugin {
150
+ #attachToEntryPoint;
151
+ #collectStats;
152
+ #compareMediaQueries;
153
+ #resolverFactory;
154
+ #stats = {};
155
+ constructor(options = {}) {
156
+ this.#attachToEntryPoint = options.unstable_attachToEntryPoint;
157
+ this.#collectStats = options.collectStats ?? false;
158
+ this.#compareMediaQueries = options.compareMediaQueries ?? defaultCompareMediaQueries;
159
+ this.#resolverFactory = options.resolverFactory ?? createEnhancedResolverFactory();
160
+ }
161
+ apply(compiler) {
162
+ const IS_RSPACK = Object.prototype.hasOwnProperty.call(compiler.webpack, "rspackVersion");
163
+ const { Compilation, NormalModule } = compiler.webpack;
164
+ if (!IS_RSPACK) {
165
+ compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (nmf) => {
166
+ nmf.hooks.createModule.tap(
167
+ PLUGIN_NAME,
168
+ // @ts-expect-error CreateData is typed as 'object'...
169
+ (createData) => {
170
+ if (createData.matchResource && createData.matchResource.endsWith(".griffel.css")) {
171
+ createData.settings.sideEffects = true;
172
+ }
173
+ }
174
+ );
175
+ });
176
+ }
177
+ if (compiler.options.optimization.splitChunks) {
178
+ compiler.options.optimization.splitChunks.cacheGroups ??= {};
179
+ compiler.options.optimization.splitChunks.cacheGroups["griffel"] = {
180
+ name: "griffel",
181
+ // @ Rspack compat:
182
+ // Rspack does not support functions in test due performance concerns
183
+ // https://github.com/web-infra-dev/rspack/issues/3425#issuecomment-1577890202
184
+ test: IS_RSPACK ? /griffel\.css/ : isGriffelCSSModule,
185
+ chunks: "all",
186
+ enforce: true
187
+ };
188
+ }
189
+ compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
190
+ const resolveModule = this.#resolverFactory(compilation);
191
+ if (!IS_RSPACK) {
192
+ const cssByModuleMap = /* @__PURE__ */ new Map();
193
+ NormalModule.getCompilationHooks(compilation).loader.tap(PLUGIN_NAME, (loaderContext, module) => {
194
+ const resourcePath = module.resource;
195
+ loaderContext[GriffelCssLoaderContextKey] = {
196
+ resolveModule,
197
+ registerExtractedCss(css) {
198
+ cssByModuleMap.set(resourcePath, css);
199
+ },
200
+ getExtractedCss() {
201
+ const css = cssByModuleMap.get(resourcePath) ?? "";
202
+ cssByModuleMap.delete(resourcePath);
203
+ return css;
204
+ },
205
+ runWithTimer: (cb) => {
206
+ if (this.#collectStats) {
207
+ const start = process.hrtime.bigint();
208
+ const { meta, result } = cb();
209
+ const end = process.hrtime.bigint();
210
+ this.#stats[meta.filename] = {
211
+ time: end - start,
212
+ evaluationMode: meta.evaluationMode
213
+ };
214
+ return result;
215
+ }
216
+ return cb().result;
217
+ }
218
+ };
219
+ });
220
+ }
221
+ if (!compiler.options.optimization.splitChunks) {
222
+ if (IS_RSPACK) {
223
+ throw new Error(
224
+ [
225
+ `You are using Rspack, but don't have "optimization.splitChunks" enabled.`,
226
+ '"optimization.splitChunks" should be enabled for "@griffel/webpack-extraction-plugin" to function properly.'
227
+ ].join(" ")
228
+ );
229
+ }
230
+ compilation.hooks.optimizeChunks.tap({ name: PLUGIN_NAME, stage: OPTIMIZE_CHUNKS_STAGE_ADVANCED }, () => {
231
+ moveCSSModulesToGriffelChunk(compilation);
232
+ });
233
+ }
234
+ if (this.#attachToEntryPoint) {
235
+ if (IS_RSPACK) {
236
+ throw new Error('You are using Rspack, "attachToMainEntryPoint" option is supported only with Webpack.');
237
+ }
238
+ compilation.hooks.optimizeChunks.tap({ name: PLUGIN_NAME, stage: OPTIMIZE_CHUNKS_STAGE_ADVANCED }, () => {
239
+ const griffelChunk = compilation.namedChunks.get("griffel");
240
+ if (typeof griffelChunk !== "undefined") {
241
+ griffelChunk.disconnectFromGroups();
242
+ attachGriffelChunkToAnotherChunk(compilation, griffelChunk, this.#attachToEntryPoint);
243
+ }
244
+ });
245
+ }
246
+ compilation.hooks.processAssets.tap(
247
+ {
248
+ name: PLUGIN_NAME,
249
+ stage: Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS
250
+ },
251
+ (assets) => {
252
+ let cssAssetDetails;
253
+ if (IS_RSPACK) {
254
+ cssAssetDetails = Object.entries(assets).find(
255
+ ([assetName]) => assetName.endsWith(".css") && assetName.includes("griffel")
256
+ );
257
+ } else {
258
+ const griffelChunk = compilation.namedChunks.get("griffel");
259
+ if (typeof griffelChunk === "undefined") {
260
+ return;
261
+ }
262
+ cssAssetDetails = Object.entries(assets).find(([assetName]) => griffelChunk.files.has(assetName));
263
+ }
264
+ if (typeof cssAssetDetails === "undefined") {
265
+ return;
266
+ }
267
+ const [cssAssetName, cssAssetSource] = cssAssetDetails;
268
+ const cssContent = getAssetSourceContents(cssAssetSource);
269
+ const { cssRulesByBucket, remainingCSS } = parseCSSRules(cssContent);
270
+ const cssSource = sortCSSRules([cssRulesByBucket], this.#compareMediaQueries);
271
+ compilation.updateAsset(cssAssetName, new compiler.webpack.sources.RawSource(remainingCSS + cssSource));
272
+ }
273
+ );
274
+ compilation.hooks.statsPrinter.tap(
275
+ {
276
+ name: PLUGIN_NAME
277
+ },
278
+ () => {
279
+ if (this.#collectStats) {
280
+ let logTime = function(time) {
281
+ if (time > 1000000n) {
282
+ return (time / 1000000n).toString() + "ms";
283
+ }
284
+ if (time > 1000n) {
285
+ return (time / 1000n).toString() + "μs";
286
+ }
287
+ return time.toString() + "ns";
288
+ };
289
+ const entries = Object.entries(this.#stats).sort(([, a], [, b]) => Number(b.time - a.time));
290
+ console.log("\nGriffel CSS extraction stats:");
291
+ console.log("------------------------------------");
292
+ console.log(
293
+ "Total time spent in Griffel loader:",
294
+ logTime(entries.reduce((acc, cur) => acc + cur[1].time, 0n))
295
+ );
296
+ console.log(
297
+ "AST evaluation hit: ",
298
+ (entries.filter((s) => s[1].evaluationMode === "ast").length / entries.length * 100).toFixed(2) + "%"
299
+ );
300
+ console.log("------------------------------------");
301
+ for (const [filename, info] of entries) {
302
+ console.log(` ${logTime(info.time)} - ${filename} (evaluation mode: ${info.evaluationMode})`);
303
+ }
304
+ console.log();
305
+ }
306
+ }
307
+ );
308
+ });
309
+ }
310
+ }
311
+ const RESOLVE_OPTIONS_DEFAULTS$1 = {
312
+ conditionNames: ["require"],
313
+ extensions: [".js", ".jsx", ".cjs", ".mjs", ".ts", ".tsx", ".json"]
314
+ };
315
+ function createOxcResolverFactory() {
316
+ return function(compilation) {
317
+ const resolverFactory = new ResolverFactory({
318
+ ...RESOLVE_OPTIONS_DEFAULTS$1
319
+ // ...resolveOptionsFromWebpackConfig,
320
+ });
321
+ return function resolveModule(id, { filename }) {
322
+ const resolvedResolver = resolverFactory.sync(path.dirname(filename), id);
323
+ if (resolvedResolver.error) {
324
+ throw resolvedResolver.error;
325
+ }
326
+ if (!resolvedResolver.path) {
327
+ throw new Error(`oxc-resolver: Failed to resolve module "${id}"`);
328
+ }
329
+ return resolvedResolver.path;
330
+ };
331
+ };
332
+ }
333
+ function defaultIsFluentPackage(id) {
334
+ return id.startsWith("@fluentui/");
335
+ }
336
+ const RESOLVE_OPTIONS_DEFAULTS = {
337
+ conditionNames: ["require"],
338
+ extensions: [".raw.js", ".js", ".jsx", ".cjs", ".mjs", ".ts", ".tsx", ".json"]
339
+ };
340
+ function createFluentOxcResolverFactory(resolveOptions) {
341
+ const { isFluentPackage = defaultIsFluentPackage, ...restOptions } = resolveOptions ?? {};
342
+ return function(compilation) {
343
+ const defaultResolver = new ResolverFactory({
344
+ ...RESOLVE_OPTIONS_DEFAULTS,
345
+ ...restOptions
346
+ });
347
+ const esmResolver = defaultResolver.cloneWithOptions({
348
+ conditionNames: ["import", "require"]
349
+ });
350
+ return function resolveModule(id, { filename }) {
351
+ const resolver = isFluentPackage(id) ? esmResolver : defaultResolver;
352
+ const resolved = resolver.sync(path.dirname(filename), id);
353
+ if (resolved.error) {
354
+ throw resolved.error;
355
+ }
356
+ if (!resolved.path) {
357
+ throw new Error(`oxc-resolver: Failed to resolve module "${id}"`);
358
+ }
359
+ return resolved.path;
360
+ };
361
+ };
362
+ }
363
+ export {
364
+ GriffelPlugin,
365
+ createEnhancedResolverFactory,
366
+ createFluentOxcResolverFactory,
367
+ createOxcResolverFactory
368
+ };
@@ -0,0 +1,7 @@
1
+ import { TransformOptions } from '@griffel/transform';
2
+ import { SupplementedLoaderContext } from './constants.mjs';
3
+ import type * as webpack from 'webpack';
4
+ export type WebpackLoaderOptions = Omit<TransformOptions, 'filename' | 'generateMetadata'>;
5
+ type WebpackLoaderParams = Parameters<webpack.LoaderDefinitionFunction<WebpackLoaderOptions>>;
6
+ declare function webpackLoader(this: SupplementedLoaderContext<WebpackLoaderOptions>, sourceCode: WebpackLoaderParams[0], inputSourceMap: WebpackLoaderParams[1]): void;
7
+ export default webpackLoader;