Reverts vercel/next.js#57154
Brings back the changes but uses the compiled version of async-retry instead. Previously these changes causes an error as async-retry wasn't installed.
### What?
Add the same re-retrieval process for subseted font files of Google Font as for CSS files.
+ make use of [async-retry](https://github.com/vercel/async-retry)
### Why?
It was reported in #45080 that Japanese fonts such as Noto Sans JP were frequently `Failed to fetch`.
A retry process was added in #51890, but it did not resolve the issue completely ( https://github.com/vercel/next.js/pull/51890#issuecomment-1614558064 ).
Here is my reproduction code with 13.5.5-canary.4 (please run locally).
https://stackblitz.com/edit/stackblitz-starters-n8zxlq?file=app%2Fpage.tsx
<details>
<summary>And my local error log is here(folded)</summary>
```
$ npm run -- dev
> nextjs@0.1.0 dev
> next dev
⚠ Port 3000 is in use, trying 3001 instead.
▲ Next.js 13.5.5-canary.4
- Local: http://localhost:3001
✓ Ready in 23.9s
○ Compiling /page ...
FetchError: request to https://fonts.gstatic.com/s/notosansjp/v52/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj757Y0rw_qMHVdbR2L8Y9QTJ1LwkRmR5GprQAe69m.4.woff2 failed, reason:
at ClientRequest.<anonymous> (/mnt/c/Users/berlysia/Downloads/stackblitz-starters-n8zxlq/node_modules/next/dist/compiled/node-fetch/index.js:1:65756)
at ClientRequest.emit (node:events:514:28)
at TLSSocket.socketErrorListener (node:_http_client:495:9)
at TLSSocket.emit (node:events:514:28)
at emitErrorNT (node:internal/streams/destroy:151:8)
at emitErrorCloseNT (node:internal/streams/destroy:116:3)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
type: 'system',
errno: 'ETIMEDOUT',
code: 'ETIMEDOUT'
}
⨯ Failed to download `Noto Sans JP` from Google Fonts. Using fallback font instead.
Failed to fetch `Noto Sans JP` from Google Fonts.}
FetchError: request to https://fonts.gstatic.com/s/notosansjp/v52/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj757Y0rw_qMHVdbR2L8Y9QTJ1LwkRmR5GprQAe69m.28.woff2 failed, reason:
at ClientRequest.<anonymous> (/mnt/c/Users/berlysia/Downloads/stackblitz-starters-n8zxlq/node_modules/next/dist/compiled/node-fetch/index.js:1:65756)
at ClientRequest.emit (node:events:514:28)
at TLSSocket.socketErrorListener (node:_http_client:495:9)
at TLSSocket.emit (node:events:514:28)
at emitErrorNT (node:internal/streams/destroy:151:8)
at emitErrorCloseNT (node:internal/streams/destroy:116:3)
at processTicksAndRejections (node:internal/process/task_queues:82:21)
at runNextTicks (node:internal/process/task_queues:64:3)
at listOnTimeout (node:internal/timers:540:9)
at process.processTimers (node:internal/timers:514:7) {
type: 'system',
errno: 'ETIMEDOUT',
code: 'ETIMEDOUT'
}
...(15 errors emitted)
```
</details>
I've found that the issue is not limited to fetching CSS, fetching subset font files is also failing.
By adding retry handling to the fetch of individual subseted font files as well, I (almost) never see `Failed to fetch` anymore.
The issue tends to become more apparent when downloading a larger number of subsetted fonts.
This suggests that the problem is more likely to occur with larger fonts, such as those designed for CJK languages.
### How?
Add the same re-retrieval process for subseted font files of Google Font as for CSS files.
Related to #51890#53239#45080#53279
This PR adds the optional `limit` parameter on String.prototype.split uses.
> If provided, splits the string at each occurrence of the specified separator, but stops when limit entries have been placed in the array. Any leftover text is not included in the array at all.
[MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split#syntax)
While the performance gain may not be significant for small texts, it can be huge for large ones.
I made a benchmark on the following repository : https://github.com/Yovach/benchmark-nodejs
On my machine, I get the following results:
`node index.js`
> normal 1: 570.092ms
> normal 50: 2.284s
> normal 100: 3.543s
`node index-optimized.js`
> optmized 1: 644.301ms
> optmized 50: 929.39ms
> optmized 100: 1.020s
The "benchmarks" numbers are :
- "lorem-1" file contains 1 paragraph of "lorem ipsum"
- "lorem-50" file contains 50 paragraphes of "lorem ipsum"
- "lorem-100" file contains 100 paragraphes of "lorem ipsum"
## What?
This PR fixes issue #54304. As discussed in the issue it was suspected that the values of weights were not being sorted correctly. The weights were being sorted in lexicographical order.
In order for Google fonts to work, Google Fonts need the values of weights present in the url in sorted order.
Due to this the url which was being generated for Google Fonts which had a value of "1000" in weight was giving an error and the font was not working.
## Why I Think This Solution Works?
Let's assume that we have an object named axes with the property named named "wght" which will have the value of weights as following:
```
const axes = {
wght: ["100", "200", "300", "400", "500", "600", "700", "800", "900", '1000']
}
```
Let's create another variable named "display" with the value set as "swap" and create another variable named "fontFamily" with the value as "DM Sans" (this is the font which was being used in the issue).
```
const fontFamily = "DM Sans";
const display = "swap";
```
If we call the function using them
```
const FONT_URL = getGoogleFontsUrl(fontFamily, axes, display);
console.log("The font url is: ", FONT_URL)
Output Will Be:
The font url is: https://fonts.googleapis.com/css2?family=DM+Sans:wght@100;1000;200;300;400;500;600;700;800;900&display=swap
```
It will give the incorrect value which was shown in the issue.
If you see at line number 56 there was no comparison function passed in the sort function which is what was suspected earlier and it was sorting the values lexicographically.
Create another variable inside the function above line 54 where we are updating the value of "url" variable.
```
const variantValues = variants.map((variant) => variant.map(([, val]) => val).join(','))
console.log("Variant Values: ", variantValues)
Output Will Be:
Variant Values: ["100", "200", "300", "400", "500", "600", "700", "800", "900", "1000"]
```
Now call the function again with the same values you will see the values in the correct order but as soon as you call "sort" function it will give you incorrect sorted values which is what is happening in the "url" variable.
```
const variantValues = variants.map((variant) => variant.map(([, val]) => val).join(',')).sort()
console.log("Variant Values: ", variantValues)
Output Will Be:
["100", "1000", "200", "300", "400", "500", "600", "700", "800", "900"]
(As you can see the values are sorted incorrectly)
```
There was one case when I implemented this solution. When "ital" is also passed then the values are in "ital,wght" format and because of it the sorting was giving incorrect result for this case.
Let's update the "axes" variable which we made previously to contain these values:
```
const axes = {
wght: ["500","300", "400"],
ital: ["0","1"]
}
```
Now when we run the function it gives us the result as:
`
https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,500;0,300;0,400;1,500;1,300;1,400&display=swap`
Where as the expected result was:
`https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap`
So, I rewrote the "sort" function to also handle this case.
## What Did I Do To Fix It?
I passed the comparison function inside the "sort" function which was being to ensure that the elements are compared as integers, and thus the sorting will be done numerically.
Which gave me the correct output as url:
`https://fonts.googleapis.com/css2?family=DM+Sans:wght@100;200;300;400;500;600;700;800;900;1000&display=swap`
With values being sorted correctly and if you visit the link you will see the response instead of the error.
Any thought's or suggestions would be greatly appreciated.