jones
jones

Reputation: 625

Conditionally import assets in create-react-app

Is it possible to conditionally import assets when creating a React app using create-react-app? I'm aware of the require syntax - example:

import React from "react";


const path = process.env.REACT_APP_TYPE === "app_1" ? "app_1" : "app_2";

const imagePath = require(`./assets/${path}/main.png`);

export default function Test() {
  return (
      <img src={imagePath} alt="" />
  );
}

This however bundles all my assets no matter what.

It will load the proper image, but it will still bundle all the files together in the final build.

When I look in the dev tools for the final build, I can see all the assets there even though I only wanted to load the assets for app_1.

Am I forced to touch the webpack config, if so, what should I change? or is there another way?

Upvotes: 15

Views: 6807

Answers (4)

Andrew Zhilin
Andrew Zhilin

Reputation: 1708

There is a way to do it with default CRA settings

You can add to .env something like

REACT_APP_SKIN=1

Put your skin assets in public/css1, public/css2 etc. And include them in public/index.html using code like

<link href="/css%REACT_APP_SKIN%/theme.css" rel="stylesheet">

Upvotes: 0

jayarjo
jayarjo

Reputation: 16736

In the days when React didn't exist we didn't put assets into our JS files. We let the CSS to decide, what assets to load for what selectors. Then you could simply switch a corresponding class on or off for a corresponding element (or even the whole page) and viola it changes color, background, or even a form. Pure magic!

Ah. What times these were!

All above is true and I do not understand why would anyone do or recommend doing it differently. However if you still want to do it (for any reason) - you can! Latest create-react-app comes with out-of-the-box support for lazy loading of arbitrary components via dynamic importing and code splitting. All you need to do is use parenthesized version of the import() statement instead of the regular one. import() takes in a request string as usual and returns a Promise. That's it. Source code of the dynamicaly requested component won't be bundled in, but instead stored in separate chunks to be loaded on demand.

Before:

import OtherComponent from './OtherComponent';

function MyComponent() {   
  return (
    <div>
      <OtherComponent />
    </div>   
  ); 
}

After:

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}

Notice how function MyComponent part is identical.

For those wondering if it is tied to CRA or React, it's not. It's a generic concept that can be used in vanilla JavaScript.

Upvotes: 9

Abulafia
Abulafia

Reputation: 1765

You will need to use webpack (or other bundler.) The code is not being run when it's bundled, so the compiler has no way of knowing which branch of logic to follow (app_1 or app_2). Therefore you have to get into the bundler's logic in order to achieve your goal.

However, this isn't as scary as it seems since webpack has built in capability to do this (no 3rd parties required...)

I would look into using webpack.providePlugin

(https://webpack.js.org/plugins/provide-plugin)

or its sibling DefinePlugin

(https://webpack.js.org/plugins/define-plugin)

(I'm afraid these examples are off the top of my head, so it's very unlikely they'll work on first pass.)

Examples:

Both will require a provider module...

// in path/provider.js

module.exports = {
  live: '/path/to/live/image',
  dev: '/path/to/dev/image'
}

Provide Plugin Example

// in webpack

new webpack.ProvidePlugin({
    imagePath: [
      'path/provider',         // the file defined above
      process.env.ENVIRONMENT  // either 'dev' or 'live'
    ]
  }),
// in code

export default function Test() {
  return (
      <img src={imagePath} alt="" />
  );
}

Define Plugin example:

// in webpack

new webpack.DefinePlugin({
  'process.env.ENVIRONMENT': JSON.stringify(process.env.ENVIRONMENT)
});
// in code

var providers = require('path/provider'); // same path provider as above

export default function Test() {
  return (
      <img src={providers[process.env.ENVIRONMENT]} alt="" />
  );
}

In both cases the bundler is forced to collapse your variable to an actual literal value at compile time - before bundling has taken place. Since you have now collapsed the logical path down to a single option, it is now free to only bundle the relevant assets.

Upvotes: 7

Joseph
Joseph

Reputation: 4725

You can't do this with default CRA settings.

Because if your dynamic require or dynamic import path is not static, webpack won't be able to determine which assets to include in the final build folder, therefore, it will grab everything from your ./src folder, and put them all to your build folder.

Upvotes: 3

Related Questions