Initial commit
This commit is contained in:
12
packages/shared/README.md
Normal file
12
packages/shared/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Shared Package
|
||||
|
||||
This package contains code shared with other packages.
|
||||
To use the code in the package, you need to add the following to the package.json file.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@extension/shared": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
3
packages/shared/const.ts
Normal file
3
packages/shared/const.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const PROJECT_URL_OBJECT = {
|
||||
url: 'https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite',
|
||||
} as const;
|
||||
4
packages/shared/index.mts
Normal file
4
packages/shared/index.mts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './lib/hooks/index.js';
|
||||
export * from './lib/hoc/index.js';
|
||||
export * from './lib/utils/index.js';
|
||||
export * from './const.js';
|
||||
2
packages/shared/lib/hoc/index.ts
Normal file
2
packages/shared/lib/hoc/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { withSuspense } from './with-suspense.js';
|
||||
export { withErrorBoundary } from './with-error-boundary.js';
|
||||
15
packages/shared/lib/hoc/with-error-boundary.tsx
Normal file
15
packages/shared/lib/hoc/with-error-boundary.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import type { ComponentType } from 'react';
|
||||
import type { FallbackProps } from 'react-error-boundary';
|
||||
|
||||
export const withErrorBoundary = <T extends Record<string, unknown>>(
|
||||
Component: ComponentType<T>,
|
||||
FallbackComponent: ComponentType<FallbackProps>,
|
||||
) =>
|
||||
function WithErrorBoundary(props: T) {
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={FallbackComponent}>
|
||||
<Component {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
10
packages/shared/lib/hoc/with-suspense.tsx
Normal file
10
packages/shared/lib/hoc/with-suspense.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Suspense } from 'react';
|
||||
import type { ComponentType, ReactElement } from 'react';
|
||||
|
||||
export const withSuspense =
|
||||
<T extends Record<string, unknown>>(Component: ComponentType<T>, SuspenseComponent: ReactElement) =>
|
||||
(props: T) => (
|
||||
<Suspense fallback={SuspenseComponent}>
|
||||
<Component {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
1
packages/shared/lib/hooks/index.ts
Normal file
1
packages/shared/lib/hooks/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './use-storage.js';
|
||||
56
packages/shared/lib/hooks/use-storage.tsx
Normal file
56
packages/shared/lib/hooks/use-storage.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useRef, useSyncExternalStore } from 'react';
|
||||
import type { BaseStorageType } from '@extension/storage';
|
||||
|
||||
type WrappedPromise = ReturnType<typeof wrapPromise>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const storageMap: Map<BaseStorageType<any>, WrappedPromise> = new Map();
|
||||
|
||||
const wrapPromise = <R,>(promise: Promise<R>) => {
|
||||
let status = 'pending';
|
||||
let result: R;
|
||||
|
||||
const suspender = promise.then(
|
||||
r => {
|
||||
status = 'success';
|
||||
result = r;
|
||||
},
|
||||
e => {
|
||||
status = 'error';
|
||||
result = e;
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
read() {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
throw suspender;
|
||||
case 'error':
|
||||
throw result;
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const useStorage = <
|
||||
Storage extends BaseStorageType<Data>,
|
||||
Data = Storage extends BaseStorageType<infer Data> ? Data : unknown,
|
||||
>(
|
||||
storage: Storage,
|
||||
) => {
|
||||
const initializedRef = useRef(false);
|
||||
const _data = useSyncExternalStore<Data | null>(storage.subscribe, storage.getSnapshot);
|
||||
|
||||
if (!storageMap.has(storage)) {
|
||||
storageMap.set(storage, wrapPromise(storage.get()));
|
||||
}
|
||||
|
||||
if (_data || initializedRef.current) {
|
||||
storageMap.set(storage, { read: () => _data });
|
||||
initializedRef.current = true;
|
||||
}
|
||||
|
||||
return (_data ?? storageMap.get(storage)!.read()) as Exclude<Data, PromiseLike<unknown>>;
|
||||
};
|
||||
27
packages/shared/lib/utils/colorful-logger.ts
Normal file
27
packages/shared/lib/utils/colorful-logger.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { COLORS } from './const.js';
|
||||
import type { ColorType, ValueOf } from './types.js';
|
||||
|
||||
export const colorfulLog = (message: string, type: ColorType) => {
|
||||
let color: ValueOf<typeof COLORS>;
|
||||
|
||||
switch (type) {
|
||||
case 'success':
|
||||
color = COLORS.FgGreen;
|
||||
break;
|
||||
case 'info':
|
||||
color = COLORS.FgBlue;
|
||||
break;
|
||||
case 'error':
|
||||
color = COLORS.FgRed;
|
||||
break;
|
||||
case 'warning':
|
||||
color = COLORS.FgYellow;
|
||||
break;
|
||||
default:
|
||||
color = COLORS[type];
|
||||
break;
|
||||
}
|
||||
|
||||
console.info(color, message);
|
||||
console.info(COLORS['Reset']);
|
||||
};
|
||||
25
packages/shared/lib/utils/const.ts
Normal file
25
packages/shared/lib/utils/const.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export const COLORS = {
|
||||
Reset: '\x1b[0m',
|
||||
Bright: '\x1b[1m',
|
||||
Dim: '\x1b[2m',
|
||||
Underscore: '\x1b[4m',
|
||||
Blink: '\x1b[5m',
|
||||
Reverse: '\x1b[7m',
|
||||
Hidden: '\x1b[8m',
|
||||
FgBlack: '\x1b[30m',
|
||||
FgRed: '\x1b[31m',
|
||||
FgGreen: '\x1b[32m',
|
||||
FgYellow: '\x1b[33m',
|
||||
FgBlue: '\x1b[34m',
|
||||
FgMagenta: '\x1b[35m',
|
||||
FgCyan: '\x1b[36m',
|
||||
FgWhite: '\x1b[37m',
|
||||
BgBlack: '\x1b[40m',
|
||||
BgRed: '\x1b[41m',
|
||||
BgGreen: '\x1b[42m',
|
||||
BgYellow: '\x1b[43m',
|
||||
BgBlue: '\x1b[44m',
|
||||
BgMagenta: '\x1b[45m',
|
||||
BgCyan: '\x1b[46m',
|
||||
BgWhite: '\x1b[47m',
|
||||
} as const;
|
||||
8
packages/shared/lib/utils/helpers.ts
Normal file
8
packages/shared/lib/utils/helpers.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { ExcludeValuesFromBaseArrayType } from './types.js';
|
||||
|
||||
export const excludeValuesFromBaseArray = <B extends string[], E extends (string | number)[]>(
|
||||
baseArray: B,
|
||||
excludeArray: E,
|
||||
) => baseArray.filter(value => !excludeArray.includes(value)) as ExcludeValuesFromBaseArrayType<B, E>;
|
||||
|
||||
export const sleep = async (time: number) => new Promise(r => setTimeout(r, time));
|
||||
4
packages/shared/lib/utils/index.ts
Normal file
4
packages/shared/lib/utils/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './helpers.js';
|
||||
export * from './colorful-logger.js';
|
||||
export * from './init-app-with-shadow.js';
|
||||
export type * from './types.js';
|
||||
34
packages/shared/lib/utils/init-app-with-shadow.ts
Normal file
34
packages/shared/lib/utils/init-app-with-shadow.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
export const initAppWithShadow = ({ id, app, inlineCss }: { id: string; inlineCss: string; app: ReactElement }) => {
|
||||
const root = document.createElement('div');
|
||||
root.id = id;
|
||||
|
||||
document.body.append(root);
|
||||
|
||||
const rootIntoShadow = document.createElement('div');
|
||||
rootIntoShadow.id = `shadow-root-${id}`;
|
||||
|
||||
const shadowRoot = root.attachShadow({ mode: 'open' });
|
||||
|
||||
if (navigator.userAgent.includes('Firefox')) {
|
||||
/**
|
||||
* In the firefox environment, adoptedStyleSheets cannot be used due to the bug
|
||||
* @url https://bugzilla.mozilla.org/show_bug.cgi?id=1770592
|
||||
*
|
||||
* Injecting styles into the document, this may cause style conflicts with the host page
|
||||
*/
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.innerHTML = inlineCss;
|
||||
shadowRoot.appendChild(styleElement);
|
||||
} else {
|
||||
/** Inject styles into shadow dom */
|
||||
const globalStyleSheet = new CSSStyleSheet();
|
||||
globalStyleSheet.replaceSync(inlineCss);
|
||||
shadowRoot.adoptedStyleSheets = [globalStyleSheet];
|
||||
}
|
||||
|
||||
shadowRoot.appendChild(rootIntoShadow);
|
||||
createRoot(rootIntoShadow).render(app);
|
||||
};
|
||||
10
packages/shared/lib/utils/types.ts
Normal file
10
packages/shared/lib/utils/types.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { COLORS } from './const.js';
|
||||
import type { TupleToUnion } from 'type-fest';
|
||||
|
||||
export type * from 'type-fest';
|
||||
export type ColorType = 'success' | 'info' | 'error' | 'warning' | keyof typeof COLORS;
|
||||
export type ExcludeValuesFromBaseArrayType<B extends string[], E extends (string | number)[]> = Exclude<
|
||||
TupleToUnion<B>,
|
||||
TupleToUnion<E>
|
||||
>[];
|
||||
export type ManifestType = chrome.runtime.ManifestV3;
|
||||
33
packages/shared/package.json
Normal file
33
packages/shared/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@extension/shared",
|
||||
"version": "0.5.0",
|
||||
"description": "chrome extension - shared code",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"types": "index.mts",
|
||||
"main": "dist/index.mjs",
|
||||
"scripts": {
|
||||
"clean:bundle": "rimraf dist",
|
||||
"clean:node_modules": "pnpx rimraf node_modules",
|
||||
"clean:turbo": "rimraf .turbo",
|
||||
"clean": "pnpm clean:bundle && pnpm clean:node_modules && pnpm clean:turbo",
|
||||
"ready": "tsc -b",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"format": "prettier . --write --ignore-path ../../.prettierignore",
|
||||
"type-check": "tsc --noEmit",
|
||||
"postinstall": "pnpm ready"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-error-boundary": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@extension/storage": "workspace:*",
|
||||
"@extension/tsconfig": "workspace:*",
|
||||
"type-fest": "^4.41.0"
|
||||
}
|
||||
}
|
||||
8
packages/shared/tsconfig.json
Normal file
8
packages/shared/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@extension/tsconfig/module",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["index.mts", "lib"]
|
||||
}
|
||||
Reference in New Issue
Block a user