Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webpack dynamic import .json file?

I'm using React Intl for x number of languages (example below) and at the moment Im importing the following where I setup my App:

import { addLocaleData } from 'react-intl';
import locale_en from 'react-intl/locale-data/en';
import locale_de from 'react-intl/locale-data/de';

import messages_en from './translations/en.json';
import messages_de from './translations/de.json';

addLocaleData([...locale_en, ...locale_de]);

...
export const messages = {
    en: messages_en,
    de: messages_de
}

Since these language files are being imported no matter which language is being used my main bundle js file is getting pretty big, especially from the .json files.

How can I with Webpack split these language files (or copy them to my dist folder using CopyWebpackPlugin) and then dynamically import them based on the language being used at the moment?

The app is isomorphic so this same code is being run on the server.

like image 651
nickelman Avatar asked Nov 06 '22 13:11

nickelman


1 Answers

I've been working on something like this lately, although I don't need SSR for my project. I found that pairing dynamic import syntax with React's Suspense component achieves the desired result. Here's a rough overview of what I found to work, at least in my case, which doesn't include SSR:

// wrap this around your JSX in App.js:
<React.Suspense fallback={<SomeLoadingComponent />}>
  <AsyncIntlProvider>
    {/* app child components go here */}
  </AsyncIntlProvider>
</React.Suspense>

// the rest is in support of this
// can be placed in another file
// simply import AsyncIntlProvider in App.js

const messagesCache = {};

const AsyncIntlProvider = ({ children }) => {
  // replace with your app's locale getting logic
  // if based on a hook like useState, should kick off re-render and load new message bundle when locale changes (but I haven't tested this yet)
  const locale = getLocale();

  const messages = getMessages(locale);
  return (
    <IntlProvider locale={locale} messages={messages}>
      {children}
    </IntlProvider>
  );
};

function getMessages(locale) {
  if (messagesCache[locale]) {
    return messagesCache[locale];
  }
  // Suspense is based on ErrorBoundary
  // throwing a promise will cause <SomeLoadingComponent /> to render until the promise resolves
  throw loadMessages(locale);
}

async function loadMessages(locale) {
  // dynamic import syntax tells webpack to split this module into its own chunk
  const messages = await import('./path/to/${locale}.json`);
  messagesCache[locale] = messages;
  return messages;
}

Webpack should split each locale JSON file into its own chunk. If it doesn't, something is likely transpiling the dynamic import syntax to a different module system (require, etc) before it reaches webpack. For example: if using Typescript, tsconfig needs "module": "esnext" to preserve import() syntax. If using Babel, it may try to do module transpilation too.

The chunk output for a single locale will look something like this; definitely more than would be achieved via CopyWebpackPlugin:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{

/***/ "./path/to/en-US.json":
/*!*************************************!*\
  !*** ./path/to/en-US.json ***!
  \*************************************/
/*! exports provided: message.id, default */
/***/ (function(module) {

eval("module.exports = JSON.parse(\"{\\\"message.id\\\":\\\"Localized message text\\\"}\");//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvbG9jYWxpemF0aW9uL2VuLVVTLmpzb24uanMiLCJzb3VyY2VzIjpbXSwibWFwcGluZ3MiOiIiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./path/to/en-US.json\n");

/***/ })

}]);

Hopefully, this is a good starting point and either works with SSR or can be modified to work with SSR. Please report back with your findings on that subject. 🙂

like image 184
nickiaconis Avatar answered Nov 10 '22 00:11

nickiaconis