react-map-gl
react-map-gl copied to clipboard
Webpack cannot handle dynamic import of mapbox-gl
Hello guys!
Today I tried to change from using mapbox-gl to use maplibre-gl and to do that I followed this tutorial. To be more precise, I removed the mapbox-gl dependency from my package.json and added maplibre-gl v2 to it. In addition, I passed maplibre to the Map component using the mapLib prop. After doing that, I run yarn install and once everything was installed, I run yarn start to launch the project again.
Once the project started, the following error prompted on screen:
./node_modules/react-map-gl/dist/esm/components/map.js Module not found: Can't resolve 'mapbox-gl' in '{MY_PATH}/node_modules/react-map-gl/dist/esm/components'
It looks like reat-map-gl is still looking for mapbox-gl. What am I missing?
Thank you very much!
Originally posted by @victcebesp in https://github.com/visgl/react-map-gl/discussions/1773
You can do one of the following
- Tell Webpack to not look for the module locally with
externals: [ 'mapbox-gl' ]
. - Add
"mapbox-gl": "^1.0.0"
to your project's dependencies. Webpack will create a separate js file for the dynamic import, which in your case is never used, and you can safely delete it after build. - If for some reason you don't have access to the webpack config, and you do not want to include mapbox in your project at all, you could add
"mapbox-gl": "npm:[email protected]"
instead
It was intentional to use import('mapbox-gl')
as the default for mapLib
because we want the library to work for mapbox-gl by default. Using dynamic import also means that for anyone who overwrites the default value, their runtime bundle size is not negatively impacted.
I agree that the documentation on this could be better.
I looked into this behavior a bit and after reading Webpack docs on import(), and I've learned that Webpack won't explicitly look to bundle mapbox if we use a template literal string in our dynamic import. The code would look something like this (L81):
// src/components/map.tsx#L76
useEffect(() => {
const mapLib = props.mapLib;
let isMounted = true;
let mapbox;
const defaultMapLib = 'mapbox';
Promise.resolve(mapLib || import(`${defaultMapLib}-gl`))
.then(...)
Why does this work?
Taking a look at how Webpack handles dynamic expressions in import(), the above code would bundle packages that match *-gl
so each can be available at runtime. Thus, it won't explicitly look to bundle mapbox like it does with import('mapbox-gl')
.
This seems... pretty hacky. And particular to how Webpack does code splitting. I haven't tested this with something like Rollup, which may handle dynamic imports completely differently. However, it does get around the issue for apps built with frameworks that use Webpack (Create React App, Next.js, etc). It could immediately unblock devs looking to use the library with maplibre without adding mapbox as a dependency or fiddling with configuration.
If you block Webpack from bundling mapbox-gl, then it will stop working for devs who DO use Mapbox as the renderer.
Ultimately this issue comes from the decision to make things automatically work for mapbox-gl users. It is not going to be configure-free for everyone.
Wow this is a super interesting conversation. Thank you very much to all of you.
@Pessimistress but the solution proposed by @slaymance I think it will not block Webpack from bundling mapbox-gl for those who want to use it right? In that case, users will not pass the mapLib property and mapbox-gl would be loaded right? Am I wrong?
Thanks!
Additionally, RTLTextPlugin
should be set to an empty string on the Map
component if you do not want to rely on Mapbox RTL plugin by default.
The documentation and get-started example are updated.
As far as I can tell, the solution proposed above does not work with any bundler other than Webpack, and will lead to a breaking change.
In the current release, mapbox-gl is listed as a peer dependency, so it's technically not a bug to require it during build.
This will be addressed in v7.1.