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

Fix types and exports of the package #197

Merged
merged 12 commits into from
Jan 4, 2024
Merged
11 changes: 11 additions & 0 deletions .changeset/strong-otters-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"single-spa-react": major
---

### Fixed

- Enhanced compatibility with various bundlers and TypeScript `moduleResolution` strategies. The package's export patterns have been refined to address issues previously encountered with different bundling tools, ensuring more consistent and reliable integration across diverse build environments. Additionally, TypeScript type definitions have been improved, enhancing type safety and developer experience in varied TypeScript setups.

### BREAKING CHANGES

- The changes in export patterns / types may require updates in how projects import from `single-spa-react/*`.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ node_modules/
.DS_Store
lib/
yarn-error.log
coverage
parcel/
coverage
25 changes: 18 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,39 @@
"exports": {
"./package.json": "./package.json",
".": {
"import": "./lib/esm/single-spa-react.js",
"require": "./lib/cjs/single-spa-react.cjs"
"import": {
"types": "./types/single-spa-react.d.ts",
"default": "./lib/esm/single-spa-react.js"
},
"require": {
"types": "./types/single-spa-react.d.cts",
"default": "./lib/cjs/single-spa-react.cjs"
}
},
"./parcel": {
"import": "./lib/esm/parcel.js",
"require": "./lib/cjs/parcel.cjs"
"import": {
"types": "./types/parcel/index.d.ts",
"default": "./lib/esm/parcel.js"
},
"require": {
"types": "./types/parcel/index.d.cts",
"default": "./lib/cjs/parcel.cjs"
}
}
},
"types": "types/single-spa-react.d.cts",
"files": [
"lib",
"parcel",
"types/single-spa-react.d.ts",
"types",
"README.md"
],
"tsd": {
"directory": "src"
},
"types": "types/single-spa-react.d.ts",
"scripts": {
"build": "concurrently pnpm:build:*",
"build:rollup": "rollup -c",
"build:types": "rimraf parcel && copyfiles -f types/parcel/index.d.ts ./parcel",
"lint": "eslint src",
"test": "concurrently pnpm:test:*",
"test:browser": "cross-env BABEL_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --coverage",
Expand Down
20 changes: 20 additions & 0 deletions parcel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Parcel Folder

## Overview
This folder contains a `package.json` stub that redirects module resolutions, following the `package-json-redirects` strategy.

## Why It's Here
Serves as a compatibility layer for Node 10, which does not support the `exports` field in `package.json`. A separate `package.json` with `main` and `types` fields directs Node 10's resolution strategy to the appropriate files.

```json
{
"main": "./lib/cjs/parcel.cjs",
"types": "./types/parcel/index.d.cts"
}
```

## How It Works
When Node 10 attempts to import from this folder, it consults the `main` and `types` fields in `package.json` to locate the actual implementation and types files. This facilitates keeping those files in separate subfolders while making them accessible to older Node versions.

## Reference
For more detailed information and examples, see [this GitHub repository](https://github.com/andrewbranch/example-subpath-exports-ts-compat/tree/main/examples/node_modules/package-json-redirects).
4 changes: 4 additions & 0 deletions parcel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "../lib/umd/parcel.js",
"types": "../types/parcel/index.d.cts"
}
4 changes: 3 additions & 1 deletion src/single-spa-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const defaultOpts = {
unmountResolves: {},
};

export default function singleSpaReact(userOpts) {
function singleSpaReact(userOpts) {
if (typeof userOpts !== "object") {
throw new Error(`single-spa-react requires a configuration object`);
}
Expand Down Expand Up @@ -375,3 +375,5 @@ function createSingleSpaRoot(opts) {

return SingleSpaRoot;
}

export default singleSpaReact;
36 changes: 36 additions & 0 deletions types/parcel/index.d.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from "react";
joeldenning marked this conversation as resolved.
Show resolved Hide resolved
import {
ParcelConfig,
ParcelProps,
Parcel as SingleSpaParcel,
} from "single-spa";

interface ParcelCompProps<ExtraProps = {}> {
Copy link
Member

@joeldenning joeldenning Nov 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be moved to a shared declarations file, and then imported into both the .d.ts and .d.cts files?

import { ParcelCompProps } from './shared.d.ts';

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you agree, we can leave this as-is for now, since these files will not be needed when we migrate to typescript.

config: ParcelConfig<ExtraProps>;
mountParcel?: (
parcelConfig: ParcelConfig,
parcelProps: ParcelProps & ExtraProps
) => SingleSpaParcel;
wrapWith?: string;
wrapStyle?: React.CSSProperties;
wrapClassName?: string;
appendTo?: HTMLElement;
parcelDidMount?: () => any;
handleError?: (err: Error) => any;
[extraProp: string]: any;
}

interface ParcelState {
hasError: boolean;
}

declare class Parcel<ExtraProps = {}> extends React.Component<
ParcelCompProps<ExtraProps>,
ParcelState
> {}

export = Parcel;

declare namespace Parcel {
joeldenning marked this conversation as resolved.
Show resolved Hide resolved
export { ParcelCompProps, ParcelState };
}
63 changes: 63 additions & 0 deletions types/single-spa-react.d.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as React from "react";
joeldenning marked this conversation as resolved.
Show resolved Hide resolved
import { AppProps, CustomProps, LifeCycleFn } from "single-spa";

declare const SingleSpaContext: React.Context<CustomProps & AppProps>;

type DeprecatedRenderTypes =
| "createBlockingRoot"
| "unstable_createRoot"
| "unstable_createBlockingRoot";

type LegacyRenderType = "hydrate" | "render";

type RenderType = "createRoot" | "hydrateRoot" | LegacyRenderType;

interface SingleSpaReactOpts<RootComponentProps> {
React: any;
ReactDOM?: {
[T in LegacyRenderType]?: any;
};
ReactDOMClient?: {
[T in RenderType]?: any;
};
rootComponent?:
| React.ComponentClass<RootComponentProps, any>
| React.FunctionComponent<RootComponentProps>;
loadRootComponent?: (
props?: RootComponentProps
) => Promise<React.ElementType<typeof props>>;
errorBoundary?: (
err: Error,
errInfo: React.ErrorInfo,
props: RootComponentProps
) => React.ReactElement;
errorBoundaryClass?: React.ComponentClass<RootComponentProps>;
parcelCanUpdate?: boolean;
suppressComponentDidCatchWarning?: boolean;
domElementGetter?: (props: RootComponentProps) => HTMLElement;
renderType?: RenderType | (() => RenderType);
}

interface ReactAppOrParcel<ExtraProps> {
bootstrap: LifeCycleFn<ExtraProps>;
mount: LifeCycleFn<ExtraProps>;
unmount: LifeCycleFn<ExtraProps>;
update?: LifeCycleFn<ExtraProps>;
}

declare function singleSpaReact<ExtraProps = {}>(
opts: SingleSpaReactOpts<ExtraProps & AppProps>
): ReactAppOrParcel<ExtraProps>;

export = singleSpaReact;

declare namespace singleSpaReact {
export {
SingleSpaContext,
DeprecatedRenderTypes,
LegacyRenderType,
RenderType,
SingleSpaReactOpts,
ReactAppOrParcel,
};
}