Tailwind v4 (#3208)

Co-authored-by: Brandon <brandon@tendency.me>
Co-authored-by: csorrentino <cjsorren@gmail.com>
Co-authored-by: stuart <stuartjwong@gmail.com>
This commit is contained in:
Ben Word
2025-02-02 07:53:33 -05:00
committed by GitHub
parent 8d5a1ac4f4
commit 723506daba
12 changed files with 704 additions and 1461 deletions

View File

@@ -8,33 +8,35 @@ namespace App;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\Str;
/**
* Inject the Vite assets into the head.
* Inject styles into the block editor.
*
* @return void
* @return array
*/
add_filter('wp_head', function () {
echo Str::wrap(app('assets.vite')([
'resources/css/app.css',
'resources/js/app.js',
]), "\n");
add_filter('block_editor_settings_all', function ($settings) {
$style = Vite::asset('resources/css/editor.css');
$settings['styles'][] = [
'css' => Vite::isRunningHot()
? "@import url('{$style}')"
: Vite::content('resources/css/editor.css'),
];
return $settings;
});
/**
* Inject assets into the block editor.
* Inject scripts into the block editor.
*
* @return void
*/
add_filter('admin_head', function () {
$screen = get_current_screen();
if (! $screen?->is_block_editor()) {
if (! get_current_screen()?->is_block_editor()) {
return;
}
$dependencies = File::json(public_path('build/editor.deps.json')) ?? [];
$dependencies = json_decode(Vite::content('_editor.deps.json'));
foreach ($dependencies as $dependency) {
if (! wp_script_is($dependency)) {
@@ -42,25 +44,49 @@ add_filter('admin_head', function () {
}
}
echo Str::wrap(app('assets.vite')([
'resources/css/editor.css',
echo Vite::withEntryPoints([
'resources/js/editor.js',
]), "\n");
])->toHtml();
});
/**
* Use theme.json from the build directory
* Add Vite's HMR client to the block editor.
*
* @param string $path
* @param string $file
* @return string
* @return void
*/
add_filter('theme_file_path', function (string $path, string $file): string {
if ($file === 'theme.json') {
return public_path().'/build/assets/theme.json';
add_action('enqueue_block_assets', function () {
if (! is_admin() || ! get_current_screen()?->is_block_editor()) {
return;
}
return $path;
if (! Vite::isRunningHot()) {
return;
}
$script = sprintf(
<<<'JS'
window.__vite_client_url = '%s';
window.self !== window.top && document.head.appendChild(
Object.assign(document.createElement('script'), { type: 'module', src: '%s' })
);
JS,
untrailingslashit(Vite::asset('')),
Vite::asset('@vite/client')
);
wp_add_inline_script('wp-blocks', $script);
});
/**
* Use the generated theme.json file.
*
* @return string
*/
add_filter('theme_file_path', function ($path, $file) {
return $file === 'theme.json'
? public_path('build/assets/theme.json')
: $path;
}, 10, 2);
/**

View File

@@ -40,7 +40,7 @@
},
"require": {
"php": ">=8.2",
"roots/acorn": "v5.0.0-beta.2"
"roots/acorn": "dev-main"
},
"require-dev": {
"laravel/pint": "^1.13"

1874
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,11 +16,10 @@
"translate:mo": "wp i18n make-mo ./resources/lang ./resources/lang"
},
"devDependencies": {
"@tailwindcss/vite": "^4.0.0",
"@wordpress/dependency-extraction-webpack-plugin": "^6.15.0",
"autoprefixer": "^10.4.20",
"laravel-vite-plugin": "^1.1.1",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"tailwindcss": "^4.0.0",
"vite": "^6.0.7"
}
}

View File

@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@@ -1,3 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
@source "../views/";
@source "../../app/";

View File

@@ -0,0 +1 @@
@import "tailwindcss";

View File

@@ -1,4 +1,3 @@
import resolveConfig from 'tailwindcss/resolveConfig'
import {
defaultRequestToExternal,
defaultRequestToHandle,
@@ -121,6 +120,7 @@ export function wordpressPlugin() {
generateBundle() {
this.emitFile({
type: 'asset',
name: 'editor.deps.json',
fileName: 'editor.deps.json',
source: JSON.stringify([...dependencies]),
})
@@ -178,65 +178,126 @@ export function wordpressThemeJson({
disableTailwindFonts = false,
disableTailwindFontSizes = false,
}) {
function flattenColors(colors, prefix = '') {
return Object.entries(colors).reduce((acc, [name, value]) => {
const formattedName = name.charAt(0).toUpperCase() + name.slice(1)
if (typeof value === 'string') {
acc.push({
name: prefix ? `${prefix.charAt(0).toUpperCase() + prefix.slice(1)}-${formattedName}` : formattedName,
slug: prefix ? `${prefix}-${name}`.toLowerCase() : name.toLowerCase(),
color: value,
})
} else if (typeof value === 'object') {
acc.push(...flattenColors(value, name))
}
return acc
}, [])
}
const resolvedConfig = resolveConfig(tailwindConfig)
let cssContent = null
return {
name: 'wordpress-theme-json',
enforce: 'post',
transform(code, id) {
if (id.includes('app.css')) {
cssContent = code
}
return null
},
async generateBundle() {
if (!cssContent) {
return;
}
const baseThemeJson = JSON.parse(
fs.readFileSync(path.resolve('./theme.json'), 'utf8')
)
const themeMatch = cssContent.match(/@(?:layer\s+)?theme\s*{([^}]*)}/s)
if (!themeMatch) {
return;
}
const themeContent = themeMatch[1]
if (!themeContent.trim().startsWith(':root')) {
return;
}
const rootContent = themeContent.slice(themeContent.indexOf('{') + 1, themeContent.lastIndexOf('}'))
const colorVariables = {}
const colorVarRegex = /--color-([^:]+):\s*([^;}]+)[;}]?/g
let match
while ((match = colorVarRegex.exec(rootContent)) !== null) {
const [, name, value] = match
colorVariables[name] = value.trim()
}
const colors = []
Object.entries(colorVariables).forEach(([name, value]) => {
if (name.endsWith('-*')) return
if (name.includes('-')) {
const [colorName, shade] = name.split('-')
if (shade && !isNaN(shade)) {
colors.push({
name: `${colorName}-${shade}`,
slug: `${colorName}-${shade}`.toLowerCase(),
color: value,
})
} else {
colors.push({
name: name,
slug: name.toLowerCase(),
color: value,
})
}
} else {
colors.push({
name: name,
slug: name.toLowerCase(),
color: value,
})
}
})
const fontFamilies = []
const fontVarRegex = /--font-([^:]+):\s*([^;}]+)[;}]?/g
while ((match = fontVarRegex.exec(rootContent)) !== null) {
const [, name, value] = match
if (!name.includes('-feature-settings') && !name.includes('-variation-settings')) {
fontFamilies.push({
name: name,
slug: name.toLowerCase(),
fontFamily: value.trim(),
})
}
}
const fontSizes = []
const fontSizeVarRegex = /--text-([^:]+):\s*([^;}]+)[;}]?/g
while ((match = fontSizeVarRegex.exec(rootContent)) !== null) {
const [, name, value] = match
if (!name.includes('--line-height')) {
fontSizes.push({
name: name,
slug: name.toLowerCase(),
size: value.trim(),
})
}
}
const themeJson = {
__processed__: "This file was generated from the Vite build",
__processed__: "This file was generated from Tailwind v4 CSS variables",
...baseThemeJson,
settings: {
...baseThemeJson.settings,
...((!disableTailwindColors && resolvedConfig.theme?.colors && {
...((!disableTailwindColors && colors.length > 0) && {
color: {
...baseThemeJson.settings?.color,
palette: flattenColors(resolvedConfig.theme.colors),
palette: colors,
},
}) || {}),
...((!disableTailwindFonts && resolvedConfig.theme?.fontFamily && {
}),
...((!disableTailwindFonts && fontFamilies.length > 0) && {
typography: {
...baseThemeJson.settings?.typography,
fontFamilies: Object.entries(resolvedConfig.theme.fontFamily)
.map(([name, value]) => ({
name,
slug: name,
fontFamily: Array.isArray(value) ? value.join(',') : value,
})),
fontFamilies,
},
}) || {}),
...((!disableTailwindFontSizes && resolvedConfig.theme?.fontSize && {
}),
...((!disableTailwindFontSizes && fontSizes.length > 0) && {
typography: {
...baseThemeJson.settings?.typography,
fontSizes: Object.entries(resolvedConfig.theme.fontSize)
.map(([name, value]) => ({
name,
slug: name,
size: Array.isArray(value) ? value[0] : value,
})),
fontSizes,
},
}) || {}),
}),
},
}

View File

@@ -3,3 +3,43 @@ import domReady from '@wordpress/dom-ready';
domReady(() => {
// DOM has been loaded
});
if (import.meta.hot) {
import.meta.hot.on('vite:beforeUpdate', (payload) => {
const cssUpdates = payload.updates.filter(update => update.type === 'css-update');
if (cssUpdates.length > 0) {
const update = cssUpdates[0];
// Find the iframe
const editorIframe = document.querySelector('iframe[name="editor-canvas"]');
if (!editorIframe?.contentDocument) {
window.location.reload();
return;
}
// Find the existing style tag in the iframe
const styles = editorIframe.contentDocument.getElementsByTagName('style');
let editorStyle = null;
for (const style of styles) {
if (style.textContent.includes('editor.css')) {
editorStyle = style;
break;
}
}
if (!editorStyle) {
window.location.reload();
return;
}
// Update the style content with new import and cache-busting timestamp
const timestamp = Date.now();
editorStyle.textContent = `@import url('${window.__vite_client_url}${update.path}?t=${timestamp}')`;
return;
}
// For non-CSS updates, reload
window.location.reload();
});
}

View File

@@ -5,6 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
@php(do_action('get_header'))
@php(wp_head())
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body @php(body_class())>

View File

@@ -1,12 +0,0 @@
/** @type {import('tailwindcss').Config} config */
const config = {
content: ['./app/**/*.php', './resources/**/*.{php,vue,js}'],
theme: {
extend: {
colors: {}, // Extend Tailwind's default colors
},
},
plugins: [],
};
export default config;

View File

@@ -1,15 +1,16 @@
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite';
import laravel from 'laravel-vite-plugin'
import {
wordpressPlugin,
wordpressRollupPlugin,
wordpressThemeJson,
} from './resources/js/build/wordpress'
import tailwindConfig from './tailwind.config.js'
export default defineConfig({
base: '/app/themes/sage/public/build/',
plugins: [
tailwindcss(),
laravel({
input: [
'resources/css/app.css',
@@ -26,7 +27,6 @@ export default defineConfig({
// Generate the theme.json file in the public/build/assets directory
// based on the Tailwind config and the theme.json file from base theme folder
wordpressThemeJson({
tailwindConfig,
disableTailwindColors: false,
disableTailwindFonts: false,
disableTailwindFontSizes: false,