Публикация расширений в kepler.gl строится вокруг понимания архитектуры библиотеки и контрактов, через которые она допускает внешнее расширение функциональности. Экосистема Kepler.gl предполагает, что расширения не просто подключаются как вспомогательные модули, а интегрируются в поток данных, состояние карты и UI-слой React-приложения.
Kepler.gl построен на связке React + Redux, где карта управляется через единое состояние (mapState, visState, uiState). Расширения в этой модели могут вмешиваться в три ключевые точки:
Ключевая идея публикации расширений заключается в том, чтобы не ломать ядро, а внедряться через заранее определённые точки расширения.
Типичное расширение Kepler.gl представляет собой JavaScript/TypeScript пакет, который экспортирует:
Структура пакета обычно выглядит следующим образом:
kepler-gl-extension/
├── src/
│ ├── components/
│ ├── reducers/
│ ├── actions/
│ ├── layer-types/
│ └── index.ts
├── package.json
├── rollup.config.js
└── tsconfig.json
Расширение часто начинается с добавления собственного редьюсера:
export const customReducer = (state = initialState, action) => {
switch (action.type) {
case 'CUSTOM_ACTION':
return {
...state,
value: action.payload
};
default:
return state;
}
};
После этого редьюсер должен быть подключён к корневому редьюсеру
Kepler.gl через combineReducers.
Действия позволяют расширению взаимодействовать с состоянием карты:
export const setCustomValue = (value) => ({
type: 'CUSTOM_ACTION',
payload: value
});
Важно учитывать, что действия Kepler.gl должны быть совместимы с его middleware pipeline, иначе возможны конфликты при асинхронных обновлениях состояния карты.
UI-расширения обычно внедряются через контейнер KeplerGl:
import KeplerGl from 'kepler.gl';
export default function MapWrapper() {
return (
<KeplerGl
id="map"
width={800}
height={600}
/>
);
}
Расширение может добавлять собственные панели через HOC (Higher Order Components):
const withCustomPanel = (Panel) => (props) => {
return (
<>
<Panel {...props} />
<div className="custom-panel">
Custom content
</div>
</>
);
};
Одной из наиболее сложных частей является добавление новых типов слоёв (Layer Types). Kepler.gl использует декларативную модель описания слоя.
Пример базовой структуры слоя:
import { Layer } from 'kepler.gl/layers';
class CustomLayer extends Layer {
constructor(props) {
super(props);
this.type = 'customLayer';
}
renderLayer(opts) {
const { data, objectColor } = opts;
return {
vertices: data,
color: objectColor
};
}
}
После создания слоя его необходимо зарегистрировать в системе:
export const customLayerType = {
type: 'customLayer',
Layer: CustomLayer
};
Публикация расширения невозможна без корректной сборки. Обычно используется Rollup или Vite в библиотечном режиме.
Пример Rollup-конфига:
export default {
input: 'src/index.ts',
output: [
{
file: 'dist/index.cjs.js',
format: 'cjs'
},
{
file: 'dist/index.esm.js',
format: 'esm'
}
],
external: [
'react',
'react-dom',
'kepler.gl'
]
};
Критически важно объявлять Kepler.gl как peerDependency,
чтобы избежать дублирования зависимостей:
{
"peerDependencies": {
"kepler.gl": ">=2.0.0",
"react": ">=17.0.0"
}
}
Расширения Kepler.gl должны строго соблюдать semantic versioning:
При публикации в npm важно учитывать совместимость с конкретной версией Kepler.gl, так как внутренняя структура store может меняться.
Процесс публикации включает подготовку пакета и его отправку в registry:
npm login
npm publish --access public
Перед публикацией необходимо убедиться, что:
main,
module, types)Пример package.json:
{
"name": "kepler-gl-custom-extension",
"version": "1.0.0",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
}
После публикации расширение подключается в приложении:
import { customReducer } from 'kepler-gl-custom-extension';
const reducers = combineReducers({
keplerGl: keplerGlReducer,
custom: customReducer
});
Далее расширение становится частью общего состояния приложения и может взаимодействовать с картой через Redux.
Основные проблемы при публикации расширений возникают из-за:
Особенно критична проблема двойного React, которая приводит к ошибкам хуков:
Invalid hook call. Hooks can only be called inside of the body of a function component
Для устранения используется строгая настройка
peerDependencies и внешних зависимостей в сборщике.
Расширения должны тестироваться в двух плоскостях:
Пример теста редьюсера:
test('custom reducer sets value', () => {
const state = customReducer(undefined, {
type: 'CUSTOM_ACTION',
payload: 10
});
expect(state.value).toBe(10);
});
Публикуемое расширение должно включать:
Структура документации обычно отражает реальные сценарии использования:
Публикация расширений в Kepler.gl фактически означает работу с контрактной архитектурой. Любое расширение должно:
Такая модель обеспечивает возможность масштабирования экосистемы без разрушения базовой архитектуры карты и визуализации пространственных данных.