Developing a separate NPM package for a portion of the app causes an increase in the total app size

Our projects utilize create-react-app and Typescript, resulting in the emergence of a collection of commonly used React components. To enhance maintenance and reusability, we decided to extract these components into their own NPM package named PackageA. Surprisingly, this transition caused the overall size of our test application, referred to as TestApp, to increase compared to when the components were integrated within the app itself. TestApp is a simplistic app that primarily demonstrates some, but not all, aspects of the components in PackageA, previously housed within the project but now imported from the privately published PackageA.

Initial sizes of JS chunks before extracting PackageA out from TestApp ("main" indicating code from the project itself, while the other chunk holds dependencies):

  • Gzipped: "main" chunk ~ 31 kB, chunk with external dependencies ~ 193 kB (post build output)
  • Unzipped: "main" chunk ~ 93 kB, chunk with external dependencies ~ 653 kB (browser size)

Sizes after separating PackageA from TestApp:

  • Gzipped: main chunk ~ 22 kB, chunk with external dependencies ~ 215 kB (post build output)
  • Unzipped: main chunk ~ 57 kB, chunk with external dependencies ~ 745 kB (browser size)

This results in an increase of 13 kB gzipped and 56 kB unzipped, corresponding to approximately 6% gzipped and 8% unzipped size increment. Though not drastic, the difference is notable considering the expectations.

Additional Details

  • The content of PackageA is published as ES6 modules to enable efficient tree-shaking, ensuring that unused parts of PackageA do not affect TestApp.
  • PackageA undergoes minification by UglifyJS before being published, utilizing both --compress and --mangle options.
  • Source maps are excluded from the source files of PackageA, only available separately.
  • Dependencies listed under dependencies in PackageA's package.json are exclusive to PackageA and are removed from TestApp's package.json, with identical versions used. All other dependencies of PackageA are listed under peerDependencies.
  • Size validation was conducted after clearing the node_modules folder and reinstalling dependencies.
  • The content of PackageA mirrors the deleted folder from
    TestApp</code, with the addition of an <code>index.ts
    file that exports the other files' contents, less than 3 kB unzipped.

The potential sources of this size increase and the starting points for investigation are crucial. It's possible that the increase stems from how the code is transpiled in the package, with create-react-app transpiling in a more efficient manner for in-project code compared to imported code. Answering this query is challenging given the numerous possibilities and complexities involved.

Below is the tsconfig.json file used in the extracted PackageA:

{
  "compilerOptions": {
    "target": "es6",
    "lib": ["es6", "dom"],
    "jsx": "react-jsx",
    "module": "es6",
    "rootDir": "./src",
    "moduleResolution": "node",
    "declaration": true,
    "sourceMap": true,
    "outDir": "./build/esm",
    "inlineSources": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["./src"],
  "exclude": ["**/*.test.tsx", "**/*.test.ts", "**/*.stories.tsx"]
}

Answer №1

Every situation may require a different approach, but I wanted to share my process anyway.

Assessing sizes

Typically, when building, your framework will display the sizes of the built components. For example, in the case of create-react-app, these sizes are usually the gzipped versions.

You can also use your browser's dev tools to see the sizes of the downloaded bundles, which are usually the actual sizes, not gzipped.

When making changes or comparisons, compare the sizes before and after the change to determine if they align with your expectations.

Examining the bundle

In NextJS, you can utilize @next/bundle-analyzer, while in other cases, source-map-explorer is recommended. This tool uses source maps to map bundled code to sources. It's beneficial to explore both the visual and JSON representations to fully understand the components used in your application.

Using source-map-explorer allows you to compare different versions of your application effectively.

Delving into source code to production code scenarios

After analyzing source code expansion with tools like source-map-explorer, further research may be necessary. Tracking the impact of specific source code portions in the output bundle can provide valuable insights. Utilizing source maps enables you to map bundled code to source code, aiding in identifying the source of code expansions.

Considering your use case

In my case, the increased size was due to the following factors:

  • Importing from an index in my package led to ambiguities without the sideEffects: false flag in package.json. Adding this flag reduced the package size by eliminating unnecessary dependencies.

  • Removing convenience methods added by Babel in transpiled files using @babel/plugin-transform-runtime plugin resulted in a further size reduction.

  • Transitioning from tsc to Babel for compilation led to a more optimized output size.

Overall, these adjustments resulted in a slight increase of 1.7 kB in the output size after refactoring, which was deemed acceptable.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can I inform Typescript that an interface will exclusively consist of defined members?

My interface looks like this interface Person { name?:string; age? :number; gender?:string } I need to reuse the same interface type, but with a modification indicating that all members will never be undefined. The updated version would look like this: ...

Are you struggling with perplexing TypeScript error messages caused by a hyphen in the package name?

After creating a JavaScript/TypeScript library, my goal is for it to function as: A global variable when called from either JavaScript or TypeScript Accessible via RequireJS when called from either JavaScript or TypeScript Complete unit test coverage Th ...

The export 'ChartObject' is not available in highcharts

Trying to integrate highcharts into my Angular 4 project has been a bit challenging as I keep encountering the following error: highcharts has no exported member 'ChartObject' I have experimented with different options such as angular-highchart ...

Angular 2, the change event is not triggering when a bootstrap checkbox is clicked

Encountering an issue, the (change) function is not triggering in a specific checkbox. <input [(ngModel)]="driver.glasses" name="glasses" type="checkbox" class="make-switch" (change)="changeFunction()" data-on-color="primary" data-off-color="info"&g ...

Is there a way to render a component without having to render AppComponent constantly?

I am looking to display two components (AppComponent and UserComponent) without constantly displaying AppComponent. Here's how my code is structured: app.routing.module.ts const routes: Routes = [ { path: '', component: AppComponent ...

Guide to packaging TypeScript type declarations with an npm JavaScript library

I'm facing an issue with providing TypeScript type definitions for a JavaScript library. The library itself is written in TypeScript and transpiled by Babel, although this detail shouldn't affect the outcome. The problem lies in the fact that ne ...

Developing a VS Code extension with npm tar module integration, ensuring all rejected promises are handled promptly within a 1-second

I am currently working on a vscode extension where I have the task of creating a tar file of the entire workspace folder (all the files within the workspace folder need to be tar-ed). To accomplish this, I am utilizing the npm tar module. After registering ...

Optimizing Materialize-css, Laravel, and VueJS integration: best practices for setting up modals, side-nav, and

Currently, I am in the process of constructing a website utilizing Laravel 5.4, Laravel Mix, VueJS, and Materialize-css. Fortunately, VueJS and jQuery are already integrated with Laravel, so no additional setup is required in that regard. All custom compo ...

What is the correct way to configure VS Code for debugging in Chrome while utilizing an already logged-in user profile?

Currently, I am developing a React application in VS Code. To test and debug my work, I typically run npm start in the terminal to initiate the application server. This action successfully launches Chrome with the React Devtools extension installed under t ...

Uncovering the Image Orientation in Angular: Is it Possible to Determine the Direction Post-view or Upon Retrieval from Database?

I am currently working on creating centered and cropped thumbnails for images retrieved from a database. I came across some helpful information on how to achieve this: The resource I found is written for JavaScript, but I am using Angular 7. I am facing d ...

Eternal Command Line featuring NPM Parameters

I am in the process of developing a project where I have several instances running simultaneously, all using the same code and managed by the forever tool. By utilizing forever, I can assign a unique identifier to each instance through the use of --id "i ...

Adding dynamic row values to a bootstrap table in angular 4 - what's the best approach?

When using an Angular 4 bootstrap table, I am encountering an issue where only the first row value is displayed in the table from the backend when entering a product in the text box and pressing enter. Even though the alert window shows the data for the se ...

@angular/common@~5.1.1 is needed as a peer dependency for @angular/[email protected], however it is not currently installed

There seems to be a peer dependency issue with @angular/common@~5.1.1 while trying to install the angular date picker from NPM console. Upon running the command npm install angular2-material-datepicker, I encounter the above error message. npm install ...

Does the Yarn repository function independently from NPM, or does it rely on the NPM repository in the background?

Contemplating switching to Yarn or PNPM in place of NPM. But unsure if they have distinct repositories or share the same one. I'm curious because if they are separate, the concern arises over package availability across repositories. ...

Vue 3 Composable console error: Unable to access properties of undefined (specifically 'isError') due to TypeError

I recently developed a Vue 3 / TypeScript Composable for uploading images to Firebase storage. The code snippet below illustrates the structure of the ImageUpload interface: interface ImageUpload { uploadTask?: UploadTask; downloadURL?: string; progr ...

What is the contrast between element.getAttribute() value and a String in protractor?

When using protractor and typescript, I need to verify that the text saved in a textbox matches a certain string by comparing it with the resulting value of element.getAttribute("value"). Unfortunately, getText() does not work for this scenario b ...

Error message encountered during angular project build using ng build: angular.json file missing

I had successfully created a project on my laptop a month ago and didn't encounter any errors. However, when I cloned the repository to a new computer today, I encountered some issues. After running 'npm i' to install packages, I attempted t ...

Error: The page you are trying to access does not have a valid default export. The provided type is not acceptable

Hello, I am a newcomer to the world of react and NextJS. Currently, I am working on a small project using NextJS 13 where I am attempting to display products stored in a JSON file (which will later be moved to a database). The application runs correctly, ...

Encountering issues with React Nextjs - unable to utilize useState hook or

Hi everyone, I'm new to React and I think I might have overlooked something. I've been trying to create a simple registration form, but it seems like I'm having trouble changing the state. ` import {useState} from 'react' export ...

Using Moment JS to display the days of the upcoming week

I'm in the process of developing a weather application and I need to create code that will display the upcoming week's weather forecast. The only information I have from the server is a "time" entity with a "value" set for next Monday such as "20 ...