diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml
index 57f2661..478f353 100644
--- a/.github/workflows/actions.yml
+++ b/.github/workflows/actions.yml
@@ -25,7 +25,7 @@ jobs:
- run: cd serverless/cloudflare && cp wrangler.toml.example wrangler.toml && npm ci && npx tsc && npm run biome-check && npm run build && cp dist/index.js ../../app/dist
- run: cd spec/v3 && cp *.pmtiles ../../app/dist
- run: cd js/examples && mkdir ../../app/dist/examples && cp *.html ../../app/dist/examples/
- - run: cd js && npm ci && npx typedoc index.ts --out ../app/dist/typedoc
+ - run: cd js && npm ci && npx typedoc src/index.ts --out ../app/dist/typedoc
- run : cd openlayers && npm ci && npm run tsc
- run: cd openlayers/examples && mkdir ../../app/dist/examples/openlayers && cp *.html ../../app/dist/examples/openlayers
- name: build_app
diff --git a/app/src/Inspector.tsx b/app/src/Inspector.tsx
index cfdeb39..5c89eda 100644
--- a/app/src/Inspector.tsx
+++ b/app/src/Inspector.tsx
@@ -5,7 +5,13 @@ import Protobuf from "pbf";
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
import { UncontrolledReactSVGPanZoom } from "react-svg-pan-zoom";
import { useMeasure } from "react-use";
-import { Entry, Header, PMTiles, TileType, tileIdToZxy } from "../../js/index";
+import {
+ Entry,
+ Header,
+ PMTiles,
+ TileType,
+ tileIdToZxy,
+} from "../../js/src/index";
import { styled } from "./stitches.config";
const TableContainer = styled("div", {
diff --git a/app/src/Loader.tsx b/app/src/Loader.tsx
index 85591dc..4c10c9c 100644
--- a/app/src/Loader.tsx
+++ b/app/src/Loader.tsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { PMTiles } from "../../js/index";
+import { PMTiles } from "../../js/src/index";
import { styled } from "./stitches.config";
import MaplibreMap from "./MaplibreMap";
diff --git a/app/src/MapViewComponent.tsx b/app/src/MapViewComponent.tsx
index 15f16c9..2596d10 100644
--- a/app/src/MapViewComponent.tsx
+++ b/app/src/MapViewComponent.tsx
@@ -1,7 +1,7 @@
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { GitHubLogoIcon } from "@radix-ui/react-icons";
import React, { useState, useEffect } from "react";
-import { PMTiles } from "../../js/index";
+import { PMTiles } from "../../js/src/index";
import { globalStyles, styled } from "./stitches.config";
import Loader from "./Loader";
diff --git a/app/src/MaplibreMap.tsx b/app/src/MaplibreMap.tsx
index 1da4996..4207502 100644
--- a/app/src/MaplibreMap.tsx
+++ b/app/src/MaplibreMap.tsx
@@ -9,8 +9,8 @@ import "maplibre-gl/dist/maplibre-gl.css";
import baseTheme from "protomaps-themes-base";
import React, { useState, useEffect, useRef } from "react";
import { renderToString } from "react-dom/server";
-import { Protocol } from "../../js/adapters";
-import { PMTiles, TileType } from "../../js/index";
+import { Protocol } from "../../js/src/adapters";
+import { PMTiles, TileType } from "../../js/src/index";
import { styled } from "./stitches.config";
const BASEMAP_THEME = "black";
diff --git a/app/src/Metadata.tsx b/app/src/Metadata.tsx
index 6eec3e6..f184409 100644
--- a/app/src/Metadata.tsx
+++ b/app/src/Metadata.tsx
@@ -1,6 +1,6 @@
import { JsonViewer } from "@textea/json-viewer";
import { useEffect, useState } from "react";
-import { Header, PMTiles } from "../../js/index";
+import { Header, PMTiles } from "../../js/src/index";
import { styled } from "./stitches.config";
const Padded = styled("div", {
diff --git a/app/src/Start.tsx b/app/src/Start.tsx
index 7b69a6c..1209e66 100644
--- a/app/src/Start.tsx
+++ b/app/src/Start.tsx
@@ -1,6 +1,6 @@
import { Dispatch, SetStateAction, useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
-import { FileSource, PMTiles } from "../../js/index";
+import { FileSource, PMTiles } from "../../js/src/index";
import { styled } from "./stitches.config";
import * as LabelPrimitive from "@radix-ui/react-label";
diff --git a/app/src/TileInspectComponent.tsx b/app/src/TileInspectComponent.tsx
index 20e2317..3cefcb3 100644
--- a/app/src/TileInspectComponent.tsx
+++ b/app/src/TileInspectComponent.tsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
-import { PMTiles } from "../../js/index";
+import { PMTiles } from "../../js/src/index";
import { globalStyles, styled } from "./stitches.config";
import Inspector from "./Inspector";
import Start from "./Start";
diff --git a/js/CHANGELOG.md b/js/CHANGELOG.md
index ffcb9af..c92f75a 100644
--- a/js/CHANGELOG.md
+++ b/js/CHANGELOG.md
@@ -1,3 +1,8 @@
+4.0.0
+* remove pmtiles spec v2 support, which reduces bundle size significantly [#287]
+* use tsup for creating cjs/esm packages, which fixes typescript usage [#498]
+* re-structure files in js project to be more conventional.
+
3.2.0
* MapLibre `Protocol` constructor takes an options object.
* add protocol option `metadata:boolean` that controls whether TileJSON metadata is fetched synchronously on map load. [#247]
diff --git a/js/examples/leaflet.html b/js/examples/leaflet.html
index 2bfb9f7..f5e2a68 100644
--- a/js/examples/leaflet.html
+++ b/js/examples/leaflet.html
@@ -4,7 +4,7 @@
-
+