Bug: Webpack bundler pulls browser module from multi-platform packages

An increasing number of packages in the npm ecosystem use the package-browser-spec - GitHub - defunctzombie/package-browser-field-spec: Spec document for the 'browser' field in package.json - to differentiate between code intended for use in browsers vs. code for use in nodejs (along with other flavors).

As configured currently, the @forge/bundler webpack configuration leads to the use of browser settings when present, resulting in platform errors as those packages try to use browser-based objects/code that does not exist in the node/lambda environment.

One sample library that does this is OpenTelemetry which uses this pattern for all of their js libraries, but even the most basic uses it - e.g. opentelemetry-js/package.json at main · open-telemetry/opentelemetry-js · GitHub

Impact
This is a really difficult bug to figure out as it manifests as either Snapshotting errors, when the system is unable to find expected objects, or runtime errors. The developer is stuck scratching their head wondering why their server-side function is trying to access browser objects they never called. It creates an amazingly frustrating developer experience.

Workaround
This issue is fixed by manually editing @forge/bundler/out/webpack.js, adding aliasFields: [] to the resolve block near line 83.

In context:

        node: {
            __dirname: true
        },
        performance: false,
        resolve: {
            extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
ADDED==>    aliasFields: []
        },
        resolveLoader: {
            modules: require.resolve.paths('babel-loader') || undefined
        },

This is not sustainable as it does not work in CI/CD, and would better be the default I believe.

Recommended Fix
Since the bundler is only every targeting node and not the browser, setting aliasFields to an empty list should be the default.

Webpack config reference: Resolve | webpack

More webpack config docs on how/why packages target multiple environments - Package exports | webpack

1 Like

Hey Micah,

Thanks for the bug report. We’ve been unable to reproduce the use of the browser settings in our tests. Would you be able to provide some code to reproduce?

Thanks,
Owen

1 Like

Sample code - Bitbucket

NOTE: The demo uses @opentelemetry/core which makes the bundling issue very obvious, but has other issues that makes it not really appropriate for Forge.

Reproduction

  1. Checkout the repository
  2. run npm ci
  3. Get your own app id
  4. Run npx forge deploy

Output:

Running forge lint...
No issues found.

✕ Deploying your app to development...

ℹ Packaging app files
ℹ Uploading app
ℹ Validating manifest
ℹ Snapshotting functions

Error: Error thrown in the snapshot context.
App code snapshot error: Snapshot error occurred: Error: performance is not defined
Learn more about the snapshot context at https://go.atlassian.com/forge-snapshot-context.

Rerunning the command with --verbose may give more details.

Why this failure is a bundling issue -

Workaround

  1. Edit node_modules/@forge/cli/node_modules/@forge/bundler/out/webpack.js as in the original post to set aliasFields: []
  2. Run npx forge deploy
Running forge lint...
No issues found.

✔ Deploying your app to development...

ℹ Packaging app files
ℹ Uploading app
ℹ Validating manifest
ℹ Snapshotting functions

WARN    ⚠️  the 'perf_hooks' module is not supported by Forge, please refer to the documentation at https://go.atlassian.com/forge-runtime-js-environment

ℹ Deploying to environment

✔ Deployed

Deployed your app to the development environment

Deploy succeeds, because of the warning around perf_hooks we know it is pulling from the ‘node’ platform version of the file here - opentelemetry-js/performance.ts at main · open-telemetry/opentelemetry-js · GitHub

I do want to emphasize that I do not care about the specific issue around performance or perf_hooks - the issue is with the bundler, this library, due to the errors with performance, just makes the issue very visible and (hopefully) easy to debug.

1 Like

I should add, you can also see via inspecting the bundle and searching for how otperformance is being initialized - if via performance or via perf_hooks - without going through the upload/deploy.

1 Like