Skip to main content

vendor-styles-components

Vendor CSS precompilation system for Turbopack compatibility. How to add third-party CSS to components without violating Pages Router global CSS restriction. Auto-invoked when working in apps/app.

Source facts

Repository
growilabs/growi
Last source activity
March 6, 2026 at 07:51
Detected SKILL.md language
English
Stars
1,470
Forks
246

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
vendor-styles-components
description
Vendor CSS precompilation system for Turbopack compatibility. How to add third-party CSS to components without violating Pages Router global CSS restriction. Auto-invoked when working in apps/app.
# Vendor CSS Precompilation (apps/app) ## Problem Turbopack (Pages Router) strictly enforces: **global CSS can only be imported from `_app.page.tsx`**. Components cannot `import 'package/style.css'` directly — Turbopack rejects these at compile time. Centralizing all vendor CSS in `_app` would degrade FCP for pages that don't need those styles. ## Solution: Two-Track Vendor CSS System ### Commons Track (globally shared CSS) - **File**: `src/styles/vendor.scss` - **For**: CSS needed on most pages (e.g., `simplebar-react`) - **Mechanism**: Compiled via `vite.vendor-styles-commons.ts` into `src/styles/prebuilt/` - **Imported from**: `_app.page.tsx` ### Components Track (component-specific CSS) - **For**: CSS needed only by specific components - **Mechanism**: Vite precompiles `*.vendor-styles.ts` entry points into `*.vendor-styles.prebuilt.ts` using `?inline` CSS import suffix - **Output**: Pure JS modules (no CSS imports) — Turbopack sees them as regular JS ## How It Works 1. **Entry point** (`ComponentName.vendor-styles.ts`): imports CSS via Vite `?inline` suffix, which inlines CSS as a string 2. **Runtime injection**: the entry point creates a `<style>` tag and appends CSS to `document.head` 3. **Vite prebuild** (`pre:styles-components` Turborepo task): compiles entry points into `*.vendor-styles.prebuilt.ts` 4. **Component import**: imports the `.prebuilt.ts` file instead of raw CSS ### Entry Point Template ```typescript // @ts-nocheck -- Processed by Vite only; ?inline is a Vite-specific import suffix import css from 'some-package/dist/style.css?inline'; const s = document.createElement('style'); s.textContent = css; document.head.appendChild(s); ``` For multiple CSS sources in one component: ```typescript // @ts-nocheck import css1 from 'package-a/style.css?inline'; import css2 from 'package-b/style.css?inline'; const s = document.createElement('style'); s.textContent = css1 + css2; document.head.appendChild(s); ``` ## Current Entry Points | Entry Point | CSS Sources | Consuming Components | |---|---|---| | `Renderer.vendor-styles.ts` | `@growi/remark-lsx`, `@growi/remark-attachment-refs`, `katex` | renderer.tsx | | `GrowiEditor.vendor-styles.ts` | `@growi/editor` | PageEditor, CommentEditor | | `HandsontableModal.vendor-styles.ts` | `handsontable` (non-full variant) | HandsontableModal | | `DateRangePicker.vendor-styles.ts` | `react-datepicker` | DateRangePicker | | `RevisionDiff.vendor-styles.ts` | `diff2html` | RevisionDiff | | `DrawioViewerWithEditButton.vendor-styles.ts` | `@growi/remark-drawio` | DrawioViewerWithEditButton | | `ImageCropModal.vendor-styles.ts` | `react-image-crop` | ImageCropModal | | `Presentation.vendor-styles.ts` | `@growi/presentation` | Presentation, Slides | ## Adding New Vendor CSS 1. Create `{ComponentName}.vendor-styles.ts` next to the consuming component: ```typescript // @ts-nocheck import css from 'new-package/dist/style.css?inline'; const s = document.createElement('style'); s.textContent = css; document.head.appendChild(s); ``` 2. In the component, replace `import 'new-package/dist/style.css'` with: ```typescript import './ComponentName.vendor-styles.prebuilt'; ``` 3. Run `pnpm run pre:styles-components` (or let Turborepo handle it during `dev`/`build`) 4. The `.prebuilt.js` file is git-ignored and auto-generated **Decision guide**: If the CSS is needed on nearly every page, add it to the commons track (`vendor.scss`) instead. ## Font/Asset Handling When vendor CSS references external assets (e.g., KaTeX `@font-face` with `url(fonts/KaTeX_*.woff2)`): - Vite emits asset files to `src/assets/` during build - The `moveAssetsToPublic` plugin (in `vite.vendor-styles-components.ts`) relocates them to `public/static/fonts/` - URL references in prebuilt JS are rewritten from `/assets/` to `/static/fonts/` - Fonts are served by the existing `express.static(crowi.publicDir)` middleware - Both `public/static/fonts/` and `src/**/*.vendor-styles.prebuilt.ts` are git-ignored ## Build Pipeline Integration ``` turbo.json tasks: pre:styles-components → build (dependency) dev:pre:styles-components → dev (dependency) Inputs: vite.vendor-styles-components.ts, src/**/*.vendor-styles.ts, package.json Outputs: src/**/*.vendor-styles.prebuilt.ts, public/static/fonts/** ``` ## Important Caveats - **SSR**: CSS is injected via `<style>` tags at runtime — not available during SSR. Most consuming components use `next/dynamic({ ssr: false })`, so FOUC is not a practical concern - **`@ts-nocheck`**: Required because `?inline` is a Vite-specific import suffix not understood by TypeScript - **handsontable**: Must use `handsontable/dist/handsontable.css` (non-full, non-minified). The "full" variant (`handsontable.full.min.css`) contains IE CSS hacks (`*zoom:1`, `filter:alpha()`) that Turbopack's CSS parser (lightningcss) cannot parse. The "full" variant also includes Pikaday which is unused.
View on GitHub