@delmaredigital/payload-puck 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,6 +43,50 @@ pnpm add @delmaredigital/payload-puck @puckeditor/core
43
43
 
44
44
  > **Security:** If your app uses Next.js middleware (or proxy.ts) to protect dynamic routes, use `next` >= 15.5.16 / 16.2.5 to pick up the fix for [CVE-2026-44574](https://github.com/vercel/next.js/security/advisories/GHSA-492v-c6pp-mqqv) (middleware bypass via dynamic route parameter injection). Turbopack users need >= 15.5.18 / 16.2.6.
45
45
 
46
+ ### Upgrading to 0.8.0 (breaking)
47
+
48
+ **Editor CSS is now built by your app, not by this plugin.** Three options collapse into one, and `withPuckCSS` is gone.
49
+
50
+ Add a build step using Tailwind's own CLI. Tailwind v4 ships the CLI as a separate package:
51
+
52
+ ```bash
53
+ pnpm add -D @tailwindcss/cli # v4 only; v3 already provides the `tailwindcss` binary
54
+ ```
55
+
56
+ ```jsonc
57
+ // package.json — quote the paths; App Router route groups like (frontend) are shell syntax
58
+ "scripts": {
59
+ "build:puck-css": "tailwindcss -i './src/app/(frontend)/globals.css' -o './public/puck-editor-styles.css'",
60
+ "dev:puck-css": "tailwindcss -i './src/app/(frontend)/globals.css' -o './public/puck-editor-styles.css' --watch",
61
+ "build": "pnpm build:puck-css && next build",
62
+ "dev": "pnpm build:puck-css && next dev"
63
+ }
64
+ ```
65
+
66
+ Add `public/puck-editor-styles.css` to `.gitignore` — it's a build artifact. Run `dev:puck-css` in a second terminal while actively editing theme CSS.
67
+
68
+ Then pass the URL:
69
+
70
+ ```typescript
71
+ // before
72
+ createPuckPlugin({
73
+ editorStylesheet: 'src/app/(frontend)/globals.css',
74
+ editorStylesheetCompiled: '/puck-editor-styles.css',
75
+ editorStylesheetUrls: ['https://fonts.googleapis.com/css2?family=Inter'],
76
+ })
77
+
78
+ // after
79
+ createPuckPlugin({
80
+ editorStylesheets: ['/puck-editor-styles.css', 'https://fonts.googleapis.com/css2?family=Inter'],
81
+ })
82
+ ```
83
+
84
+ Finally, remove the `withPuckCSS` import and wrapper from `next.config.js`, and drop any `editorStylesheets` prop on `PuckConfigProvider` — the plugin wires it through automatically now.
85
+
86
+ > **Why:** the old approach compiled CSS at runtime in dev and via a **webpack plugin** in production. Next.js 16 defaults to Turbopack, which never runs `webpack()` hooks — so the production stylesheet was silently never generated and the editor rendered unstyled, while local dev looked perfect. One artifact, built by your own toolchain, now resolves identically everywhere.
87
+
88
+ Also removed: the `/api/puck/styles` endpoint, the `/next` entry point, and the `postcss` / `postcss-load-config` peer dependencies.
89
+
46
90
  ### Upgrading to 0.7.0 (breaking)
47
91
 
48
92
  `0.7.0` raises two floors. Both are a one-line change for most projects:
@@ -42,7 +42,6 @@ export { generatePagesCollection } from './collections/Pages.js';
42
42
  export { TemplatesCollection } from '../collections/Templates.js';
43
43
  export { getPuckFields, getPuckCollectionConfig, puckDataField, editorVersionField, createEditorVersionField, pageLayoutField, createPageLayoutField, isHomepageField, seoFieldGroup, conversionFieldGroup, } from './fields/index.js';
44
44
  export { generatePuckEditField };
45
- export { PUCK_STYLES_ENDPOINT } from '../endpoints/styles.js';
46
45
  export { createIsHomepageUniqueHook, unsetHomepage, HomepageConflictError, } from './hooks/index.js';
47
46
  export type { IsHomepageUniqueHookOptions } from './hooks/index.js';
48
47
  export type { PuckPluginOptions, PuckAdminConfig } from '../types/index.js';
@@ -5,7 +5,6 @@ import { AiContextCollection } from '../ai/collections/AiContext.js';
5
5
  import { getPuckFields } from './fields/index.js';
6
6
  import { createIsHomepageUniqueHook } from './hooks/isHomepageUnique.js';
7
7
  import { createListHandler, createCreateHandler, createGetHandler, createUpdateHandler, createDeleteHandler, createVersionsHandler, createRestoreHandler } from '../endpoints/index.js';
8
- import { createStylesHandler, PUCK_STYLES_ENDPOINT } from '../endpoints/styles.js';
9
8
  import { createAiEndpointHandler } from '../endpoints/ai.js';
10
9
  import { createPromptsListHandler, createPromptsCreateHandler, createPromptsUpdateHandler, createPromptsDeleteHandler } from '../endpoints/prompts.js';
11
10
  import { createContextListHandler, createContextCreateHandler, createContextUpdateHandler, createContextDeleteHandler } from '../endpoints/context.js';
@@ -105,7 +104,7 @@ import { createContextListHandler, createContextCreateHandler, createContextUpda
105
104
  * })
106
105
  * ```
107
106
  */ export function createPuckPlugin(options = {}) {
108
- const { pagesCollection = 'pages', autoGenerateCollection = true, admin: pluginAdminConfig = {}, enableAdminView = true, adminViewPath = '/puck-editor', enableEndpoints = true, pageTreeIntegration, editorStylesheet, editorStylesheetUrls = [], editorStylesheetCompiled, ai: aiConfig, previewUrl, rootPropsMapping } = options;
107
+ const { pagesCollection = 'pages', autoGenerateCollection = true, admin: pluginAdminConfig = {}, enableAdminView = true, adminViewPath = '/puck-editor', enableEndpoints = true, pageTreeIntegration, editorStylesheets: editorStylesheetsOption = [], ai: aiConfig, previewUrl, rootPropsMapping } = options;
109
108
  const { addEditButton = true } = pluginAdminConfig;
110
109
  // Parse page-tree integration config
111
110
  // - undefined: auto-detect at runtime (null stored, view will check for pageSegment field)
@@ -274,32 +273,17 @@ import { createContextListHandler, createContextCreateHandler, createContextUpda
274
273
  '/puck/:collection/:id/versions',
275
274
  '/puck/:collection/:id/restore'
276
275
  ]);
277
- // Build styles endpoint URL list for PuckConfigProvider
278
- // In production, prefer the pre-compiled static CSS file if provided
279
- // In development, use runtime compilation endpoint for hot reload
280
- const isProduction = process.env.NODE_ENV === 'production';
281
- const useCompiledCss = isProduction && editorStylesheetCompiled;
276
+ // Stylesheet URLs for the editor preview iframe. These are passed straight
277
+ // through: the same URLs resolve in development and production, so the
278
+ // editor can no longer look correct locally and unstyled in production.
282
279
  const editorStylesheets = [
283
- ...useCompiledCss ? [
284
- editorStylesheetCompiled
285
- ] : editorStylesheet ? [
286
- PUCK_STYLES_ENDPOINT
287
- ] : [],
288
- ...editorStylesheetUrls
280
+ ...editorStylesheetsOption
289
281
  ];
290
282
  // Filter out parameterized puck endpoints from previous plugin instances
291
283
  // so we can re-register them with the merged collections list
292
284
  const incomingEndpoints = (incomingConfig.endpoints || []).filter((ep)=>!parameterizedPuckPaths.has(ep.path));
293
285
  const endpoints = enableEndpoints ? [
294
286
  ...incomingEndpoints,
295
- // Styles endpoint MUST be first - exact match before parameterized routes
296
- ...editorStylesheet ? [
297
- {
298
- path: '/puck/styles',
299
- method: 'get',
300
- handler: createStylesHandler(editorStylesheet)
301
- }
302
- ] : [],
303
287
  // AI endpoint (exact match, before parameterized routes)
304
288
  ...aiConfig?.enabled ? [
305
289
  {
@@ -441,7 +425,5 @@ export { TemplatesCollection } from '../collections/Templates.js';
441
425
  export { getPuckFields, getPuckCollectionConfig, puckDataField, editorVersionField, createEditorVersionField, pageLayoutField, createPageLayoutField, isHomepageField, seoFieldGroup, conversionFieldGroup } from './fields/index.js';
442
426
  // Export the edit button generator for hybrid collections
443
427
  export { generatePuckEditField };
444
- // Export styles endpoint constant
445
- export { PUCK_STYLES_ENDPOINT } from '../endpoints/styles.js';
446
428
  // Re-export hooks for hybrid collection integration
447
429
  export { createIsHomepageUniqueHook, unsetHomepage, HomepageConflictError } from './hooks/index.js';
@@ -141,42 +141,44 @@ export interface PuckPluginOptions {
141
141
  */
142
142
  pageTreeIntegration?: boolean | PageTreeIntegrationOptions;
143
143
  /**
144
- * Path to CSS file for editor iframe styling.
145
- * The plugin compiles this file with PostCSS/Tailwind and serves it at /api/puck/styles.
146
- * This allows the editor preview to display your frontend styles (CSS variables, Tailwind utilities).
144
+ * Stylesheet URLs to load inside the editor preview iframe, in order.
147
145
  *
148
- * @example 'src/app/(frontend)/globals.css'
149
- * @example 'src/styles/globals.css'
150
- */
151
- editorStylesheet?: string;
152
- /**
153
- * Additional stylesheet URLs to load in the editor iframe.
154
- * Use this for external stylesheets like Google Fonts that can't be compiled.
146
+ * These are plain URLs the browser fetches — a static file your build emits,
147
+ * or any external stylesheet. The plugin does not compile CSS: your app's own
148
+ * toolchain already does that far better than we can, and compiling it a
149
+ * second time was the source of a dev/production split where the editor
150
+ * looked correct locally and unstyled in production.
155
151
  *
156
- * @example ['https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700']
157
- */
158
- editorStylesheetUrls?: string[];
159
- /**
160
- * Path to pre-compiled CSS file for production use.
161
- * When set, the editor will load this static file instead of using the runtime compilation endpoint.
162
- * Use with `withPuckCSS()` from `@delmaredigital/payload-puck/next` to compile CSS at build time.
152
+ * Generate the file with Tailwind's own CLI as part of your build, so the
153
+ * editor loads byte-identical CSS in every environment:
163
154
  *
164
- * @example '/puck-editor-styles.css'
155
+ * ```jsonc
156
+ * // package.json
157
+ * {
158
+ * "scripts": {
159
+ * "build:puck-css": "tailwindcss -i ./src/app/(frontend)/globals.css -o ./public/puck-editor-styles.css",
160
+ * "build": "pnpm build:puck-css && next build",
161
+ * "dev": "pnpm build:puck-css --watch & next dev"
162
+ * }
163
+ * }
164
+ * ```
165
165
  *
166
- * @example
167
166
  * ```typescript
168
- * // next.config.js
169
- * import { withPuckCSS } from '@delmaredigital/payload-puck/next'
170
- * export default withPuckCSS({ cssInput: 'src/globals.css' })(nextConfig)
171
- *
172
- * // payload.config.ts
173
167
  * createPuckPlugin({
174
- * editorStylesheet: 'src/globals.css', // For dev (runtime compilation)
175
- * editorStylesheetCompiled: '/puck-editor-styles.css', // For prod (static file)
168
+ * editorStylesheets: [
169
+ * '/puck-editor-styles.css',
170
+ * 'https://fonts.googleapis.com/css2?family=Inter:wght@400;700',
171
+ * ],
176
172
  * })
177
173
  * ```
174
+ *
175
+ * Resolved URLs are published on `config.custom.puck.editorStylesheets` and
176
+ * passed to the editor automatically — you do not need to repeat them on
177
+ * `PuckConfigProvider`.
178
+ *
179
+ * @example ['/puck-editor-styles.css']
178
180
  */
179
- editorStylesheetCompiled?: string;
181
+ editorStylesheets?: string[];
180
182
  /**
181
183
  * AI configuration for the plugin.
182
184
  * Enables AI-powered page generation in the editor.
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.7.0";
1
+ export declare const VERSION = "0.8.1";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/generate-version.js - do not edit manually
2
- export const VERSION = '0.7.0';
2
+ export const VERSION = '0.8.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delmaredigital/payload-puck",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Puck visual page builder plugin for Payload CMS",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -82,11 +82,6 @@
82
82
  "types": "./dist/ai/index.d.ts",
83
83
  "default": "./dist/ai/index.js"
84
84
  },
85
- "./next": {
86
- "import": "./dist/next/index.js",
87
- "types": "./dist/next/index.d.ts",
88
- "default": "./dist/next/index.js"
89
- },
90
85
  "./email": {
91
86
  "import": "./dist/email/index.js",
92
87
  "types": "./dist/email/index.d.ts",
@@ -123,8 +118,6 @@
123
118
  "@tailwindcss/postcss": ">=4.0.0",
124
119
  "next": ">=15.4.8",
125
120
  "payload": ">=3.69.0",
126
- "postcss": ">=8.0.0",
127
- "postcss-load-config": ">=4.0.0",
128
121
  "react": ">=19.2.1",
129
122
  "react-dom": ">=19.2.1",
130
123
  "tailwindcss": ">=3.0.0 || >=4.0.0",
@@ -137,12 +130,6 @@
137
130
  "@payloadcms/next": {
138
131
  "optional": true
139
132
  },
140
- "postcss": {
141
- "optional": true
142
- },
143
- "postcss-load-config": {
144
- "optional": true
145
- },
146
133
  "tailwindcss": {
147
134
  "optional": true
148
135
  },
@@ -1,4 +0,0 @@
1
- /**
2
- * Ambient type declarations for optional PostCSS/Tailwind peer dependencies
3
- * These modules are dynamically imported at runtime from the consumer's project
4
- */
@@ -1,19 +0,0 @@
1
- /**
2
- * Styles Endpoint Handler
3
- *
4
- * Compiles and serves CSS for the editor iframe.
5
- * Uses the consumer's PostCSS/Tailwind installation via peer dependencies.
6
- * Loads the project's postcss.config.js for proper plugin configuration.
7
- */
8
- import type { PayloadHandler } from 'payload';
9
- /**
10
- * Creates a handler that serves compiled CSS for the editor iframe
11
- *
12
- * @param cssFilePath - Path to CSS file relative to project root
13
- * @returns PayloadHandler that serves compiled CSS
14
- */
15
- export declare function createStylesHandler(cssFilePath: string): PayloadHandler;
16
- /**
17
- * Helper constant for the styles endpoint URL
18
- */
19
- export declare const PUCK_STYLES_ENDPOINT = "/api/puck/styles";
@@ -1,153 +0,0 @@
1
- /**
2
- * Styles Endpoint Handler
3
- *
4
- * Compiles and serves CSS for the editor iframe.
5
- * Uses the consumer's PostCSS/Tailwind installation via peer dependencies.
6
- * Loads the project's postcss.config.js for proper plugin configuration.
7
- */ import { readFileSync, statSync, existsSync } from 'fs';
8
- import { join } from 'path';
9
- const cssCache = new Map();
10
- /**
11
- * Compile CSS using PostCSS with the project's configuration
12
- * Loads postcss.config.js from project root for proper plugin setup
13
- * Falls back to minimal Tailwind-only config if no config file found
14
- */ async function compileCss(css, filePath) {
15
- try {
16
- // Dynamic import to use consumer's PostCSS installation
17
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
- let postcss;
19
- try {
20
- postcss = (await import(/* webpackIgnore: true */ 'postcss')).default;
21
- } catch {
22
- console.warn('[payload-puck] PostCSS not found. CSS will not be processed. Install postcss as a dependency.');
23
- return css;
24
- }
25
- // Try to load the project's postcss.config.js using postcss-load-config
26
- // This ensures all plugins (typography, etc.) are properly loaded
27
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
- let processor;
29
- let usedProjectConfig = false;
30
- try {
31
- // Dynamic import of postcss-load-config (optional peer dependency)
32
- // This package is commonly installed alongside PostCSS
33
- const loadConfigModule = await import(/* webpackIgnore: true */ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
34
- // @ts-ignore - optional dependency may not have types
35
- 'postcss-load-config');
36
- const postcssLoadConfig = loadConfigModule.default;
37
- // Load config from project root (where postcss.config.js lives)
38
- const { plugins } = await postcssLoadConfig({}, process.cwd());
39
- processor = postcss(plugins);
40
- usedProjectConfig = true;
41
- } catch {
42
- // postcss-load-config not available or no config found - this is fine
43
- // Fall back to direct Tailwind import silently
44
- // Try Tailwind v4 first (@tailwindcss/postcss)
45
- try {
46
- const tailwindcss = (await import(/* webpackIgnore: true */ '@tailwindcss/postcss')).default;
47
- processor = postcss([
48
- tailwindcss
49
- ]);
50
- } catch {
51
- // Fall back to Tailwind v3 (tailwindcss)
52
- try {
53
- const tailwindcss = (await import(/* webpackIgnore: true */ 'tailwindcss')).default;
54
- processor = postcss([
55
- tailwindcss
56
- ]);
57
- } catch {
58
- // No Tailwind available - just return the CSS as-is
59
- console.warn('[payload-puck] No Tailwind CSS installation found. CSS will not be processed.');
60
- return css;
61
- }
62
- }
63
- }
64
- const result = await processor.process(css, {
65
- from: filePath
66
- });
67
- return result.css;
68
- } catch (error) {
69
- console.error('[payload-puck] CSS compilation error:', error);
70
- throw error;
71
- }
72
- }
73
- /**
74
- * Creates a handler that serves compiled CSS for the editor iframe
75
- *
76
- * @param cssFilePath - Path to CSS file relative to project root
77
- * @returns PayloadHandler that serves compiled CSS
78
- */ export function createStylesHandler(cssFilePath) {
79
- return async (req)=>{
80
- try {
81
- const fullPath = join(process.cwd(), cssFilePath);
82
- // Check if file exists
83
- if (!existsSync(fullPath)) {
84
- console.error(`[payload-puck] CSS file not found: ${fullPath}`);
85
- return new Response(`/* CSS file not found: ${cssFilePath} */`, {
86
- status: 404,
87
- headers: {
88
- 'Content-Type': 'text/css'
89
- }
90
- });
91
- }
92
- // Get file modification time for cache invalidation
93
- const stats = statSync(fullPath);
94
- const mtime = stats.mtimeMs;
95
- // ETag derived from mtime - changes whenever the source file changes,
96
- // whether from a dev-mode edit or a version upgrade recompiling output.
97
- const etag = `"${mtime}"`;
98
- // If the browser's cached copy is still fresh, tell it so without
99
- // doing any file read/compilation work.
100
- const ifNoneMatch = req.headers?.get('if-none-match');
101
- if (ifNoneMatch === etag) {
102
- return new Response(null, {
103
- status: 304,
104
- headers: {
105
- ETag: etag,
106
- 'Cache-Control': 'no-cache'
107
- }
108
- });
109
- }
110
- // Check cache
111
- const cached = cssCache.get(cssFilePath);
112
- if (cached && cached.mtime === mtime) {
113
- return new Response(cached.css, {
114
- headers: {
115
- 'Content-Type': 'text/css',
116
- 'Cache-Control': 'no-cache',
117
- ETag: etag,
118
- 'Last-Modified': new Date(mtime).toUTCString(),
119
- 'X-Puck-Cache': 'hit'
120
- }
121
- });
122
- }
123
- // Read and compile CSS
124
- const rawCss = readFileSync(fullPath, 'utf-8');
125
- const compiledCss = await compileCss(rawCss, fullPath);
126
- // Update cache
127
- cssCache.set(cssFilePath, {
128
- css: compiledCss,
129
- mtime
130
- });
131
- return new Response(compiledCss, {
132
- headers: {
133
- 'Content-Type': 'text/css',
134
- 'Cache-Control': 'no-cache',
135
- ETag: etag,
136
- 'Last-Modified': new Date(mtime).toUTCString(),
137
- 'X-Puck-Cache': 'miss'
138
- }
139
- });
140
- } catch (error) {
141
- console.error('[payload-puck] Styles endpoint error:', error);
142
- return new Response(`/* Error compiling CSS: ${error instanceof Error ? error.message : 'Unknown error'} */`, {
143
- status: 500,
144
- headers: {
145
- 'Content-Type': 'text/css'
146
- }
147
- });
148
- }
149
- };
150
- }
151
- /**
152
- * Helper constant for the styles endpoint URL
153
- */ export const PUCK_STYLES_ENDPOINT = '/api/puck/styles';
@@ -1,64 +0,0 @@
1
- /**
2
- * Next.js Configuration Wrapper for Puck CSS
3
- *
4
- * Compiles CSS at build time using the project's PostCSS/Tailwind configuration.
5
- * This ensures the editor iframe styles work in production (Vercel, etc.) where
6
- * source files aren't available at runtime.
7
- *
8
- * @example
9
- * ```js
10
- * // next.config.js
11
- * import { withPuckCSS } from '@delmaredigital/payload-puck/next'
12
- * import { withPayload } from '@payloadcms/next/withPayload'
13
- *
14
- * export default withPuckCSS({
15
- * cssInput: 'src/app/(frontend)/globals.css',
16
- * })(withPayload(nextConfig))
17
- * ```
18
- */
19
- import type { NextConfig } from 'next';
20
- /**
21
- * Options for the withPuckCSS wrapper
22
- */
23
- export interface WithPuckCSSOptions {
24
- /**
25
- * Path to the source CSS file (relative to project root)
26
- * @example 'src/app/(frontend)/globals.css'
27
- */
28
- cssInput: string;
29
- /**
30
- * Output path for compiled CSS (relative to public/)
31
- * @default 'puck-editor-styles.css'
32
- */
33
- cssOutput?: string;
34
- /**
35
- * Whether to skip compilation in development
36
- * @default true
37
- */
38
- skipInDev?: boolean;
39
- }
40
- /**
41
- * Default output filename for compiled CSS
42
- */
43
- export declare const PUCK_CSS_OUTPUT_DEFAULT = "puck-editor-styles.css";
44
- /**
45
- * Next.js configuration wrapper that compiles Puck editor CSS at build time
46
- *
47
- * @param options - Configuration options
48
- * @returns A function that wraps your Next.js config
49
- *
50
- * @example
51
- * ```js
52
- * import { withPuckCSS } from '@delmaredigital/payload-puck/next'
53
- *
54
- * export default withPuckCSS({
55
- * cssInput: 'src/app/(frontend)/globals.css',
56
- * })(nextConfig)
57
- * ```
58
- */
59
- export declare function withPuckCSS(options: WithPuckCSSOptions): (nextConfig: NextConfig) => NextConfig;
60
- /**
61
- * Get the URL path for the compiled CSS file
62
- * Use this in your plugin configuration
63
- */
64
- export declare function getPuckCSSPath(cssOutput?: string): string;
@@ -1,155 +0,0 @@
1
- /**
2
- * Next.js Configuration Wrapper for Puck CSS
3
- *
4
- * Compiles CSS at build time using the project's PostCSS/Tailwind configuration.
5
- * This ensures the editor iframe styles work in production (Vercel, etc.) where
6
- * source files aren't available at runtime.
7
- *
8
- * @example
9
- * ```js
10
- * // next.config.js
11
- * import { withPuckCSS } from '@delmaredigital/payload-puck/next'
12
- * import { withPayload } from '@payloadcms/next/withPayload'
13
- *
14
- * export default withPuckCSS({
15
- * cssInput: 'src/app/(frontend)/globals.css',
16
- * })(withPayload(nextConfig))
17
- * ```
18
- */ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
19
- import { dirname, resolve } from 'path';
20
- /**
21
- * Default output filename for compiled CSS
22
- */ export const PUCK_CSS_OUTPUT_DEFAULT = 'puck-editor-styles.css';
23
- /**
24
- * Compile CSS using PostCSS with the project's configuration
25
- */ async function compileCss(css, filePath) {
26
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
- let postcss;
28
- try {
29
- postcss = (await import('postcss')).default;
30
- } catch {
31
- console.warn('[payload-puck] PostCSS not found. CSS will not be compiled.');
32
- return css;
33
- }
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
- let processor;
36
- try {
37
- // Try to load project's postcss.config.js
38
- const loadConfigModule = await import('postcss-load-config');
39
- const postcssLoadConfig = loadConfigModule.default;
40
- const { plugins } = await postcssLoadConfig({}, process.cwd());
41
- processor = postcss(plugins);
42
- } catch {
43
- // Fall back to direct Tailwind import
44
- try {
45
- const tailwindcss = (await import('@tailwindcss/postcss')).default;
46
- processor = postcss([
47
- tailwindcss
48
- ]);
49
- } catch {
50
- try {
51
- const tailwindcss = (await import('tailwindcss')).default;
52
- processor = postcss([
53
- tailwindcss
54
- ]);
55
- } catch {
56
- console.warn('[payload-puck] No Tailwind CSS found. CSS will not be compiled.');
57
- return css;
58
- }
59
- }
60
- }
61
- const result = await processor.process(css, {
62
- from: filePath
63
- });
64
- return result.css;
65
- }
66
- /**
67
- * Webpack plugin that compiles CSS at build time
68
- */ class PuckCSSWebpackPlugin {
69
- options;
70
- compiled = false;
71
- constructor(options){
72
- this.options = options;
73
- }
74
- apply(compiler) {
75
- compiler.hooks.beforeCompile.tapPromise('PuckCSSWebpackPlugin', async ()=>{
76
- // Only compile once per build
77
- if (this.compiled) return;
78
- this.compiled = true;
79
- const { cssInput, cssOutput, skipInDev } = this.options;
80
- // Skip in development if configured
81
- if (skipInDev && process.env.NODE_ENV === 'development') {
82
- console.log('[payload-puck] Skipping CSS compilation in development');
83
- return;
84
- }
85
- const inputPath = resolve(process.cwd(), cssInput);
86
- const outputPath = resolve(process.cwd(), 'public', cssOutput);
87
- // Check if source file exists
88
- if (!existsSync(inputPath)) {
89
- console.error(`[payload-puck] CSS source file not found: ${inputPath}`);
90
- return;
91
- }
92
- try {
93
- console.log(`[payload-puck] Compiling CSS: ${cssInput} -> public/${cssOutput}`);
94
- // Read source CSS
95
- const rawCss = readFileSync(inputPath, 'utf-8');
96
- // Compile with PostCSS/Tailwind
97
- const compiledCss = await compileCss(rawCss, inputPath);
98
- // Ensure public directory exists
99
- const outputDir = dirname(outputPath);
100
- if (!existsSync(outputDir)) {
101
- mkdirSync(outputDir, {
102
- recursive: true
103
- });
104
- }
105
- // Write compiled CSS
106
- writeFileSync(outputPath, compiledCss, 'utf-8');
107
- console.log(`[payload-puck] CSS compiled successfully (${(compiledCss.length / 1024).toFixed(1)}KB)`);
108
- } catch (error) {
109
- console.error('[payload-puck] CSS compilation failed:', error);
110
- }
111
- });
112
- }
113
- }
114
- /**
115
- * Next.js configuration wrapper that compiles Puck editor CSS at build time
116
- *
117
- * @param options - Configuration options
118
- * @returns A function that wraps your Next.js config
119
- *
120
- * @example
121
- * ```js
122
- * import { withPuckCSS } from '@delmaredigital/payload-puck/next'
123
- *
124
- * export default withPuckCSS({
125
- * cssInput: 'src/app/(frontend)/globals.css',
126
- * })(nextConfig)
127
- * ```
128
- */ export function withPuckCSS(options) {
129
- const resolvedOptions = {
130
- cssInput: options.cssInput,
131
- cssOutput: options.cssOutput ?? PUCK_CSS_OUTPUT_DEFAULT,
132
- skipInDev: options.skipInDev ?? true
133
- };
134
- return (nextConfig)=>{
135
- return {
136
- ...nextConfig,
137
- webpack: (webpackConfig, context)=>{
138
- // Add our CSS compilation plugin
139
- webpackConfig.plugins = webpackConfig.plugins || [];
140
- webpackConfig.plugins.push(new PuckCSSWebpackPlugin(resolvedOptions));
141
- // Call existing webpack config if present
142
- if (typeof nextConfig.webpack === 'function') {
143
- return nextConfig.webpack(webpackConfig, context);
144
- }
145
- return webpackConfig;
146
- }
147
- };
148
- };
149
- }
150
- /**
151
- * Get the URL path for the compiled CSS file
152
- * Use this in your plugin configuration
153
- */ export function getPuckCSSPath(cssOutput) {
154
- return `/${cssOutput ?? PUCK_CSS_OUTPUT_DEFAULT}`;
155
- }