diff --git a/jest.bundle.config.js b/jest.bundle.config.js new file mode 100644 index 00000000..26f71de7 --- /dev/null +++ b/jest.bundle.config.js @@ -0,0 +1,17 @@ +const path = require('path'); + +module.exports = { + roots: ['/tests/bundle'], + testEnvironment: 'jsdom', + moduleNameMapper: { + '^@vis.gl/react-google-maps/3d$': '/dist/3d/index.modern.mjs', + '^@vis.gl/react-google-maps/server$': '/dist/server/index.modern.mjs', + '^@vis.gl/react-google-maps$': '/dist/index.modern.mjs' + }, + transform: { + '^.+.tsx?$': [ + 'ts-jest', + {tsconfig: path.join(__dirname, 'tsconfig.test.json')} + ] + } +}; diff --git a/jest.config.js b/jest.config.js index 9f21b950..91f32f6a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,7 +3,7 @@ const path = require('path'); module.exports = { roots: [''], testEnvironment: 'jsdom', - testPathIgnorePatterns: ['/node_modules/', '/dist/', '/__utils__/'], + testPathIgnorePatterns: ['/node_modules/', '/dist/', '/__utils__/', '/tests/'], transform: { '^.+.tsx?$': [ 'ts-jest', diff --git a/package-lock.json b/package-lock.json index 5746fe75..964c19d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "jest-environment-jsdom": "^30.4.1", "npm-run-all": "^4.1.5", "prettier": "^3.9.6", + "publint": "^0.3.24", "react": "^19.2.8", "react-dom": "^19.2.8", "rollup": "^4.62.4", @@ -9489,6 +9490,22 @@ "integrity": "sha512-4VpAyMHOqydSvPlEyHwXaE+AkIdR03nX+Qhlxsk2D/IW4OVmDZgIsvJB1cDzyEEtcfKcnaEbfXeiPgejBceT6g==", "license": "MIT" }, + "node_modules/@publint/pack": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.7.tgz", + "integrity": "sha512-4EDEmvxWtgsCnnVeBvtFIFZtUhPPt1+bA9JrSwU4Sa//6oKtzCSlGGXYJr44OD9aGISymbieJ4mCKHUygUDU+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyexec": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, "node_modules/@restart/hooks": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", @@ -28303,6 +28320,16 @@ "node": "*" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -29301,6 +29328,13 @@ "node": ">=10" } }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "dev": true, + "license": "MIT" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -31615,6 +31649,28 @@ "node": ">= 0.10" } }, + "node_modules/publint": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.24.tgz", + "integrity": "sha512-9zS56KrKBoqi5Qt8h92uMP8TTM9AYZSgnmCo4u2priMqkOZvQnTsziZ2p5LJ2ywbYkAjoCDp2jda9u4cgFefIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@publint/pack": "^0.1.7", + "package-manager-detector": "^1.8.0", + "picocolors": "^1.1.1", + "sade": "^1.8.1" + }, + "bin": { + "publint": "src/cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -33031,6 +33087,19 @@ "license": "BSD-3-Clause", "peer": true }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-array-concat": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", @@ -35011,6 +35080,16 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", diff --git a/package.json b/package.json index 7ee73c19..bbbf4192 100644 --- a/package.json +++ b/package.json @@ -12,23 +12,27 @@ "module": "dist/index.modern.mjs", "exports": { ".": { - "require": "./dist/index.umd.js", "types": "./dist/index.d.ts", + "require": "./dist/index.umd.js", "default": "./dist/index.modern.mjs" }, "./server": { - "require": "./dist/server/index.umd.js", "types": "./dist/server/index.d.ts", + "require": "./dist/server/index.umd.js", "default": "./dist/server/index.modern.mjs" }, "./3d": { - "require": "./dist/3d/index.umd.js", "types": "./dist/3d/index.d.ts", + "require": "./dist/3d/index.umd.js", "default": "./dist/3d/index.modern.mjs" }, "./examples.css": "./dist/examples.css" }, "types": "dist/index.d.ts", + "sideEffects": [ + "*.css", + "./dist/examples.css" + ], "repository": { "type": "git", "url": "git+https://github.com/visgl/react-google-maps.git" @@ -52,7 +56,9 @@ "test:tsc:examples": "bash ./scripts/typecheck-examples.sh", "test:prettier": "prettier --check ./src ./examples", "test:unit": "jest", - "test": "npm-run-all -p test:linter test:prettier test:tsc -s test:unit", + "test:bundle": "npm run build && NODE_OPTIONS=--experimental-vm-modules jest --config jest.bundle.config.js", + "lint:pub": "publint", + "test": "npm-run-all -p test:linter test:prettier test:tsc -s test:unit test:bundle lint:pub", "prepublishOnly": "npm run test && npm run build", "prepack": "npm-run-all clean build" }, @@ -84,8 +90,8 @@ "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-replace": "^6.0.3", "@rollup/plugin-typescript": "^12.3.0", - "@testing-library/jest-dom": "^7.0.1", "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.3", "@types/jest": "^30.0.0", @@ -100,6 +106,7 @@ "jest-environment-jsdom": "^30.4.1", "npm-run-all": "^4.1.5", "prettier": "^3.9.6", + "publint": "^0.3.24", "react": "^19.2.8", "react-dom": "^19.2.8", "rollup": "^4.62.4", diff --git a/rollup.config.mjs b/rollup.config.mjs index 73b5515f..899308c1 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -26,46 +26,63 @@ const plugins = [ }) ]; -const createConfig = (input, outputBase) => [ - // ESM and UMD builds - { - input, - output: [ - { - file: `${outputBase}.modern.mjs`, - format: 'es', - sourcemap: true - }, - { - file: `${outputBase}.umd.js`, - format: 'umd', - name: 'ReactGoogleMaps', - globals: { - react: 'React', - 'react-dom': 'ReactDOM', - 'react/jsx-runtime': 'React', - 'fast-deep-equal': 'fastDeepEqual' - }, - sourcemap: true - } - ], - external, - plugins +const entries = { + index: './src/index.ts', + 'server/index': './src/server/index.ts', + '3d/index': './src/3d/index.ts' +}; + +// One ESM build for every entry, so the modules they share (the APIProvider +// and map contexts above all) are emitted once as chunks and each entry +// imports the same instance. Built one entry at a time, `/3d` and the root +// each carried their own `APIProviderContext`, and a `Map3D` imported from +// `/3d` could not see the `APIProvider` imported from the root. +const esmConfig = { + input: entries, + output: { + dir: 'dist', + format: 'es', + entryFileNames: '[name].modern.mjs', + chunkFileNames: 'chunks/[name]-[hash].mjs', + sourcemap: true }, - // TypeScript declarations - { - input, - output: { - file: `${outputBase}.d.ts`, - format: 'es' + external, + plugins +}; + +// UMD cannot code-split; each entry stays self-contained. +const umdConfig = (input, outputBase) => ({ + input, + output: { + file: `${outputBase}.umd.js`, + format: 'umd', + name: 'ReactGoogleMaps', + globals: { + react: 'React', + 'react-dom': 'ReactDOM', + 'react/jsx-runtime': 'React', + 'fast-deep-equal': 'fastDeepEqual' }, - external, - plugins: [dts()] - } -]; + sourcemap: true + }, + external, + plugins +}); + +const dtsConfig = (input, outputBase) => ({ + input, + output: { + file: `${outputBase}.d.ts`, + format: 'es' + }, + external, + plugins: [dts()] +}); export default [ - ...createConfig('./src/index.ts', './dist/index'), - ...createConfig('./src/server/index.ts', './dist/server/index'), - ...createConfig('./src/3d/index.ts', './dist/3d/index') + esmConfig, + ...Object.entries(entries).flatMap(([name, input]) => [ + umdConfig(input, `./dist/${name}`), + dtsConfig(input, `./dist/${name}`) + ]) ]; diff --git a/src/3d/index.ts b/src/3d/index.ts index ecb45eab..b6fe7cf1 100644 --- a/src/3d/index.ts +++ b/src/3d/index.ts @@ -12,3 +12,4 @@ export type * from '../components/3d/types'; export * from '../components/map-3d'; export * from '../components/marker-3d'; export * from '../components/popover'; +export * from '../hooks/use-map-3d'; diff --git a/src/deprecated-3d-exports.ts b/src/deprecated-3d-exports.ts index da39c534..2fc55d25 100644 --- a/src/deprecated-3d-exports.ts +++ b/src/deprecated-3d-exports.ts @@ -27,6 +27,7 @@ import type { } from './components/marker-3d'; import {Popover as PopoverImpl} from './components/popover'; import type {PopoverProps as PopoverPropsImpl} from './components/popover'; +import {useMap3D as useMap3DImpl} from './hooks/use-map-3d'; /** * @deprecated Import from `@vis.gl/react-google-maps/3d` instead. @@ -112,3 +113,7 @@ export const Popover = PopoverImpl; * @deprecated Import from `@vis.gl/react-google-maps/3d` instead. */ export type PopoverProps = PopoverPropsImpl; +/** + * @deprecated Import from `@vis.gl/react-google-maps/3d` instead. + */ +export const useMap3D = useMap3DImpl; diff --git a/src/index.ts b/src/index.ts index 1c79a299..05cc5c6e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,7 +21,6 @@ export * from './hooks/use-api-loading-status'; export * from './hooks/use-api-is-loaded'; export * from './hooks/use-maps-library'; export * from './hooks/use-map'; -export * from './hooks/use-map-3d'; export * from './libraries/lat-lng-utils'; export * from './libraries/api-loading-status'; export {limitTiltRange} from './libraries/limit-tilt-range'; diff --git a/tests/bundle/bundle.test.ts b/tests/bundle/bundle.test.ts new file mode 100644 index 00000000..a1822a43 --- /dev/null +++ b/tests/bundle/bundle.test.ts @@ -0,0 +1,28 @@ +import React from 'react'; +import {renderToString} from 'react-dom/server'; + +import * as root from '@vis.gl/react-google-maps'; +import * as threeD from '@vis.gl/react-google-maps/3d'; + +describe('Bundle Integration', () => { + test('shared contexts and components are identical instances across entrypoints', () => { + expect(root.GoogleMaps3DContext).toBe(threeD.GoogleMaps3DContext); + expect(root.Marker3DContext).toBe(threeD.Marker3DContext); + expect(root.Map3D).toBe(threeD.Map3D); + expect(root.Marker3D).toBe(threeD.Marker3D); + expect(root.Popover).toBe(threeD.Popover); + expect(root.useMap3D).toBe(threeD.useMap3D); + }); + + test(' from /3d renders inside from root without throwing', () => { + expect(() => { + renderToString( + React.createElement( + root.APIProvider, + {apiKey: 'test-api-key'}, + React.createElement(threeD.Map3D, {mode: 'SATELLITE'}) + ) + ); + }).not.toThrow(); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 150a5fc4..3574d37c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "skipLibCheck": true, "paths": { - "@vis.gl/react-google-maps": ["./src"] + "@vis.gl/react-google-maps": ["./src"], + "@vis.gl/react-google-maps/*": ["./src/*"] } }, "include": ["./src/**/*", "./types/**/*"], diff --git a/tsconfig.test.json b/tsconfig.test.json index a495bee6..6d73146b 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,6 +1,6 @@ { "extends": "./tsconfig.json", - "include": ["./src/**/*", "./types/**/*"], + "include": ["./src/**/*", "./types/**/*", "./tests/**/*"], "exclude": ["./examples/**/*"], "compilerOptions": { "noEmit": false,