Skip to content

Commit

Permalink
Handle async module for client components
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Aug 25, 2022
1 parent d267c30 commit a3c5c38
Show file tree
Hide file tree
Showing 17 changed files with 12,235 additions and 4,647 deletions.
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -177,8 +177,8 @@
"react-17": "npm:react@17.0.2",
"react-dom": "18.2.0",
"react-dom-17": "npm:react-dom@17.0.2",
"react-dom-exp": "npm:react-dom@0.0.0-experimental-6ef466c68-20220816",
"react-exp": "npm:react@0.0.0-experimental-6ef466c68-20220816",
"react-dom-exp": "npm:react-dom@0.0.0-experimental-c8b778b7f-20220825",
"react-exp": "npm:react@0.0.0-experimental-c8b778b7f-20220825",
"react-ssr-prepass": "1.0.8",
"react-virtualized": "9.22.3",
"relay-compiler": "13.0.2",
Expand Down
177 changes: 0 additions & 177 deletions packages/next/build/webpack/loaders/next-flight-client-loader.ts

This file was deleted.

@@ -0,0 +1,24 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export default async function transformSource(
this: any,
source: string
): Promise<string> {
const { resourcePath } = this

const transformedSource = source
if (typeof transformedSource !== 'string') {
throw new Error('Expected source to have been transformed to a string.')
}

const output = `
const { createProxy } = require("next/dist/build/webpack/loaders/next-flight-client-loader/module-proxy")\n
module.exports = createProxy(${JSON.stringify(resourcePath)})
`
return output
}
@@ -0,0 +1,99 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// Modified from https://github.com/facebook/react/blob/main/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js

const MODULE_REFERENCE = Symbol.for('react.module.reference')
const PROMISE_PROTOTYPE = Promise.prototype

const proxyHandlers: ProxyHandler<object> = {
get: function (target: any, name: string, receiver: any) {
switch (name) {
// These names are read by the Flight runtime if you end up using the exports object.
case '$$typeof':
// These names are a little too common. We should probably have a way to
// have the Flight runtime extract the inner target instead.
return target.$$typeof
case 'filepath':
return target.filepath
case 'name':
return target.name
case 'async':
return target.async
// We need to special case this because createElement reads it if we pass this
// reference.
case 'defaultProps':
return undefined
case '__esModule':
// Something is conditionally checking which export to use. We'll pretend to be
// an ESM compat module but then we'll check again on the client.
target.default = {
$$typeof: MODULE_REFERENCE,
filepath: target.filepath,
// This a placeholder value that tells the client to conditionally use the
// whole object or just the default export.
name: '',
async: target.async,
}
return true
case 'then':
if (!target.async) {
// If this module is expected to return a Promise (such as an AsyncModule) then
// we should resolve that with a client reference that unwraps the Promise on
// the client.
const then = function then(
resolve: (res: any) => void,
reject: (err: any) => void
) {
const moduleReference: Record<string, any> = {
$$typeof: MODULE_REFERENCE,
filepath: target.filepath,
name: '*', // Represents the whole object instead of a particular import.
async: true,
}
return Promise.resolve(
resolve(new Proxy(moduleReference, proxyHandlers))
)
}
// If this is not used as a Promise but is treated as a reference to a `.then`
// export then we should treat it as a reference to that name.
then.$$typeof = MODULE_REFERENCE
then.filepath = target.filepath
// then.name is conveniently already "then" which is the export name we need.
// This will break if it's minified though.
return then
}
}
let cachedReference = target[name]
if (!cachedReference) {
cachedReference = target[name] = {
$$typeof: MODULE_REFERENCE,
filepath: target.filepath,
name: name,
async: target.async,
}
}
return cachedReference
},
getPrototypeOf(target: object) {
// Pretend to be a Promise in case anyone asks.
return PROMISE_PROTOTYPE
},
set: function () {
throw new Error('Cannot assign to a client module from a server module.')
},
}

export function createProxy(moduleId: string) {
const moduleReference = {
$$typeof: MODULE_REFERENCE,
filepath: moduleId,
name: '*', // Represents the whole object instead of a particular import.
async: false,
}
return new Proxy(moduleReference, proxyHandlers)
}

0 comments on commit a3c5c38

Please sign in to comment.