11import * as path from 'node:path' ;
2+ import * as fs from 'node:fs' ;
23import type { NextConfig } from 'next' ;
34import { findPagesDir } from 'next/dist/lib/find-pages-dir' ;
45import { webpack as webpackPlugin , extendTheme , type PigmentOptions } from '@pigment-css/unplugin' ;
6+ import { slugify } from '@wyw-in-js/shared' ;
7+ import { generateTokenCss } from '@pigment-css/react/utils' ;
58
69export { type PigmentOptions } ;
710
@@ -10,15 +13,133 @@ const extractionFile = path.join(
1013 'zero-virtual.css' ,
1114) ;
1215
16+ function scanDirectory ( dir : string , fileList : string [ ] = [ ] ) : string [ ] {
17+ const dirName = path . basename ( dir ) ;
18+ if (
19+ dirName === 'node_modules' ||
20+ dirName === '.next' ||
21+ dirName === '.git' ||
22+ dir === path . resolve ( __dirname , '..' , 'virtual' )
23+ ) {
24+ return fileList ;
25+ }
26+ try {
27+ const files = fs . readdirSync ( dir , { withFileTypes : true } ) ;
28+ files . forEach ( ( file ) => {
29+ const fullPath = path . join ( dir , file . name ) ;
30+ if ( file . isDirectory ( ) ) {
31+ scanDirectory ( fullPath , fileList ) ;
32+ } else if ( file . isFile ( ) && / \. ( t s x | t s | j s x | j s ) $ / . test ( file . name ) ) {
33+ fileList . push ( fullPath ) ;
34+ }
35+ } ) ;
36+ } catch {
37+ // Ignore
38+ }
39+ return fileList ;
40+ }
41+
1342export function withPigment ( nextConfig : NextConfig , pigmentConfig ?: PigmentOptions ) {
1443 const { babelOptions = { } , asyncResolve, ...other } = pigmentConfig ?? { } ;
15- if ( process . env . TURBOPACK === '1' ) {
16- // eslint-disable-next-line no-console
17- console . log (
18- `\x1B[33m ${ process . env . PACKAGE_NAME } : Turbo mode is not supported yet. Please disable it by removing the "--turbo" flag from your "next dev" command to use Pigment CSS.\x1B[39m` ,
19- ) ;
20- return nextConfig ;
44+
45+ // === Turbopack configuration (always prepared) ===
46+ const virtualDir = path . resolve ( __dirname , '../virtual' ) ;
47+
48+ if ( ! fs . existsSync ( virtualDir ) ) {
49+ fs . mkdirSync ( virtualDir , { recursive : true } ) ;
2150 }
51+ // Pre-create virtual CSS files for all source files to bypass Turbopack's startup resolver cache
52+ const sourceFiles = scanDirectory ( process . cwd ( ) ) ;
53+ const activeSlugs = new Set < string > ( ) ;
54+
55+ sourceFiles . forEach ( ( file ) => {
56+ const slug = slugify ( file ) ;
57+ const cssFileName = `${ slug } .css` ;
58+ activeSlugs . add ( cssFileName ) ;
59+ const cssPath = path . join ( virtualDir , cssFileName ) ;
60+ if ( ! fs . existsSync ( cssPath ) ) {
61+ fs . writeFileSync ( cssPath , '' , 'utf8' ) ;
62+ }
63+ } ) ;
64+ try {
65+ const files = fs . readdirSync ( virtualDir ) ;
66+ files . forEach ( ( file ) => {
67+ if ( file . endsWith ( '.css' ) && ! activeSlugs . has ( file ) ) {
68+ fs . rmSync ( path . join ( virtualDir , file ) , { force : true } ) ;
69+ }
70+ } ) ;
71+ } catch {
72+ // Ignore
73+ }
74+ const cacheDir = path . resolve ( process . cwd ( ) , '.next/cache' ) ;
75+ if ( ! fs . existsSync ( cacheDir ) ) {
76+ fs . mkdirSync ( cacheDir , { recursive : true } ) ;
77+ }
78+ const themeCachePath = path . join ( cacheDir , 'pigment-theme.json' ) ;
79+ const serializedTheme = JSON . stringify ( other . theme ?? { } , ( _key , value ) => {
80+ if ( typeof value === 'function' ) {
81+ return undefined ;
82+ }
83+ return value ;
84+ } ) ;
85+ fs . writeFileSync ( themeCachePath , serializedTheme , 'utf8' ) ;
86+
87+ // Pre-generate token CSS while the full theme (with functions) is still available.
88+ // JSON serialization strips generateStyleSheets, so generateTokenCss would return empty in the loader.
89+ const tokenCssCachePath = path . join ( cacheDir , 'pigment-token.css' ) ;
90+ const tokenCss = generateTokenCss ( other . theme ) ;
91+ fs . writeFileSync ( tokenCssCachePath , tokenCss , 'utf8' ) ;
92+
93+ const turbopackLoaderItem = {
94+ loader : require . resolve ( './turbopack-loader' ) ,
95+ options : {
96+ themeCachePath,
97+ tokenCssCachePath,
98+ transformLibraries : other . transformLibraries ?? [ ] ,
99+ babelOptions : babelOptions ?? { } ,
100+ css : other . css ?? null ,
101+ } ,
102+ } ;
103+
104+ const turbopackNewRules = {
105+ '*.ts' : { loaders : [ turbopackLoaderItem ] } ,
106+ '*.tsx' : { loaders : [ turbopackLoaderItem ] } ,
107+ '*.js' : { loaders : [ turbopackLoaderItem ] } ,
108+ '*.jsx' : { loaders : [ turbopackLoaderItem ] } ,
109+ '*.css' : { loaders : [ turbopackLoaderItem ] } ,
110+ } ;
111+
112+ type TurbopackRule = { loaders : Array < Record < string , unknown > > } ;
113+ type TurbopackRules = Record < string , TurbopackRule > ;
114+
115+ const mergeTurbopackRules = (
116+ rulesToMerge : TurbopackRules ,
117+ existingRules : TurbopackRules = { } ,
118+ ) : TurbopackRules => {
119+ const mergedRules : TurbopackRules = { ...existingRules } ;
120+ Object . entries ( rulesToMerge ) . forEach ( ( [ key , rule ] ) => {
121+ const existing = mergedRules [ key ] ;
122+ if ( existing ) {
123+ mergedRules [ key ] = {
124+ ...existing ,
125+ loaders : [ ...rule . loaders , ...existing . loaders ] ,
126+ } ;
127+ } else {
128+ mergedRules [ key ] = rule ;
129+ }
130+ } ) ;
131+ return mergedRules ;
132+ } ;
133+
134+ const nextConfigWithTurbo = nextConfig as NextConfig & {
135+ turbopack ?: {
136+ rules ?: TurbopackRules ;
137+ resolveAlias ?: Record < string , string > ;
138+ } ;
139+ } ;
140+
141+ // === Webpack configuration (lazy — only executed when Webpack is actually used) ===
142+ const originalWebpack = nextConfig . webpack ;
22143
23144 const webpack : Exclude < NextConfig [ 'webpack' ] , undefined > = ( config , context ) => {
24145 const { dir, dev, isServer, config : resolvedNextConfig } = context ;
@@ -94,17 +215,27 @@ export function withPigment(nextConfig: NextConfig, pigmentConfig?: PigmentOptio
94215 } ) ,
95216 ) ;
96217
97- if ( typeof nextConfig . webpack === 'function' ) {
98- return nextConfig . webpack ( config , context ) ;
218+ if ( typeof originalWebpack === 'function' ) {
219+ return originalWebpack ( config , context ) ;
99220 }
100221 config . ignoreWarnings = config . ignoreWarnings ?? [ ] ;
101222 config . ignoreWarnings . push ( {
102223 module : / ( z e r o - v i r t u a l \. c s s ) | ( r e a c t \/ s t y l e s \. c s s ) / ,
103224 } ) ;
104225 return config ;
105226 } ;
227+
228+ // === Return both turbopack and webpack configs ===
106229 return {
107- ...nextConfig ,
230+ ...nextConfigWithTurbo ,
231+ turbopack : {
232+ ...nextConfigWithTurbo . turbopack ,
233+ rules : mergeTurbopackRules ( turbopackNewRules , nextConfigWithTurbo . turbopack ?. rules ) ,
234+ resolveAlias : {
235+ ...nextConfigWithTurbo . turbopack ?. resolveAlias ,
236+ '@pigment-css/nextjs-plugin/virtual' : virtualDir ,
237+ } ,
238+ } ,
108239 webpack,
109240 } ;
110241}
0 commit comments