feat(axios-to-whatwg-fetch): introduce #269
Conversation
|
omg please yes |
|
cc @nodejs/userland-migrations can I have first review on this one |
There was a problem hiding this comment.
This is a great initial implementation 🙂
I think it would be better to consolidate the config handling into a helper since all axios methods support it.
Blocker: There are some axios config options that are not supported here, and, if present in userland code, the absence of support will surely break that userland code. For instance:
transformResponse I think that can probably just be converted to an initial then() prepended to the outputted fetch?
transformRequest I think this can be handled by merely setting fetch's init.body like body: transformRequest(bodyExpression). That would probably also work for paramsSerializer.
If we don't want to handle those more exotic axios config options initially, I think that's okay, but the migration needs to detect them and abort.
There was a problem hiding this comment.
This seems superfluous
There was a problem hiding this comment.
We have an utility that do that so let's use it
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.request', |
There was a problem hiding this comment.
Let's include the axios.request or its location (file.ext:row:col) in the warning output here so the user knows what triggered.
Also, I think maybe these should be console.error instead of warning because part of the migration failed.
There was a problem hiding this comment.
Let's include the axios.request or its location (file.ext:row:col) in the warning output here so the user knows what triggered.
I didn't get this
There was a problem hiding this comment.
Which part don't you get?
Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com>
Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com>
Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com>
Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com>
|
@JakobJingleheimer I tried to apply all of your proposal. But why the ci didn't ran ? |
|
It looks like CI did run? If not, was this whilst I was fiddling with the repo permissions? (I specifically told you on slack you'd temporarily have |
it's ran after the merge commit. idk what really happened but it's work now ! it's didn't help since I dunno why windows is doing windows thing |
JakobJingleheimer
left a comment
There was a problem hiding this comment.
Great tidy!
It's still missing handling for transformRequest, transformResponse, paramsSerializer, etc, which was the blocker before. It needs to either
- support them
- detect them and abort/fail the migration
Doing neither will result in broken userland code.
| const baseUpdates: { | ||
| oldBind: string; | ||
| replaceFn: BindingToReplace['replaceFn']; | ||
| supportDefaultAccess?: boolean; | ||
| }[] = [ | ||
| { | ||
| oldBind: '$.get', | ||
| replaceFn: (args, context) => { | ||
| const [url, oldOptions] = args; | ||
| if (!url) { | ||
| warnWithLocation(context, 'Missing URL in axios.get. Skipping.'); | ||
| return ''; | ||
| } | ||
| const options = createOptions({ oldOptions }); | ||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url.text()}${options ? `, ${options}` : ''}) | ||
| .then(async (res) => Object.assign(res, { data: await res.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.post', | ||
| replaceFn: (args, context) => { | ||
| const url = args[0]; | ||
| if (!url) { | ||
| warnWithLocation(context, 'Missing URL in axios.post. Skipping.'); | ||
| return ''; | ||
| } | ||
| const options = createOptions({ | ||
| oldOptions: args[2], | ||
| method: 'POST', | ||
| bodyNode: args[1] ?? null, | ||
| payloadKind: 'json', | ||
| }); | ||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url.text()}${options ? `, ${options}` : ''}) | ||
| .then(async (resp) => Object.assign(resp, { data: await resp.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.put', | ||
| replaceFn: (args, context) => { | ||
| const url = args[0]; | ||
| if (!url) { | ||
| warnWithLocation(context, 'Missing URL in axios.put. Skipping.'); | ||
| return ''; | ||
| } | ||
| const options = createOptions({ | ||
| oldOptions: args[2], | ||
| method: 'PUT', | ||
| bodyNode: args[1] ?? null, | ||
| payloadKind: 'json', | ||
| }); | ||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url.text()}${options ? `, ${options}` : ''}) | ||
| .then(async (resp) => Object.assign(resp, { data: await resp.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.patch', | ||
| replaceFn: (args, context) => { | ||
| const url = args[0]; | ||
| if (!url) { | ||
| warnWithLocation(context, 'Missing URL in axios.patch. Skipping.'); | ||
| return ''; | ||
| } | ||
| const options = createOptions({ | ||
| oldOptions: args[2], | ||
| method: 'PATCH', | ||
| bodyNode: args[1] ?? null, | ||
| payloadKind: 'json', | ||
| }); | ||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url.text()}${options ? `, ${options}` : ''}) | ||
| .then(async (resp) => Object.assign(resp, { data: await resp.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.postForm', | ||
| replaceFn: (args, context) => { | ||
| const url = args[0]; | ||
| if (!url) { | ||
| warnWithLocation(context, 'Missing URL in axios.postForm. Skipping.'); | ||
| return ''; | ||
| } | ||
| const options = createOptions({ | ||
| oldOptions: args[2], | ||
| method: 'POST', | ||
| bodyNode: args[1] ?? null, | ||
| payloadKind: 'form', | ||
| }); | ||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url.text()}${options ? `, ${options}` : ''}) | ||
| .then(async (resp) => Object.assign(resp, { data: await resp.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.putForm', | ||
| replaceFn: (args, context) => { | ||
| const url = args[0]; | ||
| if (!url) { | ||
| warnWithLocation(context, 'Missing URL in axios.putForm. Skipping.'); | ||
| return ''; | ||
| } | ||
| const options = createOptions({ | ||
| oldOptions: args[2], | ||
| method: 'PUT', | ||
| bodyNode: args[1] ?? null, | ||
| payloadKind: 'form', | ||
| }); | ||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url.text()}${options ? `, ${options}` : ''}) | ||
| .then(async (resp) => Object.assign(resp, { data: await resp.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.patchForm', | ||
| replaceFn: (args, context) => { | ||
| const url = args[0]; | ||
| if (!url) { | ||
| warnWithLocation(context, 'Missing URL in axios.patchForm. Skipping.'); | ||
| return ''; | ||
| } | ||
| const options = createOptions({ | ||
| oldOptions: args[2], | ||
| method: 'PATCH', | ||
| bodyNode: args[1] ?? null, | ||
| payloadKind: 'form', | ||
| }); | ||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url.text()}${options ? `, ${options}` : ''}) | ||
| .then(async (resp) => Object.assign(resp, { data: await resp.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.delete', | ||
| replaceFn: (args, context) => { | ||
| const url = args[0]; | ||
| if (!url) { | ||
| warnWithLocation(context, 'Missing URL in axios.delete. Skipping.'); | ||
| return ''; | ||
| } | ||
| const options = createOptions({ | ||
| oldOptions: args[1], | ||
| method: 'DELETE', | ||
| }); | ||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url.text()}, ${options}) | ||
| .then(async (resp) => Object.assign(resp, { data: await resp.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.head', | ||
| replaceFn: (args, context) => { | ||
| const url = args[0]; | ||
| if (!url) { | ||
| warnWithLocation(context, 'Missing URL in axios.head. Skipping.'); | ||
| return ''; | ||
| } | ||
| const options = createOptions({ | ||
| oldOptions: args[1], | ||
| method: 'HEAD', | ||
| }); | ||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url.text()}, ${options}) | ||
| .then(async (resp) => Object.assign(resp, { data: await resp.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.options', | ||
| replaceFn: (args, context) => { | ||
| const url = args[0]; | ||
| if (!url) { | ||
| warnWithLocation(context, 'Missing URL in axios.options. Skipping.'); | ||
| return ''; | ||
| } | ||
| const options = createOptions({ | ||
| oldOptions: args[1], | ||
| method: 'OPTIONS', | ||
| }); | ||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url.text()}, ${options}) | ||
| .then(async (resp) => Object.assign(resp, { data: await resp.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| { | ||
| oldBind: '$.request', | ||
| replaceFn: (args, context) => { | ||
| const config = args[0]; | ||
| if (!config) { | ||
| warnWithLocation( | ||
| context, | ||
| 'Missing config object in axios.request. Skipping.', | ||
| ); | ||
| return ''; | ||
| } | ||
|
|
||
| if (config.kind() !== 'object') { | ||
| warnWithLocation( | ||
| context, | ||
| 'Unsupported axios.request configuration shape. Skipping migration.', | ||
| config, | ||
| ); | ||
| return ''; | ||
| } | ||
|
|
||
| const urlNode = getObjectPropertyValue(config, 'url'); | ||
| if (!urlNode) { | ||
| warnWithLocation( | ||
| context, | ||
| 'Missing URL in axios.request config. Skipping migration.', | ||
| config, | ||
| ); | ||
| return ''; | ||
| } | ||
| const url = urlNode.text(); | ||
|
|
||
| const methodNode = getObjectPropertyValue(config, 'method'); | ||
|
|
||
| const method = methodNode.child(1)?.text().toUpperCase(); | ||
|
|
||
| const options = createOptions({ | ||
| oldOptions: config, | ||
| method: method ?? 'GET', // axios.request's default is GET | ||
| bodyNode: getObjectPropertyValue(config, 'data') ?? null, | ||
| payloadKind: 'json', | ||
| }); | ||
|
|
||
| return dedent.withOptions({ alignValues: true })` | ||
| fetch(${url}${options ? `, ${options}` : ''}) | ||
| .then(async (resp) => Object.assign(resp, { data: await resp.json() })) | ||
| .catch(() => null) | ||
| `; | ||
| }, | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
These seem extremely repetative, can they be simplified with a loop?
There was a problem hiding this comment.
Yes, exactly :)
Related issue
close #191