11import type { Plugin } from 'vite'
22import resolveConfig from 'tailwindcss/resolveConfig'
3+ import {
4+ defaultRequestToExternal ,
5+ defaultRequestToHandle ,
6+ } from '@wordpress/dependency-extraction-webpack-plugin/lib/util'
37
8+ // Theme JSON Types
49interface ThemeJsonColor {
510 name : string
611 slug : string
@@ -78,33 +83,51 @@ interface ThemeJsonOptions {
7883 title ?: string
7984}
8085
81- /**
82- * Converts a slug or kebab-case string into Title Case.
83- *
84- * Examples:
85- * - Input: "primary-color" -> Output: "Primary Color"
86- * - Input: "text-lg" -> Output: "Text Lg"
87- */
86+ // WordPress Plugin Helper Functions
87+ function extractNamedImports ( imports : string ) : string [ ] {
88+ return imports
89+ . match ( / { ( [ ^ } ] + ) } / ) [ 1 ]
90+ . split ( ',' )
91+ . map ( ( s ) => s . trim ( ) )
92+ }
93+
94+ function handleNamedReplacement ( namedImports : string [ ] , external : string [ ] ) : string {
95+ return namedImports
96+ . map ( ( imports ) => {
97+ const [ name , alias = name ] = imports
98+ . split ( ' as ' )
99+ . map ( ( script ) => script . trim ( ) )
100+
101+ return `const ${ alias } = ${ external . join ( '.' ) } .${ name } ;`
102+ } )
103+ . join ( '\n' )
104+ }
105+
106+ function handleReplacements ( imports : string , external : string [ ] ) : string {
107+ if ( imports . includes ( '{' ) ) {
108+ const namedImports = extractNamedImports ( imports )
109+ return handleNamedReplacement ( namedImports , external )
110+ }
111+
112+ if ( imports . includes ( '* as' ) ) {
113+ const alias = imports . match ( / \* \s + a s \s + ( \w + ) / ) [ 1 ]
114+ return `const ${ alias } = ${ external . join ( '.' ) } ;`
115+ }
116+
117+ const name = imports . trim ( )
118+ return `const ${ name } = ${ external . join ( '.' ) } ;`
119+ }
120+
121+ // Theme JSON Helper Functions
88122const toTitleCase = ( slug : string ) : string =>
89123 slug
90124 . split ( '-' )
91125 . map ( word => word . charAt ( 0 ) . toUpperCase ( ) + word . slice ( 1 ) )
92126 . join ( ' ' )
93127
94- /**
95- * Checks if a value is a valid CSS color.
96- *
97- * Examples:
98- * - Input: "#ff0000" -> true
99- * - Input: "rgb(255, 255, 0)" -> true
100- * - Input: "invalid-color" -> false
101- */
102128const isValidColor = ( value : any ) : boolean =>
103129 typeof value === 'string' && ( value . startsWith ( '#' ) || value . startsWith ( 'rgb' ) )
104130
105- /**
106- * Recursively processes a Tailwind color object into an array of ThemeJsonColor.
107- */
108131const processColors = (
109132 obj : Record < string , any > ,
110133 prefix = ''
@@ -129,7 +152,7 @@ const processColors = (
129152 return palette
130153}
131154
132- // Convert Tailwind values to Theme JSON structures
155+ // Conversion Functions
133156const convertTailwindColorsToThemeJson = ( config : any ) : ThemeJsonColor [ ] =>
134157 processColors ( resolveConfig ( config ) . theme . colors )
135158
@@ -139,7 +162,7 @@ const convertTailwindFontFamiliesToThemeJson = (
139162 Object . entries ( resolveConfig ( config ) . theme . fontFamily ) . map ( ( [ name , value ] ) => ( {
140163 name : toTitleCase ( name ) ,
141164 slug : name . toLowerCase ( ) ,
142- fontFamily : Array . isArray ( value ) ? value . join ( ', ' ) : value ,
165+ fontFamily : Array . isArray ( value ) ? value . join ( ', ' ) : String ( value ) ,
143166 } ) )
144167
145168const convertTailwindFontSizesToThemeJson = (
@@ -151,14 +174,91 @@ const convertTailwindFontSizesToThemeJson = (
151174 size : Array . isArray ( value ) ? value [ 0 ] : value ,
152175 } ) )
153176
154- // Merge default settings with user options
155177const mergeSettings = (
156178 defaults : ThemeJsonSettings ,
157179 overrides : ThemeJsonSettings | undefined
158180) : ThemeJsonSettings => ( { ...defaults , ...overrides } )
159181
160- // Plugin definition
161- export default function wordPressThemeJson ( options : ThemeJsonOptions = { } ) : Plugin {
182+ // Plugin Exports
183+ export function wordpressPlugin ( ) : Plugin {
184+ const dependencies = new Set < string > ( )
185+
186+ return {
187+ name : 'wordpress-plugin' ,
188+ enforce : 'post' ,
189+ transform ( code : string , id : string ) {
190+ if ( ! id . endsWith ( '.js' ) ) {
191+ return
192+ }
193+
194+ const imports = [
195+ ...( code . match ( / ^ i m p o r t .+ f r o m [ ' " ] @ w o r d p r e s s \/ [ ^ ' " ] + [ ' " ] / gm) || [ ] ) ,
196+ ...( code . match ( / ^ i m p o r t [ ' " ] @ w o r d p r e s s \/ [ ^ ' " ] + [ ' " ] / gm) || [ ] ) ,
197+ ]
198+
199+ imports . forEach ( ( statement ) => {
200+ const match =
201+ statement . match ( / ^ i m p o r t ( .+ ) f r o m [ ' " ] @ w o r d p r e s s \/ ( [ ^ ' " ] + ) [ ' " ] / ) ||
202+ statement . match ( / ^ i m p o r t [ ' " ] @ w o r d p r e s s \/ ( [ ^ ' " ] + ) [ ' " ] / )
203+
204+ if ( ! match ) {
205+ return
206+ }
207+
208+ const [ , imports , pkg ] = match
209+
210+ if ( ! pkg ) {
211+ return
212+ }
213+
214+ const external = defaultRequestToExternal ( `@wordpress/${ pkg } ` )
215+ const handle = defaultRequestToHandle ( `@wordpress/${ pkg } ` )
216+
217+ if ( external && handle ) {
218+ dependencies . add ( handle )
219+
220+ const replacement = imports
221+ ? handleReplacements ( imports , external )
222+ : `const ${ pkg . replace ( / - ( [ a - z ] ) / g, ( _ , letter ) =>
223+ letter . toUpperCase ( )
224+ ) } = ${ external . join ( '.' ) } ;`
225+
226+ code = code . replace ( statement , replacement )
227+ }
228+ } )
229+
230+ return { code, map : null }
231+ } ,
232+ generateBundle ( ) {
233+ this . emitFile ( {
234+ type : 'asset' ,
235+ fileName : 'editor.deps.json' ,
236+ source : JSON . stringify ( [ ...dependencies ] ) ,
237+ } )
238+ } ,
239+ }
240+ }
241+
242+ export function wordpressRollupPlugin ( ) : Plugin {
243+ return {
244+ name : 'wordpress-rollup-plugin' ,
245+ options ( opts : any ) {
246+ opts . external = ( id : string ) => id . startsWith ( '@wordpress/' )
247+ opts . output = opts . output || { }
248+ opts . output . globals = ( id : string ) => {
249+ if ( id . startsWith ( '@wordpress/' ) ) {
250+ const packageName = id . replace ( '@wordpress/' , '' )
251+ return `wp.${ packageName . replace ( / - ( [ a - z ] ) / g, ( _ , letter ) =>
252+ letter . toUpperCase ( )
253+ ) } `
254+ }
255+ }
256+ return opts
257+ } ,
258+ }
259+ }
260+
261+ export function wordPressThemeJson ( options : ThemeJsonOptions = { } ) : Plugin {
162262 const defaultSettings : ThemeJsonSettings = {
163263 background : { backgroundImage : true } ,
164264 color : {
@@ -182,7 +282,6 @@ export default function wordPressThemeJson(options: ThemeJsonOptions = {}): Plug
182282
183283 return {
184284 name : 'wordpress-theme-json' ,
185-
186285 generateBundle ( ) {
187286 const themeJson : Record < string , any > = {
188287 __generated__ : '⚠️ This file is generated. Do not edit.' ,
@@ -216,7 +315,6 @@ export default function wordPressThemeJson(options: ThemeJsonOptions = {}): Plug
216315 }
217316 }
218317
219- // Append optional fields
220318 if ( options . customTemplates ) themeJson . customTemplates = options . customTemplates
221319 if ( options . patterns ) themeJson . patterns = options . patterns
222320 if ( options . styles ) themeJson . styles = options . styles
@@ -230,4 +328,4 @@ export default function wordPressThemeJson(options: ThemeJsonOptions = {}): Plug
230328 } )
231329 } ,
232330 }
233- }
331+ }
0 commit comments