Next.js Discord

Discord Forum

Trying to bundle my component library to be compatible with RSC

Unanswered
Yellow and black potter wasp posted this in #help-forum
Open in Discord
Yellow and black potter waspOP
I am trying to bundle my own component library via tsup https://tsup.egoist.dev/

import { defineConfig } from 'tsup'
import fs from 'fs/promises'

export default defineConfig({
  clean: true,
  format: ['esm', 'cjs'],
  entry: ['src/index.ts'],
  outDir: 'dist',
  dts: true,
  external: ['react', 'react-dom'],
  async onSuccess() {
    await fs.copyFile('src/tailwind.css', 'dist/tailwind.css')
    await fs.copyFile('static/gellix.woff2', 'dist/gellix.woff2')
  }
})

this creates the following output:
dist: gellix.woff2 index.cjs index.d.cts index.d.ts index.js tailwind.css

however it seems the use client directives in my components are lost.

in my index.ts I basically re-export all my components:
export * from '@/components/ui/accordion'
export * from '@/components/ui/alert'
export * from '@/components/ui/alert-dialog'
export * from '@/components/ui/aspect-ratio'
export * from '@/components/ui/avatar'
export * from '@/components/ui/badge'
...


But I cannot import any of them in my NextJS server components without marking the entire component as 'client ony'. How do I get around this. I do not want to make wrapper client components in my NextJS application for all of the components from my design system

5 Replies

Yellow and black potter waspOP
This is the error that I get when importing a component from my design system into a nextjs server component:
TypeError: (0 , import_react.createContext) is not a function
    at __TURBOPACK__module__evaluation__ (app/page.tsx:1:1)
    at __TURBOPACK__module__evaluation__ (.next/server/chunks/ssr/[root-of-the-server]__7f2aaf77._.js:54:47)
    at Object.<anonymous> (.next/server/app/page.js:22:3)
> 1 | import { Badge } from "@grey/design-system";
    | ^
  2 |
  3 | export default function Home() {
  4 |   return ( {
  page: '/'
}

The badge component in my component library does not use any client side directives:
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'

import { cn } from '@/lib/utils'

const badgeVariants = cva(
  'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
  {
    variants: {
      variant: {
        default:
          'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
        secondary:
          'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
        destructive:
          'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
        outline:
          'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground'
      }
    },
    defaultVariants: {
      variant: 'default'
    }
  }
)

function Badge({
  className,
  variant,
  asChild = false,
  ...props
}: React.ComponentProps<'span'> &
  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
  const Comp = asChild ? Slot : 'span'

  return (
    <Comp
      data-slot='badge'
      className={cn(badgeVariants({ variant }), className)}
      {...props}
    />
  )
}

export { Badge, badgeVariants }
Yellow and black potter waspOP
I added
import { defineConfig } from 'tsup'
import fs from 'fs/promises'

export default defineConfig({
  clean: true,
  format: ['esm', 'cjs'],
  entry: ['src/index.ts'],
  outDir: 'dist',
  dts: true,
  external: ['react', 'react-dom'],
  async onSuccess() {
    await fs.copyFile('src/tailwind.css', 'dist/tailwind.css')
    await fs.copyFile('static/gellix.woff2', 'dist/gellix.woff2')
  },
+  esbuildOptions(options) {
+    options.banner = {
+      js: '"use client"'
+    }
+  },
})

it feels kinda gross, but seems to work. not sure if there is a better solution
a possible solution may be to use a transpiler like swc or tsc instead of a bundler like tsup?