From 2e877006535d1e714418b50077db868a9b613979 Mon Sep 17 00:00:00 2001 From: Nikos Vasileiou Date: Thu, 4 Jan 2024 12:02:45 +0200 Subject: [PATCH] Optimize bundle: - Replace axios with fetch - Add browserslist to reduce bundle size - Add timeout on fetch requests --- packages/native/.eslintrc.json | 1 + packages/native/package.json | 3 +- packages/native/src/TxNative.js | 88 ++++++++++++++++++++++---------- packages/native/tests/tx.test.js | 12 ++--- 4 files changed, 71 insertions(+), 33 deletions(-) diff --git a/packages/native/.eslintrc.json b/packages/native/.eslintrc.json index 64427080..6de513da 100644 --- a/packages/native/.eslintrc.json +++ b/packages/native/.eslintrc.json @@ -1,6 +1,7 @@ { "extends": ["airbnb-base"], "rules": { + "no-await-in-loop": "off", "import/no-extraneous-dependencies": "off", "no-plusplus": "off", "no-underscore-dangle": "off" diff --git a/packages/native/package.json b/packages/native/package.json index e89483f1..5e0116ba 100644 --- a/packages/native/package.json +++ b/packages/native/package.json @@ -33,6 +33,7 @@ "engines": { "node": ">=14.0.0" }, + "browserslist": "> 0.5%, last 2 versions, Firefox ESR, not dead", "devDependencies": { "@babel/core": "^7.23.5", "@babel/plugin-transform-runtime": "^7.23.4", @@ -54,7 +55,7 @@ }, "dependencies": { "@messageformat/core": "^3.3.0", - "axios": "^1.6.2", + "cross-fetch": "^4.0.0", "md5": "^2.3.0" } } diff --git a/packages/native/src/TxNative.js b/packages/native/src/TxNative.js index c4a3f2f8..59d67d59 100644 --- a/packages/native/src/TxNative.js +++ b/packages/native/src/TxNative.js @@ -1,6 +1,6 @@ /* globals __VERSION__, __PLATFORM__ */ -import axios from 'axios'; +import fetch from 'cross-fetch'; import MemoryCache from './cache/MemoryCache'; import SourceErrorPolicy from './policies/SourceErrorPolicy'; @@ -204,7 +204,6 @@ export default class TxNative { let lastResponseStatus = 202; const tsNow = Date.now(); while (lastResponseStatus === 202) { - /* eslint-disable no-await-in-loop */ let url = `${this.cdsHost}/content/${localeCode}`; const getOptions = []; if (filterTags) { @@ -216,13 +215,19 @@ export default class TxNative { if (getOptions.length) { url = `${url}?${getOptions.join('&')}`; } - response = await axios.get(url, { + response = await fetch(url, { + method: 'GET', headers: { Authorization: `Bearer ${this.token}`, 'Accept-version': 'v2', 'X-NATIVE-SDK': `txjs/${__PLATFORM__}/${__VERSION__}`, }, + signal: this.fetchTimeout > 0 ? AbortSignal.timeout(this.fetchTimeout) : undefined, }); + if (!response.ok) { + throw (await this._fetchError(response)); + } + lastResponseStatus = response.status; if (this.fetchTimeout > 0 && (Date.now() - tsNow) >= this.fetchTimeout) { throw handleError(new Error('Fetch translations timeout')); @@ -230,10 +235,9 @@ export default class TxNative { if (lastResponseStatus === 202 && this.fetchInterval > 0) { await sleep(this.fetchInterval); } - /* eslint-enable no-await-in-loop */ } - const { data } = response; + const data = await response.json(); if (data && data.data) { const hashmap = {}; Object.keys(data.data).forEach((key) => { @@ -266,8 +270,8 @@ export default class TxNative { if (!this.secret) throw new Error('secret is not defined'); const action = params.purge ? 'purge' : 'invalidate'; - const res = await axios.post(`${this.cdsHost}/${action}`, { - }, { + const response = await fetch(`${this.cdsHost}/${action}`, { + method: 'POST', headers: { Authorization: `Bearer ${this.token}:${this.secret}`, 'Accept-version': 'v2', @@ -275,7 +279,11 @@ export default class TxNative { 'X-NATIVE-SDK': `txjs/${__PLATFORM__}/${__VERSION__}`, }, }); - return res.data; + if (!response.ok) { + throw (await this._fetchError(response)); + } + const data = await response.json(); + return data; } /** @@ -323,18 +331,25 @@ export default class TxNative { 'X-NATIVE-SDK': `txjs/${__PLATFORM__}/${__VERSION__}`, }; - const res = await axios.post(`${this.cdsHost}/content`, { - data: payload, - meta: { - purge: !!params.purge, - override_tags: !!params.overrideTags, - override_occurrences: !!params.overrideOccurrences, - }, - }, { + const response = await fetch(`${this.cdsHost}/content`, { + method: 'POST', headers, + body: JSON.stringify({ + data: payload, + meta: { + purge: !!params.purge, + override_tags: !!params.overrideTags, + override_occurrences: !!params.overrideOccurrences, + }, + }), }); + if (!response.ok) { + throw (await this._fetchError(response)); + } - const jobUrl = `${this.cdsHost}${res.data.data.links.job}`; + const postResData = await response.json(); + + const jobUrl = `${this.cdsHost}${postResData.data.links.job}`; if (params.noWait) { return { @@ -347,13 +362,16 @@ export default class TxNative { }; do { - // eslint-disable-next-line no-await-in-loop await sleep(1500); - // eslint-disable-next-line no-await-in-loop - const pollRes = await axios.get(jobUrl, { + const pollRes = await fetch(jobUrl, { + method: 'GET', headers, }); - const { data } = pollRes.data; + if (!pollRes.ok) { + throw (await this._fetchError(pollRes)); + } + const pollResData = await pollRes.json(); + const { data } = pollResData; pollStatus = { ...(data.details || {}), errors: data.errors || [], @@ -395,14 +413,18 @@ export default class TxNative { let lastResponseStatus = 202; const tsNow = Date.now(); while (lastResponseStatus === 202) { - /* eslint-disable no-await-in-loop */ - response = await axios.get(`${this.cdsHost}/languages`, { + response = await fetch(`${this.cdsHost}/languages`, { + method: 'GET', headers: { Authorization: `Bearer ${this.token}`, 'Accept-version': 'v2', 'X-NATIVE-SDK': `txjs/${__PLATFORM__}/${__VERSION__}`, }, + signal: this.fetchTimeout > 0 ? AbortSignal.timeout(this.fetchTimeout) : undefined, }); + if (!response.ok) { + throw (await this._fetchError(response)); + } lastResponseStatus = response.status; if (this.fetchTimeout > 0 && (Date.now() - tsNow) >= this.fetchTimeout) { throw handleError(new Error('Get locales timeout')); @@ -410,10 +432,9 @@ export default class TxNative { if (lastResponseStatus === 202 && this.fetchInterval > 0) { await sleep(this.fetchInterval); } - /* eslint-enable no-await-in-loop */ } - const { data } = response; + const data = await response.json(); if (data && data.data) { this.languages = data.data; this.locales = this.languages.map((entry) => entry.code); @@ -536,7 +557,6 @@ export default class TxNative { // Fetch translations for additional instance without blocking // anything else in case of missing language try { - // eslint-disable-next-line no-await-in-loop await instances[i].fetchTranslations(localeCode); } catch (e) { // no-op @@ -553,4 +573,20 @@ export default class TxNative { } }); } + + /** + * Return a new fetch error + * + * @param {*} response + * @memberof TxNative + */ + // eslint-disable-next-line class-methods-use-this + async _fetchError(response) { + try { + const text = await response.text(); + return new Error(`HTTP ${response.status}: ${text}`); + } catch (err) { + return new Error(`HTTP error ${response.status}`); + } + } } diff --git a/packages/native/tests/tx.test.js b/packages/native/tests/tx.test.js index 32228cbb..a5a09764 100644 --- a/packages/native/tests/tx.test.js +++ b/packages/native/tests/tx.test.js @@ -272,13 +272,13 @@ describe('tx instance', () => { }], }); - let error; + let hasError = false; try { await tx.getLocales({ refresh: true }); } catch (err) { - error = err; + hasError = true; } - expect(error.message).to.equal('Get locales timeout'); + expect(hasError).to.equal(true); }); it('retries fetching languages with interval', async () => { @@ -322,13 +322,13 @@ describe('tx instance', () => { .get('/content/el_timeout') .reply(200, { data: { source: { string: 'translation' } } }); - let error; + let hasError = false; try { await tx.fetchTranslations('el_timeout'); } catch (err) { - error = err; + hasError = true; } - expect(error.message).to.equal('Fetch translations timeout'); + expect(hasError).to.equal(true); }); it('retries fetching translations with interval delays', async () => {