@next/codemod 15.0.0-canary.17 → 15.0.0-canary.170
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/next-codemod.js +27 -3
- package/bin/transform.js +104 -0
- package/bin/upgrade.js +224 -0
- package/lib/codemods.js +103 -0
- package/lib/cra-to-next/global-css-transform.js +2 -3
- package/lib/cra-to-next/index-to-component.js +2 -3
- package/lib/handle-package.js +76 -0
- package/lib/install.js +2 -3
- package/lib/run-jscodeshift.js +9 -2
- package/lib/utils.js +92 -0
- package/package.json +8 -5
- package/transforms/add-missing-react-import.js +1 -1
- package/transforms/built-in-next-font.js +1 -1
- package/transforms/cra-to-next.js +238 -236
- package/transforms/lib/async-request-api/index.js +21 -0
- package/transforms/lib/async-request-api/next-async-dynamic-api.js +221 -0
- package/transforms/lib/async-request-api/next-async-dynamic-prop.js +345 -0
- package/transforms/lib/async-request-api/utils.js +198 -0
- package/transforms/metadata-to-viewport-export.js +1 -1
- package/transforms/name-default-component.js +3 -4
- package/transforms/new-link.js +5 -4
- package/transforms/next-async-request-api.js +9 -0
- package/transforms/next-dynamic-access-named-export.js +65 -0
- package/transforms/next-image-experimental.js +9 -13
- package/transforms/next-image-to-legacy-image.js +5 -7
- package/transforms/next-og-import.js +1 -1
- package/transforms/next-request-geo-ip.js +338 -0
- package/transforms/url-to-withrouter.js +1 -1
- package/transforms/withamp-to-config.js +1 -1
- package/bin/cli.js +0 -216
- package/lib/uninstall-package.js +0 -32
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transformDynamicAPI = transformDynamicAPI;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function wrapParathnessIfNeeded(hasChainAccess, j, expression) {
|
|
6
|
+
return hasChainAccess ? j.parenthesizedExpression(expression) : expression;
|
|
7
|
+
}
|
|
8
|
+
function transformDynamicAPI(source, api, filePath) {
|
|
9
|
+
const j = api.jscodeshift.withParser('tsx');
|
|
10
|
+
const root = j(source);
|
|
11
|
+
let modified = false;
|
|
12
|
+
// Check if 'use' from 'react' needs to be imported
|
|
13
|
+
let needsReactUseImport = false;
|
|
14
|
+
const insertedTypes = new Set();
|
|
15
|
+
function isImportedInModule(path, functionName) {
|
|
16
|
+
const closestDef = j(path)
|
|
17
|
+
.closestScope()
|
|
18
|
+
.findVariableDeclarators(functionName);
|
|
19
|
+
return closestDef.size() === 0;
|
|
20
|
+
}
|
|
21
|
+
function processAsyncApiCalls(asyncRequestApiName, originRequestApiName) {
|
|
22
|
+
// Process each call to cookies() or headers()
|
|
23
|
+
root
|
|
24
|
+
.find(j.CallExpression, {
|
|
25
|
+
callee: {
|
|
26
|
+
type: 'Identifier',
|
|
27
|
+
name: asyncRequestApiName,
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
.forEach((path) => {
|
|
31
|
+
const isImportedTopLevel = isImportedInModule(path, asyncRequestApiName);
|
|
32
|
+
if (!isImportedTopLevel) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const closetScope = j(path).closestScope();
|
|
36
|
+
// First search the closed function of the current path
|
|
37
|
+
let closestFunction;
|
|
38
|
+
closestFunction = j(path).closest(j.FunctionDeclaration);
|
|
39
|
+
if (closestFunction.size() === 0) {
|
|
40
|
+
closestFunction = j(path).closest(j.FunctionExpression);
|
|
41
|
+
}
|
|
42
|
+
if (closestFunction.size() === 0) {
|
|
43
|
+
closestFunction = j(path).closest(j.ArrowFunctionExpression);
|
|
44
|
+
}
|
|
45
|
+
const isAsyncFunction = closestFunction
|
|
46
|
+
.nodes()
|
|
47
|
+
.some((node) => node.async);
|
|
48
|
+
const isCallAwaited = path.parentPath?.node?.type === 'AwaitExpression';
|
|
49
|
+
const hasChainAccess = path.parentPath.value.type === 'MemberExpression' &&
|
|
50
|
+
path.parentPath.value.object === path.node;
|
|
51
|
+
// For cookies/headers API, only transform server and shared components
|
|
52
|
+
if (isAsyncFunction) {
|
|
53
|
+
if (!isCallAwaited) {
|
|
54
|
+
// Add 'await' in front of cookies() call
|
|
55
|
+
const expr = j.awaitExpression(
|
|
56
|
+
// add parentheses to wrap the function call
|
|
57
|
+
j.callExpression(j.identifier(asyncRequestApiName), []));
|
|
58
|
+
j(path).replaceWith(wrapParathnessIfNeeded(hasChainAccess, j, expr));
|
|
59
|
+
modified = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Determine if the function is an export
|
|
64
|
+
const closetScopePath = closetScope.get();
|
|
65
|
+
const isFromExport = (0, utils_1.isMatchedFunctionExported)(closetScopePath, j);
|
|
66
|
+
const closestFunctionNode = closetScope.size()
|
|
67
|
+
? closetScopePath.node
|
|
68
|
+
: null;
|
|
69
|
+
// If it's exporting a function directly, exportFunctionNode is same as exportNode
|
|
70
|
+
// e.g. export default function MyComponent() {}
|
|
71
|
+
// If it's exporting a variable declaration, exportFunctionNode is the function declaration
|
|
72
|
+
// e.g. export const MyComponent = function() {}
|
|
73
|
+
let exportFunctionNode;
|
|
74
|
+
if (isFromExport) {
|
|
75
|
+
if (closestFunctionNode &&
|
|
76
|
+
(0, utils_1.isFunctionType)(closestFunctionNode.type)) {
|
|
77
|
+
exportFunctionNode = closestFunctionNode;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Is normal async function
|
|
82
|
+
exportFunctionNode = closestFunctionNode;
|
|
83
|
+
}
|
|
84
|
+
let canConvertToAsync = false;
|
|
85
|
+
// check if current path is under the default export function
|
|
86
|
+
if (isFromExport) {
|
|
87
|
+
// if default export function is not async, convert it to async, and await the api call
|
|
88
|
+
if (!isCallAwaited) {
|
|
89
|
+
// If the scoped function is async function
|
|
90
|
+
if ((0, utils_1.isFunctionType)(exportFunctionNode.type) &&
|
|
91
|
+
exportFunctionNode.async === false) {
|
|
92
|
+
canConvertToAsync = true;
|
|
93
|
+
exportFunctionNode.async = true;
|
|
94
|
+
}
|
|
95
|
+
if (canConvertToAsync) {
|
|
96
|
+
const expr = j.awaitExpression(j.callExpression(j.identifier(asyncRequestApiName), []));
|
|
97
|
+
j(path).replaceWith(wrapParathnessIfNeeded(hasChainAccess, j, expr));
|
|
98
|
+
(0, utils_1.turnFunctionReturnTypeToAsync)(closetScopePath.node, j);
|
|
99
|
+
modified = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// if parent is function and it's a hook, which starts with 'use', wrap the api call with 'use()'
|
|
105
|
+
const parentFunction = j(path).closest(j.FunctionDeclaration) ||
|
|
106
|
+
j(path).closest(j.FunctionExpression) ||
|
|
107
|
+
j(path).closest(j.ArrowFunctionExpression);
|
|
108
|
+
if (parentFunction.size() > 0) {
|
|
109
|
+
const parentFunctionName = parentFunction.get().node.id?.name;
|
|
110
|
+
const isParentFunctionHook = parentFunctionName?.startsWith('use');
|
|
111
|
+
if (isParentFunctionHook) {
|
|
112
|
+
j(path).replaceWith(j.callExpression(j.identifier('use'), [
|
|
113
|
+
j.callExpression(j.identifier(asyncRequestApiName), []),
|
|
114
|
+
]));
|
|
115
|
+
needsReactUseImport = true;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes);
|
|
123
|
+
}
|
|
124
|
+
modified = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const isClientComponent = (0, utils_1.determineClientDirective)(root, j, source);
|
|
130
|
+
const importedNextAsyncRequestApisMapping = findImportMappingFromNextHeaders(root, j);
|
|
131
|
+
// Only transform the valid calls in server or shared components
|
|
132
|
+
if (!isClientComponent) {
|
|
133
|
+
for (const originName in importedNextAsyncRequestApisMapping) {
|
|
134
|
+
const aliasName = importedNextAsyncRequestApisMapping[originName];
|
|
135
|
+
processAsyncApiCalls(aliasName, originName);
|
|
136
|
+
}
|
|
137
|
+
// Add import { use } from 'react' if needed and not already imported
|
|
138
|
+
if (needsReactUseImport) {
|
|
139
|
+
(0, utils_1.insertReactUseImport)(root, j);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return modified ? root.toSource() : null;
|
|
143
|
+
}
|
|
144
|
+
// cast to unknown first, then the specific type
|
|
145
|
+
const API_CAST_TYPE_MAP = {
|
|
146
|
+
cookies: 'UnsafeUnwrappedCookies',
|
|
147
|
+
headers: 'UnsafeUnwrappedHeaders',
|
|
148
|
+
draftMode: 'UnsafeUnwrappedDraftMode',
|
|
149
|
+
};
|
|
150
|
+
function castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes) {
|
|
151
|
+
const isTsFile = filePath.endsWith('.ts') || filePath.endsWith('.tsx');
|
|
152
|
+
if (isTsFile) {
|
|
153
|
+
/* Do type cast for headers, cookies, draftMode
|
|
154
|
+
import {
|
|
155
|
+
type UnsafeUnwrappedHeaders,
|
|
156
|
+
type UnsafeUnwrappedCookies,
|
|
157
|
+
type UnsafeUnwrappedDraftMode
|
|
158
|
+
} from 'next/headers'
|
|
159
|
+
|
|
160
|
+
cookies() as unknown as UnsafeUnwrappedCookies
|
|
161
|
+
headers() as unknown as UnsafeUnwrappedHeaders
|
|
162
|
+
draftMode() as unknown as UnsafeUnwrappedDraftMode
|
|
163
|
+
|
|
164
|
+
e.g. `<path>` is cookies(), convert it to `(<path> as unknown as UnsafeUnwrappedCookies)`
|
|
165
|
+
*/
|
|
166
|
+
const targetType = API_CAST_TYPE_MAP[originRequestApiName];
|
|
167
|
+
const newCastExpression = j.tsAsExpression(j.tsAsExpression(path.node, j.tsUnknownKeyword()), j.tsTypeReference(j.identifier(targetType)));
|
|
168
|
+
// Replace the original expression with the new cast expression,
|
|
169
|
+
// also wrap () around the new cast expression.
|
|
170
|
+
j(path).replaceWith(j.parenthesizedExpression(newCastExpression));
|
|
171
|
+
// If cast types are not imported, add them to the import list
|
|
172
|
+
const importDeclaration = root.find(j.ImportDeclaration, {
|
|
173
|
+
source: { value: 'next/headers' },
|
|
174
|
+
});
|
|
175
|
+
if (importDeclaration.size() > 0) {
|
|
176
|
+
const hasImportedType = importDeclaration
|
|
177
|
+
.find(j.TSTypeAliasDeclaration, {
|
|
178
|
+
id: { name: targetType },
|
|
179
|
+
})
|
|
180
|
+
.size() > 0 ||
|
|
181
|
+
importDeclaration
|
|
182
|
+
.find(j.ImportSpecifier, {
|
|
183
|
+
imported: { name: targetType },
|
|
184
|
+
})
|
|
185
|
+
.size() > 0;
|
|
186
|
+
if (!hasImportedType && !insertedTypes.has(targetType)) {
|
|
187
|
+
importDeclaration
|
|
188
|
+
.get()
|
|
189
|
+
.node.specifiers.push(j.importSpecifier(j.identifier(`type ${targetType}`)));
|
|
190
|
+
insertedTypes.add(targetType);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Otherwise for JS file, leave a message to the user to manually handle the transformation
|
|
196
|
+
path.node.comments = [
|
|
197
|
+
j.commentBlock(' TODO: please manually await this call, codemod cannot transform due to undetermined async scope '),
|
|
198
|
+
...(path.node.comments || []),
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function findImportMappingFromNextHeaders(root, j) {
|
|
203
|
+
const mappings = {};
|
|
204
|
+
// Find the import declaration from 'next/headers'
|
|
205
|
+
root
|
|
206
|
+
.find(j.ImportDeclaration, { source: { value: 'next/headers' } })
|
|
207
|
+
.forEach((importPath) => {
|
|
208
|
+
const importDeclaration = importPath.node;
|
|
209
|
+
// Iterate over the specifiers and build the mappings
|
|
210
|
+
importDeclaration.specifiers.forEach((specifier) => {
|
|
211
|
+
if (j.ImportSpecifier.check(specifier)) {
|
|
212
|
+
const importedName = specifier.imported.name; // Original name (e.g., cookies)
|
|
213
|
+
const localName = specifier.local.name; // Local name (e.g., myCookies or same as importedName)
|
|
214
|
+
// Add to the mappings
|
|
215
|
+
mappings[importedName] = localName;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
return mappings;
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=next-async-dynamic-api.js.map
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transformDynamicProps = transformDynamicProps;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
const PAGE_PROPS = 'props';
|
|
6
|
+
function isAsyncFunctionDeclaration(path) {
|
|
7
|
+
const decl = path.value.declaration;
|
|
8
|
+
const isAsyncFunction = (decl.type === 'FunctionDeclaration' ||
|
|
9
|
+
decl.type === 'FunctionExpression' ||
|
|
10
|
+
decl.type === 'ArrowFunctionExpression') &&
|
|
11
|
+
decl.async;
|
|
12
|
+
return isAsyncFunction;
|
|
13
|
+
}
|
|
14
|
+
function transformDynamicProps(source, api, _filePath) {
|
|
15
|
+
let modified = false;
|
|
16
|
+
const j = api.jscodeshift.withParser('tsx');
|
|
17
|
+
const root = j(source);
|
|
18
|
+
// Check if 'use' from 'react' needs to be imported
|
|
19
|
+
let needsReactUseImport = false;
|
|
20
|
+
// Based on the prop names
|
|
21
|
+
// e.g. destruct `params` { slug } = params
|
|
22
|
+
// e.g. destruct `searchParams `{ search } = searchParams
|
|
23
|
+
let insertedDestructPropNames = new Set();
|
|
24
|
+
// Rename props to `prop` argument for the function
|
|
25
|
+
let insertedRenamedPropFunctionNames = new Set();
|
|
26
|
+
function processAsyncPropOfEntryFile(isClientComponent) {
|
|
27
|
+
// find `params` and `searchParams` in file, and transform the access to them
|
|
28
|
+
function renameAsyncPropIfExisted(path) {
|
|
29
|
+
const decl = path.value.declaration;
|
|
30
|
+
if (decl.type !== 'FunctionDeclaration' &&
|
|
31
|
+
decl.type !== 'FunctionExpression' &&
|
|
32
|
+
decl.type !== 'ArrowFunctionExpression') {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const params = decl.params;
|
|
36
|
+
const propertiesMap = new Map();
|
|
37
|
+
// If there's no first param, return
|
|
38
|
+
if (params.length !== 1) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const propsIdentifier = (0, utils_1.generateUniqueIdentifier)(PAGE_PROPS, path, j);
|
|
42
|
+
const currentParam = params[0];
|
|
43
|
+
// Argument destructuring case
|
|
44
|
+
if (currentParam.type === 'ObjectPattern') {
|
|
45
|
+
// Validate if the properties are not `params` and `searchParams`,
|
|
46
|
+
// if they are, quit the transformation
|
|
47
|
+
for (const prop of currentParam.properties) {
|
|
48
|
+
if ('key' in prop && prop.key.type === 'Identifier') {
|
|
49
|
+
const propName = prop.key.name;
|
|
50
|
+
if (!utils_1.TARGET_PROP_NAMES.has(propName)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
currentParam.properties.forEach((prop) => {
|
|
56
|
+
if (
|
|
57
|
+
// Could be `Property` or `ObjectProperty`
|
|
58
|
+
'key' in prop &&
|
|
59
|
+
prop.key.type === 'Identifier') {
|
|
60
|
+
const value = 'value' in prop ? prop.value : null;
|
|
61
|
+
propertiesMap.set(prop.key.name, value);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const paramTypeAnnotation = currentParam.typeAnnotation;
|
|
65
|
+
if (paramTypeAnnotation && paramTypeAnnotation.typeAnnotation) {
|
|
66
|
+
const typeAnnotation = paramTypeAnnotation.typeAnnotation;
|
|
67
|
+
if (typeAnnotation.type === 'TSTypeLiteral') {
|
|
68
|
+
const typeLiteral = typeAnnotation;
|
|
69
|
+
// Find the type property for `params`
|
|
70
|
+
typeLiteral.members.forEach((member) => {
|
|
71
|
+
if (member.type === 'TSPropertySignature' &&
|
|
72
|
+
member.key.type === 'Identifier' &&
|
|
73
|
+
propertiesMap.has(member.key.name)) {
|
|
74
|
+
// if it's already a Promise, don't wrap it again, return
|
|
75
|
+
if (member.typeAnnotation &&
|
|
76
|
+
member.typeAnnotation.typeAnnotation &&
|
|
77
|
+
member.typeAnnotation.typeAnnotation.type ===
|
|
78
|
+
'TSTypeReference' &&
|
|
79
|
+
member.typeAnnotation.typeAnnotation.typeName.type ===
|
|
80
|
+
'Identifier' &&
|
|
81
|
+
member.typeAnnotation.typeAnnotation.typeName.name ===
|
|
82
|
+
'Promise') {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Wrap the `params` type in Promise<>
|
|
86
|
+
if (member.typeAnnotation &&
|
|
87
|
+
member.typeAnnotation.typeAnnotation &&
|
|
88
|
+
j.TSType.check(member.typeAnnotation.typeAnnotation)) {
|
|
89
|
+
member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
|
|
90
|
+
// @ts-ignore
|
|
91
|
+
member.typeAnnotation.typeAnnotation,
|
|
92
|
+
]));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else if (typeAnnotation.type === 'TSTypeReference') {
|
|
98
|
+
// If typeAnnotation is a type or interface, change the properties to Promise<type of property>
|
|
99
|
+
// e.g. interface PageProps { params: { slug: string } } => interface PageProps { params: Promise<{ slug: string }> }
|
|
100
|
+
const typeReference = typeAnnotation;
|
|
101
|
+
if (typeReference.typeName.type === 'Identifier') {
|
|
102
|
+
// Find the actual type of the type reference
|
|
103
|
+
const foundTypes = findAllTypes(root, j, typeReference.typeName.name);
|
|
104
|
+
// Deal with interfaces
|
|
105
|
+
if (foundTypes.interfaces.length > 0) {
|
|
106
|
+
const interfaceDeclaration = foundTypes.interfaces[0];
|
|
107
|
+
if (interfaceDeclaration.type === 'TSInterfaceDeclaration' &&
|
|
108
|
+
interfaceDeclaration.body?.type === 'TSInterfaceBody') {
|
|
109
|
+
const typeBody = interfaceDeclaration.body.body;
|
|
110
|
+
// if it's already a Promise, don't wrap it again, return
|
|
111
|
+
// traverse the typeReference's properties, if any is in propNames, wrap it in Promise<> if needed
|
|
112
|
+
typeBody.forEach((member) => {
|
|
113
|
+
if (member.type === 'TSPropertySignature' &&
|
|
114
|
+
member.key.type === 'Identifier' &&
|
|
115
|
+
utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
|
|
116
|
+
// if it's already a Promise, don't wrap it again, return
|
|
117
|
+
if (member.typeAnnotation &&
|
|
118
|
+
member.typeAnnotation.typeAnnotation &&
|
|
119
|
+
member.typeAnnotation?.typeAnnotation?.typeName
|
|
120
|
+
?.name === 'Promise') {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Wrap the prop type in Promise<>
|
|
124
|
+
if (member.typeAnnotation &&
|
|
125
|
+
member.typeAnnotation.typeAnnotation &&
|
|
126
|
+
// check if member name is in propNames
|
|
127
|
+
utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
|
|
128
|
+
member.typeAnnotation.typeAnnotation =
|
|
129
|
+
j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
|
|
130
|
+
member.typeAnnotation.typeAnnotation,
|
|
131
|
+
]));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
propsIdentifier.typeAnnotation = paramTypeAnnotation;
|
|
140
|
+
}
|
|
141
|
+
// Override the first param to `props`
|
|
142
|
+
params[0] = propsIdentifier;
|
|
143
|
+
modified = true;
|
|
144
|
+
}
|
|
145
|
+
if (modified) {
|
|
146
|
+
resolveAsyncProp(path, propertiesMap, propsIdentifier.name);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function getBodyOfFunctionDeclaration(path) {
|
|
150
|
+
const decl = path.value.declaration;
|
|
151
|
+
let functionBody;
|
|
152
|
+
if (decl.type === 'FunctionDeclaration' ||
|
|
153
|
+
decl.type === 'FunctionExpression' ||
|
|
154
|
+
decl.type === 'ArrowFunctionExpression') {
|
|
155
|
+
if (decl.body && decl.body.type === 'BlockStatement') {
|
|
156
|
+
functionBody = decl.body.body;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return functionBody;
|
|
160
|
+
}
|
|
161
|
+
// Helper function to insert `const params = await asyncParams;` at the beginning of the function body
|
|
162
|
+
function resolveAsyncProp(path, propertiesMap, propsIdentifierName) {
|
|
163
|
+
const isDefaultExport = path.value.type === 'ExportDefaultDeclaration';
|
|
164
|
+
// If it's sync default export, and it's also server component, make the function async
|
|
165
|
+
if (isDefaultExport &&
|
|
166
|
+
!isClientComponent &&
|
|
167
|
+
!isAsyncFunctionDeclaration(path)) {
|
|
168
|
+
if ('async' in path.value.declaration) {
|
|
169
|
+
path.value.declaration.async = true;
|
|
170
|
+
(0, utils_1.turnFunctionReturnTypeToAsync)(path.value.declaration, j);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const isAsyncFunc = isAsyncFunctionDeclaration(path);
|
|
174
|
+
// @ts-ignore quick way to check if it's a function and it has a name
|
|
175
|
+
const functionName = path.value.declaration.id?.name || 'default';
|
|
176
|
+
const functionBody = getBodyOfFunctionDeclaration(path);
|
|
177
|
+
for (const [propName, paramsProperty] of propertiesMap) {
|
|
178
|
+
const propNameIdentifier = j.identifier(propName);
|
|
179
|
+
const propsIdentifier = j.identifier(propsIdentifierName);
|
|
180
|
+
const accessedPropId = j.memberExpression(propsIdentifier, propNameIdentifier);
|
|
181
|
+
// Check param property value, if it's destructed, we need to destruct it as well
|
|
182
|
+
// e.g.
|
|
183
|
+
// input: Page({ params: { slug } })
|
|
184
|
+
// output: const { slug } = await props.params; rather than const props = await props.params;
|
|
185
|
+
const uid = functionName + ':' + propName;
|
|
186
|
+
if (paramsProperty?.type === 'ObjectPattern') {
|
|
187
|
+
const objectPattern = paramsProperty;
|
|
188
|
+
const objectPatternProperties = objectPattern.properties;
|
|
189
|
+
// destruct the object pattern, e.g. { slug } => const { slug } = params;
|
|
190
|
+
const destructedObjectPattern = j.variableDeclaration('const', [
|
|
191
|
+
j.variableDeclarator(j.objectPattern(objectPatternProperties.map((prop) => {
|
|
192
|
+
if (prop.type === 'Property' &&
|
|
193
|
+
prop.key.type === 'Identifier') {
|
|
194
|
+
return j.objectProperty(j.identifier(prop.key.name), j.identifier(prop.key.name));
|
|
195
|
+
}
|
|
196
|
+
return prop;
|
|
197
|
+
})), propNameIdentifier),
|
|
198
|
+
]);
|
|
199
|
+
if (!insertedDestructPropNames.has(uid) && functionBody) {
|
|
200
|
+
functionBody.unshift(destructedObjectPattern);
|
|
201
|
+
insertedDestructPropNames.add(uid);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (isAsyncFunc) {
|
|
205
|
+
// If it's async function, add await to the async props.<propName>
|
|
206
|
+
const paramAssignment = j.variableDeclaration('const', [
|
|
207
|
+
j.variableDeclarator(j.identifier(propName), j.awaitExpression(accessedPropId)),
|
|
208
|
+
]);
|
|
209
|
+
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
210
|
+
functionBody.unshift(paramAssignment);
|
|
211
|
+
insertedRenamedPropFunctionNames.add(uid);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const isFromExport = path.value.type === 'ExportNamedDeclaration';
|
|
216
|
+
if (isFromExport) {
|
|
217
|
+
// If it's export function, populate the function to async
|
|
218
|
+
if ((0, utils_1.isFunctionType)(path.value.declaration.type) &&
|
|
219
|
+
// Make TS happy
|
|
220
|
+
'async' in path.value.declaration) {
|
|
221
|
+
path.value.declaration.async = true;
|
|
222
|
+
(0, utils_1.turnFunctionReturnTypeToAsync)(path.value.declaration, j);
|
|
223
|
+
// Insert `const <propName> = await props.<propName>;` at the beginning of the function body
|
|
224
|
+
const paramAssignment = j.variableDeclaration('const', [
|
|
225
|
+
j.variableDeclarator(j.identifier(propName), j.awaitExpression(accessedPropId)),
|
|
226
|
+
]);
|
|
227
|
+
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
228
|
+
functionBody.unshift(paramAssignment);
|
|
229
|
+
insertedRenamedPropFunctionNames.add(uid);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
const paramAssignment = j.variableDeclaration('const', [
|
|
235
|
+
j.variableDeclarator(j.identifier(propName), j.callExpression(j.identifier('use'), [accessedPropId])),
|
|
236
|
+
]);
|
|
237
|
+
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
238
|
+
needsReactUseImport = true;
|
|
239
|
+
functionBody.unshift(paramAssignment);
|
|
240
|
+
insertedRenamedPropFunctionNames.add(uid);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Process Function Declarations
|
|
247
|
+
// Matching: default export function XXX(...) { ... }
|
|
248
|
+
const defaultExportFunctionDeclarations = root.find(j.ExportDefaultDeclaration, {
|
|
249
|
+
declaration: {
|
|
250
|
+
type: (type) => type === 'FunctionDeclaration' ||
|
|
251
|
+
type === 'FunctionExpression' ||
|
|
252
|
+
type === 'ArrowFunctionExpression',
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
defaultExportFunctionDeclarations.forEach((path) => {
|
|
256
|
+
renameAsyncPropIfExisted(path);
|
|
257
|
+
});
|
|
258
|
+
// Matching Next.js functional named export of route entry:
|
|
259
|
+
// - export function <named>(...) { ... }
|
|
260
|
+
// - export const <named> = ...
|
|
261
|
+
const targetNamedExportDeclarations = root.find(j.ExportNamedDeclaration,
|
|
262
|
+
// Filter the name is in TARGET_NAMED_EXPORTS
|
|
263
|
+
{
|
|
264
|
+
declaration: {
|
|
265
|
+
id: {
|
|
266
|
+
name: (idName) => {
|
|
267
|
+
return utils_1.TARGET_NAMED_EXPORTS.has(idName);
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
targetNamedExportDeclarations.forEach((path) => {
|
|
273
|
+
renameAsyncPropIfExisted(path);
|
|
274
|
+
});
|
|
275
|
+
// TDOO: handle targetNamedDeclarators
|
|
276
|
+
// const targetNamedDeclarators = root.find(
|
|
277
|
+
// j.VariableDeclarator,
|
|
278
|
+
// (node) =>
|
|
279
|
+
// node.id.type === 'Identifier' &&
|
|
280
|
+
// TARGET_NAMED_EXPORTS.has(node.id.name)
|
|
281
|
+
// )
|
|
282
|
+
}
|
|
283
|
+
const isClientComponent = (0, utils_1.determineClientDirective)(root, j, source);
|
|
284
|
+
// Apply to `params` and `searchParams`
|
|
285
|
+
processAsyncPropOfEntryFile(isClientComponent);
|
|
286
|
+
// Add import { use } from 'react' if needed and not already imported
|
|
287
|
+
if (needsReactUseImport) {
|
|
288
|
+
(0, utils_1.insertReactUseImport)(root, j);
|
|
289
|
+
}
|
|
290
|
+
return modified ? root.toSource() : null;
|
|
291
|
+
}
|
|
292
|
+
function findAllTypes(root, j, typeName) {
|
|
293
|
+
const types = {
|
|
294
|
+
interfaces: [],
|
|
295
|
+
typeAliases: [],
|
|
296
|
+
imports: [],
|
|
297
|
+
references: [],
|
|
298
|
+
};
|
|
299
|
+
// Step 1: Find all interface declarations with the specified name
|
|
300
|
+
root
|
|
301
|
+
.find(j.TSInterfaceDeclaration, {
|
|
302
|
+
id: {
|
|
303
|
+
type: 'Identifier',
|
|
304
|
+
name: typeName,
|
|
305
|
+
},
|
|
306
|
+
})
|
|
307
|
+
.forEach((path) => {
|
|
308
|
+
types.interfaces.push(path.node);
|
|
309
|
+
});
|
|
310
|
+
// Step 2: Find all type alias declarations with the specified name
|
|
311
|
+
root
|
|
312
|
+
.find(j.TSTypeAliasDeclaration, {
|
|
313
|
+
id: {
|
|
314
|
+
type: 'Identifier',
|
|
315
|
+
name: typeName,
|
|
316
|
+
},
|
|
317
|
+
})
|
|
318
|
+
.forEach((path) => {
|
|
319
|
+
types.typeAliases.push(path.node);
|
|
320
|
+
});
|
|
321
|
+
// Step 3: Find all imported types with the specified name
|
|
322
|
+
root
|
|
323
|
+
.find(j.ImportSpecifier, {
|
|
324
|
+
imported: {
|
|
325
|
+
type: 'Identifier',
|
|
326
|
+
name: typeName,
|
|
327
|
+
},
|
|
328
|
+
})
|
|
329
|
+
.forEach((path) => {
|
|
330
|
+
types.imports.push(path.node);
|
|
331
|
+
});
|
|
332
|
+
// Step 4: Find all references to the specified type
|
|
333
|
+
root
|
|
334
|
+
.find(j.TSTypeReference, {
|
|
335
|
+
typeName: {
|
|
336
|
+
type: 'Identifier',
|
|
337
|
+
name: typeName,
|
|
338
|
+
},
|
|
339
|
+
})
|
|
340
|
+
.forEach((path) => {
|
|
341
|
+
types.references.push(path.node);
|
|
342
|
+
});
|
|
343
|
+
return types;
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=next-async-dynamic-prop.js.map
|