next-yak 9.10.1 → 9.10.2
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/dist/withYak/index.cjs +44 -0
- package/dist/withYak/index.cjs.map +1 -1
- package/dist/withYak/index.js +44 -0
- package/dist/withYak/index.js.map +1 -1
- package/package.json +2 -2
- package/withYak/allow-global-css.ts +94 -0
- package/withYak/index.ts +10 -0
package/dist/withYak/index.cjs
CHANGED
|
@@ -31,6 +31,49 @@ node_path = __toESM(node_path, 1);
|
|
|
31
31
|
let node_url = require("node:url");
|
|
32
32
|
let node_fs = require("node:fs");
|
|
33
33
|
|
|
34
|
+
//#region withYak/allow-global-css.ts
|
|
35
|
+
function hasAppDir(projectDir) {
|
|
36
|
+
return (0, node_fs.existsSync)(node_path.default.join(projectDir, "app")) || (0, node_fs.existsSync)(node_path.default.join(projectDir, "src", "app"));
|
|
37
|
+
}
|
|
38
|
+
function allowYakGlobalCss(webpackConfig) {
|
|
39
|
+
const rules = webpackConfig.module?.rules;
|
|
40
|
+
if (!Array.isArray(rules)) return;
|
|
41
|
+
const nextGlobalCssRegExpSource = "(?<!\\.module)\\.css$";
|
|
42
|
+
const isRule = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43
|
+
const hasAppIssuer = (rule) => {
|
|
44
|
+
if (!isRule(rule.issuer)) return false;
|
|
45
|
+
const and = rule.issuer.and;
|
|
46
|
+
return Array.isArray(and) && and[0] instanceof RegExp && and[0].source.endsWith("_app");
|
|
47
|
+
};
|
|
48
|
+
const isAppGlobalCssRule = (rule) => rule.test instanceof RegExp && rule.test.source === nextGlobalCssRegExpSource && hasAppIssuer(rule) && Array.isArray(rule.use);
|
|
49
|
+
const findAppRule = (ruleList) => {
|
|
50
|
+
if (!Array.isArray(ruleList)) return void 0;
|
|
51
|
+
for (const rule of ruleList) {
|
|
52
|
+
if (!isRule(rule)) continue;
|
|
53
|
+
if (Array.isArray(rule.oneOf)) {
|
|
54
|
+
const appRule = rule.oneOf.find((item) => isRule(item) && isAppGlobalCssRule(item));
|
|
55
|
+
if (appRule) return {
|
|
56
|
+
oneOf: rule.oneOf,
|
|
57
|
+
appRule
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const found = findAppRule(rule.oneOf) ?? findAppRule(rule.rules);
|
|
61
|
+
if (found) return found;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const found = findAppRule(rules);
|
|
65
|
+
if (!found) {
|
|
66
|
+
console.warn("next-yak: could not find the next.js global-CSS rules. Please report this issue with your Next.js version at https://github.com/DigitecGalaxus/next-yak/issues/new");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
found.oneOf.unshift({
|
|
70
|
+
test: /\.yak\.css$/,
|
|
71
|
+
sideEffects: true,
|
|
72
|
+
use: [...found.appRule.use]
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
34
77
|
//#region ../yak-internals/config.ts
|
|
35
78
|
function buildYakPluginOptions(yakOptions, basePath) {
|
|
36
79
|
const minify = yakOptions.minify ?? process.env.NODE_ENV === "production";
|
|
@@ -116,6 +159,7 @@ function addYakWebpack(nextConfig, yakOptions, yakPluginOptions) {
|
|
|
116
159
|
const previousConfig = nextConfig.webpack;
|
|
117
160
|
nextConfig.webpack = (webpackConfig, options) => {
|
|
118
161
|
if (previousConfig) webpackConfig = previousConfig(webpackConfig, options);
|
|
162
|
+
if (yakOptions.experiments?.transpilationMode === "Css" && !options.isServer && !hasAppDir(options.dir)) allowYakGlobalCss(webpackConfig);
|
|
119
163
|
webpackConfig.module.rules.push({
|
|
120
164
|
test: yakOptions.experiments?.transpilationMode === "Css" ? /\.yak\.css$/ : /\.yak\.module\.css$/,
|
|
121
165
|
loader: node_path.default.join(currentDir, "../loaders/webpack-loader.cjs"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["path","existsSync","dirname","fileURLToPath","path"],"sources":["../../../yak-internals/config.ts","../../withYak/index.ts"],"sourcesContent":["/// <reference types=\"node\" />\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport type YakConfigOptions = {\n /**\n * Generate compact CSS class and variable names.\n * @defaultValue\n * enabled if NODE_ENV is set to `production`, otherwise disabled\n */\n minify?: boolean;\n contextPath?: string;\n /**\n * Optional prefix for generated CSS identifiers.\n * This can be used to ensure unique class names across different applications\n * or to add organization-specific prefixes.\n */\n prefix?: string;\n /**\n * Adds `displayName` to each component for better React DevTools debugging\n * - Enabled by default in development mode\n * - Disabled by default in production\n * - Increases bundle size slightly when enabled\n */\n displayNames?: boolean;\n /**\n * Fold statically known styles at build time: JSX usages of fully static\n * styled components become plain DOM elements, and a static `css` prop\n * becomes a plain `className`. Both skip the runtime wrapper and merge calls.\n * @defaultValue true\n */\n foldStatic?: boolean;\n /**\n * Fail the build when a `css` prop has a value next-yak can't handle\n * (e.g. an array, an object or a plain string). next-yak claims the `css`\n * prop, so a malformed value is almost always a mistake worth surfacing.\n *\n * Set to `false` to leave such props untouched instead, e.g. when another\n * library on the same element uses its own `css` prop.\n * @defaultValue true\n */\n strictCssProp?: boolean;\n experiments?: {\n /**\n * Debug logging for transformed files.\n * - `true` - log all files\n * - `object` - filter by pattern and/or output types (at least one required)\n */\n debug?:\n | true\n | { pattern: string; types?: Array<\"ts\" | \"css\" | \"css-resolved\"> }\n | { pattern?: string; types: Array<\"ts\" | \"css\" | \"css-resolved\"> };\n transpilationMode?: \"CssModule\" | \"Css\";\n /**\n * Suppress deprecation warnings for :global() selectors during migration period\n * @defaultValue false\n */\n suppressDeprecationWarnings?: boolean;\n };\n};\n\n/**\n * Build the base yak-swc plugin options shared by every bundler integration\n * (webpack, turbopack, vite, rsbuild). The caller adds the bundler-specific\n * `importMode` on top.\n *\n * @param yakOptions - Yak configuration options\n * @param basePath - Base path used by yak-swc to derive stable identifiers\n */\nexport function buildYakPluginOptions(yakOptions: YakConfigOptions, basePath: string) {\n const minify = yakOptions.minify ?? process.env.NODE_ENV === \"production\";\n return {\n minify,\n basePath,\n prefix: yakOptions.prefix,\n displayNames: yakOptions.displayNames ?? !minify,\n foldStatic: yakOptions.foldStatic ?? true,\n strictCssProp: yakOptions.strictCssProp ?? true,\n suppressDeprecationWarnings: yakOptions.experiments?.suppressDeprecationWarnings ?? false,\n reactRefreshReg: true,\n };\n}\n\n/**\n * Try to resolve yak\n */\nexport function resolveYakContext(contextPath: string | undefined, cwd: string) {\n const yakContext = contextPath\n ? path.resolve(cwd, contextPath)\n : path.resolve(cwd, \"yak.context\");\n const extensions = [\"\", \".ts\", \".tsx\", \".js\", \".jsx\"];\n for (const extension in extensions) {\n const fileName = yakContext + extensions[extension];\n if (existsSync(fileName)) {\n return fileName;\n }\n }\n if (contextPath) {\n throw new Error(`Could not find yak context file at ${yakContext}`);\n }\n}\n","/// <reference types=\"node\" />\nimport type { NextConfig } from \"next\";\nimport path, { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n buildYakPluginOptions,\n resolveYakContext,\n type YakConfigOptions,\n} from \"yak-internals/config\";\n\nexport { buildYakPluginOptions, resolveYakContext } from \"yak-internals/config\";\nexport type { YakConfigOptions } from \"yak-internals/config\";\n\nconst currentDir =\n typeof __dirname !== \"undefined\" ? __dirname : dirname(fileURLToPath(import.meta.url));\n\nconst addYak = (yakOptions: YakConfigOptions, nextConfig: NextConfig) => {\n const yakPluginOptions = buildYakPluginOptions(yakOptions, currentDir);\n\n const transpilation = yakOptions.experiments?.transpilationMode ?? \"CssModule\";\n const cssExtension = transpilation === \"CssModule\" ? \".yak.module.css\" : \".yak.css\";\n\n if (process.env.TURBOPACK === \"1\" || process.env.TURBOPACK === \"auto\") {\n addYakTurbopack(nextConfig, yakOptions, {\n ...yakPluginOptions,\n importMode: {\n value: \"data:text/css;base64,\",\n transpilation: \"Css\",\n encoding: \"Base64\",\n },\n });\n } else {\n addYakWebpack(nextConfig, yakOptions, {\n ...yakPluginOptions,\n importMode: {\n value: `./{{__BASE_NAME__}}${cssExtension}!=!./{{__BASE_NAME__}}?./{{__BASE_NAME__}}${cssExtension}`,\n transpilation,\n encoding: \"None\",\n },\n });\n }\n return nextConfig;\n};\n\n/**\n * Configure Turbopack with yak loader for CSS-in-JS transformation\n * @param nextConfig - Next.js configuration object\n * @param yakOptions - Yak configuration options\n * @param yakPluginOptions - Processed plugin options for yak-swc\n */\nfunction addYakTurbopack(\n nextConfig: NextConfig,\n yakOptions: YakConfigOptions,\n yakPluginOptions: {\n minify: boolean;\n basePath: string;\n prefix?: string;\n displayNames: boolean;\n foldStatic: boolean;\n strictCssProp: boolean;\n importMode: {\n value: string;\n transpilation: string;\n encoding: string;\n };\n },\n) {\n // turbopack can't handle options with undefined values, so we remove them\n const yakLoader = removeUndefinedRecursive({\n loader: path.join(currentDir, \"../loaders/turbo-loader.cjs\"),\n options: {\n yakOptions: yakOptions,\n yakPluginOptions: yakPluginOptions,\n },\n }) as { loader: string; options: {} };\n\n nextConfig.turbopack ||= {};\n nextConfig.turbopack.rules ||= {};\n\n const ruleKey = \"*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}\";\n const rule = {\n loaders: [] as { loader: string; options: {} }[],\n ...nextConfig.turbopack.rules[ruleKey],\n };\n rule.loaders.push(yakLoader);\n nextConfig.turbopack.rules[ruleKey] = rule;\n\n // Configure resolveAlias for custom yak context (similar to webpack)\n // This allows users to provide a custom context file that will be used\n // instead of the default baseContext\n const yakContext = resolveYakContext(yakOptions.contextPath, process.cwd());\n if (yakContext) {\n nextConfig.turbopack.resolveAlias ||= {};\n nextConfig.turbopack.resolveAlias[\"next-yak/context/baseContext\"] =\n // This is a hack around the fact that turbopack currently only supports relative paths\n // turbopack: \"server relative imports are not implemented yet\"\n // Relative is quite dangerous here as it relies on the cwd being the starting point\n `./${path.relative(process.cwd(), yakContext)}`;\n }\n}\n\n/**\n * Configure Webpack with yak SWC plugin and webpack loader for CSS-in-JS transformation\n * @param nextConfig - Next.js configuration object\n * @param yakOptions - Yak configuration options\n * @param yakPluginOptions - Processed plugin options for yak-swc\n */\nfunction addYakWebpack(\n nextConfig: NextConfig,\n yakOptions: YakConfigOptions,\n yakPluginOptions: {\n minify: boolean;\n basePath: string;\n prefix?: string;\n displayNames: boolean;\n foldStatic: boolean;\n strictCssProp: boolean;\n importMode: {\n value: string;\n transpilation: string;\n encoding: string;\n };\n },\n) {\n // Add SWC plugin for Webpack\n nextConfig.experimental ||= {};\n nextConfig.experimental.swcPlugins ||= [];\n nextConfig.experimental.swcPlugins.push([\"yak-swc\", yakPluginOptions]);\n\n // Configure webpack loader\n const previousConfig = nextConfig.webpack;\n nextConfig.webpack = (webpackConfig, options) => {\n if (previousConfig) {\n webpackConfig = previousConfig(webpackConfig, options);\n }\n\n webpackConfig.module.rules.push({\n test:\n yakOptions.experiments?.transpilationMode === \"Css\" ? /\\.yak\\.css$/ : /\\.yak\\.module\\.css$/,\n loader: path.join(currentDir, \"../loaders/webpack-loader.cjs\"),\n options: yakOptions,\n });\n\n // With the following alias the internal next-yak code\n // is able to import a context which works for server components\n const yakContext = resolveYakContext(\n yakOptions.contextPath,\n webpackConfig.context || process.cwd(),\n );\n if (yakContext) {\n webpackConfig.resolve.alias[\"next-yak/context/baseContext\"] = yakContext;\n }\n\n return webpackConfig;\n };\n}\n\n/**\n * Recursively removes undefined values from an object or array.\n *\n * This function deeply traverses the input object/array and creates a new structure\n * with all undefined values filtered out. For objects, properties with undefined values\n * are omitted. For arrays, undefined elements are removed from the result.\n *\n * @param obj - The object or array to process\n * @returns A new object/array with undefined values removed, or the original value if no changes were needed\n */\nfunction removeUndefinedRecursive<T>(obj: T): {} {\n if (typeof obj !== \"object\" || obj === null) {\n return obj as {};\n }\n\n if (Array.isArray(obj)) {\n const filtered: unknown[] = [];\n for (let i = 0; i < obj.length; i++) {\n const processed = removeUndefinedRecursive(obj[i]);\n if (processed !== undefined) {\n filtered.push(processed);\n }\n }\n return filtered as {};\n }\n\n const newObj: Record<string, unknown> = {};\n let hasChanges = false;\n\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n const value = removeUndefinedRecursive((obj as any)[key]);\n if (value !== undefined) {\n newObj[key] = value;\n hasChanges = true;\n }\n }\n }\n\n return hasChanges ? (newObj as {}) : obj;\n}\n\n// Wrapper to allow sync, async, and function configuration of Next.js\n/**\n * Add Yak to your Next.js app\n *\n * @usage\n *\n * ```ts\n * // next.config.js\n * const { withYak } = require(\"next-yak/withYak\");\n * const nextConfig = {\n * // your next config here\n * };\n * module.exports = withYak(nextConfig);\n * ```\n *\n * With a custom yakConfig\n *\n * ```ts\n * // next.config.js\n * const { withYak } = require(\"next-yak/withYak\");\n * const nextConfig = {\n * // your next config here\n * };\n * const yakConfig = {\n * // Optional prefix for generated CSS identifiers\n * prefix: \"my-app\",\n * // Other yak config options...\n * };\n * module.exports = withYak(yakConfig, nextConfig);\n * ```\n */\nexport const withYak: {\n <\n T extends\n | Record<string, any>\n | ((...args: any[]) => Record<string, any>)\n | ((...args: any[]) => Promise<Record<string, any>>),\n >(\n yakOptions: YakConfigOptions,\n nextConfig: T,\n ): T;\n // no yakConfig\n <\n T extends\n | Record<string, any>\n | ((...args: any[]) => Record<string, any>)\n | ((...args: any[]) => Promise<Record<string, any>>),\n >(\n nextConfig: T,\n _?: undefined,\n ): T;\n} = (maybeYakOptions, nextConfig) => {\n if (nextConfig === undefined) {\n return withYak({}, maybeYakOptions);\n }\n // If the second parameter is present the first parameter must be a YakConfigOptions\n const yakOptions = maybeYakOptions as YakConfigOptions;\n if (typeof nextConfig === \"function\") {\n /**\n * A NextConfig can be a sync or async function\n * https://nextjs.org/docs/pages/api-reference/next-config-js\n * @param {any[]} args\n */\n return (...args) => {\n /** Dynamic Next Configs can be async or sync */\n const config = nextConfig(...args) as NextConfig | Promise<NextConfig>;\n return config instanceof Promise\n ? config.then((config) => addYak(yakOptions, config))\n : addYak(yakOptions, config);\n };\n }\n return addYak(yakOptions, nextConfig);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["existsSync","path","path","existsSync","dirname","fileURLToPath","path"],"sources":["../../withYak/allow-global-css.ts","../../../yak-internals/config.ts","../../withYak/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\ninterface WebpackConfig {\n module?: { rules?: unknown[] };\n}\n\ninterface Rule {\n test?: unknown;\n issuer?: unknown;\n use?: unknown;\n oneOf?: unknown;\n rules?: unknown;\n}\n\nexport function hasAppDir(projectDir: string): boolean {\n return (\n // mirrors next.js's `findDir(dir, \"app\")`\n // https://github.com/vercel/next.js/blob/v16.3.4/packages/next/src/lib/find-pages-dir.ts#L4-L13\n existsSync(path.join(projectDir, \"app\")) || existsSync(path.join(projectDir, \"src\", \"app\"))\n );\n}\n\n/**\n * Next.js (pages router) only allows global CSS imports from `_app`.\n * Everywhere else it expects CSS Modules. Yak's co-located `*.yak.css`\n * files are plain global CSS, because yak already hashes the class names\n * itself, so there's no risk of leaking styles\n *\n * Next.js has no option to allow this, so we copy its `_app` global CSS\n * rule without the `_app` issuer check and limit the copy to `.yak.css`.\n * The copy goes to the start of the same `oneOf`, so it matches before\n * Next's error rules.\n */\nexport function allowYakGlobalCss(webpackConfig: WebpackConfig) {\n const rules = webpackConfig.module?.rules;\n if (!Array.isArray(rules)) {\n return;\n }\n\n // Next.js's internal `regexCssGlobal` source\n // https://github.com/vercel/next.js/blob/v16.3.4/packages/next/src/build/webpack/config/blocks/css/index.ts#L23\n const nextGlobalCssRegExpSource = \"(?<!\\\\.module)\\\\.css$\";\n\n const isRule = (value: unknown): value is Rule =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n // Next.js sets `issuer: { and: [ctx.customAppFile] }`, where\n // `customAppFile` is a RegExp for the path of `pages/_app`\n // https://github.com/vercel/next.js/blob/v16.3.4/packages/next/src/build/webpack-config.ts#L2550-L2552\n const hasAppIssuer = (rule: Rule): boolean => {\n if (!isRule(rule.issuer)) return false;\n const and = (rule.issuer as { and?: unknown }).and;\n return Array.isArray(and) && and[0] instanceof RegExp && and[0].source.endsWith(\"_app\");\n };\n\n const isAppGlobalCssRule = (rule: Rule): boolean =>\n rule.test instanceof RegExp &&\n rule.test.source === nextGlobalCssRegExpSource &&\n hasAppIssuer(rule) &&\n Array.isArray(rule.use);\n\n const findAppRule = (ruleList: unknown): { oneOf: unknown[]; appRule: Rule } | undefined => {\n if (!Array.isArray(ruleList)) return undefined;\n for (const rule of ruleList) {\n if (!isRule(rule)) continue;\n if (Array.isArray(rule.oneOf)) {\n const appRule = rule.oneOf.find(\n (item): item is Rule => isRule(item) && isAppGlobalCssRule(item),\n );\n if (appRule) return { oneOf: rule.oneOf, appRule };\n }\n const found = findAppRule(rule.oneOf) ?? findAppRule(rule.rules);\n if (found) return found;\n }\n return undefined;\n };\n\n const found = findAppRule(rules);\n if (!found) {\n console.warn(\n \"next-yak: could not find the next.js global-CSS rules. \" +\n \"Please report this issue with your Next.js version at \" +\n \"https://github.com/DigitecGalaxus/next-yak/issues/new\",\n );\n return;\n }\n\n found.oneOf.unshift({\n test: /\\.yak\\.css$/,\n sideEffects: true,\n use: [...(found.appRule.use as unknown[])],\n });\n}\n","/// <reference types=\"node\" />\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport type YakConfigOptions = {\n /**\n * Generate compact CSS class and variable names.\n * @defaultValue\n * enabled if NODE_ENV is set to `production`, otherwise disabled\n */\n minify?: boolean;\n contextPath?: string;\n /**\n * Optional prefix for generated CSS identifiers.\n * This can be used to ensure unique class names across different applications\n * or to add organization-specific prefixes.\n */\n prefix?: string;\n /**\n * Adds `displayName` to each component for better React DevTools debugging\n * - Enabled by default in development mode\n * - Disabled by default in production\n * - Increases bundle size slightly when enabled\n */\n displayNames?: boolean;\n /**\n * Fold statically known styles at build time: JSX usages of fully static\n * styled components become plain DOM elements, and a static `css` prop\n * becomes a plain `className`. Both skip the runtime wrapper and merge calls.\n * @defaultValue true\n */\n foldStatic?: boolean;\n /**\n * Fail the build when a `css` prop has a value next-yak can't handle\n * (e.g. an array, an object or a plain string). next-yak claims the `css`\n * prop, so a malformed value is almost always a mistake worth surfacing.\n *\n * Set to `false` to leave such props untouched instead, e.g. when another\n * library on the same element uses its own `css` prop.\n * @defaultValue true\n */\n strictCssProp?: boolean;\n experiments?: {\n /**\n * Debug logging for transformed files.\n * - `true` - log all files\n * - `object` - filter by pattern and/or output types (at least one required)\n */\n debug?:\n | true\n | { pattern: string; types?: Array<\"ts\" | \"css\" | \"css-resolved\"> }\n | { pattern?: string; types: Array<\"ts\" | \"css\" | \"css-resolved\"> };\n transpilationMode?: \"CssModule\" | \"Css\";\n /**\n * Suppress deprecation warnings for :global() selectors during migration period\n * @defaultValue false\n */\n suppressDeprecationWarnings?: boolean;\n };\n};\n\n/**\n * Build the base yak-swc plugin options shared by every bundler integration\n * (webpack, turbopack, vite, rsbuild). The caller adds the bundler-specific\n * `importMode` on top.\n *\n * @param yakOptions - Yak configuration options\n * @param basePath - Base path used by yak-swc to derive stable identifiers\n */\nexport function buildYakPluginOptions(yakOptions: YakConfigOptions, basePath: string) {\n const minify = yakOptions.minify ?? process.env.NODE_ENV === \"production\";\n return {\n minify,\n basePath,\n prefix: yakOptions.prefix,\n displayNames: yakOptions.displayNames ?? !minify,\n foldStatic: yakOptions.foldStatic ?? true,\n strictCssProp: yakOptions.strictCssProp ?? true,\n suppressDeprecationWarnings: yakOptions.experiments?.suppressDeprecationWarnings ?? false,\n reactRefreshReg: true,\n };\n}\n\n/**\n * Try to resolve yak\n */\nexport function resolveYakContext(contextPath: string | undefined, cwd: string) {\n const yakContext = contextPath\n ? path.resolve(cwd, contextPath)\n : path.resolve(cwd, \"yak.context\");\n const extensions = [\"\", \".ts\", \".tsx\", \".js\", \".jsx\"];\n for (const extension in extensions) {\n const fileName = yakContext + extensions[extension];\n if (existsSync(fileName)) {\n return fileName;\n }\n }\n if (contextPath) {\n throw new Error(`Could not find yak context file at ${yakContext}`);\n }\n}\n","/// <reference types=\"node\" />\nimport type { NextConfig } from \"next\";\nimport path, { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { allowYakGlobalCss, hasAppDir } from \"./allow-global-css.ts\";\nimport {\n buildYakPluginOptions,\n resolveYakContext,\n type YakConfigOptions,\n} from \"yak-internals/config\";\n\nexport { buildYakPluginOptions, resolveYakContext } from \"yak-internals/config\";\nexport type { YakConfigOptions } from \"yak-internals/config\";\n\nconst currentDir =\n typeof __dirname !== \"undefined\" ? __dirname : dirname(fileURLToPath(import.meta.url));\n\nconst addYak = (yakOptions: YakConfigOptions, nextConfig: NextConfig) => {\n const yakPluginOptions = buildYakPluginOptions(yakOptions, currentDir);\n\n const transpilation = yakOptions.experiments?.transpilationMode ?? \"CssModule\";\n const cssExtension = transpilation === \"CssModule\" ? \".yak.module.css\" : \".yak.css\";\n\n if (process.env.TURBOPACK === \"1\" || process.env.TURBOPACK === \"auto\") {\n addYakTurbopack(nextConfig, yakOptions, {\n ...yakPluginOptions,\n importMode: {\n value: \"data:text/css;base64,\",\n transpilation: \"Css\",\n encoding: \"Base64\",\n },\n });\n } else {\n addYakWebpack(nextConfig, yakOptions, {\n ...yakPluginOptions,\n importMode: {\n value: `./{{__BASE_NAME__}}${cssExtension}!=!./{{__BASE_NAME__}}?./{{__BASE_NAME__}}${cssExtension}`,\n transpilation,\n encoding: \"None\",\n },\n });\n }\n return nextConfig;\n};\n\n/**\n * Configure Turbopack with yak loader for CSS-in-JS transformation\n * @param nextConfig - Next.js configuration object\n * @param yakOptions - Yak configuration options\n * @param yakPluginOptions - Processed plugin options for yak-swc\n */\nfunction addYakTurbopack(\n nextConfig: NextConfig,\n yakOptions: YakConfigOptions,\n yakPluginOptions: {\n minify: boolean;\n basePath: string;\n prefix?: string;\n displayNames: boolean;\n foldStatic: boolean;\n strictCssProp: boolean;\n importMode: {\n value: string;\n transpilation: string;\n encoding: string;\n };\n },\n) {\n // turbopack can't handle options with undefined values, so we remove them\n const yakLoader = removeUndefinedRecursive({\n loader: path.join(currentDir, \"../loaders/turbo-loader.cjs\"),\n options: {\n yakOptions: yakOptions,\n yakPluginOptions: yakPluginOptions,\n },\n }) as { loader: string; options: {} };\n\n nextConfig.turbopack ||= {};\n nextConfig.turbopack.rules ||= {};\n\n const ruleKey = \"*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}\";\n const rule = {\n loaders: [] as { loader: string; options: {} }[],\n ...nextConfig.turbopack.rules[ruleKey],\n };\n rule.loaders.push(yakLoader);\n nextConfig.turbopack.rules[ruleKey] = rule;\n\n // Configure resolveAlias for custom yak context (similar to webpack)\n // This allows users to provide a custom context file that will be used\n // instead of the default baseContext\n const yakContext = resolveYakContext(yakOptions.contextPath, process.cwd());\n if (yakContext) {\n nextConfig.turbopack.resolveAlias ||= {};\n nextConfig.turbopack.resolveAlias[\"next-yak/context/baseContext\"] =\n // This is a hack around the fact that turbopack currently only supports relative paths\n // turbopack: \"server relative imports are not implemented yet\"\n // Relative is quite dangerous here as it relies on the cwd being the starting point\n `./${path.relative(process.cwd(), yakContext)}`;\n }\n}\n\n/**\n * Configure Webpack with yak SWC plugin and webpack loader for CSS-in-JS transformation\n * @param nextConfig - Next.js configuration object\n * @param yakOptions - Yak configuration options\n * @param yakPluginOptions - Processed plugin options for yak-swc\n */\nfunction addYakWebpack(\n nextConfig: NextConfig,\n yakOptions: YakConfigOptions,\n yakPluginOptions: {\n minify: boolean;\n basePath: string;\n prefix?: string;\n displayNames: boolean;\n foldStatic: boolean;\n strictCssProp: boolean;\n importMode: {\n value: string;\n transpilation: string;\n encoding: string;\n };\n },\n) {\n // Add SWC plugin for Webpack\n nextConfig.experimental ||= {};\n nextConfig.experimental.swcPlugins ||= [];\n nextConfig.experimental.swcPlugins.push([\"yak-swc\", yakPluginOptions]);\n\n // Configure webpack loader\n const previousConfig = nextConfig.webpack;\n nextConfig.webpack = (webpackConfig, options) => {\n if (previousConfig) {\n webpackConfig = previousConfig(webpackConfig, options);\n }\n\n if (\n yakOptions.experiments?.transpilationMode === \"Css\" &&\n !options.isServer &&\n !hasAppDir(options.dir)\n ) {\n // Next.js restricts global CSS imports only in the client build of a pages-only project, so only patch its rules there\n allowYakGlobalCss(webpackConfig);\n }\n\n webpackConfig.module.rules.push({\n test:\n yakOptions.experiments?.transpilationMode === \"Css\" ? /\\.yak\\.css$/ : /\\.yak\\.module\\.css$/,\n loader: path.join(currentDir, \"../loaders/webpack-loader.cjs\"),\n options: yakOptions,\n });\n\n // With the following alias the internal next-yak code\n // is able to import a context which works for server components\n const yakContext = resolveYakContext(\n yakOptions.contextPath,\n webpackConfig.context || process.cwd(),\n );\n if (yakContext) {\n webpackConfig.resolve.alias[\"next-yak/context/baseContext\"] = yakContext;\n }\n\n return webpackConfig;\n };\n}\n\n/**\n * Recursively removes undefined values from an object or array.\n *\n * This function deeply traverses the input object/array and creates a new structure\n * with all undefined values filtered out. For objects, properties with undefined values\n * are omitted. For arrays, undefined elements are removed from the result.\n *\n * @param obj - The object or array to process\n * @returns A new object/array with undefined values removed, or the original value if no changes were needed\n */\nfunction removeUndefinedRecursive<T>(obj: T): {} {\n if (typeof obj !== \"object\" || obj === null) {\n return obj as {};\n }\n\n if (Array.isArray(obj)) {\n const filtered: unknown[] = [];\n for (let i = 0; i < obj.length; i++) {\n const processed = removeUndefinedRecursive(obj[i]);\n if (processed !== undefined) {\n filtered.push(processed);\n }\n }\n return filtered as {};\n }\n\n const newObj: Record<string, unknown> = {};\n let hasChanges = false;\n\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n const value = removeUndefinedRecursive((obj as any)[key]);\n if (value !== undefined) {\n newObj[key] = value;\n hasChanges = true;\n }\n }\n }\n\n return hasChanges ? (newObj as {}) : obj;\n}\n\n// Wrapper to allow sync, async, and function configuration of Next.js\n/**\n * Add Yak to your Next.js app\n *\n * @usage\n *\n * ```ts\n * // next.config.js\n * const { withYak } = require(\"next-yak/withYak\");\n * const nextConfig = {\n * // your next config here\n * };\n * module.exports = withYak(nextConfig);\n * ```\n *\n * With a custom yakConfig\n *\n * ```ts\n * // next.config.js\n * const { withYak } = require(\"next-yak/withYak\");\n * const nextConfig = {\n * // your next config here\n * };\n * const yakConfig = {\n * // Optional prefix for generated CSS identifiers\n * prefix: \"my-app\",\n * // Other yak config options...\n * };\n * module.exports = withYak(yakConfig, nextConfig);\n * ```\n */\nexport const withYak: {\n <\n T extends\n | Record<string, any>\n | ((...args: any[]) => Record<string, any>)\n | ((...args: any[]) => Promise<Record<string, any>>),\n >(\n yakOptions: YakConfigOptions,\n nextConfig: T,\n ): T;\n // no yakConfig\n <\n T extends\n | Record<string, any>\n | ((...args: any[]) => Record<string, any>)\n | ((...args: any[]) => Promise<Record<string, any>>),\n >(\n nextConfig: T,\n _?: undefined,\n ): T;\n} = (maybeYakOptions, nextConfig) => {\n if (nextConfig === undefined) {\n return withYak({}, maybeYakOptions);\n }\n // If the second parameter is present the first parameter must be a YakConfigOptions\n const yakOptions = maybeYakOptions as YakConfigOptions;\n if (typeof nextConfig === \"function\") {\n /**\n * A NextConfig can be a sync or async function\n * https://nextjs.org/docs/pages/api-reference/next-config-js\n * @param {any[]} args\n */\n return (...args) => {\n /** Dynamic Next Configs can be async or sync */\n const config = nextConfig(...args) as NextConfig | Promise<NextConfig>;\n return config instanceof Promise\n ? config.then((config) => addYak(yakOptions, config))\n : addYak(yakOptions, config);\n };\n }\n return addYak(yakOptions, nextConfig);\n};\n"],"mappings":"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"}
|
package/dist/withYak/index.js
CHANGED
|
@@ -2,6 +2,49 @@ import path, { dirname } from "node:path";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
|
|
5
|
+
//#region withYak/allow-global-css.ts
|
|
6
|
+
function hasAppDir(projectDir) {
|
|
7
|
+
return existsSync(path.join(projectDir, "app")) || existsSync(path.join(projectDir, "src", "app"));
|
|
8
|
+
}
|
|
9
|
+
function allowYakGlobalCss(webpackConfig) {
|
|
10
|
+
const rules = webpackConfig.module?.rules;
|
|
11
|
+
if (!Array.isArray(rules)) return;
|
|
12
|
+
const nextGlobalCssRegExpSource = "(?<!\\.module)\\.css$";
|
|
13
|
+
const isRule = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
const hasAppIssuer = (rule) => {
|
|
15
|
+
if (!isRule(rule.issuer)) return false;
|
|
16
|
+
const and = rule.issuer.and;
|
|
17
|
+
return Array.isArray(and) && and[0] instanceof RegExp && and[0].source.endsWith("_app");
|
|
18
|
+
};
|
|
19
|
+
const isAppGlobalCssRule = (rule) => rule.test instanceof RegExp && rule.test.source === nextGlobalCssRegExpSource && hasAppIssuer(rule) && Array.isArray(rule.use);
|
|
20
|
+
const findAppRule = (ruleList) => {
|
|
21
|
+
if (!Array.isArray(ruleList)) return void 0;
|
|
22
|
+
for (const rule of ruleList) {
|
|
23
|
+
if (!isRule(rule)) continue;
|
|
24
|
+
if (Array.isArray(rule.oneOf)) {
|
|
25
|
+
const appRule = rule.oneOf.find((item) => isRule(item) && isAppGlobalCssRule(item));
|
|
26
|
+
if (appRule) return {
|
|
27
|
+
oneOf: rule.oneOf,
|
|
28
|
+
appRule
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const found = findAppRule(rule.oneOf) ?? findAppRule(rule.rules);
|
|
32
|
+
if (found) return found;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const found = findAppRule(rules);
|
|
36
|
+
if (!found) {
|
|
37
|
+
console.warn("next-yak: could not find the next.js global-CSS rules. Please report this issue with your Next.js version at https://github.com/DigitecGalaxus/next-yak/issues/new");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
found.oneOf.unshift({
|
|
41
|
+
test: /\.yak\.css$/,
|
|
42
|
+
sideEffects: true,
|
|
43
|
+
use: [...found.appRule.use]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
5
48
|
//#region ../yak-internals/config.ts
|
|
6
49
|
function buildYakPluginOptions(yakOptions, basePath) {
|
|
7
50
|
const minify = yakOptions.minify ?? process.env.NODE_ENV === "production";
|
|
@@ -87,6 +130,7 @@ function addYakWebpack(nextConfig, yakOptions, yakPluginOptions) {
|
|
|
87
130
|
const previousConfig = nextConfig.webpack;
|
|
88
131
|
nextConfig.webpack = (webpackConfig, options) => {
|
|
89
132
|
if (previousConfig) webpackConfig = previousConfig(webpackConfig, options);
|
|
133
|
+
if (yakOptions.experiments?.transpilationMode === "Css" && !options.isServer && !hasAppDir(options.dir)) allowYakGlobalCss(webpackConfig);
|
|
90
134
|
webpackConfig.module.rules.push({
|
|
91
135
|
test: yakOptions.experiments?.transpilationMode === "Css" ? /\.yak\.css$/ : /\.yak\.module\.css$/,
|
|
92
136
|
loader: path.join(currentDir, "../loaders/webpack-loader.cjs"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../yak-internals/config.ts","../../withYak/index.ts"],"sourcesContent":["/// <reference types=\"node\" />\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport type YakConfigOptions = {\n /**\n * Generate compact CSS class and variable names.\n * @defaultValue\n * enabled if NODE_ENV is set to `production`, otherwise disabled\n */\n minify?: boolean;\n contextPath?: string;\n /**\n * Optional prefix for generated CSS identifiers.\n * This can be used to ensure unique class names across different applications\n * or to add organization-specific prefixes.\n */\n prefix?: string;\n /**\n * Adds `displayName` to each component for better React DevTools debugging\n * - Enabled by default in development mode\n * - Disabled by default in production\n * - Increases bundle size slightly when enabled\n */\n displayNames?: boolean;\n /**\n * Fold statically known styles at build time: JSX usages of fully static\n * styled components become plain DOM elements, and a static `css` prop\n * becomes a plain `className`. Both skip the runtime wrapper and merge calls.\n * @defaultValue true\n */\n foldStatic?: boolean;\n /**\n * Fail the build when a `css` prop has a value next-yak can't handle\n * (e.g. an array, an object or a plain string). next-yak claims the `css`\n * prop, so a malformed value is almost always a mistake worth surfacing.\n *\n * Set to `false` to leave such props untouched instead, e.g. when another\n * library on the same element uses its own `css` prop.\n * @defaultValue true\n */\n strictCssProp?: boolean;\n experiments?: {\n /**\n * Debug logging for transformed files.\n * - `true` - log all files\n * - `object` - filter by pattern and/or output types (at least one required)\n */\n debug?:\n | true\n | { pattern: string; types?: Array<\"ts\" | \"css\" | \"css-resolved\"> }\n | { pattern?: string; types: Array<\"ts\" | \"css\" | \"css-resolved\"> };\n transpilationMode?: \"CssModule\" | \"Css\";\n /**\n * Suppress deprecation warnings for :global() selectors during migration period\n * @defaultValue false\n */\n suppressDeprecationWarnings?: boolean;\n };\n};\n\n/**\n * Build the base yak-swc plugin options shared by every bundler integration\n * (webpack, turbopack, vite, rsbuild). The caller adds the bundler-specific\n * `importMode` on top.\n *\n * @param yakOptions - Yak configuration options\n * @param basePath - Base path used by yak-swc to derive stable identifiers\n */\nexport function buildYakPluginOptions(yakOptions: YakConfigOptions, basePath: string) {\n const minify = yakOptions.minify ?? process.env.NODE_ENV === \"production\";\n return {\n minify,\n basePath,\n prefix: yakOptions.prefix,\n displayNames: yakOptions.displayNames ?? !minify,\n foldStatic: yakOptions.foldStatic ?? true,\n strictCssProp: yakOptions.strictCssProp ?? true,\n suppressDeprecationWarnings: yakOptions.experiments?.suppressDeprecationWarnings ?? false,\n reactRefreshReg: true,\n };\n}\n\n/**\n * Try to resolve yak\n */\nexport function resolveYakContext(contextPath: string | undefined, cwd: string) {\n const yakContext = contextPath\n ? path.resolve(cwd, contextPath)\n : path.resolve(cwd, \"yak.context\");\n const extensions = [\"\", \".ts\", \".tsx\", \".js\", \".jsx\"];\n for (const extension in extensions) {\n const fileName = yakContext + extensions[extension];\n if (existsSync(fileName)) {\n return fileName;\n }\n }\n if (contextPath) {\n throw new Error(`Could not find yak context file at ${yakContext}`);\n }\n}\n","/// <reference types=\"node\" />\nimport type { NextConfig } from \"next\";\nimport path, { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n buildYakPluginOptions,\n resolveYakContext,\n type YakConfigOptions,\n} from \"yak-internals/config\";\n\nexport { buildYakPluginOptions, resolveYakContext } from \"yak-internals/config\";\nexport type { YakConfigOptions } from \"yak-internals/config\";\n\nconst currentDir =\n typeof __dirname !== \"undefined\" ? __dirname : dirname(fileURLToPath(import.meta.url));\n\nconst addYak = (yakOptions: YakConfigOptions, nextConfig: NextConfig) => {\n const yakPluginOptions = buildYakPluginOptions(yakOptions, currentDir);\n\n const transpilation = yakOptions.experiments?.transpilationMode ?? \"CssModule\";\n const cssExtension = transpilation === \"CssModule\" ? \".yak.module.css\" : \".yak.css\";\n\n if (process.env.TURBOPACK === \"1\" || process.env.TURBOPACK === \"auto\") {\n addYakTurbopack(nextConfig, yakOptions, {\n ...yakPluginOptions,\n importMode: {\n value: \"data:text/css;base64,\",\n transpilation: \"Css\",\n encoding: \"Base64\",\n },\n });\n } else {\n addYakWebpack(nextConfig, yakOptions, {\n ...yakPluginOptions,\n importMode: {\n value: `./{{__BASE_NAME__}}${cssExtension}!=!./{{__BASE_NAME__}}?./{{__BASE_NAME__}}${cssExtension}`,\n transpilation,\n encoding: \"None\",\n },\n });\n }\n return nextConfig;\n};\n\n/**\n * Configure Turbopack with yak loader for CSS-in-JS transformation\n * @param nextConfig - Next.js configuration object\n * @param yakOptions - Yak configuration options\n * @param yakPluginOptions - Processed plugin options for yak-swc\n */\nfunction addYakTurbopack(\n nextConfig: NextConfig,\n yakOptions: YakConfigOptions,\n yakPluginOptions: {\n minify: boolean;\n basePath: string;\n prefix?: string;\n displayNames: boolean;\n foldStatic: boolean;\n strictCssProp: boolean;\n importMode: {\n value: string;\n transpilation: string;\n encoding: string;\n };\n },\n) {\n // turbopack can't handle options with undefined values, so we remove them\n const yakLoader = removeUndefinedRecursive({\n loader: path.join(currentDir, \"../loaders/turbo-loader.cjs\"),\n options: {\n yakOptions: yakOptions,\n yakPluginOptions: yakPluginOptions,\n },\n }) as { loader: string; options: {} };\n\n nextConfig.turbopack ||= {};\n nextConfig.turbopack.rules ||= {};\n\n const ruleKey = \"*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}\";\n const rule = {\n loaders: [] as { loader: string; options: {} }[],\n ...nextConfig.turbopack.rules[ruleKey],\n };\n rule.loaders.push(yakLoader);\n nextConfig.turbopack.rules[ruleKey] = rule;\n\n // Configure resolveAlias for custom yak context (similar to webpack)\n // This allows users to provide a custom context file that will be used\n // instead of the default baseContext\n const yakContext = resolveYakContext(yakOptions.contextPath, process.cwd());\n if (yakContext) {\n nextConfig.turbopack.resolveAlias ||= {};\n nextConfig.turbopack.resolveAlias[\"next-yak/context/baseContext\"] =\n // This is a hack around the fact that turbopack currently only supports relative paths\n // turbopack: \"server relative imports are not implemented yet\"\n // Relative is quite dangerous here as it relies on the cwd being the starting point\n `./${path.relative(process.cwd(), yakContext)}`;\n }\n}\n\n/**\n * Configure Webpack with yak SWC plugin and webpack loader for CSS-in-JS transformation\n * @param nextConfig - Next.js configuration object\n * @param yakOptions - Yak configuration options\n * @param yakPluginOptions - Processed plugin options for yak-swc\n */\nfunction addYakWebpack(\n nextConfig: NextConfig,\n yakOptions: YakConfigOptions,\n yakPluginOptions: {\n minify: boolean;\n basePath: string;\n prefix?: string;\n displayNames: boolean;\n foldStatic: boolean;\n strictCssProp: boolean;\n importMode: {\n value: string;\n transpilation: string;\n encoding: string;\n };\n },\n) {\n // Add SWC plugin for Webpack\n nextConfig.experimental ||= {};\n nextConfig.experimental.swcPlugins ||= [];\n nextConfig.experimental.swcPlugins.push([\"yak-swc\", yakPluginOptions]);\n\n // Configure webpack loader\n const previousConfig = nextConfig.webpack;\n nextConfig.webpack = (webpackConfig, options) => {\n if (previousConfig) {\n webpackConfig = previousConfig(webpackConfig, options);\n }\n\n webpackConfig.module.rules.push({\n test:\n yakOptions.experiments?.transpilationMode === \"Css\" ? /\\.yak\\.css$/ : /\\.yak\\.module\\.css$/,\n loader: path.join(currentDir, \"../loaders/webpack-loader.cjs\"),\n options: yakOptions,\n });\n\n // With the following alias the internal next-yak code\n // is able to import a context which works for server components\n const yakContext = resolveYakContext(\n yakOptions.contextPath,\n webpackConfig.context || process.cwd(),\n );\n if (yakContext) {\n webpackConfig.resolve.alias[\"next-yak/context/baseContext\"] = yakContext;\n }\n\n return webpackConfig;\n };\n}\n\n/**\n * Recursively removes undefined values from an object or array.\n *\n * This function deeply traverses the input object/array and creates a new structure\n * with all undefined values filtered out. For objects, properties with undefined values\n * are omitted. For arrays, undefined elements are removed from the result.\n *\n * @param obj - The object or array to process\n * @returns A new object/array with undefined values removed, or the original value if no changes were needed\n */\nfunction removeUndefinedRecursive<T>(obj: T): {} {\n if (typeof obj !== \"object\" || obj === null) {\n return obj as {};\n }\n\n if (Array.isArray(obj)) {\n const filtered: unknown[] = [];\n for (let i = 0; i < obj.length; i++) {\n const processed = removeUndefinedRecursive(obj[i]);\n if (processed !== undefined) {\n filtered.push(processed);\n }\n }\n return filtered as {};\n }\n\n const newObj: Record<string, unknown> = {};\n let hasChanges = false;\n\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n const value = removeUndefinedRecursive((obj as any)[key]);\n if (value !== undefined) {\n newObj[key] = value;\n hasChanges = true;\n }\n }\n }\n\n return hasChanges ? (newObj as {}) : obj;\n}\n\n// Wrapper to allow sync, async, and function configuration of Next.js\n/**\n * Add Yak to your Next.js app\n *\n * @usage\n *\n * ```ts\n * // next.config.js\n * const { withYak } = require(\"next-yak/withYak\");\n * const nextConfig = {\n * // your next config here\n * };\n * module.exports = withYak(nextConfig);\n * ```\n *\n * With a custom yakConfig\n *\n * ```ts\n * // next.config.js\n * const { withYak } = require(\"next-yak/withYak\");\n * const nextConfig = {\n * // your next config here\n * };\n * const yakConfig = {\n * // Optional prefix for generated CSS identifiers\n * prefix: \"my-app\",\n * // Other yak config options...\n * };\n * module.exports = withYak(yakConfig, nextConfig);\n * ```\n */\nexport const withYak: {\n <\n T extends\n | Record<string, any>\n | ((...args: any[]) => Record<string, any>)\n | ((...args: any[]) => Promise<Record<string, any>>),\n >(\n yakOptions: YakConfigOptions,\n nextConfig: T,\n ): T;\n // no yakConfig\n <\n T extends\n | Record<string, any>\n | ((...args: any[]) => Record<string, any>)\n | ((...args: any[]) => Promise<Record<string, any>>),\n >(\n nextConfig: T,\n _?: undefined,\n ): T;\n} = (maybeYakOptions, nextConfig) => {\n if (nextConfig === undefined) {\n return withYak({}, maybeYakOptions);\n }\n // If the second parameter is present the first parameter must be a YakConfigOptions\n const yakOptions = maybeYakOptions as YakConfigOptions;\n if (typeof nextConfig === \"function\") {\n /**\n * A NextConfig can be a sync or async function\n * https://nextjs.org/docs/pages/api-reference/next-config-js\n * @param {any[]} args\n */\n return (...args) => {\n /** Dynamic Next Configs can be async or sync */\n const config = nextConfig(...args) as NextConfig | Promise<NextConfig>;\n return config instanceof Promise\n ? config.then((config) => addYak(yakOptions, config))\n : addYak(yakOptions, config);\n };\n }\n return addYak(yakOptions, nextConfig);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../withYak/allow-global-css.ts","../../../yak-internals/config.ts","../../withYak/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\ninterface WebpackConfig {\n module?: { rules?: unknown[] };\n}\n\ninterface Rule {\n test?: unknown;\n issuer?: unknown;\n use?: unknown;\n oneOf?: unknown;\n rules?: unknown;\n}\n\nexport function hasAppDir(projectDir: string): boolean {\n return (\n // mirrors next.js's `findDir(dir, \"app\")`\n // https://github.com/vercel/next.js/blob/v16.3.4/packages/next/src/lib/find-pages-dir.ts#L4-L13\n existsSync(path.join(projectDir, \"app\")) || existsSync(path.join(projectDir, \"src\", \"app\"))\n );\n}\n\n/**\n * Next.js (pages router) only allows global CSS imports from `_app`.\n * Everywhere else it expects CSS Modules. Yak's co-located `*.yak.css`\n * files are plain global CSS, because yak already hashes the class names\n * itself, so there's no risk of leaking styles\n *\n * Next.js has no option to allow this, so we copy its `_app` global CSS\n * rule without the `_app` issuer check and limit the copy to `.yak.css`.\n * The copy goes to the start of the same `oneOf`, so it matches before\n * Next's error rules.\n */\nexport function allowYakGlobalCss(webpackConfig: WebpackConfig) {\n const rules = webpackConfig.module?.rules;\n if (!Array.isArray(rules)) {\n return;\n }\n\n // Next.js's internal `regexCssGlobal` source\n // https://github.com/vercel/next.js/blob/v16.3.4/packages/next/src/build/webpack/config/blocks/css/index.ts#L23\n const nextGlobalCssRegExpSource = \"(?<!\\\\.module)\\\\.css$\";\n\n const isRule = (value: unknown): value is Rule =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n // Next.js sets `issuer: { and: [ctx.customAppFile] }`, where\n // `customAppFile` is a RegExp for the path of `pages/_app`\n // https://github.com/vercel/next.js/blob/v16.3.4/packages/next/src/build/webpack-config.ts#L2550-L2552\n const hasAppIssuer = (rule: Rule): boolean => {\n if (!isRule(rule.issuer)) return false;\n const and = (rule.issuer as { and?: unknown }).and;\n return Array.isArray(and) && and[0] instanceof RegExp && and[0].source.endsWith(\"_app\");\n };\n\n const isAppGlobalCssRule = (rule: Rule): boolean =>\n rule.test instanceof RegExp &&\n rule.test.source === nextGlobalCssRegExpSource &&\n hasAppIssuer(rule) &&\n Array.isArray(rule.use);\n\n const findAppRule = (ruleList: unknown): { oneOf: unknown[]; appRule: Rule } | undefined => {\n if (!Array.isArray(ruleList)) return undefined;\n for (const rule of ruleList) {\n if (!isRule(rule)) continue;\n if (Array.isArray(rule.oneOf)) {\n const appRule = rule.oneOf.find(\n (item): item is Rule => isRule(item) && isAppGlobalCssRule(item),\n );\n if (appRule) return { oneOf: rule.oneOf, appRule };\n }\n const found = findAppRule(rule.oneOf) ?? findAppRule(rule.rules);\n if (found) return found;\n }\n return undefined;\n };\n\n const found = findAppRule(rules);\n if (!found) {\n console.warn(\n \"next-yak: could not find the next.js global-CSS rules. \" +\n \"Please report this issue with your Next.js version at \" +\n \"https://github.com/DigitecGalaxus/next-yak/issues/new\",\n );\n return;\n }\n\n found.oneOf.unshift({\n test: /\\.yak\\.css$/,\n sideEffects: true,\n use: [...(found.appRule.use as unknown[])],\n });\n}\n","/// <reference types=\"node\" />\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport type YakConfigOptions = {\n /**\n * Generate compact CSS class and variable names.\n * @defaultValue\n * enabled if NODE_ENV is set to `production`, otherwise disabled\n */\n minify?: boolean;\n contextPath?: string;\n /**\n * Optional prefix for generated CSS identifiers.\n * This can be used to ensure unique class names across different applications\n * or to add organization-specific prefixes.\n */\n prefix?: string;\n /**\n * Adds `displayName` to each component for better React DevTools debugging\n * - Enabled by default in development mode\n * - Disabled by default in production\n * - Increases bundle size slightly when enabled\n */\n displayNames?: boolean;\n /**\n * Fold statically known styles at build time: JSX usages of fully static\n * styled components become plain DOM elements, and a static `css` prop\n * becomes a plain `className`. Both skip the runtime wrapper and merge calls.\n * @defaultValue true\n */\n foldStatic?: boolean;\n /**\n * Fail the build when a `css` prop has a value next-yak can't handle\n * (e.g. an array, an object or a plain string). next-yak claims the `css`\n * prop, so a malformed value is almost always a mistake worth surfacing.\n *\n * Set to `false` to leave such props untouched instead, e.g. when another\n * library on the same element uses its own `css` prop.\n * @defaultValue true\n */\n strictCssProp?: boolean;\n experiments?: {\n /**\n * Debug logging for transformed files.\n * - `true` - log all files\n * - `object` - filter by pattern and/or output types (at least one required)\n */\n debug?:\n | true\n | { pattern: string; types?: Array<\"ts\" | \"css\" | \"css-resolved\"> }\n | { pattern?: string; types: Array<\"ts\" | \"css\" | \"css-resolved\"> };\n transpilationMode?: \"CssModule\" | \"Css\";\n /**\n * Suppress deprecation warnings for :global() selectors during migration period\n * @defaultValue false\n */\n suppressDeprecationWarnings?: boolean;\n };\n};\n\n/**\n * Build the base yak-swc plugin options shared by every bundler integration\n * (webpack, turbopack, vite, rsbuild). The caller adds the bundler-specific\n * `importMode` on top.\n *\n * @param yakOptions - Yak configuration options\n * @param basePath - Base path used by yak-swc to derive stable identifiers\n */\nexport function buildYakPluginOptions(yakOptions: YakConfigOptions, basePath: string) {\n const minify = yakOptions.minify ?? process.env.NODE_ENV === \"production\";\n return {\n minify,\n basePath,\n prefix: yakOptions.prefix,\n displayNames: yakOptions.displayNames ?? !minify,\n foldStatic: yakOptions.foldStatic ?? true,\n strictCssProp: yakOptions.strictCssProp ?? true,\n suppressDeprecationWarnings: yakOptions.experiments?.suppressDeprecationWarnings ?? false,\n reactRefreshReg: true,\n };\n}\n\n/**\n * Try to resolve yak\n */\nexport function resolveYakContext(contextPath: string | undefined, cwd: string) {\n const yakContext = contextPath\n ? path.resolve(cwd, contextPath)\n : path.resolve(cwd, \"yak.context\");\n const extensions = [\"\", \".ts\", \".tsx\", \".js\", \".jsx\"];\n for (const extension in extensions) {\n const fileName = yakContext + extensions[extension];\n if (existsSync(fileName)) {\n return fileName;\n }\n }\n if (contextPath) {\n throw new Error(`Could not find yak context file at ${yakContext}`);\n }\n}\n","/// <reference types=\"node\" />\nimport type { NextConfig } from \"next\";\nimport path, { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { allowYakGlobalCss, hasAppDir } from \"./allow-global-css.ts\";\nimport {\n buildYakPluginOptions,\n resolveYakContext,\n type YakConfigOptions,\n} from \"yak-internals/config\";\n\nexport { buildYakPluginOptions, resolveYakContext } from \"yak-internals/config\";\nexport type { YakConfigOptions } from \"yak-internals/config\";\n\nconst currentDir =\n typeof __dirname !== \"undefined\" ? __dirname : dirname(fileURLToPath(import.meta.url));\n\nconst addYak = (yakOptions: YakConfigOptions, nextConfig: NextConfig) => {\n const yakPluginOptions = buildYakPluginOptions(yakOptions, currentDir);\n\n const transpilation = yakOptions.experiments?.transpilationMode ?? \"CssModule\";\n const cssExtension = transpilation === \"CssModule\" ? \".yak.module.css\" : \".yak.css\";\n\n if (process.env.TURBOPACK === \"1\" || process.env.TURBOPACK === \"auto\") {\n addYakTurbopack(nextConfig, yakOptions, {\n ...yakPluginOptions,\n importMode: {\n value: \"data:text/css;base64,\",\n transpilation: \"Css\",\n encoding: \"Base64\",\n },\n });\n } else {\n addYakWebpack(nextConfig, yakOptions, {\n ...yakPluginOptions,\n importMode: {\n value: `./{{__BASE_NAME__}}${cssExtension}!=!./{{__BASE_NAME__}}?./{{__BASE_NAME__}}${cssExtension}`,\n transpilation,\n encoding: \"None\",\n },\n });\n }\n return nextConfig;\n};\n\n/**\n * Configure Turbopack with yak loader for CSS-in-JS transformation\n * @param nextConfig - Next.js configuration object\n * @param yakOptions - Yak configuration options\n * @param yakPluginOptions - Processed plugin options for yak-swc\n */\nfunction addYakTurbopack(\n nextConfig: NextConfig,\n yakOptions: YakConfigOptions,\n yakPluginOptions: {\n minify: boolean;\n basePath: string;\n prefix?: string;\n displayNames: boolean;\n foldStatic: boolean;\n strictCssProp: boolean;\n importMode: {\n value: string;\n transpilation: string;\n encoding: string;\n };\n },\n) {\n // turbopack can't handle options with undefined values, so we remove them\n const yakLoader = removeUndefinedRecursive({\n loader: path.join(currentDir, \"../loaders/turbo-loader.cjs\"),\n options: {\n yakOptions: yakOptions,\n yakPluginOptions: yakPluginOptions,\n },\n }) as { loader: string; options: {} };\n\n nextConfig.turbopack ||= {};\n nextConfig.turbopack.rules ||= {};\n\n const ruleKey = \"*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}\";\n const rule = {\n loaders: [] as { loader: string; options: {} }[],\n ...nextConfig.turbopack.rules[ruleKey],\n };\n rule.loaders.push(yakLoader);\n nextConfig.turbopack.rules[ruleKey] = rule;\n\n // Configure resolveAlias for custom yak context (similar to webpack)\n // This allows users to provide a custom context file that will be used\n // instead of the default baseContext\n const yakContext = resolveYakContext(yakOptions.contextPath, process.cwd());\n if (yakContext) {\n nextConfig.turbopack.resolveAlias ||= {};\n nextConfig.turbopack.resolveAlias[\"next-yak/context/baseContext\"] =\n // This is a hack around the fact that turbopack currently only supports relative paths\n // turbopack: \"server relative imports are not implemented yet\"\n // Relative is quite dangerous here as it relies on the cwd being the starting point\n `./${path.relative(process.cwd(), yakContext)}`;\n }\n}\n\n/**\n * Configure Webpack with yak SWC plugin and webpack loader for CSS-in-JS transformation\n * @param nextConfig - Next.js configuration object\n * @param yakOptions - Yak configuration options\n * @param yakPluginOptions - Processed plugin options for yak-swc\n */\nfunction addYakWebpack(\n nextConfig: NextConfig,\n yakOptions: YakConfigOptions,\n yakPluginOptions: {\n minify: boolean;\n basePath: string;\n prefix?: string;\n displayNames: boolean;\n foldStatic: boolean;\n strictCssProp: boolean;\n importMode: {\n value: string;\n transpilation: string;\n encoding: string;\n };\n },\n) {\n // Add SWC plugin for Webpack\n nextConfig.experimental ||= {};\n nextConfig.experimental.swcPlugins ||= [];\n nextConfig.experimental.swcPlugins.push([\"yak-swc\", yakPluginOptions]);\n\n // Configure webpack loader\n const previousConfig = nextConfig.webpack;\n nextConfig.webpack = (webpackConfig, options) => {\n if (previousConfig) {\n webpackConfig = previousConfig(webpackConfig, options);\n }\n\n if (\n yakOptions.experiments?.transpilationMode === \"Css\" &&\n !options.isServer &&\n !hasAppDir(options.dir)\n ) {\n // Next.js restricts global CSS imports only in the client build of a pages-only project, so only patch its rules there\n allowYakGlobalCss(webpackConfig);\n }\n\n webpackConfig.module.rules.push({\n test:\n yakOptions.experiments?.transpilationMode === \"Css\" ? /\\.yak\\.css$/ : /\\.yak\\.module\\.css$/,\n loader: path.join(currentDir, \"../loaders/webpack-loader.cjs\"),\n options: yakOptions,\n });\n\n // With the following alias the internal next-yak code\n // is able to import a context which works for server components\n const yakContext = resolveYakContext(\n yakOptions.contextPath,\n webpackConfig.context || process.cwd(),\n );\n if (yakContext) {\n webpackConfig.resolve.alias[\"next-yak/context/baseContext\"] = yakContext;\n }\n\n return webpackConfig;\n };\n}\n\n/**\n * Recursively removes undefined values from an object or array.\n *\n * This function deeply traverses the input object/array and creates a new structure\n * with all undefined values filtered out. For objects, properties with undefined values\n * are omitted. For arrays, undefined elements are removed from the result.\n *\n * @param obj - The object or array to process\n * @returns A new object/array with undefined values removed, or the original value if no changes were needed\n */\nfunction removeUndefinedRecursive<T>(obj: T): {} {\n if (typeof obj !== \"object\" || obj === null) {\n return obj as {};\n }\n\n if (Array.isArray(obj)) {\n const filtered: unknown[] = [];\n for (let i = 0; i < obj.length; i++) {\n const processed = removeUndefinedRecursive(obj[i]);\n if (processed !== undefined) {\n filtered.push(processed);\n }\n }\n return filtered as {};\n }\n\n const newObj: Record<string, unknown> = {};\n let hasChanges = false;\n\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n const value = removeUndefinedRecursive((obj as any)[key]);\n if (value !== undefined) {\n newObj[key] = value;\n hasChanges = true;\n }\n }\n }\n\n return hasChanges ? (newObj as {}) : obj;\n}\n\n// Wrapper to allow sync, async, and function configuration of Next.js\n/**\n * Add Yak to your Next.js app\n *\n * @usage\n *\n * ```ts\n * // next.config.js\n * const { withYak } = require(\"next-yak/withYak\");\n * const nextConfig = {\n * // your next config here\n * };\n * module.exports = withYak(nextConfig);\n * ```\n *\n * With a custom yakConfig\n *\n * ```ts\n * // next.config.js\n * const { withYak } = require(\"next-yak/withYak\");\n * const nextConfig = {\n * // your next config here\n * };\n * const yakConfig = {\n * // Optional prefix for generated CSS identifiers\n * prefix: \"my-app\",\n * // Other yak config options...\n * };\n * module.exports = withYak(yakConfig, nextConfig);\n * ```\n */\nexport const withYak: {\n <\n T extends\n | Record<string, any>\n | ((...args: any[]) => Record<string, any>)\n | ((...args: any[]) => Promise<Record<string, any>>),\n >(\n yakOptions: YakConfigOptions,\n nextConfig: T,\n ): T;\n // no yakConfig\n <\n T extends\n | Record<string, any>\n | ((...args: any[]) => Record<string, any>)\n | ((...args: any[]) => Promise<Record<string, any>>),\n >(\n nextConfig: T,\n _?: undefined,\n ): T;\n} = (maybeYakOptions, nextConfig) => {\n if (nextConfig === undefined) {\n return withYak({}, maybeYakOptions);\n }\n // If the second parameter is present the first parameter must be a YakConfigOptions\n const yakOptions = maybeYakOptions as YakConfigOptions;\n if (typeof nextConfig === \"function\") {\n /**\n * A NextConfig can be a sync or async function\n * https://nextjs.org/docs/pages/api-reference/next-config-js\n * @param {any[]} args\n */\n return (...args) => {\n /** Dynamic Next Configs can be async or sync */\n const config = nextConfig(...args) as NextConfig | Promise<NextConfig>;\n return config instanceof Promise\n ? config.then((config) => addYak(yakOptions, config))\n : addYak(yakOptions, config);\n };\n }\n return addYak(yakOptions, nextConfig);\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-yak",
|
|
3
|
-
"version": "9.10.
|
|
3
|
+
"version": "9.10.2",
|
|
4
4
|
"description": "next-yak is a CSS-in-JS solution tailored for Next.js that seamlessly combines the expressive power of styled-components syntax with efficient build-time extraction of CSS using Next.js's built-in CSS configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css-in-js",
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
"dependencies": {
|
|
163
163
|
"@babel/parser": "^8.0.4",
|
|
164
164
|
"@swc/core": "~1.15.47",
|
|
165
|
-
"yak-swc": "9.10.
|
|
165
|
+
"yak-swc": "9.10.2"
|
|
166
166
|
},
|
|
167
167
|
"devDependencies": {
|
|
168
168
|
"@rsbuild/core": "2.2.2",
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
interface WebpackConfig {
|
|
5
|
+
module?: { rules?: unknown[] };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Rule {
|
|
9
|
+
test?: unknown;
|
|
10
|
+
issuer?: unknown;
|
|
11
|
+
use?: unknown;
|
|
12
|
+
oneOf?: unknown;
|
|
13
|
+
rules?: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function hasAppDir(projectDir: string): boolean {
|
|
17
|
+
return (
|
|
18
|
+
// mirrors next.js's `findDir(dir, "app")`
|
|
19
|
+
// https://github.com/vercel/next.js/blob/v16.3.4/packages/next/src/lib/find-pages-dir.ts#L4-L13
|
|
20
|
+
existsSync(path.join(projectDir, "app")) || existsSync(path.join(projectDir, "src", "app"))
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Next.js (pages router) only allows global CSS imports from `_app`.
|
|
26
|
+
* Everywhere else it expects CSS Modules. Yak's co-located `*.yak.css`
|
|
27
|
+
* files are plain global CSS, because yak already hashes the class names
|
|
28
|
+
* itself, so there's no risk of leaking styles
|
|
29
|
+
*
|
|
30
|
+
* Next.js has no option to allow this, so we copy its `_app` global CSS
|
|
31
|
+
* rule without the `_app` issuer check and limit the copy to `.yak.css`.
|
|
32
|
+
* The copy goes to the start of the same `oneOf`, so it matches before
|
|
33
|
+
* Next's error rules.
|
|
34
|
+
*/
|
|
35
|
+
export function allowYakGlobalCss(webpackConfig: WebpackConfig) {
|
|
36
|
+
const rules = webpackConfig.module?.rules;
|
|
37
|
+
if (!Array.isArray(rules)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Next.js's internal `regexCssGlobal` source
|
|
42
|
+
// https://github.com/vercel/next.js/blob/v16.3.4/packages/next/src/build/webpack/config/blocks/css/index.ts#L23
|
|
43
|
+
const nextGlobalCssRegExpSource = "(?<!\\.module)\\.css$";
|
|
44
|
+
|
|
45
|
+
const isRule = (value: unknown): value is Rule =>
|
|
46
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47
|
+
|
|
48
|
+
// Next.js sets `issuer: { and: [ctx.customAppFile] }`, where
|
|
49
|
+
// `customAppFile` is a RegExp for the path of `pages/_app`
|
|
50
|
+
// https://github.com/vercel/next.js/blob/v16.3.4/packages/next/src/build/webpack-config.ts#L2550-L2552
|
|
51
|
+
const hasAppIssuer = (rule: Rule): boolean => {
|
|
52
|
+
if (!isRule(rule.issuer)) return false;
|
|
53
|
+
const and = (rule.issuer as { and?: unknown }).and;
|
|
54
|
+
return Array.isArray(and) && and[0] instanceof RegExp && and[0].source.endsWith("_app");
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const isAppGlobalCssRule = (rule: Rule): boolean =>
|
|
58
|
+
rule.test instanceof RegExp &&
|
|
59
|
+
rule.test.source === nextGlobalCssRegExpSource &&
|
|
60
|
+
hasAppIssuer(rule) &&
|
|
61
|
+
Array.isArray(rule.use);
|
|
62
|
+
|
|
63
|
+
const findAppRule = (ruleList: unknown): { oneOf: unknown[]; appRule: Rule } | undefined => {
|
|
64
|
+
if (!Array.isArray(ruleList)) return undefined;
|
|
65
|
+
for (const rule of ruleList) {
|
|
66
|
+
if (!isRule(rule)) continue;
|
|
67
|
+
if (Array.isArray(rule.oneOf)) {
|
|
68
|
+
const appRule = rule.oneOf.find(
|
|
69
|
+
(item): item is Rule => isRule(item) && isAppGlobalCssRule(item),
|
|
70
|
+
);
|
|
71
|
+
if (appRule) return { oneOf: rule.oneOf, appRule };
|
|
72
|
+
}
|
|
73
|
+
const found = findAppRule(rule.oneOf) ?? findAppRule(rule.rules);
|
|
74
|
+
if (found) return found;
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const found = findAppRule(rules);
|
|
80
|
+
if (!found) {
|
|
81
|
+
console.warn(
|
|
82
|
+
"next-yak: could not find the next.js global-CSS rules. " +
|
|
83
|
+
"Please report this issue with your Next.js version at " +
|
|
84
|
+
"https://github.com/DigitecGalaxus/next-yak/issues/new",
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
found.oneOf.unshift({
|
|
90
|
+
test: /\.yak\.css$/,
|
|
91
|
+
sideEffects: true,
|
|
92
|
+
use: [...(found.appRule.use as unknown[])],
|
|
93
|
+
});
|
|
94
|
+
}
|
package/withYak/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { NextConfig } from "next";
|
|
3
3
|
import path, { dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { allowYakGlobalCss, hasAppDir } from "./allow-global-css.ts";
|
|
5
6
|
import {
|
|
6
7
|
buildYakPluginOptions,
|
|
7
8
|
resolveYakContext,
|
|
@@ -134,6 +135,15 @@ function addYakWebpack(
|
|
|
134
135
|
webpackConfig = previousConfig(webpackConfig, options);
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
if (
|
|
139
|
+
yakOptions.experiments?.transpilationMode === "Css" &&
|
|
140
|
+
!options.isServer &&
|
|
141
|
+
!hasAppDir(options.dir)
|
|
142
|
+
) {
|
|
143
|
+
// Next.js restricts global CSS imports only in the client build of a pages-only project, so only patch its rules there
|
|
144
|
+
allowYakGlobalCss(webpackConfig);
|
|
145
|
+
}
|
|
146
|
+
|
|
137
147
|
webpackConfig.module.rules.push({
|
|
138
148
|
test:
|
|
139
149
|
yakOptions.experiments?.transpilationMode === "Css" ? /\.yak\.css$/ : /\.yak\.module\.css$/,
|