Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: improve @magicbell/react-headless cjs/esm support #391

Merged
merged 9 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
smeijer marked this conversation as resolved.
Show resolved Hide resolved
"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 Expand Up @@ -73,7 +73,7 @@
useState(() => setupXHR(clientSettings));
useEffect(() => {
setupStores(stores);
}, []);

Check warning on line 76 in packages/react-headless/src/components/MagicBellProvider/MagicBellProvider.tsx

View workflow job for this annotation

GitHub Actions / ESLint

React Hook useEffect has a missing dependency: 'stores'. Either include it or remove the dependency array

const config = useConfig();

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}`,
smeijer marked this conversation as resolved.
Show resolved Hide resolved
};

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