-
Notifications
You must be signed in to change notification settings - Fork 27.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
codemod: add next/dynamic imports codemod (#67126)
### What Provide a codemod to transform the promise of the access to named export properties in dynamic import `next/dynamic`, this codemod transform all the `next/dynamic` imports to ensure returning an object value with `default` property, aligning with what `React.lazy` is returning ### Why Follow up for #66990 It's not allowed to do dynamic import and access it's named export while using `next/dynamic` in server component, and the dynamic import module is from a client component. It's like accessing the nested client side property of a module
- Loading branch information
Showing
12 changed files
with
164 additions
and
0 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
...orms/__testfixtures__/next-dynamic-access-named-export/access-named-export-block.input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicComponent = dynamic( | ||
() => import('./component').then(mod => { | ||
return mod.default; | ||
}) | ||
) |
9 changes: 9 additions & 0 deletions
9
...rms/__testfixtures__/next-dynamic-access-named-export/access-named-export-block.output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicComponent = dynamic( | ||
() => import('./component').then(mod => { | ||
return { | ||
default: mod.default | ||
}; | ||
}) | ||
) |
5 changes: 5 additions & 0 deletions
5
...transforms/__testfixtures__/next-dynamic-access-named-export/access-named-export.input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicComponent = dynamic( | ||
() => import('./component').then(mod => mod.Component) | ||
) |
7 changes: 7 additions & 0 deletions
7
...ransforms/__testfixtures__/next-dynamic-access-named-export/access-named-export.output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicComponent = dynamic( | ||
() => import('./component').then(mod => ({ | ||
default: mod.Component | ||
})) | ||
) |
5 changes: 5 additions & 0 deletions
5
...orms/__testfixtures__/next-dynamic-access-named-export/no-access-to-named-export.input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicComponent = dynamic( | ||
() => import('./component') | ||
) |
5 changes: 5 additions & 0 deletions
5
...rms/__testfixtures__/next-dynamic-access-named-export/no-access-to-named-export.output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicComponent = dynamic( | ||
() => import('./component') | ||
) |
7 changes: 7 additions & 0 deletions
7
..._testfixtures__/next-dynamic-access-named-export/non-next-dynamic-dynamic-import.input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import dynamic from 'my-dynamic-call' | ||
|
||
const DynamicComponent = dynamic( | ||
() => import('./component').then(mod => { | ||
return mod.Component; | ||
}) | ||
) |
7 changes: 7 additions & 0 deletions
7
...testfixtures__/next-dynamic-access-named-export/non-next-dynamic-dynamic-import.output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import dynamic from 'my-dynamic-call' | ||
|
||
const DynamicComponent = dynamic( | ||
() => import('./component').then(mod => { | ||
return mod.Component; | ||
}) | ||
) |
7 changes: 7 additions & 0 deletions
7
...ansforms/__testfixtures__/next-dynamic-access-named-export/unsupported-transform.input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicImportSourceNextDynamic1 = dynamic(() => import(source).then(mod => mod)) | ||
const DynamicImportSourceNextDynamic2 = dynamic(async () => { | ||
const mod = await import(source) | ||
return mod.Component | ||
}) |
7 changes: 7 additions & 0 deletions
7
...nsforms/__testfixtures__/next-dynamic-access-named-export/unsupported-transform.output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import dynamic from 'next/dynamic' | ||
|
||
const DynamicImportSourceNextDynamic1 = dynamic(() => import(source).then(mod => mod)) | ||
const DynamicImportSourceNextDynamic2 = dynamic(async () => { | ||
const mod = await import(source) | ||
return mod.Component | ||
}) |
17 changes: 17 additions & 0 deletions
17
packages/next-codemod/transforms/__tests__/next-dynamic-access-named-export.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* global jest */ | ||
jest.autoMockOff() | ||
const defineTest = require('jscodeshift/dist/testUtils').defineTest | ||
const { readdirSync } = require('fs') | ||
const { join } = require('path') | ||
|
||
const fixtureDir = 'next-dynamic-access-named-export' | ||
const fixtureDirPath = join(__dirname, '..', '__testfixtures__', fixtureDir) | ||
const fixtures = readdirSync(fixtureDirPath) | ||
.filter(file => file.endsWith('.input.js')) | ||
.map(file => file.replace('.input.js', '')) | ||
|
||
|
||
for (const fixture of fixtures) { | ||
const prefix = `${fixtureDir}/${fixture}`; | ||
defineTest(__dirname, fixtureDir, null, prefix, { parser: 'js' }); | ||
} |
81 changes: 81 additions & 0 deletions
81
packages/next-codemod/transforms/next-dynamic-access-named-export.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import type { FileInfo, API, ImportDeclaration } from 'jscodeshift' | ||
|
||
export default function transformer(file: FileInfo, api: API) { | ||
const j = api.jscodeshift | ||
const root = j(file.source) | ||
|
||
// Find the import declaration for 'next/dynamic' | ||
const dynamicImportDeclaration = root.find(j.ImportDeclaration, { | ||
source: { value: 'next/dynamic' }, | ||
}) | ||
|
||
// If the import declaration is found | ||
if (dynamicImportDeclaration.size() > 0) { | ||
const importDecl: ImportDeclaration = dynamicImportDeclaration.get(0).node | ||
const dynamicImportName = importDecl.specifiers?.[0]?.local?.name | ||
|
||
if (!dynamicImportName) { | ||
return root.toSource() | ||
} | ||
// Find call expressions where the callee is the imported 'dynamic' | ||
root | ||
.find(j.CallExpression, { | ||
callee: { name: dynamicImportName }, | ||
}) | ||
.forEach((path) => { | ||
const arrowFunction = path.node.arguments[0] | ||
|
||
// Ensure the argument is an ArrowFunctionExpression | ||
if (arrowFunction && arrowFunction.type === 'ArrowFunctionExpression') { | ||
const importCall = arrowFunction.body | ||
|
||
// Ensure the parent of the import call is a CallExpression with a .then | ||
if ( | ||
importCall && | ||
importCall.type === 'CallExpression' && | ||
importCall.callee.type === 'MemberExpression' && | ||
'name' in importCall.callee.property && | ||
importCall.callee.property.name === 'then' | ||
) { | ||
const thenFunction = importCall.arguments[0] | ||
// handle case of block statement case `=> { return mod.Component }` | ||
// transform to`=> { return { default: mod.Component } }` | ||
if ( | ||
thenFunction && | ||
thenFunction.type === 'ArrowFunctionExpression' && | ||
thenFunction.body.type === 'BlockStatement' | ||
) { | ||
const returnStatement = thenFunction.body.body[0] | ||
// Ensure the body of the arrow function has a return statement with a MemberExpression | ||
if ( | ||
returnStatement && | ||
returnStatement.type === 'ReturnStatement' && | ||
returnStatement.argument?.type === 'MemberExpression' | ||
) { | ||
returnStatement.argument = j.objectExpression([ | ||
j.property( | ||
'init', | ||
j.identifier('default'), | ||
returnStatement.argument | ||
), | ||
]) | ||
} | ||
} | ||
// handle case `=> mod.Component` | ||
// transform to`=> ({ default: mod.Component })` | ||
if ( | ||
thenFunction && | ||
thenFunction.type === 'ArrowFunctionExpression' && | ||
thenFunction.body.type === 'MemberExpression' | ||
) { | ||
thenFunction.body = j.objectExpression([ | ||
j.property('init', j.identifier('default'), thenFunction.body), | ||
]) | ||
} | ||
} | ||
} | ||
}) | ||
} | ||
|
||
return root.toSource() | ||
} |