Skip to content

Commit

Permalink
chore: improve @magicbell/react-headless cjs/esm support (#391)
Browse files Browse the repository at this point in the history
  • Loading branch information
smeijer authored Oct 1, 2024
1 parent c47faa4 commit 60d24da
Show file tree
Hide file tree
Showing 49 changed files with 237 additions and 162 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-walls-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@magicbell/react-headless': minor
---

fix cjs/esm dual module support
10 changes: 9 additions & 1 deletion jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const commonConfig = {
tsconfig: 'tsconfig.test.json',
}]
},
resolver: './jest.resolver.cjs',
moduleFileExtensions: ['ts', 'tsx', 'cts', 'js', 'json'],
modulePathIgnorePatterns: ['<rootDir>/packages/magicbell/dist', '<rootDir>/packages/playground', '<rootDir>/packages/embeddable/cypress'],
globals: {
__PACKAGE_NAME__: 'TEST',
Expand All @@ -41,16 +43,22 @@ const commonConfig = {
moduleNameMapper,
};

const projectConfigs = {
'@magicbell/user-client': {
testEnvironment: 'node',
}
};

/** @type {import('jest').Config} */
module.exports = {
projects: packages.map(([name, dir]) => ({
...commonConfig,
displayName: name,
testEnvironment: name === '@magicbell/user-client' ? 'node' : "jest-environment-jsdom",
testMatch: [
`${dir}/src/**/*.test.[jt]s?(x)"`,
`${dir}/test/**/*.[jt]s?(x)"`,
`${dir}/tests/**/*.spec.[jt]s?(x)"`,
],
...projectConfigs[name],
}))
};
12 changes: 12 additions & 0 deletions jest.resolver.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const fs = require('fs');

module.exports = (request, options) => {
const { defaultResolver } = options;
const resolvedPath = defaultResolver(request, options);

// try resolve tshy module resolution polyfills
const cjsPath = resolvedPath.replace(/\.ts$/, '-cjs.cts');
if (fs.existsSync(cjsPath)) return cjsPath;

return resolvedPath;
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"eslint-plugin-react": "^7.34.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^8.0.0",
"fix-esm-import-path": "^1.10.0",
"fix-esm-import-path": "^1.10.1",
"happy-dom": "^15.7.3",
"husky": "^9.0.11",
"identity-obj-proxy": "^3.0.0",
Expand All @@ -90,6 +90,7 @@
"prettier": "^2.8.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"replace-in-file": "^8.2.0",
"rimraf": "^5.0.7",
"rollup-plugin-analyzer": "^4.0.0",
"size-limit": "^8.2.6",
Expand Down
50 changes: 29 additions & 21 deletions packages/react-headless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
"Stephan Meijer <stephan.meijer@gmail.com>"
],
"license": "SEE LICENSE IN LICENSE",
"source": "./src/index.ts",
"main": "dist/index.js",
"module": "dist/magicbell-react-headless.esm.js",
"typings": "dist/index.d.ts",
"sideEffects": false,
"files": [
"/dist",
Expand Down Expand Up @@ -42,22 +38,17 @@
},
"scripts": {
"clean": "rimraf dist",
"build": "run-s clean build:*",
"build:dev": "vite build -c ../../scripts/vite/vite.config.js",
"build:prod": "vite build -c ../../scripts/vite/vite.config.js --minify",
"start": "yarn build:dev --watch",
"size": "size-limit"
"build": "run-s clean build:bundle build:replace test:bundle build:attw",
"build:bundle": "tshy",
"build:replace": "tsx scripts/post-build.ts",
"build:attw": "attw -P",
"test:bundle": "node tests/bundle/commonjs.cjs && node tests/bundle/esm.mjs",
"start": "tshy --watch"
},
"tshy": {
"project": "./tsconfig.build.json",
"exports": "./src/*.ts"
},
"size-limit": [
{
"path": "dist/magicbell-react-headless.cjs.min.js",
"limit": "125 KB"
},
{
"path": "dist/magicbell-react-headless.esm.min.js",
"limit": "125 KB"
}
],
"devDependencies": {
"@faker-js/faker": "^6.3.1",
"@size-limit/preset-small-lib": "^11.1.4",
Expand All @@ -77,7 +68,7 @@
"dependencies": {
"dayjs": "^1.11.10",
"humps": "^2.0.1",
"immer": "^9.0.21",
"immer": "^10.1.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"magicbell": "4.1.0",
Expand All @@ -90,5 +81,22 @@
},
"peerDependencies": {
"react": ">= 18.3.1"
}
},
"type": "module",
"exports": {
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
},
"./package.json": "./package.json"
},
"module": "./dist/esm/index.js",
"main": "./dist/commonjs/index.js",
"types": "./dist/commonjs/index.d.ts"
}
19 changes: 19 additions & 0 deletions packages/react-headless/scripts/post-build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { replaceInFile, ReplaceInFileConfig } from 'replace-in-file';

import pkgJson from '../package.json';
import { pkg } from '../src/lib/pkg';

const config = {
files: 'dist/*/lib/pkg.js',
from: ['__PACKAGE_NAME__', '__PACKAGE_VERSION__'],
to: [pkgJson.name, pkgJson.version],
} satisfies ReplaceInFileConfig;

for (const key of Object.values(pkg)) {
if (!config.from.includes(key)) {
process.stdout.write(`Unknown replacement key '${key}' in lib/pkg.ts\n`);
process.exit(1);
}
}

await replaceInFile(config);
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';

import clientSettings, { ClientSettings } from '../../stores/clientSettings';
import useConfig from '../../stores/config';
import { useNotificationStoresCollection } from '../../stores/notifications';
import buildStore from '../../stores/notifications/helpers/buildStore';
import { QueryParams } from '../../types/INotificationsStoresCollection';
import INotificationStore from '../../types/INotificationStore';
import RealtimeListener from '../RealtimeListener';
import clientSettings, { ClientSettings } from '../../stores/clientSettings.js';
import useConfig from '../../stores/config/index.js';
import buildStore from '../../stores/notifications/helpers/buildStore.js';
import { useNotificationStoresCollection } from '../../stores/notifications/index.js';
import { QueryParams } from '../../types/INotificationsStoresCollection.js';
import INotificationStore from '../../types/INotificationStore.js';
import RealtimeListener from '../RealtimeListener.js';

type StoreConfig = {
id: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import MagicBellProvider from './MagicBellProvider';
import MagicBellProvider from './MagicBellProvider.js';

export type { MagicBellProviderProps } from './MagicBellProvider';
export type { MagicBellProviderProps } from './MagicBellProvider.js';
export default MagicBellProvider;
10 changes: 5 additions & 5 deletions packages/react-headless/src/components/RealtimeListener.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useEffect } from 'react';

import useMagicBellEvent from '../hooks/useMagicBellEvent';
import { handleAblyEvent } from '../lib/realtime';
import clientSettings from '../stores/clientSettings';
import { useNotificationStoresCollection } from '../stores/notifications';
import IRemoteNotification from '../types/IRemoteNotification';
import useMagicBellEvent from '../hooks/useMagicBellEvent.js';
import { handleAblyEvent } from '../lib/realtime.js';
import clientSettings from '../stores/clientSettings.js';
import { useNotificationStoresCollection } from '../stores/notifications/index.js';
import IRemoteNotification from '../types/IRemoteNotification.js';

/**
* Component that setups a listener to realtime events and keeps notifications
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { path } from 'ramda';
import { useEffect } from 'react';

import { createPushSubscription, createSafariPushSubscription } from '../../lib/push';
import useConfig from '../../stores/config';
import { createPushSubscription, createSafariPushSubscription } from '../../lib/push.js';
import useConfig from '../../stores/config/index.js';

export interface Props {
children: (params: { createSubscription: () => Promise<unknown>; isPushAPISupported: boolean }) => JSX.Element;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import WebPushNotificationsSubscriber from './WebPushNotificationsSubscriber';
import WebPushNotificationsSubscriber from './WebPushNotificationsSubscriber.js';

export type { Props as WebPushNotificationsSubscriberProps } from './WebPushNotificationsSubscriber';
export type { Props as WebPushNotificationsSubscriberProps } from './WebPushNotificationsSubscriber.js';
export default WebPushNotificationsSubscriber;
2 changes: 1 addition & 1 deletion packages/react-headless/src/hooks/useBell.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import useNotifications, { NotificationStore } from './useNotifications';
import useNotifications, { NotificationStore } from './useNotifications.js';

interface useBellArgs {
storeId?: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/react-headless/src/hooks/useMagicBellEvent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect } from 'react';

import { eventAggregator, EventSource } from '../lib/realtime';
import { eventAggregator, EventSource } from '../lib/realtime.js';

interface HookOptions {
source: EventSource | 'any';
Expand Down
8 changes: 4 additions & 4 deletions packages/react-headless/src/hooks/useNotification.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import INotification from '../types/INotification';
import IRemoteNotification from '../types/IRemoteNotification';
import useNotificationFactory from './useNotificationFactory';
import useNotificationUnmount from './useNotificationUnmount';
import INotification from '../types/INotification.js';
import IRemoteNotification from '../types/IRemoteNotification.js';
import useNotificationFactory from './useNotificationFactory.js';
import useNotificationUnmount from './useNotificationUnmount.js';

export default function useNotification(
data: IRemoteNotification,
Expand Down
10 changes: 5 additions & 5 deletions packages/react-headless/src/hooks/useNotificationFactory.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { isNil } from 'ramda';

import { secondsToDate } from '../lib/date';
import { parseJSON } from '../lib/json';
import { useNotificationStoresCollection } from '../stores/notifications';
import INotification from '../types/INotification';
import IRemoteNotification from '../types/IRemoteNotification';
import { secondsToDate } from '../lib/date.js';
import { parseJSON } from '../lib/json.js';
import { useNotificationStoresCollection } from '../stores/notifications/index.js';
import INotification from '../types/INotification.js';
import IRemoteNotification from '../types/IRemoteNotification.js';

/**
* Hook that builds a notification object.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect } from 'react';

import INotification from '../types/INotification';
import INotification from '../types/INotification.js';

/**
* Hook that is ran when the component is unmounted. By default marks a
Expand Down
8 changes: 4 additions & 4 deletions packages/react-headless/src/hooks/useNotifications.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useCallback, useEffect } from 'react';

import useConfig from '../stores/config';
import { useNotificationStoresCollection } from '../stores/notifications';
import { QueryParams } from '../types/INotificationsStoresCollection';
import INotificationStore from '../types/INotificationStore';
import useConfig from '../stores/config/index.js';
import { useNotificationStoresCollection } from '../stores/notifications/index.js';
import { QueryParams } from '../types/INotificationsStoresCollection.js';
import INotificationStore from '../types/INotificationStore.js';

type FetchOptions = Partial<{
reset: boolean;
Expand Down
52 changes: 19 additions & 33 deletions packages/react-headless/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,19 @@
import warning from 'tiny-warning';

export { default as MagicBellProvider } from './components/MagicBellProvider';
export { default as RealtimeListener } from './components/RealtimeListener';
export { default as WebPushNotificationsSubscriber } from './components/WebPushNotificationsSubscriber';
export { default as useBell } from './hooks/useBell';
export { default as useMagicBellEvent } from './hooks/useMagicBellEvent';
export { default as useNotification } from './hooks/useNotification';
export { default as useNotificationFactory } from './hooks/useNotificationFactory';
export { default as useNotifications } from './hooks/useNotifications';
export { default as useNotificationUnmount } from './hooks/useNotificationUnmount';
export { deleteAPI, fetchAPI, postAPI, putAPI } from './lib/ajax';
export { secondsToDate, toDate, toUnix } from './lib/date';
export { eventAggregator, pushEventAggregator } from './lib/realtime';
export { default as clientSettings } from './stores/clientSettings';
export { default as useConfig } from './stores/config';
export { default as useNotificationPreferences } from './stores/notification_preferences';
export { useNotificationStoresCollection } from './stores/notifications';
export { default as buildStore } from './stores/notifications/helpers/buildStore';
export * from './types';
export { type INotification as Notification } from './types';

if (__DEV__) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const testFunc = function testFn() {};

warning(
(testFunc.name || testFunc.toString()).indexOf('testFn') !== -1,
"It looks like you're using a minified copy of the development build " +
`of ${__PACKAGE_NAME__}. When deploying your app to production, make sure to use ` +
'the production build which is faster and does not print development warnings.',
);
}
export { default as MagicBellProvider } from './components/MagicBellProvider/index.js';
export { default as RealtimeListener } from './components/RealtimeListener.js';
export { default as WebPushNotificationsSubscriber } from './components/WebPushNotificationsSubscriber/index.js';
export { default as useBell } from './hooks/useBell.js';
export { default as useMagicBellEvent } from './hooks/useMagicBellEvent.js';
export { default as useNotification } from './hooks/useNotification.js';
export { default as useNotificationFactory } from './hooks/useNotificationFactory.js';
export { default as useNotifications } from './hooks/useNotifications.js';
export { default as useNotificationUnmount } from './hooks/useNotificationUnmount.js';
export { deleteAPI, fetchAPI, postAPI, putAPI } from './lib/ajax.js';
export { secondsToDate, toDate, toUnix } from './lib/date.js';
export { eventAggregator, pushEventAggregator } from './lib/realtime.js';
export { default as clientSettings } from './stores/clientSettings.js';
export { default as useConfig } from './stores/config/index.js';
export { default as useNotificationPreferences } from './stores/notification_preferences/index.js';
export { default as buildStore } from './stores/notifications/helpers/buildStore.js';
export { useNotificationStoresCollection } from './stores/notifications/index.js';
export * from './types/index.js';
export { type INotification as Notification } from './types/index.js';
2 changes: 1 addition & 1 deletion packages/react-headless/src/lib/ajax.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import clientSettings from '../stores/clientSettings';
import clientSettings from '../stores/clientSettings.js';

/**
* Performs an ajax request to the MagicBell API server.
Expand Down
6 changes: 3 additions & 3 deletions packages/react-headless/src/lib/date.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import dayjs, { type Dayjs } from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import relativeTime from 'dayjs/plugin/relativeTime';
import updateLocale from 'dayjs/plugin/updateLocale';
import localizedFormat from 'dayjs/plugin/localizedFormat.js';
import relativeTime from 'dayjs/plugin/relativeTime.js';
import updateLocale from 'dayjs/plugin/updateLocale.js';

dayjs.extend(localizedFormat);
dayjs.extend(relativeTime);
Expand Down
6 changes: 6 additions & 0 deletions packages/react-headless/src/lib/pkg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Note, constants are replaced right after tsc built the project.
// Update scripts/post-build.ts when requirements here change.
export const pkg = {
name: '__PACKAGE_NAME__',
version: '__PACKAGE_VERSION__',
};
4 changes: 2 additions & 2 deletions packages/react-headless/src/lib/push.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { path } from 'ramda';

import { IRemoteConfig } from '../types';
import { postAPI } from './ajax';
import { IRemoteConfig } from '../types/index.js';
import { postAPI } from './ajax.js';

function stringToUint8Array(plainString: string) {
const padding = '='.repeat((4 - (plainString.length % 4)) % 4);
Expand Down
10 changes: 5 additions & 5 deletions packages/react-headless/src/lib/realtime.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import mitt from 'mitt';

import clientSettings from '../stores/clientSettings';
import NotificationRepository from '../stores/notifications/NotificationRepository';
import { mitt } from '../polyfills/mitt-module.js';
import clientSettings from '../stores/clientSettings.js';
import NotificationRepository from '../stores/notifications/NotificationRepository.js';
import { pkg } from './pkg.js';

export function getAuthHeaders() {
const { apiKey, userEmail, userExternalId, userKey } = clientSettings.getState();

const headers = {
'x-magicbell-api-key': apiKey,
'x-magicbell-client-user-agent': `${__PACKAGE_NAME__}/${__PACKAGE_VERSION__}`,
'x-magicbell-client-user-agent': `${pkg.name}/${pkg.version}`,
};

if (userEmail) headers['x-magicbell-user-email'] = userEmail;
Expand Down
Loading

0 comments on commit 60d24da

Please sign in to comment.