diff --git a/openframe-frontend-core/scripts/route-styles.cjs b/openframe-frontend-core/scripts/route-styles.cjs index e05b602dd..20d944bf9 100644 --- a/openframe-frontend-core/scripts/route-styles.cjs +++ b/openframe-frontend-core/scripts/route-styles.cjs @@ -112,7 +112,9 @@ async function stylesheetBuilder({ root, input, config }) { const postcss = require(require.resolve('postcss', { paths: [root, __dirname] })); const loadConfig = require(require.resolve('tailwindcss/loadConfig', { paths: [root, __dirname] })); const source = readInput(resolve(root, input), root); - const appConfig = loadConfig(resolve(root, config)); + const { APP_TAILWIND_CONFIG } = require('./style-sources.cjs'); + const loaded = loadConfig(resolve(root, config)); + const appConfig = loaded[APP_TAILWIND_CONFIG] ?? loaded; const minify = minifier(root); const from = resolve(root, input); return async content => { diff --git a/openframe-frontend-core/scripts/style-sources.cjs b/openframe-frontend-core/scripts/style-sources.cjs index d8cbc86b4..1276561df 100644 --- a/openframe-frontend-core/scripts/style-sources.cjs +++ b/openframe-frontend-core/scripts/style-sources.cjs @@ -259,6 +259,12 @@ if (require.main === module) { ); } +/** + * The config `withRouteStylesTailwind` was given. The route step runs inside `next build`, under the same + * `NEXT_STYLES=routes`, so the config it loads is the wrapped one: the complete sheet is built from this. + */ +const APP_TAILWIND_CONFIG = Symbol.for('next-styles.app-tailwind-config'); + /** * The Tailwind config of the stylesheet the root layout imports, for `next-styles build`: under it that sheet is * built with no utilities (its tokens, preflight and the app's own rules), because each route's utilities arrive @@ -269,7 +275,14 @@ if (require.main === module) { */ function withRouteStylesTailwind(config) { if (process.env.NEXT_STYLES !== 'routes') return config; - return { ...config, content: [{ raw: '', extension: 'html' }], safelist: [] }; + return { ...config, content: [{ raw: '', extension: 'html' }], safelist: [], [APP_TAILWIND_CONFIG]: config }; } -module.exports = { appStyleSources, routePaths, servedRouteFiles, LAZY_STYLED, withRouteStylesTailwind }; +module.exports = { + appStyleSources, + routePaths, + servedRouteFiles, + LAZY_STYLED, + withRouteStylesTailwind, + APP_TAILWIND_CONFIG, +}; diff --git a/openframe-frontend-core/scripts/style-sources.d.ts b/openframe-frontend-core/scripts/style-sources.d.ts index ceb23c2dc..1bb18571a 100644 --- a/openframe-frontend-core/scripts/style-sources.d.ts +++ b/openframe-frontend-core/scripts/style-sources.d.ts @@ -13,3 +13,6 @@ export function appStyleSources(options: AppStyleSourcesOptions): string[]; /** The root sheet's Tailwind config: no utilities under `next-styles build` (each route brings its own). */ export function withRouteStylesTailwind(config: T): T; + +/** Where a config wrapped by {@link withRouteStylesTailwind} keeps the app's own (the complete sheet's). */ +export const APP_TAILWIND_CONFIG: unique symbol; diff --git a/openframe-frontend-core/src/__tests__/route-styles.test.ts b/openframe-frontend-core/src/__tests__/route-styles.test.ts index 6e101e038..6654caef3 100644 --- a/openframe-frontend-core/src/__tests__/route-styles.test.ts +++ b/openframe-frontend-core/src/__tests__/route-styles.test.ts @@ -44,8 +44,9 @@ const { withRouteStyles, fullSheetUrl } = require('../../scripts/next-styles.cjs withRouteStyles: (config: object) => (phase: string) => Promise>; fullSheetUrl: (info: { buildId: string; assetPrefix?: string; basePath?: string }) => string; }; -const { withRouteStylesTailwind } = require('../../scripts/style-sources.cjs') as { +const { withRouteStylesTailwind, APP_TAILWIND_CONFIG } = require('../../scripts/style-sources.cjs') as { withRouteStylesTailwind: (config: T) => T; + APP_TAILWIND_CONFIG: symbol; }; describe('route-styles: the classes a page renders', () => { @@ -115,6 +116,47 @@ describe('route-styles: a route sheet is the complete sheet with rules left out' }); }); +describe('route-styles: the complete sheet under next-styles build', () => { + let root: string; + + beforeAll(() => { + root = mkdtempSync(join(tmpdir(), 'route-styles-full-')); + mkdirSync(join(root, 'app'), { recursive: true }); + writeFileSync(join(root, 'app', 'page.tsx'), 'export default () =>

'); + writeFileSync( + join(root, 'app', 'globals.css'), + `@tailwind base;\n${routeStyles.UTILITIES_MARK}\n@tailwind components;\n@tailwind utilities;\n`, + ); + writeFileSync( + join(root, 'tailwind.config.cjs'), + `const { withRouteStylesTailwind } = require(${JSON.stringify(require.resolve('../../scripts/style-sources.cjs'))});\n` + + `module.exports = withRouteStylesTailwind({ content: [${JSON.stringify(join(root, 'app', 'page.tsx'))}] });`, + ); + writeFileSync( + join(root, 'postcss.config.cjs'), + `module.exports = { plugins: { ${JSON.stringify(require.resolve('tailwindcss'))}: {} } }`, + ); + }); + + afterAll(() => rmSync(root, { recursive: true, force: true })); + + it('is built from the app’s own content, though the build step runs with NEXT_STYLES=routes', async () => { + process.env.NEXT_STYLES = 'routes'; + try { + const build = await routeStyles.stylesheetBuilder({ + root, + input: 'app/globals.css', + config: 'tailwind.config.cjs', + }); + const full = await build(null); + expect(full).toContain('.p-2{'); + expect(full).toContain('.flex{'); + } finally { + delete process.env.NEXT_STYLES; + } + }); +}); + describe('route-styles: a finished build', () => { let root: string; @@ -288,13 +330,15 @@ describe('next-styles: the route-styles config wrappers', () => { expect(fullSheetUrl({ buildId: 'b1', basePath: '/help/' })).toBe('/help/_next/static/next-styles/full-b1.css'); }); - it('under next-styles build, build the root sheet with no utilities', () => { + it('under next-styles build, build the root sheet with no utilities and keep the app config', () => { process.env.NEXT_STYLES = 'routes'; try { - expect(withRouteStylesTailwind({ content: ['a'], safelist: ['b'], theme: {} })).toEqual({ + const app = { content: ['a'], safelist: ['b'], theme: {} }; + expect(withRouteStylesTailwind(app)).toEqual({ content: [{ raw: '', extension: 'html' }], safelist: [], theme: {}, + [APP_TAILWIND_CONFIG]: app, }); } finally { delete process.env.NEXT_STYLES;