@@ -2,6 +2,7 @@ import babel from '@rollup/plugin-babel'
22import { defineConfig } from 'rollup'
33import typescript from 'rollup-plugin-typescript2'
44import packageJson from './package.json' with { type : 'json' }
5+ import MagicString from 'magic-string'
56
67const dependencies = [
78 ...Object . keys ( packageJson . peerDependencies ?? { } ) ,
@@ -26,9 +27,111 @@ export default defineConfig({
2627 extensions : [ '.ts' , '.tsx' ] ,
2728 babelHelpers : 'bundled' ,
2829 } ) ,
30+ /**
31+ * This custom rollup plugin allows us to preserve directives in source
32+ * code, such as "use client", in order to support React Server Components.
33+ *
34+ * The source for this plugin is inspired by:
35+ * https://github.com/Ephem/rollup-plugin-preserve-directives
36+ */
37+ {
38+ name : 'preserve-directives' ,
39+ transform ( code ) {
40+ const ast = this . parse ( code )
41+ if ( ast . type !== 'Program' || ! ast . body ) {
42+ return {
43+ code,
44+ ast,
45+ map : null ,
46+ }
47+ }
48+
49+ let hasClientDirective = false
50+
51+ for ( const node of ast . body ) {
52+ if ( ! node ) {
53+ continue
54+ }
55+
56+ if ( node . type !== 'ExpressionStatement' ) {
57+ continue
58+ }
59+
60+ if ( node . directive === 'use client' ) {
61+ hasClientDirective = true
62+ break
63+ }
64+ }
65+
66+ if ( hasClientDirective ) {
67+ return {
68+ code,
69+ ast,
70+ map : null ,
71+ meta : {
72+ hasClientDirective : true ,
73+ } ,
74+ }
75+ }
76+
77+ return {
78+ code,
79+ ast,
80+ map : null ,
81+ }
82+ } ,
83+ renderChunk : {
84+ order : 'post' ,
85+ handler ( code , chunk , options ) {
86+ // If `preserveModules` is not set to true, we can't be sure if the client
87+ // directive corresponds to the whole chunk or just a part of it.
88+ if ( ! options . preserveModules ) {
89+ return undefined
90+ }
91+
92+ let chunkHasClientDirective = false
93+
94+ for ( const moduleId of Object . keys ( chunk . modules ) ) {
95+ const hasClientDirective = this . getModuleInfo ( moduleId ) ?. meta ?. hasClientDirective
96+ if ( hasClientDirective ) {
97+ chunkHasClientDirective = true
98+ break
99+ }
100+ }
101+
102+ if ( chunkHasClientDirective ) {
103+ const transformed = new MagicString ( code )
104+ transformed . prepend ( `"use client";\n` )
105+ const sourcemap = transformed . generateMap ( {
106+ includeContent : true ,
107+ } )
108+ return {
109+ code : transformed . toString ( ) ,
110+ map : sourcemap ,
111+ }
112+ }
113+
114+ return null
115+ } ,
116+ } ,
117+ } ,
29118 ] ,
119+ onwarn ( warning , defaultHandler ) {
120+ // Dependencies or modules may use "use client" as an indicator for React
121+ // Server Components that this module should only be loaded on the client.
122+ if ( warning . code === 'MODULE_LEVEL_DIRECTIVE' && warning . message . includes ( 'use client' ) ) {
123+ return
124+ }
125+
126+ if ( warning . code === 'CIRCULAR_DEPENDENCY' ) {
127+ throw warning
128+ }
129+
130+ defaultHandler ( warning )
131+ } ,
30132 output : {
31133 dir : 'dist' ,
32134 format : 'esm' ,
135+ preserveModules : true ,
33136 } ,
34137} )
0 commit comments