Skip to main content
Version: 2.1.x

Common troubleshooting tips

Module not found errors

This is the most common error rules_js users encounter. These problems generally stem from a runtime require call of some library which was not declared as a dependency.

Fortunately, these problems are not unique to Bazel. As described in our documentation, rules_js should behave the same way pnpm does with hoist=false.

These problems are also reproducible under Yarn PnP because it also relies on correct dependencies.

The Node.js documentation describes the algorithm used: https://nodejs.org/api/modules.html#loading-from-node_modules-folders

Since the resolution starts from the callsite, the remedy depends on where the require statement appears.

require appears in your code

This is the case when you write an import or require statement.

In this case you should add the runtime dependency to your BUILD file alongside your source file:

For example,

js_library(
name = "requires_foo",
srcs = ["config.js"], # contains "require('foo')"
data = [":node_modules/foo"], # satisfies that require
)

and also, the foo module should be listed in your package.json#dependencies since pnpm is strict about hoisting transitive dependencies to the root of node_modules.

This case also includes when you run some other tool, passing it a config.js file.

This is the "ideal" way for JavaScript tools to be configured, because it allows an easy "symmetry" where you require a library and declare your dependency on it in the same place. When you pass a tool a config.json or other non-JavaScript file, and have string-typed references to npm packages, you'll fall into the next case: "require appears in third-party code".

require appears in third-party code

This case itself breaks down into three possible remedies, depending on whether you can move the require to your own code, the missing dependency can be considered a "bug", or the third-party package uses the "plugin pattern" to discover its plugins dynamically at runtime based on finding them based on a string you provided.

The require can move to first-party

This is the most principled solution. In many cases, a library that accepts the name of a package as a string will also accept it as an object, so you can refactor config: ['some-package'] to config: [require('some-package')]. You may need to change from json or yaml config to a JavaScript config file to allow the require syntax.

Once you've done this, it's handled like the "require appears in your code" case above.

For example, the documentation for the postcss-loader for Webpack suggests that you npm install --save-dev sugarss and then pass the string "sugarss" to the options.postcssOptions.parser property of the loader. However this violates symmetry and would require workarounds listed below. You can simply pass require("sugarss") instead of the bare string, then include the sugarss package in the data (runtime dependencies) of your webpack.config.js.

It's a bug

This is the case when a package has a require statement in its runtime code for some package, but it doesn't list that package in its package.json, or lists it only as a devDependency.

pnpm and Yarn PnP will hit the same bug. Conveniently, there's already a shared database used by both projects to list these, along with the missing dependency edge: https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-extensions/sources/index.ts

We should use this database under Bazel as well. Follow https://github.com/aspect-build/rules_js/issues/1215.

The recommended fix for both pnpm and rules_js is to use pnpm.packageExtensions in your package.json to add the missing dependencies or peerDependencies.

Example,

https://github.com/aspect-build/rules_js/blob/a8c192eed0e553acb7000beee00c60d60a32ed82/package.json#L12

Make sure you pnpm install after changing package.json, as rules_js only reads the pnpm-lock.yaml file to gather dependency information. See Fetch third-party packages

It's a plugin

Sometimes the package intentionally doesn't list dependencies, because it discovers them at runtime. This is used for tools that locate their "plugins"; eslint and prettier are common typical examples.

The solution is based on pnpm's public-hoist-pattern. Use the public_hoist_packages attribute of npm_translate_lock. The documentation says the value provided to each element in the map is:

a list of Bazel packages in which to hoist the package to the top-level of the node_modules tree

To make plugins work, you should have the Bazel package containing the pnpm workspace root (the folder containing pnpm-lock.yaml) in this list. This ensures that the tool in the package store (node_modules/.aspect_rules_js) will be able to locate the plugins. If your lockfile is in the root of the Bazel workspace, this value should be an empty string: "". If the lockfile is in some/subpkg/pnpm-lock.yaml then "some/subpkg" should appear in the list.

For example:

WORKSPACE

npm_translate_lock(
...
public_hoist_packages = {
"eslint-config-react-app": [""],
},
)

Note that public_hoist_packages affects the layout of the node_modules tree, but you still need to depend on that hoisted package, e.g. with deps = [":node_modules/hoisted_pkg"]. Continuing the example:

BUILD

eslint_bin.eslint_test(
...
data = [
...
"//:node_modules/eslint-config-react-app",
],
)

NB: We plan to add support for the .npmrc public-hoist-pattern setting to rules_js in a future release. For now, you must emulate public-hoist-pattern in rules_js using the public_hoist_packages attribute shown above.

Performance

For general bazel performance tips see the Aspect bazelrc guide.

Parallelism (build, test)

A lot of tooling in the JS ecosystem uses parallelism to speed up builds. This is great, but as Bazel also parallels builds this can lead to a lot of contention for resources.

Some rulesets configure tools to take this into account such as the rules_jest default run_in_band, while other tools (especially those without dedicated rulesets) may need to be configured manually.

For example, the default WebPack configuration uses Terser for optimization. terser-webpack-plugin defaults to parallelizing its work across os.cpus().length - 1. This can lead to builds performing slower due to IO throttling, or even failing if running in a virtualized environment where IO throughput is limited.

If you are experiencing slower than expected builds, you can try disabling or reducing parallelism for the tools you are using.

Jest

See rules_jest specific troubleshooting.