Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I convince webpack to use the node imports over web imports when using `target: node`

First the code, it's a pretty silly example but it's pulled out from a more complicated github action:

index.js

require('@octokit/rest');
console.log('hello world');

webpack.config.js

const path = require('path');

module.exports = {
  target: 'node',
  entry: './index.js',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

package.json

{
  "private": true,
  "devDependencies": {
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.10"
  },
  "dependencies": {
    "@octokit/rest": "^16.35.0"
  }
}

build command

node_modules/.bin/webpack --config webpack.config.js

running without webpack

$ node index.js
hello world

running after building webpack

$ node dist/index.js 
/tmp/x/dist/index.js:1
...

ReferenceError: navigator is not defined
    at Module.i (/tmp/x/dist/index.js:1:3659)
    at Object.<anonymous> (/tmp/x/dist/index.js:15:6701)
    at t (/tmp/x/dist/index.js:1:110)
    at Object.<anonymous> (/tmp/x/dist/index.js:15:874)
    at t (/tmp/x/dist/index.js:1:110)
    at Object.<anonymous> (/tmp/x/dist/index.js:15:697)
    at t (/tmp/x/dist/index.js:1:110)
    at Object.<anonymous> (/tmp/x/dist/index.js:1:3891)
    at t (/tmp/x/dist/index.js:1:110)
    at Object.<anonymous> (/tmp/x/dist/index.js:15:418)

analysis

From what I've found, there's a compiled typescript package inside node_modules that's being imported and run here, here's some of the relevant code from the last two frames:

node_modules/@octokit/endpoint/dist-src/defaults.js

// ...
import { getUserAgent } from "universal-user-agent";
// ...
const userAgent = `octokit-endpoint.js/${VERSION} ${getUserAgent()}`;
// ...

The universal-user-agent provides several implementations of its code in the following files:

  • node_modules/universal-user-agent/dist-node/index.js
  • node_modules/universal-user-agent/dist-web/index.js

As you would expect, the dist-web implementation uses navigator.userAgent -- the node one does something else.


my current terrible workaround

I don't actually care about the user agent so I'm currently hacking around this with:

sed -i 's/\bnavigator\b/({})/g' dist/index.js

yes, running sed to eliminate accesses to navigator


tl;dr

How do I convince webpack to pick the dist-node implementation over the dist-web one (like require(...) seems to do when running node directly)?

like image 771
Anthony Sottile Avatar asked Nov 24 '19 21:11

Anthony Sottile


People also ask

Does webpack work with node?

Webpack provieds a Command Line Interface (CLI), which we can call as webpack filename. js target/index. js from the terminal; and Node. js API, which we can use in our Node.

Does webpack use CommonJS?

Webpack supports the following module types natively: ECMAScript modules. CommonJS modules.

What is library target in webpack?

This is according to their documentation: "libraryTarget: "umd" - This exposes your library under all the module definitions, allowing it to work with CommonJS, AMD and as global variable." Also, I built the exact same code with Webpack 3 and it produced a proper bundle.

How does webpack import work?

When webpack processes your application, it internally builds a dependency graph from one or more entry points and then combines every module your project needs into one or more bundles, which are static assets to serve your content from.


1 Answers

It's a known issue for @octokit/rest: https://github.com/octokit/rest.js/issues/1485

There is also a long discussion in this universal-user-agent issue: https://github.com/gr2m/universal-user-agent/issues/23

There seems to be a permanent fix, but not released yet. Until then you can try 2 things to resolve the dist-node of universal-user-agent using Webpack Resolve:

1) Using alias to resolve the dist-node

const path = require('path');

module.exports = {
  target: 'node',
  resolve: {
    alias: {
      'universal-user-agent': path.resolve(__dirname, 'node_modules/universal-user-agent/dist-node/index.js')
    }
  },
  entry: './index.js',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

2) Using mainFields

module.exports = {
  target: 'node',
  resolve: {
    mainFields: ['main', 'module']
  },
  entry: './index.js',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

You should go with the first method, because the second one reflects to all node_modules and you'll run into issues sooner or later.

Webpack issue

There seems to be an issue and a long discussion about this problem here: https://github.com/webpack/webpack/issues/5756

like image 180
Christos Lytras Avatar answered Oct 25 '22 21:10

Christos Lytras