Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"@vitejs/plugin-react": "$@vitejs/plugin-react"
},
"patchedDependencies": {
"@remix-run/dev": "patches/@remix-run__dev.patch"
"@remix-run/dev": "patches/@remix-run__dev.patch",
"@tailwindcss/vite": "patches/@tailwindcss__vite.patch"
}
}
}
1 change: 1 addition & 0 deletions packages/react-server/examples/tailwind-v4/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tailwind v4
11 changes: 11 additions & 0 deletions packages/react-server/examples/tailwind-v4/app/_action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use server";

let count = 0;

export async function changeCount() {
count++;
}

export function getCount() {
return count;
}
9 changes: 9 additions & 0 deletions packages/react-server/examples/tailwind-v4/app/_client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use client";

import React from "react";

export function TestClient() {
const [count, setCount] = React.useState(0);

return <button onClick={() => setCount(count + 1)}>Client: {count}</button>;
}
13 changes: 13 additions & 0 deletions packages/react-server/examples/tailwind-v4/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type React from "react";
import "./styles.css";

export default function Layout(props: React.PropsWithChildren) {
return (
<html>
<body>
<div>[Layout]</div>
{props.children}
</body>
</html>
);
}
14 changes: 14 additions & 0 deletions packages/react-server/examples/tailwind-v4/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { changeCount, getCount } from "./_action";
import { TestClient } from "./_client";

export default function Page() {
return (
<>
<div>[Page]</div>
<TestClient />
<form action={changeCount}>
<button>Action: {getCount()}</button>
</form>
</>
);
}
1 change: 1 addition & 0 deletions packages/react-server/examples/tailwind-v4/app/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "tailwindcss";
25 changes: 25 additions & 0 deletions packages/react-server/examples/tailwind-v4/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@hiogawa/react-server-example-tailwind-v4",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@hiogawa/react-server": "workspace:*",
"next": "link:../../../react-server-next",
"react": "latest",
"react-dom": "latest",
"react-server-dom-webpack": "latest"
},
"devDependencies": {
"@tailwindcss/vite": "4.0.6",
"@types/react": "latest",
"@types/react-dom": "latest",
"tailwindcss": "4.0.6",
"vite": "latest"
}
}
17 changes: 17 additions & 0 deletions packages/react-server/examples/tailwind-v4/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"include": ["**/*.ts", "**/*.tsx"],
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ESNext",
"lib": ["ESNext", "DOM"],
"types": ["next"],
"jsx": "react-jsx"
}
}
7 changes: 7 additions & 0 deletions packages/react-server/examples/tailwind-v4/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import tailwindcss from "@tailwindcss/vite";
import next from "next/vite";
import { defineConfig } from "vite";

export default defineConfig({
plugins: [next(), tailwindcss()],
});
4 changes: 3 additions & 1 deletion packages/react-server/src/features/assets/shared.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export const SERVER_CSS_PROXY = "virtual:server-css-proxy.js";
export const DEV_SSR_CSS = "virtual:dev-ssr-css.css";
// use a magic word `/.vite/` to exclude the virtual from tailwind v4's waitForRequestsIdle
// https://github.com/tailwindlabs/tailwindcss/blob/d684733d804a0b8951d13c94fe27350271e076b6/packages/%40tailwindcss-vite/src/index.ts#L313-L322
export const DEV_SSR_CSS = "virtual:dev-ssr-css/.vite/.css";
7 changes: 7 additions & 0 deletions patches/@tailwindcss__vite.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
diff --git a/dist/index.mjs b/dist/index.mjs
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since xxx.css and xxx.css?direct seem to also get in dead lock, we might need to add ?direct

https://github.com/tailwindlabs/tailwindcss/blob/7d51e38d8c4a5cba20face7384be9629c9dcc3c8/packages/%40tailwindcss-vite/src/index.ts#L10

- const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url)\b/
+ const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url|direct)\b/

index b40b7c0209b0691f1e4dd21a762518d17b509d79..6cf03575835afbc63497d611ea2c040d2a5e06ab 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -1 +1 @@
-var U=(s,e)=>(e=Symbol[s])?e:Symbol.for("Symbol."+s),A=s=>{throw TypeError(s)};var R=(s,e,n)=>{if(e!=null){typeof e!="object"&&typeof e!="function"&&A("Object expected");var i,d;n&&(i=e[U("asyncDispose")]),i===void 0&&(i=e[U("dispose")],n&&(d=i)),typeof i!="function"&&A("Object not disposable"),d&&(i=function(){try{d.call(this)}catch(h){return Promise.reject(h)}}),s.push([n,i,e])}else n&&s.push([n]);return e},y=(s,e,n)=>{var i=typeof SuppressedError=="function"?SuppressedError:function(o,p,c,g){return g=Error(c),g.name="SuppressedError",g.error=o,g.suppressed=p,g},d=o=>e=n?new i(o,e,"An error was suppressed during disposal"):(n=!0,o),h=o=>{for(;o=s.pop();)try{var p=o[1]&&o[1].call(o[2]);if(o[0])return Promise.resolve(p).then(h,c=>(d(c),h()))}catch(c){d(c)}if(n)throw e};return h()};import{compile as L,env as N,Features as C,Instrumentation as F,normalizePath as G}from"@tailwindcss/node";import{clearRequireCache as O}from"@tailwindcss/node/require-cache";import{Scanner as T}from"@tailwindcss/oxide";import{Features as P,transform as $}from"lightningcss";import q from"node:fs/promises";import S from"node:path";var v=N.DEBUG,z=/[?&](?:worker|sharedworker|raw|url)\b/,J=/\?commonjs-proxy/,K=/[?&]index\=\d+\.css$/,W=["tailwind-merge"];function j(){let s=[],e=null,n=!1,i=!1,d=new E(()=>new Set),h=new T({}),o=new E(t=>{let r=e.createResolver({...e.resolve,extensions:[".css"],mainFields:["style"],conditions:["style","development|production"],tryIndex:!1,preferRelative:!0});function l(m,f){return r(m,f,!0,n)}let u=e.createResolver(e.resolve);function a(m,f){return u(m,f,!0,n)}return new M(t,()=>d,e.base,l,a)});function p(t,r,l){for(let a of W)if(t.includes(`.vite/deps/${a}.js`)||t.includes(`/node_modules/${a}/`))return;let u=!1;for(let a of h.scanFiles([{content:r,extension:l}]))u=!0,d.get(t).add(a);u&&c()}function c(){for(let t of s){let r=[];for(let[l]of o.entries()){let u=t.moduleGraph.getModuleById(l);u&&(o.get(l).requiresRebuild=!1,t.moduleGraph.invalidateModule(u),r.push({type:`${u.type}-update`,path:u.url,acceptedPath:u.url,timestamp:Date.now()}))}r.length>0&&t.hot.send({type:"update",updates:r})}}async function g(t,r,l){let u=t.lastContent,a=await t.generate(u,r,l);if(a===!1)return;v&&l.start("Optimize CSS");let m=k(a,{minify:i});return v&&l.end("Optimize CSS"),m}async function b(t,r,l){let u={...t,getCombinedSourcemap:()=>{throw new Error("getCombinedSourcemap not implemented")}};for(let a of e.plugins){if(!a.transform||a.name.startsWith("@tailwindcss/"))continue;if(a.name.startsWith("vite:")&&a.name!=="vite:css"&&a.name!=="vite:css-post"&&a.name!=="vite:vue")continue;if(a.name==="ssr-styles")continue;let m="handler"in a.transform?a.transform.handler:a.transform;try{let f=await m.call(u,l,r);if(!f)continue;typeof f=="string"?l=f:f.code&&(l=f.code)}catch{console.error(`Error running ${a.name} on Tailwind CSS output. Skipping.`)}}return l}return[{name:"@tailwindcss/vite:scan",enforce:"pre",configureServer(t){s.push(t)},async configResolved(t){e=t,i=e.build.cssMinify!==!1,n=e.build.ssr!==!1&&e.build.ssr!==void 0},transformIndexHtml(t,{path:r}){r&&p(r,t,"html")},transform(t,r,l){let u=V(r);I(r)||p(r,t,u)}},{name:"@tailwindcss/vite:generate:serve",apply:"serve",enforce:"pre",async transform(t,r,l){var f=[];try{if(!I(r))return;let u=R(f,new F);u.start("[@tailwindcss/vite] Generate CSS (serve)");let a=o.get(r);l?.ssr||await Promise.all(s.map(_=>_.waitForRequestsIdle(r)));let m=await a.generate(t,_=>this.addWatchFile(_),u);if(!m)return o.delete(r),t;return{code:m}}catch(w){var x=w,D=!0}finally{y(f,x,D)}}},{name:"@tailwindcss/vite:generate:build",apply:"build",enforce:"pre",async transform(t,r){var m=[];try{if(!I(r))return;let l=R(m,new F);l.start("[@tailwindcss/vite] Generate CSS (build)");let u=o.get(r);let a=await u.generate(t,D=>this.addWatchFile(D),l);if(!a)return o.delete(r),t;return{code:a}}catch(f){var w=f,x=!0}finally{y(m,w,x)}},async renderStart(){var r=[];try{let t=R(r,new F);t.start("[@tailwindcss/vite] (render start)");for(let[m,f]of o.entries()){let w=await g(f,()=>{},t);if(!w){o.delete(m);continue}await b(this,m,w)}}catch(l){var u=l,a=!0}finally{y(r,u,a)}}}]}function V(s){let[e]=s.split("?",2);return S.extname(e).slice(1)}function I(s){return s.includes("/.vite/")?void 0:(V(s)==="css"||s.includes("&lang.css")||s.match(K))&&!z.test(s)&&!J.test(s)}function k(s,{file:e="input.css",minify:n=!1}={}){function i(d){return $({filename:e,code:d,minify:n,sourceMap:!1,drafts:{customMedia:!0},nonStandard:{deepSelectorCombinator:!0},include:P.Nesting,exclude:P.LogicalProperties|P.DirSelector|P.LightDark,targets:{safari:16<<16|1024,ios_saf:16<<16|1024,firefox:8388608,chrome:7274496},errorRecovery:!0}).code}return i(i(Buffer.from(s))).toString()}function B(s){return S.resolve(s.replace(/\?.*$/,""))}var E=class extends Map{constructor(n){super();this.factory=n}get(n){let i=super.get(n);return i===void 0&&(i=this.factory(n,this),this.set(n,i)),i}},M=class{constructor(e,n,i,d,h){this.id=e;this.getSharedCandidates=n;this.base=i;this.customCssResolver=d;this.customJsResolver=h}lastContent="";compiler;requiresRebuild=!0;scanner;candidates=new Set;dependencies=new Set;basePath=null;overwriteCandidates=null;async generate(e,n,i){this.lastContent=e;let d=B(this.id),h=S.dirname(S.resolve(d));if(!this.compiler||!this.scanner||this.requiresRebuild){O(Array.from(this.dependencies)),this.dependencies=new Set([B(d)]),v&&i.start("Setup compiler"),this.compiler=await L(e,{base:h,shouldRewriteUrls:!0,onDependency:c=>{n(c),this.dependencies.add(c)},customCssResolver:this.customCssResolver,customJsResolver:this.customJsResolver}),v&&i.end("Setup compiler");let p=(this.compiler.root==="none"?[]:this.compiler.root===null?[]:[this.compiler.root]).concat(this.compiler.globs);this.scanner=new T({sources:p})}if(!(this.compiler.features&(C.AtApply|C.JsPluginCompat|C.ThemeFunction|C.Utilities)))return!1;if(!this.overwriteCandidates||this.compiler.features&C.Utilities){v&&i.start("Scan for candidates");for(let p of this.scanner.scan())this.candidates.add(p);v&&i.end("Scan for candidates")}if(this.compiler.features&C.Utilities){for(let p of this.scanner.files)n(p);for(let p of this.scanner.globs){if(p.pattern[0]==="!")continue;let c=S.relative(this.base,p.base);c[0]!=="."&&(c="./"+c),c=G(c),n(S.posix.join(c,p.pattern));let g=this.compiler.root;if(g!=="none"&&g!==null){let b=G(S.resolve(g.base,g.pattern));if(!await q.stat(b).then(r=>r.isDirectory(),()=>!1))throw new Error(`The path given to \`source(\u2026)\` must be a directory but got \`source(${b})\` instead.`);this.basePath=b}else g===null&&(this.basePath=null)}}this.requiresRebuild=!0,v&&i.start("Build CSS");let o=this.compiler.build(this.overwriteCandidates?this.overwriteCandidates:[...this.sharedCandidates(),...this.candidates]);return v&&i.end("Build CSS"),o}sharedCandidates(){if(!this.compiler)return new Set;if(this.compiler.root==="none")return new Set;let e=/^[A-Z]:/,n=d=>this.basePath===null||d.startsWith(this.basePath)?!0:e.test(d)?!1:!d.startsWith("/"),i=new Set;for(let[d,h]of this.getSharedCandidates())if(n(d))for(let o of h)i.add(o);return i}};export{j as default};
+var U=(s,e)=>(e=Symbol[s])?e:Symbol.for("Symbol."+s),A=s=>{throw TypeError(s)};var R=(s,e,n)=>{if(e!=null){typeof e!="object"&&typeof e!="function"&&A("Object expected");var i,d;n&&(i=e[U("asyncDispose")]),i===void 0&&(i=e[U("dispose")],n&&(d=i)),typeof i!="function"&&A("Object not disposable"),d&&(i=function(){try{d.call(this)}catch(h){return Promise.reject(h)}}),s.push([n,i,e])}else n&&s.push([n]);return e},y=(s,e,n)=>{var i=typeof SuppressedError=="function"?SuppressedError:function(o,p,c,g){return g=Error(c),g.name="SuppressedError",g.error=o,g.suppressed=p,g},d=o=>e=n?new i(o,e,"An error was suppressed during disposal"):(n=!0,o),h=o=>{for(;o=s.pop();)try{var p=o[1]&&o[1].call(o[2]);if(o[0])return Promise.resolve(p).then(h,c=>(d(c),h()))}catch(c){d(c)}if(n)throw e};return h()};import{compile as L,env as N,Features as C,Instrumentation as F,normalizePath as G}from"@tailwindcss/node";import{clearRequireCache as O}from"@tailwindcss/node/require-cache";import{Scanner as T}from"@tailwindcss/oxide";import{Features as P,transform as $}from"lightningcss";import q from"node:fs/promises";import S from"node:path";var v=N.DEBUG,z=/[?&](?:worker|sharedworker|raw|url|direct)\b/,J=/\?commonjs-proxy/,K=/[?&]index\=\d+\.css$/,W=["tailwind-merge"];function j(){let s=[],e=null,n=!1,i=!1,d=new E(()=>new Set),h=new T({}),o=new E(t=>{let r=e.createResolver({...e.resolve,extensions:[".css"],mainFields:["style"],conditions:["style","development|production"],tryIndex:!1,preferRelative:!0});function l(m,f){return r(m,f,!0,n)}let u=e.createResolver(e.resolve);function a(m,f){return u(m,f,!0,n)}return new M(t,()=>d,e.base,l,a)});function p(t,r,l){for(let a of W)if(t.includes(`.vite/deps/${a}.js`)||t.includes(`/node_modules/${a}/`))return;let u=!1;for(let a of h.scanFiles([{content:r,extension:l}]))u=!0,d.get(t).add(a);u&&c()}function c(){for(let t of s){let r=[];for(let[l]of o.entries()){let u=t.moduleGraph.getModuleById(l);u&&(o.get(l).requiresRebuild=!1,t.moduleGraph.invalidateModule(u),r.push({type:`${u.type}-update`,path:u.url,acceptedPath:u.url,timestamp:Date.now()}))}r.length>0&&t.hot.send({type:"update",updates:r})}}async function g(t,r,l){let u=t.lastContent,a=await t.generate(u,r,l);if(a===!1)return;v&&l.start("Optimize CSS");let m=k(a,{minify:i});return v&&l.end("Optimize CSS"),m}async function b(t,r,l){let u={...t,getCombinedSourcemap:()=>{throw new Error("getCombinedSourcemap not implemented")}};for(let a of e.plugins){if(!a.transform||a.name.startsWith("@tailwindcss/"))continue;if(a.name.startsWith("vite:")&&a.name!=="vite:css"&&a.name!=="vite:css-post"&&a.name!=="vite:vue")continue;if(a.name==="ssr-styles")continue;let m="handler"in a.transform?a.transform.handler:a.transform;try{let f=await m.call(u,l,r);if(!f)continue;typeof f=="string"?l=f:f.code&&(l=f.code)}catch{console.error(`Error running ${a.name} on Tailwind CSS output. Skipping.`)}}return l}return[{name:"@tailwindcss/vite:scan",enforce:"pre",configureServer(t){s.push(t)},async configResolved(t){e=t,i=e.build.cssMinify!==!1,n=e.build.ssr!==!1&&e.build.ssr!==void 0},transformIndexHtml(t,{path:r}){r&&p(r,t,"html")},transform(t,r,l){let u=V(r);I(r)||p(r,t,u)}},{name:"@tailwindcss/vite:generate:serve",apply:"serve",enforce:"pre",async transform(t,r,l){var f=[];try{if(!I(r))return;let u=R(f,new F);u.start("[@tailwindcss/vite] Generate CSS (serve)");let a=o.get(r);l?.ssr||await Promise.all(s.map(_=>_.waitForRequestsIdle(r)));let m=await a.generate(t,_=>this.addWatchFile(_),u);if(!m)return o.delete(r),t;return{code:m}}catch(w){var x=w,D=!0}finally{y(f,x,D)}}},{name:"@tailwindcss/vite:generate:build",apply:"build",enforce:"pre",async transform(t,r){var m=[];try{if(!I(r))return;let l=R(m,new F);l.start("[@tailwindcss/vite] Generate CSS (build)");let u=o.get(r);let a=await u.generate(t,D=>this.addWatchFile(D),l);if(!a)return o.delete(r),t;return{code:a}}catch(f){var w=f,x=!0}finally{y(m,w,x)}},async renderStart(){var r=[];try{let t=R(r,new F);t.start("[@tailwindcss/vite] (render start)");for(let[m,f]of o.entries()){let w=await g(f,()=>{},t);if(!w){o.delete(m);continue}await b(this,m,w)}}catch(l){var u=l,a=!0}finally{y(r,u,a)}}}]}function V(s){let[e]=s.split("?",2);return S.extname(e).slice(1)}function I(s){return s.includes("/.vite/")?void 0:(V(s)==="css"||s.includes("&lang.css")||s.match(K))&&!z.test(s)&&!J.test(s)}function k(s,{file:e="input.css",minify:n=!1}={}){function i(d){return $({filename:e,code:d,minify:n,sourceMap:!1,drafts:{customMedia:!0},nonStandard:{deepSelectorCombinator:!0},include:P.Nesting,exclude:P.LogicalProperties|P.DirSelector|P.LightDark,targets:{safari:16<<16|1024,ios_saf:16<<16|1024,firefox:8388608,chrome:7274496},errorRecovery:!0}).code}return i(i(Buffer.from(s))).toString()}function B(s){return S.resolve(s.replace(/\?.*$/,""))}var E=class extends Map{constructor(n){super();this.factory=n}get(n){let i=super.get(n);return i===void 0&&(i=this.factory(n,this),this.set(n,i)),i}},M=class{constructor(e,n,i,d,h){this.id=e;this.getSharedCandidates=n;this.base=i;this.customCssResolver=d;this.customJsResolver=h}lastContent="";compiler;requiresRebuild=!0;scanner;candidates=new Set;dependencies=new Set;basePath=null;overwriteCandidates=null;async generate(e,n,i){this.lastContent=e;let d=B(this.id),h=S.dirname(S.resolve(d));if(!this.compiler||!this.scanner||this.requiresRebuild){O(Array.from(this.dependencies)),this.dependencies=new Set([B(d)]),v&&i.start("Setup compiler"),this.compiler=await L(e,{base:h,shouldRewriteUrls:!0,onDependency:c=>{n(c),this.dependencies.add(c)},customCssResolver:this.customCssResolver,customJsResolver:this.customJsResolver}),v&&i.end("Setup compiler");let p=(this.compiler.root==="none"?[]:this.compiler.root===null?[]:[this.compiler.root]).concat(this.compiler.globs);this.scanner=new T({sources:p})}if(!(this.compiler.features&(C.AtApply|C.JsPluginCompat|C.ThemeFunction|C.Utilities)))return!1;if(!this.overwriteCandidates||this.compiler.features&C.Utilities){v&&i.start("Scan for candidates");for(let p of this.scanner.scan())this.candidates.add(p);v&&i.end("Scan for candidates")}if(this.compiler.features&C.Utilities){for(let p of this.scanner.files)n(p);for(let p of this.scanner.globs){if(p.pattern[0]==="!")continue;let c=S.relative(this.base,p.base);c[0]!=="."&&(c="./"+c),c=G(c),n(S.posix.join(c,p.pattern));let g=this.compiler.root;if(g!=="none"&&g!==null){let b=G(S.resolve(g.base,g.pattern));if(!await q.stat(b).then(r=>r.isDirectory(),()=>!1))throw new Error(`The path given to \`source(\u2026)\` must be a directory but got \`source(${b})\` instead.`);this.basePath=b}else g===null&&(this.basePath=null)}}this.requiresRebuild=!0,v&&i.start("Build CSS");let o=this.compiler.build(this.overwriteCandidates?this.overwriteCandidates:[...this.sharedCandidates(),...this.candidates]);return v&&i.end("Build CSS"),o}sharedCandidates(){if(!this.compiler)return new Set;if(this.compiler.root==="none")return new Set;let e=/^[A-Z]:/,n=d=>this.basePath===null||d.startsWith(this.basePath)?!0:e.test(d)?!1:!d.startsWith("/"),i=new Set;for(let[d,h]of this.getSharedCandidates())if(n(d))for(let o of h)i.add(o);return i}};export{j as default};
Loading