An experiment of trying to run
node-maxmind
Node.js library in a browser
Spoiler: it's perfectly possible.
Turns out the library has very little dependency on Node.js environment and most of the dependencies can be safely stubbed.
In order to compile it for a browser we will have to use a bundler. Webpack works like a charm in this case.
The main requirement is to have an implementation of the Node.js Buffer class available in your browser. Webpack actually provides this by default when you compile your stuff for browser targets, so we can piggy-back on that.
At this point just trying to import the library into your
Javascript code and compiling that (protip:
$ npx webpack -p
) will throw the
following compile-time error:
ERROR in ./node_modules/maxmind/lib/ip.js
Module not found: Error: Can't resolve 'net' in '/app/node_modules/maxmind/lib'
@ ./node_modules/maxmind/lib/ip.js 6:30-44
@ ./node_modules/maxmind/lib/index.js
@ ./src/index.js
Fixing this is very straightforward: just create
webpack.config.js
file in the root of your app with
the following content.
module.exports = {
node: {
net: 'empty',
},
};
So far so good. It will now in the same manner start to complain
about "fs"
module. This time it's going to be a
little trickier to fix, though. If you just add
fs: 'empty'
key to the
node: {...}
object from above, the compilation will
be green, but running the result code will trigger the following
error in your browser:
TypeError: The "original" argument must be of type Function
The thing is, the library not just imports the "fs"
,
but also promisifies some of its methods. The good news is this
call to promisify()
is isolated in it's own internal
module which is not required for our browser use case, so we can
mock this entire module and call it a day.
In order to do this you should install webpack's
null-loader
($ npm i -D null-loader
) and
add the following to your webpack.config.js
:
module: {
rules: [
{
test: path.resolve(__dirname, './node_modules/maxmind/lib/fs.js'),
use: 'null-loader',
},
],
},
Note that the webpack documentation claims that
null-loader
is obsolete and there is a more modern
way to mock unneeded modules in your dependencies, but for some
reason that didn't work for me.
That's actually it, thanks to webpack magic you can now do something along the lines:
import { Buffer } from "buffer";
import { Reader } from "maxmind";
// ...
const response = await fetch("/blob.mmdb");
const buf = Buffer.from(await response.arrayBuffer());
const lookup = new Reader(buf);
// call `lookup.get(ipAsString)` later in your code
But there is one more thing. Turns out the library uses some
undocumented Buffer.utf8Slice()
method in its
database decoder implementation. Probably it's done for performance reasons. This method is not
available in webpack's implementation of the Buffer, so in order
for the above code to work we will have to monkey-patch the
buf
instance by adding this new method before passing
it down to the Reader
.
After all this is done, we will end up with just 2 files.
Optionally, you'll also need package.json
. I created
a Github gist with the final contents which you can see below.
https://gist.github.com/nilfalse/d790596bd4728271209d70c9cc2f422f
If you'd like to see it in action, I've made a simple website where you can enter any IP address and see the full output of the library. Beware, after you click Resolve button it will download the library which is around 4 MB at the moment.
https://nilfalse.com/labs/geoip/