Skip to content

Commit

Permalink
Fix next/dynamic types for resolving named export module (vercel#43923)
Browse files Browse the repository at this point in the history
## Bug

Fixes: vercel#43915

- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)
  • Loading branch information
huozhi authored Dec 10, 2022
1 parent 6c1dd22 commit 3833aed
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 9 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ coverage
# test output
test/**/out*
test/**/next-env.d.ts
test/**/tsconfig.json
.DS_Store
/e2e-tests
test/tmp/**
Expand All @@ -46,4 +47,4 @@ test-timings.json

# Cache
*.tsbuildinfo
.swc/
.swc/
24 changes: 16 additions & 8 deletions packages/next/shared/lib/dynamic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ import React, { lazy, Suspense } from 'react'
import Loadable from './loadable'
import NoSSR from './dynamic-no-ssr'

type ComponentModule<P> = { default: React.ComponentType<P> }
type ComponentModule<P = {}> = { default: React.ComponentType<P> }

export type LoaderComponent<P = {}> = Promise<ComponentModule<P>>
export declare type LoaderComponent<P = {}> = Promise<
React.ComponentType<P> | ComponentModule<P>
>

export type Loader<P = {}> = () => LoaderComponent<P>
type NormalizedLoader<P = {}> = () => Promise<{
default: React.ComponentType<P>
}>

export declare type Loader<P = {}> =
| (() => LoaderComponent<P>)
| LoaderComponent<P>

export type LoaderMap = { [module: string]: () => Loader<any> }

Expand All @@ -26,8 +34,8 @@ export type DynamicOptionsLoadingProps = {
// Normalize loader to return the module as form { default: Component } for `React.lazy`.
// Also for backward compatible since next/dynamic allows to resolve a component directly with loader
// Client component reference proxy need to be converted to a module.
function convertModule<T>(mod: ComponentModule<T>) {
return { default: mod.default || mod }
function convertModule<P>(mod: React.ComponentType<P> | ComponentModule<P>) {
return { default: (mod as ComponentModule<P>).default || mod }
}

export type DynamicOptions<P = {}> = LoadableGeneratedOptions & {
Expand All @@ -50,7 +58,7 @@ export type LoadableFn<P = {}> = (
export type LoadableComponent<P = {}> = React.ComponentType<P>

export function noSSR<P = {}>(
LoadableInitializer: Loader,
LoadableInitializer: NormalizedLoader<P>,
loadableOptions: DynamicOptions<P>
): React.ComponentType<P> {
// Removing webpack and modules means react-loadable won't try preloading
Expand Down Expand Up @@ -118,7 +126,7 @@ export default function dynamic<P = {}>(
// Support for passing options, eg: dynamic(import('../hello-world'), {loading: () => <p>Loading something</p>})
loadableOptions = { ...loadableOptions, ...options }

const loaderFn = loadableOptions.loader as Loader<P>
const loaderFn = loadableOptions.loader as () => LoaderComponent<P>
const loader = () => loaderFn().then(convertModule)

// coming from build/babel/plugins/react-loadable-plugin.js
Expand All @@ -135,7 +143,7 @@ export default function dynamic<P = {}>(
if (typeof loadableOptions.ssr === 'boolean') {
if (!loadableOptions.ssr) {
delete loadableOptions.ssr
return noSSR(loader as Loader, loadableOptions)
return noSSR(loader, loadableOptions)
}
delete loadableOptions.ssr
}
Expand Down
3 changes: 3 additions & 0 deletions test/production/typescript-basic/app/components/named.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function NamedExport() {
return <>named-export</>
}
10 changes: 10 additions & 0 deletions test/production/typescript-basic/app/pages/dynamic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import dynamic from 'next/dynamic'

const NamedExport = dynamic(() =>
import('../components/named').then((mod) => mod.NamedExport)
)

export default function Dynamic() {
return <NamedExport />
}

0 comments on commit 3833aed

Please sign in to comment.