npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cdek-pvz-widget

v0.1.0

Published

Lightweight CDEK pickup-point (PVZ) picker with server-side city filtering. Loads offices for a single city (~1MB) instead of the whole world (~19MB). Drop-in compatible with @cdek-it/widget (window.CDEKWidget).

Readme

cdek-pvz-widget

Лёгкий виджет выбора пункта выдачи (ПВЗ/постамат) СДЭК. В отличие от официального @cdek-it/widget, грузит пункты по городу пользователя (серверная фильтрация service.php по fias_guid/city_code) — ~1 МБ на город вместо ~19 МБ на весь мир, поэтому открывается за ~1–2 с даже на ПК.

  • Drop-in по основным опциям: глобал window.CDEKWidget, new CDEKWidget({...}), onChoose(mode, tariff, address).
  • Совместим с API СДЭК: ходит в тот же service.php (action=offices).
  • Без зависимостей и без привязки к конкретному магазину: ключи, геокодер и тема передаются хостом.
  • ESM + UMD, типы .d.ts, i18n (ru/eng), тема, a11y.

Неофициальный проект, не аффилирован со СДЭК.

Зачем (мотивация)

Официальный @cdek-it/widget на старте грузит весь мировой список ПВЗ (~19 МБ, 10k+ точек) и оборачивает его в реактивность — на десктопе окно выбора пункта может открываться 15–20 секунд. Проблема подробно разобрана в статье «Виджет СДЭК» — rant.dimius.ru.

Это решение грузит пункты по городу пользователя через тот же service.php (fias_guid/city_code) — ~1 МБ вместо ~19 МБ, поэтому открывается за ~1–2 с. Независимая реализация с упором на производительность и совместимость с API СДЭК.

Установка

npm i cdek-pvz-widget

или через CDN (UMD): <script src="https://unpkg.com/cdek-pvz-widget"></script>window.CDEKWidget.

Быстрый старт

import CDEKWidget from 'cdek-pvz-widget';

const widget = new CDEKWidget({
    root: 'cdek-map',                 // <div id="cdek-map" style="height:600px"></div>
    apiKey: 'YANDEX_MAPS_KEY',        // ключ Yandex Maps JS API
    servicePath: 'https://host/cdek/service.php',
    defaultLocation: 'Москва',
    // геокодер инжектится хостом (строгая сепарация):
    geocoder: CDEKWidget.createDadataGeocoder({token: 'DADATA_TOKEN'}),
    onChoose: (mode, tariff, address) => {
        console.log(address.city, address.address, address.code);
    },
});
// widget.destroy();

Контейнеру root нужно задать высоту, иначе карта схлопнется.

Геокодер (обязателен для серверной фильтрации)

Чтобы грузить офисы по городу, виджету нужен city_code или fias_guid города. Их даёт геокодер — выберите готовый адаптер или передайте свой:

| Адаптер | Даёт код города | Замечание | |----------------------------------------------------------|-----------------------|---------------------------------------------------------------| | CDEKWidget.createCdekLocationGeocoder({ servicePath }) | city_code | Дефолт. Требует action=location в service.php (см. ниже). | | CDEKWidget.createDadataGeocoder({ token }) | fias_guid | Токен виден в браузере — используйте доменно-ограниченный. | | CDEKWidget.createYandexGeocoder({ apiKey }) | — (только координаты) | Для центрирования; серверно фильтровать по городу не может. |

Свой геокодер — любой объект, реализующий интерфейс:

interface Geocoder {
    suggest(query: string, signal?: AbortSignal): Promise<CityRef[]>;

    resolve?(query: string, signal?: AbortSignal): Promise<CityRef | null>;
}

interface CityRef {
    name: string;
    region?: string;
    cityCode?: number;
    fiasGuid?: string;
    lat?: number;
    lon?: number
}

Опционально reverse(lat, lon) — обратное геокодирование центра карты для режима followMap (есть у DaData-адаптера).

Требования к service.php

service.php — серверный прокси СДЭК (его раздаёт СДЭК; дорабатывается на вашем бэкенде).

  • GET ?action=offices — обязателен (есть в стоковом service.php). Виджет шлёт fias_guid/city_code и фильтры.
  • GET ?action=location&query=... — нужен только для дефолтного createCdekLocationGeocoder. Проксируйте к СДЭК GET /v2/location/suggest/cities (или /v2/location/cities) и верните массив [{ code, city, region, fias_guid, longitude, latitude }]. Если экшена нет — используйте DaData/Yandex адаптер или передайте initialCity.

CSP хоста должен разрешать домены: api-maps.yandex.ru (карта) и домен геокодера (например suggestions.dadata.ru).

Хосты внешних ресурсов передавайте в рантайме, не зашивая в код виджета и не запекая билд-переменными: mapsApiUrl (опция виджета), url у адаптеров геокодера (createDadataGeocoder({ url }), createYandexGeocoder({ url })), servicePath. У всех есть дефолты для standalone-использования, но хост (например из своего рантайм-конфига) может их переопределить.

Опции

| Опция | Тип | По умолчанию | Описание | |-----------------------|--------------------------------------------------------|-----------------------------------|------------------------------------------------------------------------------| | root | string | — | id контейнера. Обязательно. | | apiKey | string | — | Ключ Yandex Maps. Обязательно. | | servicePath | string | — | URL service.php. Обязательно. | | mapsApiUrl | string | https://api-maps.yandex.ru/2.1/ | База Yandex Maps API. Передавайте хост в рантайме. | | geocoder | Geocoder | CDEK location | Источник городов. | | defaultLocation | string \| [lng,lat] | — | Стартовый город. | | initialCity | CityRef | — | Готовый город (минует геокодер). | | fallbackCity | CityRef | — | Резерв, если резолв не удался. | | selectedPoint | { code?, lat?, lon? } | — | Восстановить выбранный пункт при открытии (центр + подсветка). | | followMap | boolean | false | Определять город по центру карты при перемещении (нужен geocoder.reverse). | | sender | boolean | false | Режим отправителя (handout-флаги). | | canChoose | boolean | true | Показывать кнопку выбора. | | hideFilters | { type, have_cashless, have_cash, is_dressing_room } | — | Скрыть фильтры. | | forceFilters | OfficeFilters | — | Принудительные значения фильтров (уходят в запрос). | | hideDeliveryOptions | { office, door } | — | Скрыть типы доставки. | | lang | 'rus' \| 'eng' | 'rus' | Язык. | | labels | Partial<Labels> | — | Переопределение текстов. | | theme | Theme | палитра СДЭК | Переопределение цветов (CSS-переменные). | | debug | boolean | false | Дебаг-логи + метрики производительности в консоль (console.debug). | | from | string \| FromLocation | — | Адрес отправления для расчёта тарифов. | | goods | Parcel[] | — | Габариты/вес посылок (см/г). Без них тарифы не считаются. | | tariffs | { office, door, pickup } | дефолты СДЭК | Белые списки tariff_code по типам доставки. | | currency | CurrencyCode | 'RUB' | Валюта расчёта (в API уходит числом). | | tariff | Tariff \| null | — | Фиксированный тариф (тогда calculate не вызывается). | | selected | { door, office } | — | Предвыбранные точка/адрес. | | popup | boolean | false | Модальный режим: методы open()/close(). | | requirePostcode | boolean | false | Курьер: требовать индекс, иначе onChoose не вызывается. | | onReady | () => void | — | После первой загрузки. | | onChoose | (mode, tariff, address) => void | — | Выбор: mode office/door, tariff Tariff\|null, address офис или адрес.| | onCalculate | (prices, location) => void | — | После расчёта: prices = { office, door, pickup }. | | onError | (error) => void | — | Ошибки config/maps/offices/geocoder/calculate. |

Совместимость с официальным @cdek-it/widget

| Возможность | Статус | |---|---| | root, apiKey, servicePath, defaultLocation, from, sender, canChoose, hideFilters, hideDeliveryOptions, goods, tariffs, currency, tariff, selected, popup, lang, onReady, onChoose, onCalculate | ✅ совпадают | | Выбор ПВЗ + расчёт тарифов (onChoose('office', tariff, office), onCalculate) | ✅ | | Курьерская доставка door (адрес → расчёт → onChoose('door', tariff, address)) | ✅ | | Popup-режим: open() / close() | ✅ | | Методы addParcel/getParcels/resetParcels/clearSelection/updateLocation/updateTariff/selectOffice | ✅ | | geocoder, mapsApiUrl, labels, theme, initialCity, fallbackCity, followMap, selectedPoint, onError | ➕ расширения |

Адрес курьера геокодируется через Yandex Geocoder (как у официального виджета). Расчёт тарифов требует непустой goods и заданный from — иначе tariff в onChoose будет null.

Методы

widget.open() / widget.close()              // popup-режим
widget.addParcel(parcel | parcel[])         // добавить посылку(и), пересчитать
widget.getParcels()                         // текущие посылки (копия)
widget.resetParcels()                       // очистить посылки
widget.updateTariff(tariff | null)          // зафиксировать/снять тариф
widget.clearSelection()                     // сбросить выбор
widget.updateLocation('Город' | CityRef)    // сменить город
widget.selectOffice(code)                   // выбрать пункт по коду
widget.destroy()                            // снять виджет

Тема

new CDEKWidget({ /* ... */ theme: {accent: '#1AB248', accentDark: '#158E3A', text: '#44403C'}});

Любое поле theme → CSS-переменная --cdek-* на корне; можно стилизовать и обычным CSS по .cdek-widget.

Разработка

npm install
npm run dev         # демо-страница (index.html), ключи из .env (см. .env.example)
npm run build       # dist/: ESM + UMD + .d.ts
npm run typecheck
npm run lint
npm test

Отладка и метрики

С debug: true виджет пишет в console.debug метрики производительности (с префиксом [cdek-widget]):

  • тайминги: ymaps load, geocode initial city, map init, offices fetch [Город], render list+map, total mount→ready, reverse geocode (followMap);
  • по офисам: число пунктов и реальный сетевой размер ответа (transfer/decoded из PerformanceResourceTiming);
  • снимки JS-heap (used / total / limit) на ключевых этапах (где доступно — Chromium);
  • события followMap (определённый город), destroy.

Этого достаточно, чтобы оценить, сколько виджет «ест» по сети/CPU/памяти и где узкое место. В проде debug держите выключенным.

Безопасность

  • Ключ Yandex Maps виден в браузере (так работает их JS API) — используйте доменно-ограниченный ключ из кабинета Yandex.
  • Токен DaData тоже виден на клиенте — берите доменно-ограниченный токен или проксируйте через свой backend-геокодер.
  • Не включайте debug: true в проде (в onError/консоль может попасть содержимое сетевых ошибок).
  • Данные пунктов из service.php экранируются перед вставкой в DOM/балун; виджет не использует innerHTML с непроверенными данными.

Лицензия

MIT.