Tailwind v4 support - #3
Conversation
WalkthroughReplaces the Tailwind v3 TypeScript preset with a PostCSS + Tailwind v4 CSS-processor pipeline, adds a public Changes
Sequence Diagram(s)sequenceDiagram
participant Caller
participant Render as render()
participant Parse as parseStyles()
participant CSSProc as cssProcessor (PostCSS + Tailwind v4)
participant Inliner as juice.inlineContent
participant Output as HTML
Caller->>Render: render(component, { cssProcessor })
Render->>Parse: parseStyles(style, cssProcessor)
alt cssProcessor provided
Parse->>CSSProc: run PostCSS pipeline (Tailwind, custom-props, calc, logical, preset-env)
CSSProc-->>Parse: processed CSS
else no cssProcessor
Parse-->>Render: normalized original CSS
end
Render->>Inliner: inlineContent(html, processedCSS)
Inliner-->>Render: inlined HTML
Render-->>Output: final HTML
sequenceDiagram
participant CLI
participant Builder as build.ts
participant Esbuild
participant Artifact as bundle outputs (metafile)
participant NodeAPI as toHTML()
CLI->>Builder: build(emailFilesPath, outdir, externalsCSV)
Builder->>Esbuild: bundle(entryPoints, cwd, outdir, externals[])
Esbuild-->>Artifact: build outputs + metafile
NodeAPI->>Artifact: locate output by metafile.entryPoint
NodeAPI->>NodeAPI: dynamic import(resolvedBundlePath)
NodeAPI-->>Caller: renderToHtml(..., externals: externals[])
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
…ssProcessor implementation
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
projects/angular-email/bin/build.ts (1)
52-59: Critical: Use of Dirent.parentPath requires Node v20.12.0+The code uses
Dirent.parentPathwhich was added in Node v20.12.0. According to the PR comments, there's been discussion about whether this is compatible with the project's Node version requirements.Let me verify the Node version requirements for this project:
#!/bin/bash # Check Node version requirements in the project echo "Checking .nvmrc:" cat .nvmrc 2>/dev/null || echo "No .nvmrc found" echo -e "\nChecking engines in package.json:" jq '.engines' package.json 2>/dev/null || echo "No engines specified" echo -e "\nChecking engines in projects/angular-email/package.json:" jq '.engines' projects/angular-email/package.json 2>/dev/null || echo "No engines specified" echo -e "\nChecking CI workflows for Node version:" fd -e yml -e yaml . .github/workflows --exec grep -l "node-version" {} \; | head -5 | xargs grep -A2 -B2 "node-version"Based on the PR discussion, if the project targets Node v22.x (lts/jod), then
Dirent.parentPathis valid. However, for better compatibility, consider the Node-version-agnostic approach suggested in the PR comments.projects/angular-email/README.md (1)
57-62: Resolved: shell export syntax and quoting look correct.Previous feedback about using valid export syntax and quoting expansions has been addressed. This block is now copy‑paste safe.
README.md (1)
57-62: Resolved: shell export syntax and quoting are correct.This addresses the earlier issue; the example is now POSIX‑friendly.
🧹 Nitpick comments (13)
package.json (1)
10-10: Long externals list in test script.The render:test script has a very long list of externals. Consider using a configuration file or environment variable to manage these.
You could create a
.envfile or configuration to manage the externals list:- "render:test": "keycloakify-angular-email build -p projects/showcase/emails -o dist/emails -e tailwindcss,@tailwindcss/postcss,postcss,postcss-calc,postcss-custom-properties,postcss-preset-env,postcss-logical", + "render:test": "keycloakify-angular-email build -p projects/showcase/emails -o dist/emails -e \"$RENDER_TEST_EXTERNALS\"",Or use a config file approach for better maintainability.
projects/angular-email/tailwindcss-preset-email/css-processor/css-processor.ts (1)
34-40: Consider removing commented code.There's commented-out code that appears to be the old implementation. Since it's been replaced with a better solution, it should be removed for cleaner code.
- // const match = decl.value.match(/var\((--tw-[^,)]+)\)/); - // if (match) { - // const varName = match[1]; - // if (defaultVars.has(varName)) { - // decl.value = decl.value.replace(match[0], defaultVars.get(varName)!); - // } - // }projects/angular-email/bin/build.ts (1)
52-62: Consider the Node-version-agnostic approach for recursiveGetFiles.As discussed in the PR comments, you could make this function work across all Node versions by passing the current directory and building full paths:
- const recursiveGetFiles = async (path: string): Promise<Dirent[]> => { + const recursiveGetFiles = async (path: string): Promise<string[]> => { const items = await readdir(path, { withFileTypes: true }); - const dirents: Dirent[] = []; + const files: string[] = []; for (const item of items) { - if (item.isFile()) dirents.push(item); + if (item.isFile()) { + files.push(join(path, item.name)); + } else if (item.isDirectory()) { - dirents.push(...(await recursiveGetFiles(join(item.parentPath, item.name)))); + files.push(...(await recursiveGetFiles(join(path, item.name)))); } } - return dirents; + return files; }; // Read all items in the directory - const items = (await recursiveGetFiles(dirPath)) - .map((file) => join(file.parentPath, file.name)) + const items = (await recursiveGetFiles(dirPath)) .filter((filePath) => {This approach works with all Node versions and is more straightforward.
projects/angular-email/README.md (5)
17-21: Polish the compatibility table header (“Tailwind CSS”).Use the proper product name for consistency across docs.
-| Angular | tailwindcss | @keycloakify/angular-email | Maintained | +| Angular | Tailwind CSS | @keycloakify/angular-email | Maintained |
127-139: Make the externals array copy‑paste safe (remove placeholder string).The placeholder string inside externals will break copy‑paste. Provide a realistic list users can install and externalize.
esbuild: { packages: 'bundle', - external: ['juice', '...other packages you might use to process css'], + external: [ + 'juice', + 'postcss', + 'postcss-calc', + 'postcss-custom-properties', + 'postcss-preset-env', + 'postcss-logical' + ], format: 'esm', outExtension: { '.js': '.mjs' }, plugins: [angularEsbuildPlugin(join(import.meta.dirname, 'emails'))], },Note: add any additional runtime processors you actually use. If externalized, they must be present in dependencies at runtime.
159-160: Provide a non-empty externals example to reduce confusion.An empty array is technically valid but unhelpful. Suggest showing at least one common external (juice).
- externals: [], + externals: ['juice'],
213-215: Fix grammar/capitalization in cssProcessor doc.Small clarity tweak.
- /** Optional hook for manipulate the css extracted. Useful for PostCSS processing */ + /** Optional hook to manipulate the extracted CSS. Useful for PostCSS processing. */
250-289: Clarify preset import expectations and confirm new flags are documented.
- Suggest adding one sentence after the CSS import code block clarifying that the preset export is CSS and requires a bundler/postcss pipeline that supports @import.
- The PR objectives mention a withTailwind boolean. If that flag exists in the public API, document its purpose and interaction with cssProcessor here for discoverability.
Would you like me to draft the added sentence and an example snippet that shows withTailwind usage alongside cssProcessor (if applicable)?
README.md (5)
17-21: Polish the compatibility table header (“Tailwind CSS”).Use consistent branding.
-| Angular | tailwindcss | @keycloakify/angular-email | Maintained | +| Angular | Tailwind CSS | @keycloakify/angular-email | Maintained |
127-139: Replace placeholder in externals with concrete package names.Improves copy‑paste reliability.
esbuild: { packages: 'bundle', - external: ['juice', '...other packages you might use to process css'], + external: [ + 'juice', + 'postcss', + 'postcss-calc', + 'postcss-custom-properties', + 'postcss-preset-env', + 'postcss-logical' + ], format: 'esm', outExtension: { '.js': '.mjs' }, plugins: [angularEsbuildPlugin(join(import.meta.dirname, 'emails'))], },
159-160: Show a minimal but realistic externals value in toHTML examples.Provide at least one typical external (juice) to guide users.
- externals: [], + externals: ['juice'],Also applies to: 179-180
213-215: Tighten wording for cssProcessor in API docs.Grammar + capitalization.
- /** Optional hook for manipulate the css extracted. Useful for PostCSS processing */ + /** Optional hook to manipulate the extracted CSS. Useful for PostCSS processing. */
250-289: Preset usage: add a brief note about bundler support and confirm withTailwind docs.
- After the CSS import example, add a short note that the preset is CSS (not JS) and requires an @import-capable pipeline (e.g., PostCSS with postcss-import or Tailwind v4 runner).
- If the public API includes a withTailwind boolean as per the PR objectives, add a subsection explaining when to set it relative to cssProcessor and Angular SSR.
Happy to draft the additional paragraph and example if you confirm the flag’s API shape.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (7)
README.md(7 hunks)package.json(3 hunks)projects/angular-email/README.md(7 hunks)projects/angular-email/bin/build.ts(7 hunks)projects/angular-email/node/index.ts(2 hunks)projects/angular-email/tailwindcss-preset-email/css-processor/css-processor.ts(1 hunks)scripts/build.sh(1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: luca-peruzzo
PR: keycloakify/angular-email#3
File: projects/angular-email/src/lib/render.ts:58-60
Timestamp: 2025-08-25T12:21:04.047Z
Learning: In Angular SSR context with Tailwind v4, distinguish between processing complete CSS from Angular components (which only needs PostCSS transforms like custom properties/calc) vs generating Tailwind utilities (which requires content scanning or source directives).
Learnt from: luca-peruzzo
PR: keycloakify/angular-email#3
File: projects/angular-email/src/lib/render.ts:58-60
Timestamp: 2025-08-25T12:15:21.378Z
Learning: In Angular SSR context with Tailwind v4, distinguish between processing complete CSS from Angular components (which only needs PostCSS transforms like custom properties/calc) vs generating Tailwind utilities (which requires content scanning or source directives).
Learnt from: luca-peruzzo
PR: keycloakify/angular-email#3
File: scripts/refresh-lib.sh:17-17
Timestamp: 2025-08-26T11:04:57.476Z
Learning: The user luca-peruzzo prefers not to fix minor technical issues in internal-only scripts if they're already working, even if the fix would make them more standards-compliant.
📚 Learning: 2025-08-26T11:02:51.950Z
Learnt from: luca-peruzzo
PR: keycloakify/angular-email#3
File: package.json:70-70
Timestamp: 2025-08-26T11:02:51.950Z
Learning: The `$` prefix syntax in npm overrides (e.g., `"esbuild": "$esbuild"`) is valid npm syntax that allows overrides to be defined as a reference to a spec for a direct dependency. This is documented in the npm v9+ documentation and allows overrides to reference the same version specification as defined in the dependencies section.
Applied to files:
package.json
📚 Learning: 2025-08-25T12:21:04.047Z
Learnt from: luca-peruzzo
PR: keycloakify/angular-email#3
File: projects/angular-email/src/lib/render.ts:58-60
Timestamp: 2025-08-25T12:21:04.047Z
Learning: In Angular SSR context with Tailwind v4, distinguish between processing complete CSS from Angular components (which only needs PostCSS transforms like custom properties/calc) vs generating Tailwind utilities (which requires content scanning or source directives).
Applied to files:
projects/angular-email/tailwindcss-preset-email/css-processor/css-processor.tsprojects/angular-email/node/index.tsREADME.mdprojects/angular-email/README.md
📚 Learning: 2025-08-25T12:15:21.378Z
Learnt from: luca-peruzzo
PR: keycloakify/angular-email#3
File: projects/angular-email/src/lib/render.ts:58-60
Timestamp: 2025-08-25T12:15:21.378Z
Learning: In Angular SSR context with Tailwind v4, distinguish between processing complete CSS from Angular components (which only needs PostCSS transforms like custom properties/calc) vs generating Tailwind utilities (which requires content scanning or source directives).
Applied to files:
projects/angular-email/tailwindcss-preset-email/css-processor/css-processor.tsprojects/angular-email/node/index.tsREADME.mdprojects/angular-email/README.md
📚 Learning: 2025-08-25T12:37:26.334Z
Learnt from: luca-peruzzo
PR: keycloakify/angular-email#3
File: projects/angular-email/tailwindcss-preset-email/mso.css:57-61
Timestamp: 2025-08-25T12:37:26.334Z
Learning: When CodeRabbit's internal static analysis tools flag syntax errors in files using newer CSS features (like Tailwind v4's utility DSL), first verify if the project actually uses those tools before suggesting configuration changes. Users should only need to fix linting issues for tools they're actually using in their project.
Applied to files:
projects/angular-email/node/index.ts
📚 Learning: 2025-08-25T12:36:48.452Z
Learnt from: luca-peruzzo
PR: keycloakify/angular-email#3
File: projects/angular-email/tailwindcss-preset-email/clients.css:0-0
Timestamp: 2025-08-25T12:36:48.452Z
Learning: In the Angular email project's Tailwind preset, the variant ox uses the pattern (&[class^="ox-"]) where the & is at the beginning, indicating the styled element itself should have a class starting with "ox-". This pattern was established in the original tailwindcss-email-variants.ts file and should be maintained when migrating from v3 to v4.
Applied to files:
README.mdprojects/angular-email/README.md
🪛 LanguageTool
README.md
[grammar] ~17-~17: There might be a mistake here.
Context: ...cloakify/angular-email | Maintained | | ------- | ----------- | --------------...
(QB_NEW_EN)
[grammar] ~18-~18: There might be a mistake here.
Context: ...---------------------- | ------------- | | 20 | 4.x+ | 1.x+ ...
(QB_NEW_EN)
[grammar] ~19-~19: There might be a mistake here.
Context: ... | Yes | | 20 | 3.x+ | 0.x+ ...
(QB_NEW_EN)
projects/angular-email/README.md
[grammar] ~17-~17: There might be a mistake here.
Context: ...cloakify/angular-email | Maintained | | ------- | ----------- | --------------...
(QB_NEW_EN)
[grammar] ~18-~18: There might be a mistake here.
Context: ...---------------------- | ------------- | | 20 | 4.x+ | 1.x+ ...
(QB_NEW_EN)
[grammar] ~19-~19: There might be a mistake here.
Context: ... | Yes | | 20 | 3.x+ | 0.x+ ...
(QB_NEW_EN)
🔇 Additional comments (9)
scripts/build.sh (1)
8-8: README copy step added.The addition of copying README.md to the Angular email project ensures documentation is properly bundled with the package.
package.json (1)
36-37: PostCSS plugin compatibility ✅All introduced PostCSS plugins declare peer-dependencies compatible with your installed PostCSS ^8.5.6:
- postcss-calc@^10.1.1 requires
postcss^8.4.38 → satisfies ^8.5.6 (chromium.googlesource.com)- postcss-custom-properties@^14.0.6 lists
postcssas a peer dependency (supports 8.x) (jsdocs.io)- postcss-logical@^8.1.0 is installed alongside PostCSS 8.x per its usage docs (npmjs.com)
- postcss-preset-env@^10.3.0 (and earlier 7.x versions) require
postcss^8.3 or higher (npmpeer.dev)No incompatible plugin versions were found.
projects/angular-email/node/index.ts (3)
16-20: Console.log restoration is now safe.Great! The console.log restoration has been moved to a finally block, which ensures it's always restored even if an error occurs. This addresses one of the critical issues mentioned in the PR comments.
Also applies to: 65-67
44-53: Entry point detection is now robust.Excellent improvement! The entry point detection now properly:
- Normalizes paths using
resolvefor accurate comparison- Checks for
info.entryPointexistence before comparing- Provides a clear error message when the entry cannot be found
- No longer falls back to an arbitrary first output
This addresses the second critical issue mentioned in the PR comments.
13-13: Dynamic externals support well implemented.The externals parameter is properly integrated:
- Added to the function signature with a default empty array
- Spread into the esbuild external array alongside 'juice'
- Properly documented in the type signature
Also applies to: 21-21, 37-37
projects/angular-email/tailwindcss-preset-email/css-processor/css-processor.ts (2)
8-80: Well-structured CSS processor with proper error handling.The CSS processor implementation is well-designed:
- Proper error handling with try-catch block
- Clear error messages for debugging
- Logical plugin order (Tailwind → custom properties → calc → custom plugins → logical → preset-env)
29-33: Good fix for multiple CSS variable replacements.The implementation correctly uses a global regex pattern to replace all
var(--tw-*)occurrences in a single declaration, addressing the potential issue with multiple variables.projects/angular-email/bin/build.ts (2)
99-106: Improved error handling for module resolution.Good addition of robust error handling when locating bundled URLs. The error message is descriptive and helps with debugging.
153-158: Well-implemented CLI externals support.The externals CLI option is properly implemented:
- Clear description of the option
- Proper parsing of comma-separated values
- Trimming and filtering of empty values
- Integration with the build function
Also applies to: 182-189, 192-199
|
@coderabbitai are all your suggestions solved? do you approve this PR? |
🧩 Analysis chain🏁 Script executed: Length of output: 3828 @luca-peruzzo Excellent work! 🎉 All the critical issues I identified have been resolved: ✅ All Critical Issues Fixed:1. Console.log Restoration - ✅ FIXEDconst __originalConsoleLog = console.log;
console.log = (/* filtered version */);
try {
// rendering logic
} finally {
console.log = __originalConsoleLog; // ✅ Properly restored!
}2. Entry Point Detection - ✅ FIXED// ✅ Now finds the correct entry instead of assuming first output
const entry = outputs.find(([, info]) =>
info.entryPoint && abs(info.entryPoint) === wanted)?.[0];
if (!entry) throw new Error(/* descriptive error */);3. Dirent.parentPath - ✅ RESOLVEDAs we established, this is valid for Node.js v22 (lts/jod) that the project uses. Approval Status:The Tailwind v4 migration is excellent - clean API design with the This PR is ready for production! 🚀 🧠 Learnings used✅ Actions performedComments resolved. |
Add tailwind v4 support
Summary by CodeRabbit
New Features
Breaking Changes
Documentation
Chores