Skip to content

Commit

Permalink
feat(react-hooks): add interceptService hook
Browse files Browse the repository at this point in the history
  • Loading branch information
noherczeg committed Nov 11, 2023
1 parent beab315 commit b729126
Show file tree
Hide file tree
Showing 7 changed files with 1,821 additions and 3,441 deletions.
5,055 changes: 1,676 additions & 3,379 deletions examples/react-systemjs/package-lock.json

Large diffs are not rendered by default.

32 changes: 16 additions & 16 deletions examples/react-systemjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@
"author": "",
"license": "MIT",
"devDependencies": {
"@lerna-lite/cli": "^1.13.0",
"@lerna-lite/run": "^1.13.0",
"@rollup/plugin-commonjs": "^24.0.0",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.4.0",
"@rollup/plugin-typescript": "^11.0.0",
"@types/node": "^18.14.2",
"@lerna-lite/cli": "^2.6.0",
"@lerna-lite/run": "^2.6.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5",
"@types/node": "^20.9.0",
"cross-env": "^7.0.3",
"dotenv": "^16.0.3",
"prettier": "^2.7.1",
"rollup": "^3.3.0",
"dotenv": "^16.3.1",
"prettier": "^3.0.3",
"rollup": "^4.3.1",
"rollup-plugin-clear": "^2.0.7",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-esbuild": "^5.0.0",
"serve": "^14.1.2",
"typescript": "^4.9.3"
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-esbuild": "^6.1.0",
"serve": "^14.2.1",
"typescript": "^5.2.2"
}
}
95 changes: 50 additions & 45 deletions examples/react-systemjs/packages/app/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,70 @@
import { useState } from 'react';
import { createRoot } from 'react-dom/client';
import Pandino from '@pandino/pandino';
import { OBJECTCLASS } from '@pandino/pandino-api';
import loaderConfiguration from '@pandino/loader-configuration-dom';
import { PandinoProvider } from '@pandino/react-hooks';
import { PandinoProvider, useServiceInterceptor } from '@pandino/react-hooks';
import { CUSTOM_COMPONENT_INTERFACE_KEY, CustomComponent } from '@react-systemjs/component-api';
import { App } from './App';
import { SOME_SERVICE_INTERFACE_KEY, SomeServiceImpl } from './service';

const root = createRoot(document.querySelector('#root')!);

const pandino = new Pandino({
...loaderConfiguration,
});
(async () => {
const pandino = new Pandino({
...loaderConfiguration,
});

await pandino.init();
await pandino.start();
await pandino.init();
await pandino.start();

const bundleContext = pandino.getBundleContext();
const bundleContext = pandino.getBundleContext();

export const ComponentOne: CustomComponent = (props) => {
const [data, setData] = useState<{ firstName: string; lastName?: string }>({ ...props });
type Formatter = (input: string) => string;

return (
<div className="component-one" style={{ border: '1px solid black', padding: '1rem' }}>
<h3>Component One</h3>
<p>FirstName: {data.firstName}</p>
<p>LastName: {data.lastName}</p>
</div>
);
};
const ComponentOne: CustomComponent = (props) => {
const [data, setData] = useState<{ firstName: string; lastName?: string }>({ ...props });
const intercept = useServiceInterceptor();
const formatter = intercept<Formatter>(`(${OBJECTCLASS}=x-formatter)`, (input: string) => input.split('').reverse().join(''));

return (
<div className="component-one" style={{ border: '1px solid black', padding: '1rem' }}>
<h3>Component One</h3>
<p>FirstName: {data.firstName}</p>
<p>LastName: {data.lastName}</p>
<p>Decorator test: {formatter('abcd')}</p>
</div>
);
};

const reg = bundleContext.registerService<CustomComponent>(CUSTOM_COMPONENT_INTERFACE_KEY, ComponentOne);
window.setTimeout(() => {
reg.unregister();
const reg = bundleContext.registerService<CustomComponent>(CUSTOM_COMPONENT_INTERFACE_KEY, ComponentOne);
window.setTimeout(() => {
bundleContext.registerService<CustomComponent>(CUSTOM_COMPONENT_INTERFACE_KEY, ComponentOne);
reg.unregister();
bundleContext.registerService<Formatter>('x-formatter', (input) => input.toUpperCase());
window.setTimeout(() => {
bundleContext.registerService<CustomComponent>(CUSTOM_COMPONENT_INTERFACE_KEY, ComponentOne);
}, 2000);
}, 2000);
}, 2000);

// (async () => {
// const componentOneBundle = await bundleContext.installBundle('./component-one.system-manifest.json');
// window.setTimeout(() => {
// pandino.uninstallBundle(componentOneBundle as any);
// window.setTimeout(() => {
// bundleContext.installBundle('./component-one.system-manifest.json');
// }, 2000);
// }, 2000);
// })();
// const componentOneBundle = await bundleContext.installBundle('./component-one.system-manifest.json');
// window.setTimeout(() => {
// pandino.uninstallBundle(componentOneBundle as any);
// window.setTimeout(() => {
// bundleContext.installBundle('./component-one.system-manifest.json');
// }, 2000);
// }, 2000);

// (async () => {
// let someReg = await bundleContext.registerService(SOME_SERVICE_INTERFACE_KEY, new SomeServiceImpl());
// window.setTimeout(async () => {
// someReg.unregister();
// window.setTimeout(async () => {
// someReg = await bundleContext.registerService(SOME_SERVICE_INTERFACE_KEY, new SomeServiceImpl());
// }, 2000);
// }, 2000);
// })();
// let someReg = bundleContext.registerService(SOME_SERVICE_INTERFACE_KEY, new SomeServiceImpl());
// window.setTimeout(() => {
// someReg.unregister();
// window.setTimeout(() => {
// bundleContext.registerService(SOME_SERVICE_INTERFACE_KEY, new SomeServiceImpl());
// }, 2000);
// }, 2000);

root.render(
<PandinoProvider ctx={bundleContext}>
<App />
</PandinoProvider>,
);
root.render(
<PandinoProvider ctx={bundleContext}>
<App />
</PandinoProvider>,
);
})();
36 changes: 36 additions & 0 deletions packages/@pandino/react-hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,40 @@ export const MyComponent: FC = () => {
};
```

### useServiceInterceptor

This hook is intended to be used in scenarios where we would like to intercept / decorate e.g. functions.

The intercepted reference is **NOT** "renewed" if post-registration a more fitting / stronger registration occurs.

This means that e.g. once a component is loaded, the intercepted references will remain the same!

**Intercepting a function:**

```typescript jsx
import { OBJECTCLASS } from '@pandino/pandino-api';
import { useServiceInterceptor } from '@pandino/react-hooks';

type Formatter = (input: string) => string;

export const ComponentOne: CustomComponent = (props) => {
const intercept = useServiceInterceptor();
const formatter = intercept<Formatter>(`(${OBJECTCLASS}=x-formatter)`, (input: string) => input.split('').reverse().join(''));

return (
<p>Decorator test: {formatter('abcd')}</p>
);
};
```

**Defining an interceptor:**

```typescript jsx
bundleContext.registerService<Formatter>('x-formatter', (input) => input.toUpperCase());
```

The `intercept` function also takes a 3rd argument

### useTrackService

This is a simple hook which expects a `filter` parameter and returns a Service or `undefined`.
Expand All @@ -68,6 +102,8 @@ the returned value will be `undefined`.

Developers **MUST** handle the undefined scenarios explicitly.

Service changes trigger the tracker, which means related components will also be triggered!

```typescript jsx
import type { FC } from 'react';
import { OBJECTCLASS } from '@pandino/pandino-api';
Expand Down
2 changes: 1 addition & 1 deletion packages/@pandino/react-hooks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.8.29",
"description": "React helpers and hooks for applications using Pandino",
"module": "./dist/esm/react-hooks.mjs",
"types": "dist/esm/dist/index.d.ts",
"types": "dist/esm/index.d.ts",
"type": "module",
"exports": {
".": {
Expand Down
1 change: 1 addition & 0 deletions packages/@pandino/react-hooks/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './ComponentProxy';
export * from './PandinoContext';
export * from './useServiceInterceptor';
export * from './useTrackComponent';
export * from './useTrackService';
41 changes: 41 additions & 0 deletions packages/@pandino/react-hooks/src/useServiceInterceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect, useRef } from 'react';
import { FRAMEWORK_SERVICE_UTILS, ServiceReference, ServiceUtils } from '@pandino/pandino-api';
import { useBundleContext } from './PandinoContext';

export type InterceptorHook = () => <T>(filter: string, target: T) => T;

export const useServiceInterceptor: InterceptorHook = <T>() => {
const { bundleContext } = useBundleContext();
const serviceUtilsRef = bundleContext.getServiceReference<ServiceUtils>(FRAMEWORK_SERVICE_UTILS);
const serviceUtils = bundleContext.getService(serviceUtilsRef);
const refsUsed = useRef<Set<ServiceReference<any>>>(new Set<ServiceReference<any>>());

useEffect(() => {
// cleanup refs, regs
return () => {
try {
if (serviceUtilsRef) {
bundleContext.ungetService(serviceUtilsRef);
}
for (const ref of refsUsed.current) {
bundleContext.ungetService(ref);
}
} catch (e) {
console.error(e);
}
};
}, []);

return (filter: string, target: T) => {
const refs = bundleContext.getServiceReferences(undefined, filter);
const ref = serviceUtils.getBestServiceReference(refs);
for (const refI of refs) {
refsUsed.current.add(refI);
}
if (ref) {
refsUsed.current.add(ref);
return bundleContext.getService(ref);
}
return target;
};
};

0 comments on commit b729126

Please sign in to comment.