Live data from Hacker News

You may not need a bundler for your NPM library

cmdcolin.github.io

31–40 of 40 posts

Re: You may not need a bundler for your NPM library

#31
As someone who regularly publishes npm packages [1], and have dealt with packages that do this, please don't.

At least not yet until ESM is fully supported by all parties in the ecosystem (node, V8, major libraries and frameworks). As of Sept 2022, we still have support issues with node (V8), TypeScript and jest. I have links to the issues in one of my GitHub repos[2].

Once you move past trivial hello world apps, it is literally impossible to run JavaScript in node in pure ESM because of various bugs and incompatibilities. The user of the library will hit "Cannot use import statement outside a module" error sooner or later.

1: https://www.npmjs.com/~paradite

2: https://github.com/paradite/github-dramas#2021

Re: You may not need a bundler for your NPM library

#32

Earlier quoted context omitted.

> just zipped the whole folder node_modules and all and shipped that I mean, in a sense, something similar is what you should do if you're aiming for maximum reproducibility, of course, excluding the things that you don't need. For a comparison, if you wanted to build a Docker container, it's going to include the node_modules folder in it, instead of installing dependencies on startup. So, in a word, somewhat similar…

Hmm, why don't you specify your development-time dependencies as devDependencies in your package.json? Then you can simply install only the production ones in your final step Docker image - and only use the devDependencies in your build step. I'm doing it, works like a charm. It'd be really weird and wasteful to ship my TypeScript and Eslint and whatever to cloud/Lambda/...

> Hmm, why don't you specify your development-time dependencies as devDependencies in your package.json? Then you can simply install only the production ones in your final step Docker image - and only use the devDependencies in your build step.

This is pretty close to the solution and would work if I ran just Node.js server side. Then I could easily have separate sets of dependencies for development and deployment/running, which is what most folks should do. Yet in certain tech stacks, things aren't as easy and there is reliance on system folders instead of local ones.

For example, my application that's running on the server should have absolutely nothing to do with Node, since it should run Ruby alone and use the bundled files, however it should have all of the Ruby Gems (basically packages) available. Of course, I also need Node and possibly something else during build time, for generating my assets.

For example, my intermediate container images might look like:

  FROM ruby_with_node_and_python AS builder

  # Copy only what's needed for installing dependencies and other garbage (e.g. changed code files won't invalidate Docker cache)
  COPY ./src/.browserslistrc ./src/.ruby-version ./src/babel.config.js ./src/config.ru ./src/Gemfile ./src/Gemfile.lock ./src/yarn.lock ./src/package.json ./src/postcss.config.js ./src/Rakefile /app

  # install front end dependencies
  RUN yarn install
  # add code and run asset creation
  COPY ./src /app
  RUN bundle exec rake assets:clobber RAILS_ENV=production
  RUN bundle exec rake assets:precompile RAILS_ENV=production

  # install back end dependencies
  RUN bundle install

  # run tests (local SQLite, for example)
  RUN rails db:migrate RAILS_ENV=test
  RUN rails test RAILS_ENV=test

  # clean up files after build (so copying in later images doesn't drag garbage along; this could be a separate intermediate image too)
  RUN rm -rf src/node_modules
  # clean up files after test (so copying in later images doesn't drag garbage along; this could be a separate intermediate image too)
  RUN rm -rf src/log/*.log src/tmp/*
And then my container images for deployment would look like this:

  FROM ruby_with_nothing_else AS runner

  # copy over application files
  COPY --from=builder /app /app
Except that this doesn't work! Why? Because Ruby Gems aren't installed in the application directory, but instead sit in a variety of directories on the actual file system. So instead I'd need something like:

  FROM ruby_with_package_manager AS runner

  # we actually could not carry over the precompiled gems etc. because I'm stupid, so instead we now run the install again :(
  # well it's not like you need front end files here, but this doesn't really mean much
  COPY ./src/.browserslistrc ./src/.ruby-version ./src/babel.config.js ./src/config.ru ./src/Gemfile ./src/Gemfile.lock ./src/yarn.lock ./src/package.json ./src/postcss.config.js ./src/Rakefile /app

  # install back end dependencies
  RUN bundle install

  # copy over application files
  COPY --from=builder /app /app
So while npm/yarn allows you to avoid some of the issues with `npm install --production` to exclude the devDependencies, other tech stacks aren't quite there yet. In addition, it feels kind of crazy to ship package managers like npm/yarn/pip/composer/bundle/maven or anything else in containers that should be immutable, so in practice you'd end up with more stages, even if you knew how to carry over the packages from one image to another in the technology stacks where it's not entirely clear how to do that.

I actually tried something similar to this, but it simply didn't work, especially when native extensions were needed for some of the Gems: https://stackoverflow.com/a/33778136

Re: You may not need a bundler for your NPM library

#33
post #17

Earlier quoted context omitted.

It’s actually pretty annoying that some people are now going pure esm and actively removing cjs builds. There are certain libraries that don’t work with esm yet, so if you need one of those suddenly all the esm-only packages can’t be used.

> certain libraries that don’t work with esm yet I assume that you're talking about tools rather than libraries. Any simple library can import ESM asynchronously and any ESM packages can import CJS packages even with static imports. Tools and Node’s strict ERR_REQUIRE_ESM have been the problem with the migration.

In my experience, you may encounter issues when you depends on pure esm package and cjs-only packages. IMHO, it is still useful to publish a cjs fallback.

Re: You may not need a bundler for your NPM library

#35

As someone who regularly publishes npm packages [1], and have dealt with packages that do this, please don't. At least not yet until ESM is fully supported by all parties in the ecosystem (node, V8, major libraries and frameworks). As of Sept 2022, we still have support issues with node (V8), TypeScript and jest. I have links to the issues in one of my GitHub repos[2]. Once you move past trivial hello world apps, it…

Node 16+ and Typescript 4.7+ and recent versions of jest (I don't know offhand a version number) seem to have sorted most of the issues with using ESM modules at this point. I agree ESM was more trouble in 2021, but this deep in 2022 things are a lot rosier.

Re: You may not need a bundler for your NPM library

#36

Earlier quoted context omitted.

So... set ourselves up for another Log4j situation where a vulnerability is found in a sub-sub-sub dependency of some dependency you didn't even know you were using, such that only good way of fixing it is to manually scrub the code from `.jar`s?

Or wait a few days for a patch to be released.

Wait a few days for what, exactly? For your sub-sub-sub dependency to patch it, then for your sub-sub dependency to notice this and update the version, then for your sub-dependency to notice it to update it, then for your dependency to finally notice and update it?

The solution that npm presents solves this problem without this insanity -- you can just create a `npm-shrinkwrap.json` file to manually update sub-dependencies. If one of your dependencies used bundling, this solution becomes impossible.

Re: You may not need a bundler for your NPM library

#37
post #3

Nice write up. It still boggles the mind when coming from a Java background how things change but also get worse. Having zip's (.jar/.war) which had the source and compiled artefacts available and not cluttering the OS with unzip items. I've run into huge issues dealing with deployments to AWS Lambda nodejs, where the previous developer could not work out how to make a 'slip' deployment and just zipped the whole fold…

You can use esbuild to produce a single file for the Lambda handler. That’s the approach taken by AWS CDK via NodejsFunction construct.

Re: You may not need a bundler for your NPM library

#38

Earlier quoted context omitted.

Or wait a few days for a patch to be released.

Wait a few days for what, exactly? For your sub-sub-sub dependency to patch it, then for your sub-sub dependency to notice this and update the version, then for your sub-dependency to notice it to update it, then for your dependency to finally notice and update it? The solution that npm presents solves this problem without this insanity -- you can just create a `npm-shrinkwrap.json` file to manually update sub-depend…

In NPM 8 you can override sub deps quite effectively, even using references.

Another option is package-patch. Very light, elegant and yet robust solution for fixing anything in any package you depend on yourself without waiting for maintainer to fix it.

Re: You may not need a bundler for your NPM library

#39

Earlier quoted context omitted.

> certain libraries that don’t work with esm yet I assume that you're talking about tools rather than libraries. Any simple library can import ESM asynchronously and any ESM packages can import CJS packages even with static imports. Tools and Node’s strict ERR_REQUIRE_ESM have been the problem with the migration.

In my experience, you may encounter issues when you depends on pure esm package and cjs-only packages. IMHO, it is still useful to publish a cjs fallback.

You may encounter issues with mixed packages too, actually a lot more issues given that people expect `module.exports = ` and `export default` to be equal but are not.

Just save yourself some headache and publish either ESM or CJS, not both.

Re: You may not need a bundler for your NPM library

#40
post #13

Earlier quoted context omitted.

Isn't that exactly what bundling does?

i think bundling generates your build from the source, but i mean generating a source from a build. so what i mean is more like reverse bundling. so that you don't have to pull node_modules every time and install. i;m not totally sure but i dont think you can turn a minified bundled js files back into their original node_module source files...

That very much depends on the build -- if you've a source map, then it may very well be possible. And while I know not everyone publishes them, I strongly recommend doing so.

But honestly, ship your sources with your generated files. Please.

Post reply on HN