UNPKG

next-yak

Version:

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

1 lines 14.7 kB
{"version":3,"file":"index.cjs","names":["path"],"sources":["../../withYak/index.ts"],"sourcesContent":["/// <reference types=\"node\" />\nimport type { NextConfig } from \"next\";\nimport { existsSync } from \"node:fs\";\nimport path, { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst currentDir =\n typeof __dirname !== \"undefined\" ? __dirname : dirname(fileURLToPath(import.meta.url));\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. a plain string). next-yak claims the `css` prop, so a malformed\n * 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\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/**\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\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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"}