86
87
const ReactDOMServerDispatcher = enableFloat
88
? {
89
+ prefetchDNS,
90
+ preconnect,
91
preload,
92
preinit,
93
}
3466
}
3467
charsetChunks.length = 0;
3468
3469
+ // emit preconnect resources
3470
+ resources.preconnects.forEach(flushResourceInPreamble, destination);
3471
+ resources.preconnects.clear();
3472
+
3473
const preconnectChunks = responseState.preconnectChunks;
3474
for (i = 0; i < preconnectChunks.length; i++) {
3475
writeChunk(destination, preconnectChunks[i]);
3565
// We omit charsetChunks because we have already sent the shell and if it wasn't
3566
// already sent it is too late now.
3567
3568
+ resources.preconnects.forEach(flushResourceLate, destination);
3569
+ resources.preconnects.clear();
3570
+
3571
const preconnectChunks = responseState.preconnectChunks;
3572
for (i = 0; i < preconnectChunks.length; i++) {
3573
writeChunk(destination, preconnectChunks[i]);
4077
// This generally only makes sense for Resources other than PreloadResource
4078
const PreloadFlushed /* */ = 0b1000;
4079
4071
-type TResource<T: 'stylesheet' | 'style' | 'script' | 'preload', P> = {
4080
+type TResource<
4081
+ T: 'stylesheet' | 'style' | 'script' | 'preload' | 'preconnect',
4082
+ P,
4083
+> = {
4084
type: T,
4085
chunks: Array<Chunk | PrecomputedChunk>,
4086
state: ResourceStateTag,
4111
| ImperativeResourceDEV
4112
| ImplicitResourceDEV;
4113
4114
+type PreconnectProps = {
4115
+ rel: 'preconnect' | 'dns-prefetch',
4116
+ href: string,
4117
+ [string]: mixed,
4118
+};
4119
+type PreconnectResource = TResource<'preconnect', null>;
4120
+
4121
type PreloadProps = {
4122
rel: 'preload',
4123
as: string,
4150
};
4151
type ScriptResource = TResource<'script', null>;
4152
4134
-type Resource = StyleResource | ScriptResource | PreloadResource;
4153
+type Resource =
4154
+ | StyleResource
4155
+ | ScriptResource
4156
+ | PreloadResource
4157
+ | PreconnectResource;
4158
4159
export type Resources = {
4160
// Request local cache
4161
preloadsMap: Map<string, PreloadResource>,
4162
+ preconnectsMap: Map<string, PreconnectResource>,
4163
stylesMap: Map<string, StyleResource>,
4164
scriptsMap: Map<string, ScriptResource>,
4165
4166
// Flushing queues for Resource dependencies
4167
+ preconnects: Set<PreconnectResource>,
4168
fontPreloads: Set<PreloadResource>,
4169
// usedImagePreloads: Set<PreloadResource>,
4170
precedences: Map<string, Set<StyleResource>>,
4186
return {
4187
// persistent
4188
preloadsMap: new Map(),
4189
+ preconnectsMap: new Map(),
4190
stylesMap: new Map(),
4191
scriptsMap: new Map(),
4192
4193
// cleared on flush
4194
+ preconnects: new Set(),
4195
fontPreloads: new Set(),
4196
// usedImagePreloads: new Set(),
4197
precedences: new Map(),
4225
return `[${as}]${href}`;
4226
}
4227
4228
+export function prefetchDNS(href: string, options?: mixed) {
4229
+ if (!currentResources) {
4230
+ // While we expect that preconnect calls are primarily going to be observed
4231
+ // during render because effects and events don't run on the server it is
4232
+ // still possible that these get called in module scope. This is valid on
4233
+ // the client since there is still a document to interact with but on the
4234
+ // server we need a request to associate the call to. Because of this we
4235
+ // simply return and do not warn.
4236
+ return;
4237
+ }
4238
+ const resources = currentResources;
4239
+ if (__DEV__) {
4240
+ if (typeof href !== 'string' || !href) {
4241
+ console.error(
4242
+ 'ReactDOM.prefetchDNS(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.',
4243
+ getValueDescriptorExpectingObjectForWarning(href),
4244
+ );
4245
+ } else if (options != null) {
4246
+ if (
4247
+ typeof options === 'object' &&
4248
+ options.hasOwnProperty('crossOrigin')
4249
+ ) {
4250
+ console.error(
4251
+ 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
4252
+ getValueDescriptorExpectingEnumForWarning(options),
4253
+ );
4254
+ } else {
4255
+ console.error(
4256
+ 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
4257
+ getValueDescriptorExpectingEnumForWarning(options),
4258
+ );
4259
+ }
4260
+ }
4261
+ }
4262
+
4263
+ if (typeof href === 'string' && href) {
4264
+ const key = getResourceKey('prefetchDNS', href);
4265
+ let resource = resources.preconnectsMap.get(key);
4266
+ if (!resource) {
4267
+ resource = {
4268
+ type: 'preconnect',
4269
+ chunks: [],
4270
+ state: NoState,
4271
+ props: null,
4272
+ };
4273
+ resources.preconnectsMap.set(key, resource);
4274
+ pushLinkImpl(
4275
+ resource.chunks,
4276
+ ({href, rel: 'dns-prefetch'}: PreconnectProps),
4277
+ );
4278
+ }
4279
+ resources.preconnects.add(resource);
4280
+ }
4281
+}
4282
+
4283
+export function preconnect(href: string, options?: {crossOrigin?: string}) {
4284
+ if (!currentResources) {
4285
+ // While we expect that preconnect calls are primarily going to be observed
4286
+ // during render because effects and events don't run on the server it is
4287
+ // still possible that these get called in module scope. This is valid on
4288
+ // the client since there is still a document to interact with but on the
4289
+ // server we need a request to associate the call to. Because of this we
4290
+ // simply return and do not warn.
4291
+ return;
4292
+ }
4293
+ const resources = currentResources;
4294
+ if (__DEV__) {
4295
+ if (typeof href !== 'string' || !href) {
4296
+ console.error(
4297
+ 'ReactDOM.preconnect(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.',
4298
+ getValueDescriptorExpectingObjectForWarning(href),
4299
+ );
4300
+ } else if (options != null && typeof options !== 'object') {
4301
+ console.error(
4302
+ 'ReactDOM.preconnect(): Expected the `options` argument (second) to be an object but encountered %s instead. The only supported option at this time is `crossOrigin` which accepts a string.',
4303
+ getValueDescriptorExpectingEnumForWarning(options),
4304
+ );
4305
+ } else if (options != null && typeof options.crossOrigin !== 'string') {
4306
+ console.error(
4307
+ 'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered %s instead. Try removing this option or passing a string value instead.',
4308
+ getValueDescriptorExpectingObjectForWarning(options.crossOrigin),
4309
+ );
4310
+ }
4311
+ }
4312
+
4313
+ if (typeof href === 'string' && href) {
4314
+ const crossOrigin =
4315
+ options == null || typeof options.crossOrigin !== 'string'
4316
+ ? null
4317
+ : options.crossOrigin === 'use-credentials'
4318
+ ? 'use-credentials'
4319
+ : '';
4320
+
4321
+ const key = `[preconnect][${
4322
+ crossOrigin === null ? 'null' : crossOrigin
4323
+ }]${href}`;
4324
+ let resource = resources.preconnectsMap.get(key);
4325
+ if (!resource) {
4326
+ resource = {
4327
+ type: 'preconnect',
4328
+ chunks: [],
4329
+ state: NoState,
4330
+ props: null,
4331
+ };
4332
+ resources.preconnectsMap.set(key, resource);
4333
+ pushLinkImpl(
4334
+ resource.chunks,
4335
+ ({rel: 'preconnect', href, crossOrigin}: PreconnectProps),
4336
+ );
4337
+ }
4338
+ resources.preconnects.add(resource);
4339
+ }
4340
+}
4341
+
4342
type PreloadAs = 'style' | 'font' | 'script';
4343
type PreloadOptions = {
4344
as: PreloadAs,