Avoiding a long list of imports in React - reactjs

For a SET game that I am creating in React (using Create React App), I need to import 81 image files representing the 81 possible cards used in the game. With the help of a Python script, I created this extremely long list of imports
import i0000 from './assets/0000.png'
import i0001 from './assets/0001.png'
[...]
import i2221 from './assets/2221.png'
import i2222 from './assets/2222.png'
and since I will need to reference those variables using strings representing each card, I have this object:
const refs = {
'0000': i0000,
'0001': i0001,
[...]
'2220': i2220,
'2221': i2221,
'2222': i2222
};
The good thing is that now I have all the card images pre-loaded to be called easily with
<img src={refs[card]} />
But the bad thing is I have 164 lines of ridiculous code that make it work. I'm wondering if there is a better way to pre-cache and reference a directory full of images.

Move all the images to your public folder, say public/cards.
Then you can reference them with <img src={`cards/${card}.png`} /> without the need for any import statement.
Webpack can no longer warn you if the referenced images are missing though, so just make sure they are there.

Two approaches:
First, you eliminate manually creating the refs object by struturing your code like this:
export i0000 from './assets/0000.png'
export i0001 from './assets/0001.png'
And where you want to use these assets:
imports * as refs from './assets'
<img src={refs[card]} />
Secondly, you could use a bundling system that supports dynamic requires e.g. https://webpack.github.io/docs/context.html#dynamic-requires

Related

What the best way to import assets in React JS?

I'm currently working on a project with React JS, that contains lots of assets. And actually, I'm wondering what is the best way to import assets between these two methods :
- Creating a JSON file that contains all informations, with their assets paths (eg. mySuperImg: "../assets/img/myImage.jpg" then using the path for my image tags)
- Importing all assets directly in the component (or using the Context API, perhaps?) (eg. import {myImage} from "../assets/myImage.jpg" then using the img tag with that path <img src={myImage} alt="My Image"/>)
My questions : What the best way to import assets ? Is there a speed difference between these two methods ?
Thanks! Take care!
I'd say it depends on you. Whichever way you feel comfortable, you should go for it. Generally, I import the assets in the corresponding file, but the other way can also be used. Instead of json, you could use js approach as well, similar to how we export actions, for eg,
export const ADD_PROJECT = "add_project";
export const FETCH_PROJECTS = "fetch_projects";
You could use something like,
export const IMAGE = require("the location to your image");
To answer if it would take more time, I don't think that there would be any performance issue, as we use a similar approach for the actions most of the times.
it's best to keep the images in assets and use them in your source, if you work with the JSON file then you have to keep track of both of them and it will mess up as you say you have a lot of assets.

create-react-app reduce build size: main.[hash].chunk.js is 3MB+ with mostly images

I build my app with yarn build.
It generate a 3.27MB build\static\js\main.8dc5bf7f.chunk.js file:
3.27 MB build\static\js\main.8dc5bf7f.chunk.js
82.79 KB build\static\js\2.61d04f1f.chunk.js
2 KB build\static\css\main.275d97bd.chunk.css
1.93 KB build\static\css\2.8380becc.chunk.css
768 B build\static\js\runtime~main.848c2454.js
The bundle size is significantly larger than recommended.
Consider reducing it with code splitting
You can also analyze the project dependencies
I used source-map-explorer to analyze the file and it show:
So the file include all of the images of my entire app.
But funny thing is the build still have a static/media directory which has all of those images in .png form. And apparently the app still load those png files to show the images.
So what are those things in the js file? I wonder if they're even used at all?
Please:
Explain to me why all of the images in my entire application is included in my logic javascript file.
Is there anyway to remove them? I need to increase page load. 4MB at initial load is unbelievable.
This is a tough problem that most engineers have as the app grows larger.
In your case you're importing a lot of small PNGs that get base64 encoded due to CRA's webpack setting. You can override that behavior with a library like react-rewired. While it's a bit more involving process, I'd recommend that over maintaining an external image repository. It's easier to test and use in a development environment. Especially when offline.
I also see that most of your views are bundled together in main.js.
The secret to cutting down the bundle size is in leveraging code splitting and lazy loading.
Here's the link to the video that I put together for this: https://www.youtube.com/watch?v=j8NJc60H294
In a nutshell, I suggest the following patterns:
Code-split routes (if any)
This is commonly achieved by using React.lazy()
import React, { Suspense, lazy } from 'react';
// import MyRoute from 'routes/MyRoute' - we are replacing this import
const MyRoute = lazy(() => import('routes/MyRoute'));
const Loading = () => <div>Loading route...</div>;
const View = () => (
<Suspense fallback={Loading}>
<MyRoute />
</Suspense>
);
Code-split low priority elements
Every page has Critical Path content. It's the content your visitors want to experience first. It's the primary purpose of a view. It's usually in the above the fold area (the part of the view they see when the page loads, without any scrolling).
However, that part can also be dissected based on priority. A Hero is a good critical path example, so we should prioritize it. A Hamburger menu is a great element to de-prioritize because it requires interaction to be viewed.
Anything below the fold can be de-prioritized too. A great example of this is Youtube comments. They only get loaded if we scroll down sufficiently.
You can follow the same principle as above to prioritize critical path content:
import React, { Suspense, lazy } from 'react';
import Hero from 'component/Hero'
const Burger = lazy(() => import('components/Burger'));
// The fallback should be used to show content placeholders.
// It doesn't have to be a loading indicator.
const Loading = () => <img src="path/to/burger/icon" alt="Menu"/>;
const View = () => (
<main>
<Hero />
<Suspense fallback={Loading}>
<Burger />
</Suspense>
</main>
);
Create abstract components for libraries
Following the principles above, I suggest creating simple abstract components for every library. Let's say you need animations and you use Framer Motion. You can import it in one place and use everywhere.
Many libraries ship with named exports and lazy() doesn't support that. I wrote a very simple library react-lazy-named that helps with that.
// AnimatedDiv.js
import React, { lazy } from 'react';
// This is similar to
// import { motion } from 'framer-motion';
// const Div = motion.div;
// Also, we hint webpack to use resource preloading (think service worker goodness)
const Div = lazy(() => import('framer-motion' /* webpackPreload: true */), 'motion.div'));
// Use regular div as a fallback.
// It will be replaced with framer-motion animated Div when lazy-loaded
const AnimatedDiv = (props) => (
<Suspense fallback={<div>{props.children}</div>}>
<Div {...props} />
</Suspense>
);
If you have any questions or need and help with this you can reply here or in the comments of the aforementioned video on Loading React apps in under 3 seconds
Basically, anything you import in your app and handle bundling it with webpack would be considered as a dependency.
so: import img from path/to/img would make it a dependency, thus, included in your bundle, that's what you want to escape.
There are two possible scenarios to work-around this:
Stop importing images, make them available/hosted in a CDN such like AWS S3.
Apply a Move Statics Out approach, which is about moving all directories for static files out of the bundle, make them available in statics dependent folder and start using relative links instead, I would suggest copy-webpack-plugin.
For me, I would go with number 1.
Small image files will be loaded into your code when build with url-loader to reduce number of image requests. Large image files will be copied to build folder with file-loader.
You can use webpack option IMAGE_INLINE_SIZE_LIMIT to control which size of images will be put into the bundled js.
Create file .env.production at the root of project dir(same level with file package.json)
Add IMAGE_INLINE_SIZE_LIMIT=0 to this file, this tells webpack do not put any images into the js as base64 data. the default value is 10000, which will put images large than 10kb into js file.
To reduce to chunk js file size, take a look at Code splitting here: https://reactjs.org/docs/code-splitting.html

Dynamically Importing Images Based on a Variable

I have a requirement to select, from a local source, an image based on the value passed back from the REST API I am using. For example:
//Psuedo-call from the API
var imageIdToSelect = response.data.imageId
//Then later in the render()
<img src={ baseUrl + imageIdToSelect } />
I have a solution to this, which is to use require() as that allows me to append the url as such:
<img src={ require(baseUrl + imageIdToSelect) } />
This works fine, however, I am using a Microsoft TSLINT setup that does not allow require() over the prefered import at the top of the file "no-require-imports".
I know I am not meant to let linting tools control my work to the point where I am just blindly following rules. So my question is two-fold:
Why is it frowned upon to use require() in such a way. One reason I could think of is that if all of the external files/resources are declared at the top of the file, then you don't have to look through the source to find them hidden in functions.
What would the import x from './' solution look like here? I have seen people creating index.js and index.d.ts files inside their image folders to import and export all the images inside but that seems a tad extraneous.
Edit: I also have just realised that using require() with a non-literal string is a violation of my ts-linting too.
Thanks in advance

importing image with es6 import or require during runtime

I have this image folder:
- country
- EN
- FR
- IT
I want to import an image this way:
import image from "./country/{country}/test.png";
How can I do this?
i will always use both of the methods (require, import).
first thing is i do categorise images in 2 ways:
what i am using so may times and using it frequently.
what is rarely used.
then i will create an js file in that i do import all 1'st kind of images (frequently used). When project will load it will execute all import statements first so that time my all of the images are imported already so then whenever i need this images i do get that image from utility what i have created for importing images. that util file will be kind of this:
import CSK from "../assets/images/teamLogo/CSK.png";
import CSK_bw from "../assets/images/teamLogo/CSK-bw.png";
export function getImage(name) {
switch (name) {
case "CSK":
return CSK;
case "CSK_bw":
return CSK_bw;
}
}
so whenever i need image i just import above function with image name.
and second category images i will use as require.

Angular2 modular system break of modularity

I present you the following code:
https://plnkr.co/edit/xxNW1xAIPoGtTK84OxGq
NOTE: This does not work because of SystemJS which I just can not configure. Whoever wants can edit if freely to make it work. I have been using the angular-cli and default webpack config and this works.
My question is about the "AlertService" which is needed to display an "alert". It has been extracted in the core module. However when I need to use it I have import it like so import { AlertService } from '../core/alert/alert.service' as present in dashboard.component.ts in order to inject it.
Doesn't this break the modular approach since I have to give it the path to the class ? If I change the location of the AlertService within the CoreModule I still have to go and change the string in the DashboardComponent. Also in this example if AlertService is not present DashboardComponent will not fire ... but DashboardComponent is part of the DashboardModule which should be able to start on its own - otherwise what is the point of the modules if they are coupled statically I could just put everything in one place.
What I want is to create a general alert component which I only need to include and once in the whole app and be able to use it everywhere.
But I think I am misunderstanding the concept of modules and/or how to use them. I have read the Modules' section in Angular.io multiple times and gone through multiple tutorials.
Best regards
If having to change lots of file paths bothers you a lot, one possible solution is to add another layer - create a services.ts file, then have the canonical imports there rather than in each component. That way, you only have to update one file if you change the path:
services.ts:
import { AlertService } from './alert.service';
// ES2015 shorthand property initializer
export default {
AlertService
};
yourComponent.ts:
import { AlertService } from "../services";
...
If you want a component to work even if a service is not present, you can make it an optional dependency (returning null if it doesn't exist), but TypeScript forces you to still have to import the service into the file to get code completion/typings. That's always felt a little janky to me.

Resources