Tree shaking not working for node package - javascript

I have a node module that is generally shaped like this (simplifying for example's sake):
# color.js
export var red = '#f00';
export var yellow = '#ff0';
export var blue = '#00f';
# images.js
export var image1 = '<base64 string approximately ~ 500kb>';
export var image2 = '<base64 string approximately ~ 500kb>';
# index.js
// JavaScript tokens
import * as colorExport from './color';
import * as imagesExport from './images';
export var color = colorExport;
export var images = imagesExport;
And then in my Next.js app (also tested in Create React App) I import the value like this:
# Home
import { color } from 'my-module'
const Home = () => (
<h1 style={{ color: color.red }}>Hello!</h1>
)
export default Home
This works fine. However, in both Next.js and Create React App, when I go to build the production-ready site EVERYTHING from my-module (color and images) is included and the final bundle size is a little greater than 1MB.
Now, if I change the import statement in Home to this:
import * as color from 'my-module/color'
Then the final bundle only includes the color values as I would expect. But I would like to not have to import so specifically. Is there something obvious I'm doing wrong here?

Refactoring color and images to have a default export rather than named exports and then also rewriting the way index import and exports fixed the problem.
E.g.
# color.js
export default {
red: '#f00',
yellow: '#ff0',
blue: '#00f'
}
# index.js
export { default as color } from './color';
export { default as images } from './images';

Related

How to allow specific values for an icon dynamic import?

I have created a dynamic import to use icons from our library. It looks something like this:
// icons file
export { ArrowLeft } from './ArrowLeft';
export { ArrowRight } from './ArrowRight';
export { Checked } from './Checked';
export { CircleCar } from './CircleCar';
And this is the component:
import * as Icons from '../../components/Icons';
import * as z from 'zod';
const Schema = z.object({
content: z.object({
iconName: z.string()
})
})
type JustAReactComponentProps = z.infer<typeof Schema>
const JustAReactComponent = ({content}: JustAReactComponentProps) => {
const SecondaryIcon = Icons[content.iconName] as React.JSXElementConstructor<any>;//is this the correct type btw?
return <SecondaryIcon />
}
And lets say that we have 30 icons and someone tries to set iconName as an icon that is not in the list, how can I enforce with Typescript that only the ones in the icons file are enabled to be used?
This question below seems to have the same problem I do but the solutions do not work for me(And I'm not looking for an external package).
How to find all imported (in code) dependencies within a TypeScript project?

Export all constants from files into index.ts and then export those constants as keys of an object?

I have a constant library that has many files such as the following structure:
#1 index.ts
#2 src/
contact.ts
const first = [];
const second = [];
const third = [];
location.ts
const first = '';
const second = '';
const third = '';
Is it possible to import and export all of the constants from each file into index.ts such that I can import into a project such as the following:
// Imports: Constants
import { constants } from '#jeff/constants-library';
console.log(constants.contact.first);
console.log(constants.contact.second);
console.log(constants.contact.third);
What is the fastest/most efficient way to dynamically export the constants from my library files so I can import the into my projects?
In your files like contact and location, you'll need to mark the const values you want as exports. Modules are designed for encapsulation, so dynamic export isn't really an option here. However, it's just a matter of adding the keyword export:
export const first = [];
After that, you can create a constants-library.ts or constants-library/index.ts which automatically exports and imports:
import * as foo from './foo';
import * as bar from './bar';
export const constants = { foo, bar };
At this point, assuming your #jeff path is set up, your code should work as expected:
import { constants } from '#jeff/constants-library';
console.log(constants.contact.first);
Note that this will include all of the exports in your files, since TypeScript can't tell which constants you want or didn't want--it only knows which exports you've listed. As an alternative, rather than exporting all of your consts individually, you could bundle them into a single export using object shorthand.
// in contact.ts and location.ts
export constants = { first, second, third };
// in constant-library.ts or constant-library/index.ts
import { constants as contact } from './contact';
import { constants as location } from './location';
export constants = { contact, location };

Is it possible to import all classes from a directory and initialise them?

Say this is how my directory tree looks like:
/
Children/
ChildrenIndex.js
Base.js
ChildA.js
ChildB.js
ChildC.js
...
ChildN.js
Main.js
Where any given ChildX.js looks like this,
import Base from './Base.js';
class ChildX extends Base {
constructor(params) {
this.params = params
}
}
export default ChildX
And ChildrenIndex.js looks like this,
export {default as ChildA} from './ChildA.js';
export {default as ChildB} from './ChildB.js';
export {default as ChildC} from './ChildC.js';
...
export {default as ChildN} from './ChildN.js';
Is it possible that I can import all children of Base.js in Main.js and get a list full of instances, each of a child's initialisation? Here is the pseudo code doing what I want:
import { * } from './Children/ChildrenIndex.js'
class Main {
constructor() {
let childConstructors = getAllConstructorsIn('./Children/ChildrenIndex.js');
let children = [];
for (let constrt of childConstructors) {
children.push(new constrt(params));
}
}
}
I can manually do this with import { ChildA, ChildB, ChildC, ... ,ChildN } from './Children/ChildrenIndex.js' and then fill the list like this: [new ChildA(params), new ChildB(params), new ChildC(params), ... , new ChildN(params)]. And, that works fine. However, I want someone to be able to just put an implementation of the Base.js in the children directory in order to add new functionality and not have to worry about then going and writing import statements and initialisation in other parts of the code. I am doing this in the model of a React app if that is relevant.
This should work in Main.js:
import * as Children from "./Children/ChildrenIndex";
Children.ChildA // ChildA component
Children.ChildB // ChildB component

Does having two exports increase bundle size

If I have the following code
class x {
...
}
export CreateFragmentContainer(x,graphqlQuery)
Will the bundle size of my project if I do an another export of class x without the fragmentContainer such as the below?
export class x {
...
}
export CreateFragmentContainer(x,graphqlQuery)
Also, will the answer to this question change depending on the framework used?
The bundle size increases only if you import both at the same time, or when you have a default export + named exports, but just use the default export.
What i mean:
export default class {
...
}
export const a = '1';
----
Import Something from './myfile'
vs
export default class {
...
}
export const a = '1';
----
Import { a } from './myfile'
Method 2 has lower bundle size.
No, it does not depend on the framework, it is a "language feature".

How to load image path correctly?

I have the folder structure like the following.
src/
components/
Icon/
Icon.js
Style.js
images/
apple.svg
google.svg
facebook.svg
index.js
I want to export the paths of images under images folder in 'images/index.js'.
I tried something like this below.
images/index.js
export const apple = './apple.svg';
export { google } from './google.svg';
export const facebook = './facebook.svg';
And I want to load the paths of those images in '/Components/Icon/Style.js' like the following.
Style.js
import styled from 'styled-components';
// I know this works fine
import apple from '../../images/apple.svg';
// This doesn't work although I want it to work.
import { google } from '../../images/index';
// This doesn't work as well although I want it to work.
import { facebook } from '../../images/index';
const Icon = styled.i`
background: blah blah blah
`;
export default Icon;
However, I couldn't load the paths of images.
What is the right way to do that?
Is it impossible?
export const apple = './apple.svg';
Is this the only way I can load the path of image?
images/index.js
import apple from './apple.svg';
import google from './google.svg';
import facebook from './facebook.svg';
export {
apple,
google,
facebook
}
Style.js
import styled from 'styled-components';
import { apple, google, facebook } from '../../images';
const Icon = styled.i`
background: blah blah blah
`;
export default Icon;
This should work, and also looks pretty clean code to my eyes!
A Recomendation
Requesting an image for every "icon" to your server feels really expensive.
Instead, since you have access to the resources I would go with this structure:
src/
components/
Icon/
Apple.js
Google.js
Facebook.js
Facebook.js
import React from 'react';
function Facebook({ width = '266', height = '266' } = {}) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 266.893 266.895" enableBackground="new 0 0 266.893 266.895">
<path
fill="#3C5A99"
d="M248.082,262.307c7.854,0,14.223-6.369,14.223-14.225V18.812 c0-7.857-6.368-14.224-14.223-14.224H18.812c-7.857,0-14.224,6.367-14.224,14.224v229.27c0,7.855,6.366,14.225,14.224,14.225 H248.082z"
/>
<path
fill="#FFFFFF"
d="M182.409,262.307v-99.803h33.499l5.016-38.895h-38.515V98.777c0-11.261,3.127-18.935,19.275-18.935 l20.596-0.009V45.045c-3.562-0.474-15.788-1.533-30.012-1.533c-29.695,0-50.025,18.126-50.025,51.413v28.684h-33.585v38.895h33.585 v99.803H182.409z"
/>
</svg>
);
}
export default Facebook;
Advantages:
No network request
Reusable icon
Customizable with states (colors, shapes, etc)

Categories