ngageoint / leaflet-geopackage

Leaflet GeoPackage
http://ngageoint.github.io/leaflet-geopackage/
19 stars 7 forks source link

Client-Side Build Error: @ngageoint/leaflet-geopackage Accesses fs Module (Incompatible with Browser) #5

Open davidklein147 opened 6 months ago

davidklein147 commented 6 months ago

Description:

In my next.js project I use to display maps with the react-leaflet library, in some cases I need to import the tile layer from a gpkg file, to solve this I found your library which seems to solve the challenge. But when trying to import @ngageoint/leaflet-geopackage in the Next.js environment, an error occurs due to the library trying to access the fs module, which is not available in the browser environment. This prevents the application from building successfully.

Expected Behavior

from your readme file of @ngageoint/leaflet-geopackage lib

Load GeoPackage tile and feature layers in the browser without a server. When a layer is added to the map, the GeoPackage will be downloaded and then the specified layer will be loaded on the map.

So the library should import successfully in browser without any errors, allowing the application to build correctly.

Actual Behavior

An error occurs during the build process due to the library attempting to access the fs module, which is not available in the browser environment.

Example Code

import { Dispatch, FC, SetStateAction, useEffect } from "react";

import L from "leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility"
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css"
import '@geoman-io/leaflet-geoman-free';
import '@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css';
import "@ngageoint/leaflet-geopackage"
import { TileLayer, useMap, MapContainer } from "react-leaflet"

import ExistLayers from "./map-exist";
import DrawMap from "./map-drow/map-drawing";
import DrawToggle from "./map-utils/draw-toggle";

import { MapLayers } from "./map-utils/prepare-map-layers";
import { MapPurpose } from "@/types/interfaces";
import { useRouter } from "next/router";

interface MapControllerProps {
  layers: MapLayers | undefined
}

const MapController: FC<MapControllerProps> = ({ layers }) => {
  let map = useMap()

  useEffect(() => {

    const layer = L.geoPackageTileLayer({ geoPackageUrl: ".\\output2.gpkg", tableName: "some name" }).addTo(map)

    const sizeInternal = setInterval(() => {
      map?.invalidateSize()
    }, 1000)

    return () => {
      clearInterval(sizeInternal)
    }
  }, [])

  useEffect(() => {
    layers?.center && map.setView(layers.center)
  }, [layers])

  return null
}

interface ShownMapProps {
  layers: MapLayers | undefined,
  setLayers: Dispatch<SetStateAction<MapLayers | undefined>>
  mapPurpose: MapPurpose
  setMapPurpose: Dispatch<SetStateAction<MapPurpose>>
}

const ShowsMap: FC<ShownMapProps> = ({ layers, setLayers, mapPurpose, setMapPurpose }) => {
  const router = useRouter()

  return (
    <MapContainer
      key={layers?.selectedMap ? layers.selectedMap.catalogId : "all"}
      style={{ height: "calc(100vh - 72px)", width: "100%", overflow: "hidden" }}
      center={layers?.center ?? { "lat": 32.153464035661194, "lng": 34.88491667228661 }}
      zoom={router.query.catalogId ? 11 : 9}
      scrollWheelZoom={true}
      zoomControl={false}
    >

      <MapController layers={layers} />

      <DrawToggle purpose={mapPurpose} setPurpose={setMapPurpose}></DrawToggle>

      {layers && <ExistLayers layers={layers}></ExistLayers>}

    </MapContainer>
  )
}

export default ShowsMap;

NOTE: related to #2 with out finding a example how to use this lib in "react", this is my invention how to use it, I`m not sure that this is correct, but due the issue I can't even import it, so I can't test if it works either.

Error getting:

so importing this line:

import "@ngageoint/leaflet-geopackage" throws this error


./node_modules/@ngageoint/geopackage/dist/geopackage.min.js:2:486105
Module not found: Can't resolve 'fs'

https://nextjs.org/docs/messages/module-not-found

Import trace for requested module: ./node_modules/@ngageoint/leaflet-geopackage/leaflet-geopackage.js ./components/maps/map-shows.tsx



### Environment
- Operating System: Windows 11 pro,
- Node.js version: 21.1.0ne
- Next.js version: 14.0.3
- `@ngageoint/leaflet-geopackage` version: 4.1.3
danielbarela commented 6 months ago

I have not worked with react but in Browserify you can include browserify-fs in your build which basically takes over for the fs module. We do that in our own geopackage-viewer project here: https://github.com/ngageoint/geopackage-viewer-js/blob/main/package.json I would imagine there is a way to shim the fs module with react or however you are building, but I do not know how off the top of my head.

davidklein147 commented 6 months ago

@danielbarela Thank you very much for your response

I found a solution for this by adding a configuration to the next.config.js file to tell next webpack to ignore fs in is not in server side

/** @type {import('next').NextConfig} */
const nextConfig = {
  ...,
  webpack(config, { isServer }) {

    config.resolve.fallback = {
      fs: isServer ? "true" : false,
    }
    return config
  },
}

module.exports = nextConfig