Repository: cozy/cozy-ui
Pull request #2743: Improve tree shaking by removing remaining CommonJS modules
Author: login: zatteo
Labels: name: released
Created: 2025-03-05T13:50:57Z | Merged: 2025-03-07T07:48:27Z

Bundlers are able to do efficient tree shaking since the apparition of ES module (`import` and `export`). But it works only if the package is 100% using ES module. So here we update legacy code using CommonJS (`require` and `module.exports`) to enable bundlers to optimize the best they can.

**cozy-drive yarn build output**

Tested succesfully locally ✅ 

Before : 13201.2 kB 
rlink with this branch : 12256.1 kB (**-7,16%**)

**twakemail yarn build output**

Tested succesfully locally ✅ 

Before : 3074.2 kB
rlink with branch : 2151.7 kB (**-30,01%**)

Learn more :
- [web-infra-dev - Deep dive into Rspack & webpack tree shaking](https://github.com/orgs/web-infra-dev/discussions/17)
- [web.dev - Reduce Java​Script payloads with tree shaking](https://web.dev/articles/reduce-javascript-payloads-with-tree-shaking)



Diff:
diff --git a/package.json b/package.json
index 548a68cdfe..65aa9b9ea6 100644
--- a/package.json
+++ b/package.json
@@ -173,7 +173,6 @@
     "node-polyglot": "^2.5.0",
     "normalize.css": "^8.0.0",
     "pdf-lib": "1.17.1",
-    "piwik-react-router": "0.12.1",
     "react-chartjs-2": "4.1.0",
     "react-markdown": "^4.0.8",
     "react-popper": "^2.2.3",
diff --git a/react/helpers/isTesting.js b/react/helpers/isTesting.js
index 1d2f0cca53..f4653a2c49 100644
--- a/react/helpers/isTesting.js
+++ b/react/helpers/isTesting.js
@@ -1,5 +1,2 @@
-module.exports = () => {
-  return (
-    navigator && navigator.userAgent && navigator.userAgent.includes('Argos')
-  )
-}
+export default () =>
+  navigator && navigator.userAgent && navigator.userAgent.includes('Argos')
diff --git a/react/helpers/tracker.jsx b/react/helpers/tracker.jsx
deleted file mode 100644
index 5b67465a07..0000000000
--- a/react/helpers/tracker.jsx
+++ /dev/null
@@ -1,173 +0,0 @@
-/* global __PIWIK_TRACKER_URL__ __PIWIK_SITEID__ __PIWIK_DIMENSION_ID_APP__ */
-/* global Piwik */
-import memoize from 'lodash/memoize'
-
-import { readCozyDataFromDOM } from './appDataset'
-
-// Think of these functions as a singleton class with only static methods.
-let trackerInstance = null
-
-/**
- * Returns whether tracking should be enabled
- *
- * @returns {boolean|unefined} True if tracking should be enabled
- */
-export const shouldEnableTracking = memoize(() =>
-  readCozyDataFromDOM('tracking')
-)
-
-/**
- * @private
- *
- * Returns the instance of the piwik tracker, creating it on thee fly if required. All parameters are optionnal.
- * You should not use this method directly but rather `trackEvent`
- * @param   {string}  trackerUrl             The URL of the piwik instance, without the php file name
- * @param   {number}  siteId                 The siteId to use for piwik
- * @param   {boolean} automaticallyConfigure = true Pass false to skip the automatic configuration
- * @param   {boolean} injectScript = false Whether or not the Piwik tracking script should be injected
- * @returns {object | null }  An instance of `PiwikReactRouter` on success,
- *                            `null` if the creation fails (usually because of adblockers)
- *                            `null` if the user doesn't accept the tracking
- */
-export const getTracker = (
-  trackerUrl,
-  siteId,
-  automaticallyConfigure = true,
-  injectScript = false
-) => {
-  if (!shouldEnableTracking()) return null
-  if (trackerInstance !== null) return trackerInstance
-
-  try {
-    // If `injectScript` is falsy, we rely on having the Piwik tracking script already on the page, otherwise the tracking will fail.
-    // the next line is just there to throw in case the script is missing
-    if (injectScript === false) Piwik.getTracker()
-
-    var PiwikReactRouter = require('piwik-react-router')
-
-    trackerInstance = PiwikReactRouter({
-      url: trackerUrl || __PIWIK_TRACKER_URL__,
-      siteId: siteId || __PIWIK_SITEID__, // site id is required here
-      injectScript: injectScript
-    })
-
-    // apply the default configuration
-    if (automaticallyConfigure) configureTracker()
-
-    return trackerInstance
-  } catch (err) {
-    // this usually happens when there's an ad blocker
-    console.warn(err)
-    trackerInstance = null
-    return null
-  }
-}
-
-export const setTracker = tracker => (trackerInstance = tracker)
-
-/**
- * Configures the base options for the tracker which will persist during the session.
- *
- * @param {object} options A map of options that can be configured.
- * @param {string} options.userId
- * @param {number} options.appDimensionId
- * @param {string} options.app
- * @param {number} options.heartbeat
- */
-export const configureTracker = (options = {}) => {
-  // early out in case the tracker is not available
-  if (trackerInstance === null) {
-    // maybe we should throw an error here?
-    return
-  }
-
-  let appName
-
-  const root = document.querySelector('[role=application]')
-
-  if (root && root.dataset) {
-    appName = readCozyDataFromDOM('appName') || readCozyDataFromDOM('app').name
-  }
-  // merge default options with what has been provided
-  const { app, appDimensionId } = Object.assign(
-    {
-      appDimensionId: __PIWIK_DIMENSION_ID_APP__,
-      app: appName
-    },
-    options
-  )
-
-  trackerInstance.push(['setCustomDimension', appDimensionId, app])
-  trackerInstance.push([
-    'setCustomUrl',
-    window.location.pathname.substr(1) + location.search
-  ])
-}
-
-/**
- * Returns a redux middleware which tracks events if the action
- * has a `trackEvent` field containing at least `category` and `action`
- * fields.
- *
- * @returns {function}
- */
-export const createTrackerMiddleware = () => {
-  return () => next => action => {
-    if (
-      trackerInstance &&
-      action.trackEvent &&
-      action.trackEvent.category &&
-      action.trackEvent.action
-    ) {
-      trackerInstance.push([
-        'trackEvent',
-        action.trackEvent.category,
-        action.trackEvent.action,
-        action.trackEvent.name,
-        action.trackEvent.value
-      ])
-    }
-
-    return next(action)
-  }
-}
-
-/**
- * Resets the tracker; disconnecting it from history and the middleware, if
- * it was attached.
- *
- * *Please be aware*: if the tracker instance had been used outside of this
- * library (in another middleware for example), further calls to the tracking
- * server may still work.
- */
-export const resetTracker = () => {
-  if (trackerInstance) {
-    // stop tracking the history, if we were doing that
-    trackerInstance.disconnectFromHistory()
-    // we can't remove middlewares on the fly, but resetting the instance object will actually achieve that
-    trackerInstance = null
-  }
-}
-
-/**
- * Sends an event to matomo
- *
- * @param {array} event Event to track ['Drive', 'action', 'label']
- * @param {object} trackerForTest Mocked Tracker for test purpose
- */
-export const trackEvent = (event, trackerForTest) => {
-  const tracker = trackerForTest || getTracker()
-  if (tracker) {
-    let trackEventArray = []
-    if (event && event[0]) {
-      // Like that, we can do trackEvent(['Drive', 'option1']) without thinking of adding this `trackEvent` attr
-      if (event[0] !== 'trackEvent') {
-        trackEventArray = event.unshift('trackEvent')
-      }
-
-      trackEventArray = event
-    }
-
-    tracker.push(trackEventArray)
-  }
-}
diff --git a/react/helpers/tracker.spec.js b/react/helpers/tracker.spec.js
deleted file mode 100644
index 8e6d53ab6f..0000000000
--- a/react/helpers/tracker.spec.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { resetCache } from './appDataset'
-import * as trackerFile from './tracker'
-
-window.__PIWIK_DIMENSION_ID_APP__ = 1234
-
-jest.mock('./tracker', () => ({
-  ...jest.requireActual('./tracker'),
-  getTracker: jest.fn()
-}))
-
-describe('tracker / piwik action', () => {
-  beforeEach(() => {
-    trackerFile.getTracker.mockImplementation(() => ({ push: jest.fn() }))
-  })
-
-  afterEach(() => {
-    jest.resetAllMocks()
-    jest.restoreAllMocks()
-  })
-
-  it('should track event', () => {
-    const tracker = trackerFile.getTracker()
-    trackerFile.trackEvent(['trackEvent', 'toto'], tracker)
-    expect(tracker.push).toHaveBeenCalledWith(['trackEvent', 'toto'])
-  })
-  it('should add or not the trackEvent item in the array', () => {
-    const tracker = trackerFile.getTracker()
-    trackerFile.trackEvent(['toto'], tracker)
-    expect(tracker.push).toHaveBeenCalledWith(['trackEvent', 'toto'])
-    trackerFile.trackEvent(['trackEvent', 'foo'], tracker)
-    expect(tracker.push).toHaveBeenCalledWith(['trackEvent', 'foo'])
-  })
-
-  it('should memoize shouldEnableTracking', () => {
-    const querySelectorSpy = jest.fn()
-    jest
-      .spyOn(global.document, 'querySelector')
-      .mockImplementation(querySelectorSpy)
-    trackerFile.shouldEnableTracking()
-    trackerFile.shouldEnableTracking()
-    expect(querySelectorSpy).toHaveBeenCalledTimes(1)
-  })
-})
-
-describe('configureTracker', () => {
-  beforeEach(() => {
-    resetCache()
-    trackerFile.getTracker.mockImplementation(() => ({ push: jest.fn() }))
-  })
-
-  it('should read the correct values from DOM', () => {
-    const tracker = trackerFile.getTracker()
-    trackerFile.setTracker(tracker)
-    const data = { cozyDomain: 'cozy.tools:8080', appName: 'banks' }
-    document.body.innerHTML = `<div role="application" data-cozy='${JSON.stringify(
-      data
-    )}' />`
-
-    trackerFile.configureTracker({ app: 'banks2' })
-    expect(tracker.push).toHaveBeenCalledWith([
-      'setCustomDimension',
-      1234,
-      'banks2'
-    ])
-  })
-})
diff --git a/react/providers/I18n/format.jsx b/react/providers/I18n/format.jsx
index 2b494d3511..6a712e054d 100644
--- a/react/providers/I18n/format.jsx
+++ b/react/providers/I18n/format.jsx
@@ -1,50 +1,47 @@
 import format from 'date-fns/format'
 import formatDistanceToNow from 'date-fns/formatDistanceToNow'
+import {
+  enGB as enLocale,
+  fr as frLocale,
+  es as esLocale
+} from 'date-fns/locale'
 
 import { DEFAULT_LANG } from '.'
 
-const locales = {}
-let lang = DEFAULT_LANG
+let currentLocale
 
-/**
- * Ensure that the locale is in the correct format for date-fns (see 2.0.0 BC https://github.com/date-fns/date-fns/blob/main/CHANGELOG.md#200---2019-08-20)
- * @param {string} lang
- */
-const ensureLocaleFormat = lang => {
+const getDateFnsLocale = lang => {
   switch (lang) {
     case 'en':
-      return 'en-US'
-    case 'zh_cn':
-      return 'zh-CN'
-    case 'zh_tw':
-      return 'zh-TW'
+      return enLocale
+    case 'fr':
+      return frLocale
+    case 'es':
+      return esLocale
     default:
-      return lang
+      throw new Error('Locale not found')
   }
 }
 
 const getWarningMessage = lang =>
-  `The "${lang}" locale isn't supported by date-fns. or has not been included in the build. Check if you have configured a ContextReplacementPlugin that is too restrictive.`
+  `The "${lang}" locale isn't supported by date-fns or has not been included in the build. Check if you have configured a ContextReplacementPlugin that is too restrictive.`
 
 export const provideDateFnsLocale = (userLang, defaultLang = DEFAULT_LANG) => {
-  lang = ensureLocaleFormat(userLang)
-  const ensureDefaultLang = ensureLocaleFormat(defaultLang)
   try {
-    locales[
-      ensureDefaultLang
-    ] = require(`date-fns/locale/${ensureDefaultLang}/index.js`)
-  } catch (err) {
-    console.warn(getWarningMessage(ensureDefaultLang))
+    const userLocale = getDateFnsLocale(userLang)
+    currentLocale = userLocale
+    return userLocale
+  } catch (e) {
+    console.warn(getWarningMessage(userLang))
   }
 
-  if (lang && lang !== ensureDefaultLang) {
-    try {
-      locales[lang] = require(`date-fns/locale/${lang}/index.js`)
-    } catch (e) {
-      console.warn(getWarningMessage(lang))
-    }
+  try {
+    const defaultLocale = getDateFnsLocale(defaultLang)
+    currentLocale = defaultLocale
+    return defaultLocale
+  } catch (err) {
+    console.warn(getWarningMessage(defaultLang))
   }
-  return locales[lang]
 }
 
 export const initFormat =
@@ -61,5 +58,5 @@ export const initFormat =
   }
 
 export const formatLocallyDistanceToNow = date => {
-  return formatDistanceToNow(date, { locale: locales[lang] })
+  return formatDistanceToNow(date, { locale: currentLocale })
 }
diff --git a/react/providers/I18n/format.spec.jsx b/react/providers/I18n/format.spec.jsx
index 2430feb1b5..013186f319 100644
--- a/react/providers/I18n/format.spec.jsx
+++ b/react/providers/I18n/format.spec.jsx
@@ -4,9 +4,11 @@ describe('initFormat', () => {
   beforeEach(() => {
     jest.spyOn(console, 'warn').mockImplementation(() => {})
   })
+
   afterEach(() => {
     console.warn.mockRestore()
   })
+
   it('should not throw if a date-fns locale can not be found', () => {
     expect(() => initFormat('unknown-lang', 'unknown-default')).not.toThrow()
   })
@@ -17,7 +19,13 @@ describe('initFormat', () => {
     expect(date).toBe('1970-janvier-01')
   })
 
-  it('should fallback english if a date-fns locale can not be found', () => {
+  it('should use the correct default locale if user locale can not be found', () => {
+    const f = initFormat('it', 'fr')
+    const date = f(0, 'yyyy-LLLL-dd')
+    expect(date).toBe('1970-janvier-01')
+  })
+
+  it('should fallback english if user locale and default locale can not be found', () => {
     const f = initFormat('unknown-lang', 'unknown-default')
     const date = f(0, 'yyyy-LLLL-dd')
     expect(date).toBe('1970-January-01')
@@ -25,6 +33,12 @@ describe('initFormat', () => {
 })
 
 describe('formatLocallyDistanceToNow', () => {
+  beforeEach(() => {
+    // To avoid currentLocale set to 'fr' because of previous tests
+    const f = initFormat('en')
+    f(0, 'yyyy-LLLL-dd')
+  })
+
   it('should formatDistanceToNow with small value', () => {
     const date = Date.now() + 29 * 1000 // 29s
 
diff --git a/scripts/make-icon-sprite.sh b/scripts/make-icon-sprite.sh
index e7496748ff..f01441c977 100755
--- a/scripts/make-icon-sprite.sh
+++ b/scripts/make-icon-sprite.sh
@@ -10,4 +10,4 @@ echo "Making icon sprite, output file : ${outfile}..."
 echo $icons | xargs yarn svgstore --inline -o /tmp/icons-sprite.svg
 echo "// GENERATED FILE, DO NOT EDIT THIS FILE BY HAND" > $outfile
 echo "// Use yarn sprite to regenerate" >> $outfile
-echo "module.exports = \``cat /tmp/icons-sprite.svg`\`" >> $outfile
+echo "export default \``cat /tmp/icons-sprite.svg`\`" >> $outfile
diff --git a/scripts/make-palette.sh b/scripts/make-palette.sh
index 9fae6e1c95..1ea0a49b03 100755
--- a/scripts/make-palette.sh
+++ b/scripts/make-palette.sh
@@ -7,5 +7,5 @@ JS_PALETTE='react/palette.js'
 
 echo "Making palette, output file : ${JS_PALETTE}..."
 echo "// GENERATED AUTOMATICALLY FROM stylus/settings/palette.json" > $JS_PALETTE
-echo "module.exports = `cat stylus/settings/palette.json`" >> $JS_PALETTE
+echo "export default `cat stylus/settings/palette.json`" >> $JS_PALETTE
 node_modules/.bin/eslint --fix $JS_PALETTE
diff --git a/yarn.lock b/yarn.lock
index 5d7245fab7..d8bece8137 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14769,14 +14769,6 @@ pirates@^4.0.0, pirates@^4.0.1:
   dependencies:
     node-modules-regexp "^1.0.0"
 
-piwik-react-router@0.12.1:
-  version "0.12.1"
-  resolved "https://registry.yarnpkg.com/piwik-react-router/-/piwik-react-router-0.12.1.tgz#0caf984715a5b22c85610e0c02ae372b1ca37c41"
-  integrity sha512-Ebi7rBKV/S+YJ3UF/6t6n/wRx1/4yilR1caG/JhdefkYHh8gopqy2kxjaaD++LS9tTmtPNfokEHWbXTbw2W7OA==
-  dependencies:
-    url-join "^1.1.0"
-    warning "^3.0.0"
-
 pkg-conf@^1.1.2:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-1.1.3.tgz#378e56d6fd13e88bfb6f4a25df7a83faabddba5b"
@@ -20066,11 +20058,6 @@ urix@^0.1.0:
   resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
   integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
 
-url-join@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78"
-  integrity sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=
-
 url-join@^2.0.5:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728"
@@ -20368,13 +20355,6 @@ walker@^1.0.7, walker@~1.0.5:
   dependencies:
     makeerror "1.0.x"
 
-warning@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
-  integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=
-  dependencies:
-    loose-envify "^1.0.0"
-
 warning@^4.0.1, warning@^4.0.2, warning@^4.0.3:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"


Reviews:
user: login: JF-Cozy
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T14:55:43Z
user: login: JF-Cozy
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T14:56:07Z
user: login: zatteo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T15:01:22Z
user: login: zatteo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T15:02:08Z
user: login: zatteo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T05:33:51Z
user: login: JF-Cozy
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-06T13:07:30Z

Review comments:
diff_hunk: @@ -1,50 +1,47 @@
 import format from 'date-fns/format'
 import formatDistanceToNow from 'date-fns/formatDistanceToNow'
+import {
+  enGB as enLocale,
+  fr as frLocale,
+  es as esLocale
+} from 'date-fns/locale'
 
 import { DEFAULT_LANG } from '.'
 
-const locales = {}
-let lang = DEFAULT_LANG
+let currentLocale
 
-/**
- * Ensure that the locale is in the correct format for date-fns (see 2.0.0 BC https://github.com/date-fns/date-fns/blob/main/CHANGELOG.md#200---2019-08-20)
- * @param {string} lang
- */
-const ensureLocaleFormat = lang => {
+const getDateFnsLocale = lang => {
   switch (lang) {
     case 'en':
-      return 'en-US'
-    case 'zh_cn':
-      return 'zh-CN'
-    case 'zh_tw':
-      return 'zh-TW'
+      return enLocale
+    case 'fr':
+      return frLocale
+    case 'es':
+      return esLocale
     default:
-      return lang
+      throw new Error('Locale not found')
   }
 }
 
 const getWarningMessage = lang =>
-  `The "${lang}" locale isn't supported by date-fns. or has not been included in the build. Check if you have configured a ContextReplacementPlugin that is too restrictive.`
+  `The "${lang}" locale isn't supported by date-fns or has not been included in the build. Check if you have configured a ContextReplacementPlugin that is too restrictive.`
 
 export const provideDateFnsLocale = (userLang, defaultLang = DEFAULT_LANG) => {
-  lang = ensureLocaleFormat(userLang)
-  const ensureDefaultLang = ensureLocaleFormat(defaultLang)
   try {
-    locales[
-      ensureDefaultLang
-    ] = require(`date-fns/locale/${ensureDefaultLang}/index.js`)
-  } catch (err) {
-    console.warn(getWarningMessage(ensureDefaultLang))
+    const userLocale = getDateFnsLocale(userLang)
+    currentLocale = userLocale
+    return userLocale
+  } catch (e) {
+    console.warn(getWarningMessage(userLang))
   }
 
-  if (lang && lang !== ensureDefaultLang) {
path: react/providers/I18n/format.jsx
user: login: JF-Cozy
body: on perd la condition ici non ?
created_at: 2025-03-05T14:53:15Z
author_association: COLLABORATOR
path: react/providers/I18n/format.jsx
user: login: JF-Cozy
body: on ne charge plus les langues dynamiquement mais on les charge toute quoi qu'il arrive ici non ? Ca n'a pas d'impact sur le poids ? 🤔 Est-ce possible de faire un test dans MesPapiers ? Je pense que c'était surtout dans cette app qu'on utilisait provideDateFnsLocale...
created_at: 2025-03-05T14:54:48Z
author_association: COLLABORATOR
diff_hunk: @@ -1,50 +1,47 @@
 import format from 'date-fns/format'
 import formatDistanceToNow from 'date-fns/formatDistanceToNow'
+import {
+  enGB as enLocale,
+  fr as frLocale,
+  es as esLocale
+} from 'date-fns/locale'
 
 import { DEFAULT_LANG } from '.'
 
-const locales = {}
-let lang = DEFAULT_LANG
+let currentLocale
 
-/**
- * Ensure that the locale is in the correct format for date-fns (see 2.0.0 BC https://github.com/date-fns/date-fns/blob/main/CHANGELOG.md#200---2019-08-20)
- * @param {string} lang
- */
-const ensureLocaleFormat = lang => {
+const getDateFnsLocale = lang => {
   switch (lang) {
     case 'en':
-      return 'en-US'
-    case 'zh_cn':
-      return 'zh-CN'
-    case 'zh_tw':
-      return 'zh-TW'
+      return enLocale
+    case 'fr':
+      return frLocale
+    case 'es':
+      return esLocale
     default:
-      return lang
+      throw new Error('Locale not found')
   }
 }
 
 const getWarningMessage = lang =>
-  `The "${lang}" locale isn't supported by date-fns. or has not been included in the build. Check if you have configured a ContextReplacementPlugin that is too restrictive.`
+  `The "${lang}" locale isn't supported by date-fns or has not been included in the build. Check if you have configured a ContextReplacementPlugin that is too restrictive.`
 
 export const provideDateFnsLocale = (userLang, defaultLang = DEFAULT_LANG) => {
-  lang = ensureLocaleFormat(userLang)
-  const ensureDefaultLang = ensureLocaleFormat(defaultLang)
   try {
-    locales[
-      ensureDefaultLang
-    ] = require(`date-fns/locale/${ensureDefaultLang}/index.js`)
-  } catch (err) {
-    console.warn(getWarningMessage(ensureDefaultLang))
+    const userLocale = getDateFnsLocale(userLang)
+    currentLocale = userLocale
+    return userLocale
+  } catch (e) {
+    console.warn(getWarningMessage(userLang))
   }
 
-  if (lang && lang !== ensureDefaultLang) {
path: react/providers/I18n/format.jsx
user: login: JF-Cozy
body: ah pardon c'est en lien avec le commentaire, que ça ne faisait pas sens...
created_at: 2025-03-05T14:56:07Z
author_association: COLLABORATOR
path: react/providers/I18n/format.jsx
user: login: zatteo
body: Je vais vérifier sur mespapiers, en fait avant on les chargeait toutes toutes toutes (le bundler pouvait pas savoir celle qu'on allait prendre dans le require donc il prenait tout), là on charge les 3 supportées 'fr', 'en', 'es'.
created_at: 2025-03-05T15:01:22Z
author_association: MEMBER
diff_hunk: @@ -1,50 +1,47 @@
 import format from 'date-fns/format'
 import formatDistanceToNow from 'date-fns/formatDistanceToNow'
+import {
+  enGB as enLocale,
+  fr as frLocale,
+  es as esLocale
+} from 'date-fns/locale'
 
 import { DEFAULT_LANG } from '.'
 
-const locales = {}
-let lang = DEFAULT_LANG
+let currentLocale
 
-/**
- * Ensure that the locale is in the correct format for date-fns (see 2.0.0 BC https://github.com/date-fns/date-fns/blob/main/CHANGELOG.md#200---2019-08-20)
- * @param {string} lang
- */
-const ensureLocaleFormat = lang => {
+const getDateFnsLocale = lang => {
   switch (lang) {
     case 'en':
-      return 'en-US'
-    case 'zh_cn':
-      return 'zh-CN'
-    case 'zh_tw':
-      return 'zh-TW'
+      return enLocale
+    case 'fr':
+      return frLocale
+    case 'es':
+      return esLocale
     default:
-      return lang
+      throw new Error('Locale not found')
   }
 }
 
 const getWarningMessage = lang =>
-  `The "${lang}" locale isn't supported by date-fns. or has not been included in the build. Check if you have configured a ContextReplacementPlugin that is too restrictive.`
+  `The "${lang}" locale isn't supported by date-fns or has not been included in the build. Check if you have configured a ContextReplacementPlugin that is too restrictive.`
 
 export const provideDateFnsLocale = (userLang, defaultLang = DEFAULT_LANG) => {
-  lang = ensureLocaleFormat(userLang)
-  const ensureDefaultLang = ensureLocaleFormat(defaultLang)
   try {
-    locales[
-      ensureDefaultLang
-    ] = require(`date-fns/locale/${ensureDefaultLang}/index.js`)
-  } catch (err) {
-    console.warn(getWarningMessage(ensureDefaultLang))
+    const userLocale = getDateFnsLocale(userLang)
+    currentLocale = userLocale
+    return userLocale
+  } catch (e) {
+    console.warn(getWarningMessage(userLang))
   }
 
-  if (lang && lang !== ensureDefaultLang) {
path: react/providers/I18n/format.jsx
user: login: zatteo
body: J'ai un peu refacto en même temps, heuresement qu'il y avait des tests (et j'en ai profité pour en ajouter un parce qu'il y avait un cas pas supporté)
created_at: 2025-03-05T15:02:08Z
author_association: MEMBER
path: react/providers/I18n/format.jsx
user: login: zatteo
body: Ça marche dans mespapiers. Par contre aucune optimisation du build parce que je n'ai pas migré en ES modules les fichiers de configuration de webpack donc il reste en mode CommonJS donc sans tree shaking.

Je préfère ne pas m'en occuper, vu que notre nouveau standard est en train de devenir rsbuild.
created_at: 2025-03-06T05:33:50Z
author_association: MEMBER

PR comments:
user: login: bundlemon[bot]
created_at: 2025-03-05T13:54:22Z
body: <!-- bundlemon -->
## BundleMon

  
  
  
  
  
  
  
<details>
<summary>Unchanged files (3)</summary>

Status | Path | Size | Limits
:------------: | ------------ | :------------: | :------------:
:white_check_mark: | dist/cozy-ui.min.css<br/> | 20.23KB | +10%
:white_check_mark: | transpiled/react/stylesheet.css<br/> | 16.95KB | -
:white_check_mark: | dist/cozy-ui.utils.min.css<br/> | 10.91KB | +10%
</details>



No change in files bundle size


  
  
  
  
  
<details open>
<summary>Groups updated (1)</summary>

Status | Path | Size | Limits
:------------: | ------------ | :------------: | :------------:
:white_check_mark: | transpiled/react/**<br/> | 672.29KB (-2.19KB -0.33%) | -
</details>

  
  


Final result: :white_check_mark:

[View report in BundleMon website ➡️](https://app.bundlemon.dev/projects/6721fd33ab518d9c4be5a56d/reports/67c8578da1a7cbea81dc8289)

---
<p align="center"><a href="https://app.bundlemon.dev/projects/6721fd33ab518d9c4be5a56d/reports?branch=feat%2Ftype-module&resolution=all" target="_blank" rel="noreferrer noopener">Current branch size history</a> | <a href="https://app.bundlemon.dev/projects/6721fd33ab518d9c4be5a56d/reports?branch=master&resolution=days" target="_blank" rel="noreferrer noopener">Target branch size history</a></p>
author_association: NONE
user: login: JF-Cozy
created_at: 2025-03-06T13:08:03Z
body: J'ai validé mais bien attendre qu'on remette Argos en route (dans le doute...).

Donc du coup le tree shaking rien à voir avec le `type module` dans package.json, c'est juste le `module.export vs export` ?
author_association: COLLABORATOR
user: login: zatteo
created_at: 2025-03-07T07:47:13Z
body: > J'ai validé mais bien attendre qu'on remette Argos en route (dans le doute...).
> 
> Donc du coup le tree shaking rien à voir avec le `type module` dans package.json, c'est juste le `module.export vs export` ?

Si quand même, je rajouterai peut être le type module. Le type module permet de dire explicitement au bundler "tout ce qui est là dedans c'est du module". Même si c'est pas 100% vrai. Là j'ai finalement fait l'autre approche, en migrant en module le code restant. Et le bundler, vu qu'il n'y a pas type modul, parcourt rapidement les fichiers pour voir si c'est du module (si oui il optimise) ou pas (dans ce cas il fait rien).
author_association: MEMBER
user: login: cozy-bot
created_at: 2025-03-07T07:56:30Z
body: :tada: This PR is included in version 118.0.0 :tada:

The release is available on:
- [npm package (@latest dist-tag)](https://www.npmjs.com/package/cozy-ui/v/118.0.0)
- [GitHub release](https://github.com/cozy/cozy-ui/releases/tag/v118.0.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: NONE