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
This commit is contained in:
parent
ea8020158e
commit
c8925c36db
12 changed files with 164 additions and 0 deletions
|
@ -0,0 +1,7 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
() => import('./component').then(mod => {
|
||||
return mod.default;
|
||||
})
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
() => import('./component').then(mod => {
|
||||
return {
|
||||
default: mod.default
|
||||
};
|
||||
})
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
() => import('./component').then(mod => mod.Component)
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
() => import('./component').then(mod => ({
|
||||
default: mod.Component
|
||||
}))
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
() => import('./component')
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
import dynamic from 'next/dynamic'
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
() => import('./component')
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
import dynamic from 'my-dynamic-call'
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
() => import('./component').then(mod => {
|
||||
return mod.Component;
|
||||
})
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
import dynamic from 'my-dynamic-call'
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
() => import('./component').then(mod => {
|
||||
return mod.Component;
|
||||
})
|
||||
)
|
|
@ -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
|
||||
})
|
|
@ -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
|
||||
})
|
|
@ -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' });
|
||||
}
|
|
@ -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()
|
||||
}
|
Loading…
Reference in a new issue