Skip to content

perf: import ramda functions from per-function modules - #58

Closed
MarkusWendorf wants to merge 1 commit into
native-html:mainfrom
MarkusWendorf:ramda-direct-imports
Closed

MarkusWendorf wants to merge 1 commit into
native-html:mainfrom
MarkusWendorf:ramda-direct-imports

Conversation

@MarkusWendorf

@MarkusWendorf MarkusWendorf commented Nov 11, 2026 •

Copy link
Copy Markdown

I noticed that root imports from ramda pull the whole library into the bundle. This is not an issue if you have tree-shaking enabled, however by default Expo/Metro based projects do not have tree shaking enabled.

EXPO_UNSTABLE_TREE_SHAKING=1 can be used to enable it, but is marked as unstable for now.

This PR changes to deep imports via import x from 'ramda/src/x instead of barrel imports.

I setup some tests to see the difference. Not quite sure if import x from 'ramda/src/x or import x from 'ramda/es/x is the best way to go.

If this is ok to be merged, maybe adding an ESLINT rule for imports would make sense. Let me know.

AI summary ahead ↓

Production app

Expo SDK 58, React Native 0.88, Android, Hermes bytecode.

Import style Tree shaking Hermes bundle gzip ramda in bundle
import { x } from 'ramda' (current) off 10,095 KB 4,107 KB 129 KB / 367 files
import x from 'ramda/src/x' off 9,845 KB (−250 KB, −2.5%) 4,045 KB (−1.5%) 10 KB / 46 files
import { x } from 'ramda' (current) on 8,717 KB 3,564 KB 15.8 KB / 47 files
import x from 'ramda/src/x' on 8,705 KB (−13 KB, −0.15%) 3,563 KB 10 KB / 46 files

The Hermes bundle column is the shipped bytecode; ramda's size comes from the minified-JS source map. Tree shaking is Expo's
experimental mode (EXPO_UNSTABLE_TREE_SHAKING=1 + EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH=1).

Example app (already has tree shaking enabled)

apps/benchmarking (Android, minified JS). The app's own import { mean } from 'ramda' in Benchmark.js was replaced with a
local helper so only the library is measured.

Import style Tree shaking Bundle gzip ramda in bundle
import { x } from 'ramda' (current) on 2,471 KB 631 KB 16 KB / 47 files
import x from 'ramda/src/x' on 2,461 KB (−0.4%) 631 KB 10 KB / 46 files
import x from 'ramda/es/x' on 2,470 KB 631 KB 15 KB / 46 files

Root imports from 'ramda' pull the whole library into bundles that
don't tree-shake (e.g. Metro by default). Per-function 'ramda/src/*'
imports keep only the used functions and are also the smallest with
Expo's tree shaking enabled.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@5ZYSZ3K

5ZYSZ3K commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

Recently there was an issue proposing the exact opposite: #49
It made more sense to me to leave it for tree-shaking to decide, which chunks of code to keep

However I need to admit I didn't do a throughout research, I just run one agent to confirm and fix the issue
I'll assess that better this time

@MarkusWendorf

Copy link
Copy Markdown
Author

@5ZYSZ3K haha, that's interesting. I asked the creator of said issue to provide details so we know why tree-shaking was not working for him.

@MarkusWendorf

Copy link
Copy Markdown
Author

@5ZYSZ3K maybe consider removing ramda entirely and ship equivalent code instead. Just had a go with Claude and it's ~100 LOC for the functions that are imported from ramda.

Just an idea.

@5ZYSZ3K

5ZYSZ3K commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

@MarkusWendorf Yes, these days it actually sounds like a good idea, I'll consider that

@andrejpavlovic

Copy link
Copy Markdown

@MarkusWendorf I'm using babel-plugin-ramda which should have the tree-shaking working correctly without using the experimental flag.

My babel config:

module.exports = (api) => {
  api.cache(true)

  return {
    presets: [['babel-preset-expo', { jsxRuntime: 'automatic' }]],
    plugins: [
      [
        'ramda',
        {
          useES: true,
        },
      ],
    ],
  }
}

@MarkusWendorf

Copy link
Copy Markdown
Author

Before #50 this library used:

import mapObjIndexed from 'ramda/src/mapObjIndexed'` 

and switched to

import { mapObjIndexed } from 'ramda'

To then be converted to

import mapObjIndexed from 'ramda/es/mapObjIndexed'

by babel-plugin-ramda.

So the only change that happened was the subpath import src to es. Which might be better understood by whatever bundler you used.

Either way, I'm closing this PR and would advocate for shipping the lib without ramda in the future.

@andrejpavlovic

Copy link
Copy Markdown

For posterity, the difference for me was a decrease of about 6-8KB in production bundle size when using named imports with babel-plugin-ramda.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants