Skip to content

Commit e3a2b72

Browse files
authored
Automatically inject CSS resources into RSC stream (#10067)
1 parent 337c31c commit e3a2b72

File tree

29 files changed

+1109
-285
lines changed

29 files changed

+1109
-285
lines changed

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ packages/*/*/test/dist
66
packages/*/*/test/mochareporters.json
77
packages/*/*/test/fixtures
88
packages/*/*/test/.parcel-cache
9+
packages/examples/react-server-components/src/server.js
910
vendor
1011
tmp
1112
.parcel-cache

packages/core/integration-tests/test/output-formats.js

+11-5
Original file line numberDiff line numberDiff line change
@@ -552,12 +552,18 @@ describe('output formats', function () {
552552
};
553553

554554
let out = [];
555-
await run(b, {
556-
require: () => external,
557-
output(o) {
558-
out.push(o);
555+
await run(
556+
b,
557+
{
558+
output(o) {
559+
out.push(o);
560+
},
559561
},
560-
});
562+
{},
563+
{
564+
external: () => external,
565+
},
566+
);
561567

562568
assert.deepEqual(out, [1, 2]);
563569
});

packages/core/integration-tests/test/react-server.js

+98-79
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,8 @@ describe('react server components', function () {
3131
targets: {
3232
default: {
3333
context: 'react-server',
34-
outputFormat: 'commonjs',
3534
},
3635
},
37-
'@parcel/resolver-default': {
38-
packageExports: true,
39-
},
40-
'@parcel/bundler-default': {
41-
minBundleSize: 0,
42-
},
4336
}),
4437
);
4538

@@ -383,7 +376,6 @@ describe('react server components', function () {
383376
'client.jsx',
384377
'react-jsx-dev-runtime.development.js',
385378
'index.js',
386-
'jsx-dev-runtime.js',
387379
'react.development.js',
388380
],
389381
},
@@ -487,7 +479,6 @@ describe('react server components', function () {
487479
assets: [
488480
'client.jsx',
489481
'index.js',
490-
'jsx-dev-runtime.js',
491482
'react-jsx-dev-runtime.development.js',
492483
'react.development.js',
493484
],
@@ -610,7 +601,6 @@ describe('react server components', function () {
610601
// Client: shared bundle.
611602
assets: [
612603
'index.js',
613-
'jsx-dev-runtime.js',
614604
'react-jsx-dev-runtime.development.js',
615605
'react.development.js',
616606
],
@@ -620,32 +610,27 @@ describe('react server components', function () {
620610
);
621611
});
622612

623-
it('should support <Resources>', async function () {
613+
it('should support inject CSS resources', async function () {
624614
await fsFixture(overlayFS, dir)`
615+
index.jsx:
616+
import {Server} from './Page.jsx';
617+
function render() {
618+
return <Server />;
619+
}
620+
output = {render};
621+
625622
Page.jsx:
626-
import {Client} from './Client';
627-
import {Resources} from '@parcel/runtime-rsc';
623+
"use server-entry";
628624
import './server.css';
629625
export function Server() {
630-
return <><Resources /><Client /></>;
626+
return <h1>Server</h1>;
631627
}
632-
output = {Resources};
633628
634629
server.css:
635630
.server { color: red }
636-
637-
Client.jsx:
638-
"use client";
639-
import './client.css';
640-
export function Client() {
641-
return <p>Client</p>;
642-
}
643-
644-
client.css:
645-
.client { color: green }
646631
`;
647632

648-
let b = await bundle(path.join(dir, '/Page.jsx'), {
633+
let b = await bundle(path.join(dir, '/index.jsx'), {
649634
inputFS: overlayFS,
650635
targets: ['default'],
651636
defaultTargetOptions: {
@@ -657,22 +642,24 @@ describe('react server components', function () {
657642
b,
658643
[
659644
{
660-
assets: ['Page.jsx', 'resources.js'],
645+
assets: ['index.jsx'],
661646
},
662647
{
663-
assets: ['Client.jsx'],
648+
assets: ['Page.jsx'],
664649
},
665650
{
666-
assets: ['server.css', 'client.css'],
651+
assets: ['server.css'],
667652
},
668653
],
669654
{skipNodeModules: true},
670655
);
671656

672-
let res = (await run(b, null, {require: false})).output;
673-
let resources = res.Resources();
657+
let res = (await run(b, {output: null}, {require: false})).output;
658+
let output = res.render();
674659

675-
let link = resources.props.children;
660+
output.type.$$typeof;
661+
let rendered = output.type();
662+
let link = rendered.props.children[0];
676663
assert.equal(link.type, 'link');
677664
assert.equal(link.props.rel, 'stylesheet');
678665
assert.equal(link.props.precedence, 'default');
@@ -687,14 +674,17 @@ describe('react server components', function () {
687674

688675
it('should generate an inline script for bootstrap with "use client-entry"', async function () {
689676
await fsFixture(overlayFS, dir)`
677+
index.jsx:
678+
import {Server} from './Page';
679+
output = {Server};
680+
690681
Page.jsx:
682+
"use server-entry";
691683
import {Client} from './Client';
692-
import {Resources} from '@parcel/runtime-rsc';
693684
import './client-entry.jsx';
694685
export function Server() {
695-
return <><Resources /><Client /></>;
686+
return <Client />;
696687
}
697-
output = {Resources};
698688
699689
client-entry.jsx:
700690
"use client-entry";
@@ -707,7 +697,7 @@ describe('react server components', function () {
707697
}
708698
`;
709699

710-
let b = await bundle(path.join(dir, '/Page.jsx'), {
700+
let b = await bundle(path.join(dir, '/index.jsx'), {
711701
inputFS: overlayFS,
712702
targets: ['default'],
713703
defaultTargetOptions: {
@@ -719,7 +709,10 @@ describe('react server components', function () {
719709
b,
720710
[
721711
{
722-
assets: ['Page.jsx', 'resources.js'],
712+
assets: ['index.jsx'],
713+
},
714+
{
715+
assets: ['Page.jsx'],
723716
},
724717
{
725718
assets: ['client-entry.jsx', 'Client.jsx'],
@@ -729,12 +722,11 @@ describe('react server components', function () {
729722
);
730723

731724
let res = await run(b, null, {require: false});
732-
let resources = res.output.Resources();
733725
let parcelRequireName = nullthrows(
734726
Object.keys(res).find(k => /^parcelRequire(.+)$/.test(k)),
735727
);
736728
let clientEntry;
737-
b.getBundles()[1].traverseAssets(a => {
729+
b.getBundles()[2].traverseAssets(a => {
738730
if (
739731
Array.isArray(a.meta.directives) &&
740732
a.meta.directives.includes('use client-entry')
@@ -743,41 +735,35 @@ describe('react server components', function () {
743735
}
744736
});
745737

746-
let script = resources.props.children;
747-
assert.equal(script.type, 'script');
748-
assert.equal(script.props.type, 'module');
749738
assert.equal(
750-
script.props.children,
751-
`import "/${path.basename(
752-
b.getBundles()[1].filePath,
753-
)}";${parcelRequireName}("${b.getAssetPublicId(
739+
res.output.Server.bootstrapScript,
740+
`Promise.all([import("/${path.basename(
741+
b.getBundles()[2].filePath,
742+
)}")]).then(()=>${parcelRequireName}("${b.getAssetPublicId(
754743
nullthrows(clientEntry),
755-
)}")`,
744+
)}"))`,
756745
);
757746
});
758747

759-
it('dynamic import works with client components', async function () {
748+
it('dynamic import of server component to client component', async function () {
760749
await fsFixture(overlayFS, dir)`
761750
Page.jsx:
762-
import {Resources} from '@parcel/runtime-rsc/resources';
763751
import './server.css';
764752
export async function Server() {
765753
let {Client} = await import('./Client');
766-
return [<Resources />, <Client />];
754+
return <Client />;
767755
}
768-
output = {Server, Resources};
756+
output = {Server};
769757
770758
server.css:
771759
.server { color: red }
772760
773761
Client.jsx:
774762
"use client";
775-
import {Resources} from '@parcel/runtime-rsc/resources';
776763
import './client.css';
777764
export function Client() {
778-
return <p><Resources />Client</p>;
765+
return <p>Client</p>;
779766
}
780-
output = {Resources};
781767
782768
client.css:
783769
.client { color: green }
@@ -795,10 +781,10 @@ describe('react server components', function () {
795781
b,
796782
[
797783
{
798-
assets: ['Page.jsx', 'resources.js'],
784+
assets: ['Page.jsx'],
799785
},
800786
{
801-
assets: ['Client.jsx', 'resources.js'],
787+
assets: ['Client.jsx'],
802788
},
803789
{
804790
assets: ['server.css'],
@@ -811,19 +797,20 @@ describe('react server components', function () {
811797
);
812798

813799
let res = (await run(b, null, {require: false})).output;
814-
let result = await res.Server();
800+
let output = await res.Server();
801+
output.type.$$typeof;
802+
let result = output.type();
815803
assert.equal(
816-
result[1].type.$$typeof,
804+
result.props.children[1].type.$$typeof,
817805
Symbol.for('react.client.reference'),
818806
);
819-
assert.equal(result[1].type.$$name, 'Client');
820-
assert.equal(typeof result[1].type.$$id, 'string');
821-
assert.deepEqual(result[1].type.$$bundles, [
807+
assert.equal(result.props.children[1].type.$$name, 'Client');
808+
assert.equal(typeof result.props.children[1].type.$$id, 'string');
809+
assert.deepEqual(result.props.children[1].type.$$bundles, [
822810
path.basename(b.getBundles()[1].filePath),
823811
]);
824812

825-
let resources = res.Resources();
826-
let link = resources.props.children;
813+
let link = result.props.children[0];
827814
assert.equal(link.type, 'link');
828815
assert.equal(link.props.rel, 'stylesheet');
829816
assert.equal(link.props.precedence, 'default');
@@ -834,31 +821,61 @@ describe('react server components', function () {
834821
nullthrows(
835822
b
836823
.getBundles()
837-
.find(b => b.type === 'css' && b.name.startsWith('Page')),
824+
.find(b => b.type === 'css' && b.name.startsWith('Client')),
838825
).filePath,
839826
),
840827
);
828+
});
841829

842-
let entryAsset;
843-
b.getBundles()[1].traverseAssets(a => {
844-
if (a.filePath.endsWith('Client.jsx')) {
845-
entryAsset = a;
830+
it('dynamic import of server component to server component', async function () {
831+
await fsFixture(overlayFS, dir)`
832+
Page.jsx:
833+
export async function Server() {
834+
let {Dynamic} = await import('./Dynamic');
835+
return <Dynamic />;
836+
}
837+
output = {Server};
838+
839+
Dynamic.jsx:
840+
import './dynamic.css';
841+
export function Dynamic() {
842+
return <p>Dynamic</p>;
846843
}
844+
845+
dynamic.css:
846+
.dynamic { color: green }
847+
`;
848+
849+
let b = await bundle(path.join(dir, '/Page.jsx'), {
850+
inputFS: overlayFS,
851+
targets: ['default'],
852+
defaultTargetOptions: {
853+
shouldScopeHoist,
854+
},
847855
});
848856

849-
let client = (await runBundle(
857+
assertBundles(
850858
b,
851-
b.getBundles()[1],
852-
{output: null},
853-
{require: false, entryAsset},
854-
): any);
855-
let parcelRequire = nullthrows(
856-
Object.keys(client).find(k => k.startsWith('parcelRequire')),
859+
[
860+
{
861+
assets: ['Page.jsx'],
862+
},
863+
{
864+
assets: ['Dynamic.jsx'],
865+
},
866+
{
867+
assets: ['dynamic.css'],
868+
},
869+
],
870+
{skipNodeModules: true},
857871
);
858-
client[parcelRequire](b.getAssetPublicId(nullthrows(entryAsset)));
859-
resources = client.output.Resources();
860872

861-
link = resources.props.children;
873+
let res = (await run(b, null, {require: false})).output;
874+
let output = await res.Server();
875+
output.type.$$typeof;
876+
let result = output.type();
877+
878+
let link = result.props.children[0];
862879
assert.equal(link.type, 'link');
863880
assert.equal(link.props.rel, 'stylesheet');
864881
assert.equal(link.props.precedence, 'default');
@@ -869,7 +886,9 @@ describe('react server components', function () {
869886
nullthrows(
870887
b
871888
.getBundles()
872-
.find(b => b.type === 'css' && b.name.startsWith('Client')),
889+
.find(
890+
b => b.type === 'css' && b.name.startsWith('Dynamic'),
891+
),
873892
).filePath,
874893
),
875894
);

0 commit comments

Comments
 (0)