Optimizing your server's bundle size and build time

I’m not sure how many people know that with Meteor apps, packages put in your devDependencies are never put into your server-side bundle, but will be put in your client-side bundle.

With larger projects, this can lead to much smaller build times, as your server-bundle will be much smaller.

In our team, we haven’t always added packages properly, and so now I’m trying to clean this up and move lots of client-only dependencies into devDependencies.

Does someone know of a quick way to find these packages that could safely be moved into devDependencies because the server-bundle never imports them anywhere? That would speed up this process, and could maybe even help out a lot of other Meteor projects!

1 Like

This is interesting, why would dev dependencies go into either bundle? I assume you’re talking about prod bundles?

Use recast to parse your sources or bundles and locate all static and dynamic import statements.

1 Like

There is a discussion about this problem.

We have a step in our docker file to remove typescript and some trash in app folder(bundle/programs/server/):

# Remove unused files to reduce image size
RUN find . -type f -name 'README.md' -delete \
    && find . -type f -name 'LICENSE' -delete \
    && find . -type f -name '*.d.ts' -delete \
    && rm -r ./npm/node_modules/meteor/babel-compiler/node_modules/typescript

IFAIK we saved ~50mb.

Another tip to reduce image size is using multi stage docker file. It allows us to cut another 300-400mb

So, the final Dockerfile:

FROM node:14.19.1-stretch-slim as base

ENV APP_DIR=/meteor
# Install as root (otherwise node-gyp gets compiled as nobody)
USER root
WORKDIR $APP_DIR/bundle/programs/server/

RUN apt-get update \
    && apt-get install -y --no-install-recommends g++ build-essential python \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Copy bundle and scripts to the image APP_DIR
ADD ./editor-app.tar.gz $APP_DIR

# Remove unused files to reduce image size
RUN find . -type f -name 'README.md' -delete \
    && find . -type f -name 'LICENSE' -delete \
    && find . -type f -name '*.d.ts' -delete \
    && rm -r ./npm/node_modules/meteor/babel-compiler/node_modules/typescript

# the install command for debian
RUN echo "Installing the node modules..." \
	&& npm install -g node-gyp \
    && npm install --production --silent

FROM node:14.19.1-stretch-slim as app

ENV APP_DIR=/meteor

# Copy in app bundle
COPY --from=base $APP_DIR/bundle $APP_DIR/bundle/

# start the app
WORKDIR /meteor/bundle
CMD ["node","main.js"]
1 Like

@znewsham I can’t find the source for this, but this is how meteor was optimized in the past. When the client bundle needed a package from devDependencies, instead of crashing at build-time, they decided to include the package in the production bundle for simplicity. You can easily verify this with a starter meteor app.

@superfail Have you tried this yourself? How would you use recast with a meteor code-base (and handling the server/client split)?

@afrokick Very interesting, though not trivial to do right!

I’m looking at the client bundle of an app with dev dependencies - and I can’t see any reference to those dependencies, in dev or prod -can you provide a concrete example? Most dev dependencies make no sense in the client bundle. I can’t think of a situation where the client should ever require a package from devDependencies

As far as I know, this is how most bundlers work - if a file is imported, the content of the file is included in the bundle regardless of if it is from a dev dependency or not. The server is different because Meteor copies the whole node_modules folder, excluding dev dependencies, instead of bundling the specific files imported.

This is the only use case I know of. It feels weird, but in some cases it can drastically speed up deploying apps. I’ve done it in the past for a few projects, with several different bundlers.

2 Likes

Ok, so they do still have to be imported, they aren’t just randomly injected! I had a momentary panic there!