@common-stack/generate-plugin 5.0.2-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +32 -0
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/generators.json +9 -0
- package/jest.config.ts +10 -0
- package/lib/generators/add-entries/entries/antui/entry.client.tsx.template +100 -0
- package/lib/generators/add-entries/entries/antui/entry.server.tsx.template +254 -0
- package/lib/generators/add-entries/entries/antui/root.tsx.template +107 -0
- package/lib/generators/add-entries/entries/chakraui/context.tsx.template +20 -0
- package/lib/generators/add-entries/entries/chakraui/entry.client.tsx.template +105 -0
- package/lib/generators/add-entries/entries/chakraui/entry.server.tsx.template +256 -0
- package/lib/generators/add-entries/entries/chakraui/root.tsx.template +97 -0
- package/lib/generators/add-entries/files/common/AntStyles.tsx.template +8 -0
- package/lib/generators/add-entries/files/common/createEmotionCache.ts.template +7 -0
- package/lib/generators/add-entries/files/common/index.ts.template +3 -0
- package/lib/generators/add-entries/files/common/utils.ts.template +16 -0
- package/lib/generators/add-entries/generator.cjs +55 -0
- package/lib/generators/add-entries/generator.cjs.map +1 -0
- package/lib/generators/add-entries/generator.d.ts +4 -0
- package/lib/generators/add-entries/generator.mjs +55 -0
- package/lib/generators/add-entries/generator.mjs.map +1 -0
- package/lib/generators/add-entries/generator.spec.d.ts +1 -0
- package/lib/generators/add-entries/schema.json +13 -0
- package/lib/index.cjs +1 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.mjs +1 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +28 -0
- package/project.json +7 -0
- package/rollup.config.mjs +65 -0
- package/src/generators/add-entries/entries/antui/entry.client.tsx.template +100 -0
- package/src/generators/add-entries/entries/antui/entry.server.tsx.template +254 -0
- package/src/generators/add-entries/entries/antui/root.tsx.template +107 -0
- package/src/generators/add-entries/entries/chakraui/context.tsx.template +20 -0
- package/src/generators/add-entries/entries/chakraui/entry.client.tsx.template +105 -0
- package/src/generators/add-entries/entries/chakraui/entry.server.tsx.template +256 -0
- package/src/generators/add-entries/entries/chakraui/root.tsx.template +97 -0
- package/src/generators/add-entries/files/common/AntStyles.tsx.template +8 -0
- package/src/generators/add-entries/files/common/createEmotionCache.ts.template +7 -0
- package/src/generators/add-entries/files/common/index.ts.template +3 -0
- package/src/generators/add-entries/files/common/utils.ts.template +16 -0
- package/src/generators/add-entries/generator.spec.ts +20 -0
- package/src/generators/add-entries/generator.ts +66 -0
- package/src/generators/add-entries/schema.d.ts +3 -0
- package/src/generators/add-entries/schema.json +13 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, { useState, startTransition, StrictMode } from 'react';
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
import { RemixBrowser } from '@remix-run/react';
|
|
4
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
5
|
+
import { ApolloProvider } from '@apollo/client/index.js';
|
|
6
|
+
import { SlotFillProvider, removeUniversalPortals } from '@common-stack/components-pro';
|
|
7
|
+
import { InversifyProvider, PluginArea } from '@common-stack/client-react';
|
|
8
|
+
import { Provider as ReduxProvider } from 'react-redux';
|
|
9
|
+
import { PersistGate } from 'redux-persist/integration/react';
|
|
10
|
+
import { persistStore } from 'redux-persist';
|
|
11
|
+
import { CacheProvider } from '@emotion/react';
|
|
12
|
+
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
import createEmotionCache, { defaultCache } from '@app/frontend-stack-react/entries/common/createEmotionCache';
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
import { createReduxStore } from '@app/frontend-stack-react/config/redux-config.js';
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
import { createClientContainer } from '@app/frontend-stack-react/config/client.service';
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
import clientModules from '@app/frontend-stack-react/modules.js';
|
|
21
|
+
|
|
22
|
+
import i18next from 'i18next';
|
|
23
|
+
import { I18nextProvider, initReactI18next } from 'react-i18next';
|
|
24
|
+
import LanguageDetector from 'i18next-browser-languagedetector';
|
|
25
|
+
import Backend from 'i18next-http-backend';
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
import { getInitialNamespaces } from 'remix-i18next/client';
|
|
28
|
+
import config from '@app/cde-webconfig.json';
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
import { ClientStyleContext } from '@app/frontend-stack-react/entries/chakraui/context.js';
|
|
31
|
+
|
|
32
|
+
const { apolloClient: client, container, serviceFunc } = createClientContainer();
|
|
33
|
+
const { store } = createReduxStore(client, serviceFunc(), container);
|
|
34
|
+
const persistor = persistStore(store);
|
|
35
|
+
|
|
36
|
+
(window as any).__remixStore = store;
|
|
37
|
+
removeUniversalPortals((window as any).__SLOT_FILLS__ || []);
|
|
38
|
+
|
|
39
|
+
interface ClientCacheProviderProps {
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ClientCacheProvider({ children }: ClientCacheProviderProps) {
|
|
44
|
+
const [cache, setCache] = useState(defaultCache);
|
|
45
|
+
|
|
46
|
+
function reset() {
|
|
47
|
+
setCache(createEmotionCache());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<ClientStyleContext.Provider value={{ reset }}>
|
|
52
|
+
<CacheProvider value={cache}>{children}</CacheProvider>
|
|
53
|
+
</ClientStyleContext.Provider>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function hydrate() {
|
|
58
|
+
if (!i18next.isInitialized && config.i18n.enabled) {
|
|
59
|
+
await i18next
|
|
60
|
+
.use(initReactI18next)
|
|
61
|
+
.use(LanguageDetector)
|
|
62
|
+
.use(Backend)
|
|
63
|
+
.init({
|
|
64
|
+
fallbackLng: config.i18n.fallbackLng,
|
|
65
|
+
defaultNS: config.i18n.defaultNS,
|
|
66
|
+
react: config.i18n.react,
|
|
67
|
+
supportedLngs: config.i18n.supportedLngs,
|
|
68
|
+
backend: config.i18n.backend,
|
|
69
|
+
ns: getInitialNamespaces(),
|
|
70
|
+
detection: {
|
|
71
|
+
order: ['htmlTag'],
|
|
72
|
+
caches: [],
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
hydrateRoot(
|
|
77
|
+
document.getElementById('root')!,
|
|
78
|
+
<StrictMode>
|
|
79
|
+
<I18nextProvider i18n={i18next}>
|
|
80
|
+
<ClientCacheProvider>
|
|
81
|
+
<ApolloProvider client={client}>
|
|
82
|
+
<ReduxProvider store={store}>
|
|
83
|
+
<SlotFillProvider>
|
|
84
|
+
<InversifyProvider container={container} modules={clientModules}>
|
|
85
|
+
<PersistGate loading={null} persistor={persistor}>
|
|
86
|
+
{() => <RemixBrowser />}
|
|
87
|
+
</PersistGate>
|
|
88
|
+
</InversifyProvider>
|
|
89
|
+
</SlotFillProvider>
|
|
90
|
+
</ReduxProvider>
|
|
91
|
+
</ApolloProvider>
|
|
92
|
+
</ClientCacheProvider>
|
|
93
|
+
</I18nextProvider>
|
|
94
|
+
</StrictMode>,
|
|
95
|
+
);
|
|
96
|
+
// });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof requestIdleCallback === 'function') {
|
|
100
|
+
requestIdleCallback(hydrate);
|
|
101
|
+
} else {
|
|
102
|
+
// Safari doesn't support requestIdleCallback
|
|
103
|
+
// https://caniuse.com/requestidlecallback
|
|
104
|
+
setTimeout(hydrate, 1);
|
|
105
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* By default, Remix will handle generating the HTTP Response for you.
|
|
3
|
+
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
|
|
4
|
+
* For more information, see https://remix.run/file-conventions/entry.server
|
|
5
|
+
*/
|
|
6
|
+
// @ts-nocheck
|
|
7
|
+
global.__CLIENT__ = false;
|
|
8
|
+
global.__SERVER__ = true;
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { PassThrough, Transform } from 'node:stream';
|
|
11
|
+
import type { AppLoadContext, EntryContext } from '@remix-run/node';
|
|
12
|
+
import { createReadableStreamFromReadable } from '@remix-run/node';
|
|
13
|
+
import { RemixServer } from '@remix-run/react';
|
|
14
|
+
import { isbot } from 'isbot';
|
|
15
|
+
import { ApolloProvider } from '@apollo/client/index.js';
|
|
16
|
+
import { SlotFillProvider } from '@common-stack/components-pro';
|
|
17
|
+
import { InversifyProvider, PluginArea } from '@common-stack/client-react';
|
|
18
|
+
import { renderToPipeableStream, renderToString } from 'react-dom/server';
|
|
19
|
+
import { Provider as ReduxProvider } from 'react-redux';
|
|
20
|
+
import { LOCATION_CHANGE } from '@common-stack/remix-router-redux';
|
|
21
|
+
import serialize from 'serialize-javascript';
|
|
22
|
+
import { CacheProvider } from '@emotion/react';
|
|
23
|
+
import createEmotionServer from '@emotion/server/create-instance';
|
|
24
|
+
import Backend from 'i18next-fs-backend';
|
|
25
|
+
import { renderToString } from 'react-dom/server';
|
|
26
|
+
import { renderHeadToString } from 'remix-island';
|
|
27
|
+
import publicEnv from '@src/config/public-config';
|
|
28
|
+
import { I18nextProvider, initReactI18next } from 'react-i18next';
|
|
29
|
+
import { createInstance } from 'i18next';
|
|
30
|
+
import { resolve } from 'node:path';
|
|
31
|
+
// import { renderStylesToNodeStream } from '@emotion/server';
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
import { defaultCache } from '@app/frontend-stack-react/entries/common/createEmotionCache.js';
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
import config from '@app/cde-webconfig.json';
|
|
36
|
+
|
|
37
|
+
import { Head } from './root';
|
|
38
|
+
import type { IAppLoadContext } from '@common-stack/client-core';
|
|
39
|
+
import { ServerStyleContext } from '@app/frontend-stack-react/entries/chakraui/context.js';
|
|
40
|
+
import { i18nextInstance as i18next } from '@app/frontend-stack-react/i18n-localization/i18next.server.js';
|
|
41
|
+
const { extractCriticalToChunks } = createEmotionServer(defaultCache);
|
|
42
|
+
|
|
43
|
+
const ABORT_DELAY = 5000;
|
|
44
|
+
const COMMON_HEAD = `
|
|
45
|
+
<meta charset="utf-8" />
|
|
46
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
export default function handleRequest(
|
|
50
|
+
request: Request,
|
|
51
|
+
responseStatusCode: number,
|
|
52
|
+
responseHeaders: Headers,
|
|
53
|
+
remixContext: EntryContext,
|
|
54
|
+
// This is ignored so we can keep it in the template for visibility. Feel
|
|
55
|
+
// free to delete this parameter in your app if you're not using it!
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
57
|
+
loadContext: AppLoadContext,
|
|
58
|
+
) {
|
|
59
|
+
return isbot(request.headers.get('user-agent') || '')
|
|
60
|
+
? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext, loadContext)
|
|
61
|
+
: handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext, loadContext);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleBotRequest(
|
|
65
|
+
request: Request,
|
|
66
|
+
responseStatusCode: number,
|
|
67
|
+
responseHeaders: Headers,
|
|
68
|
+
remixContext: EntryContext,
|
|
69
|
+
loadContext: IAppLoadContext,
|
|
70
|
+
) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
let shellRendered = false;
|
|
73
|
+
const { pipe, abort } = renderToPipeableStream(
|
|
74
|
+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
|
|
75
|
+
{
|
|
76
|
+
onAllReady() {
|
|
77
|
+
shellRendered = true;
|
|
78
|
+
|
|
79
|
+
const head = renderHeadToString({ request, remixContext, Head });
|
|
80
|
+
const body = new PassThrough();
|
|
81
|
+
|
|
82
|
+
responseHeaders.set('Content-Type', 'text/html');
|
|
83
|
+
const stream = createReadableStreamFromReadable(body);
|
|
84
|
+
resolve(
|
|
85
|
+
new Response(stream, {
|
|
86
|
+
headers: responseHeaders,
|
|
87
|
+
status: responseStatusCode,
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
body.write(`<!DOCTYPE html><html><head>${COMMON_HEAD}${head}</head><body><div id="root">`);
|
|
91
|
+
pipe(body);
|
|
92
|
+
body.write(`</div></body></html>`);
|
|
93
|
+
},
|
|
94
|
+
onShellError(error: unknown) {
|
|
95
|
+
reject(error);
|
|
96
|
+
},
|
|
97
|
+
onError(error: unknown) {
|
|
98
|
+
responseStatusCode = 500;
|
|
99
|
+
// Log streaming rendering errors from inside the shell. Don't log
|
|
100
|
+
// errors encountered during initial shell rendering since they'll
|
|
101
|
+
// reject and get logged in handleDocumentRequest.
|
|
102
|
+
if (shellRendered) {
|
|
103
|
+
console.error(error);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
setTimeout(abort, ABORT_DELAY);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function handleBrowserRequest(
|
|
114
|
+
request: Request,
|
|
115
|
+
responseStatusCode: number,
|
|
116
|
+
responseHeaders: Headers,
|
|
117
|
+
remixContext: EntryContext,
|
|
118
|
+
loadContext: IAppLoadContext,
|
|
119
|
+
) {
|
|
120
|
+
const instance = createInstance();
|
|
121
|
+
|
|
122
|
+
// Then we could detect locale from the request
|
|
123
|
+
const lng = await i18next.getLocale(request);
|
|
124
|
+
// And here we detect what namespaces the routes about to render want to use
|
|
125
|
+
const ns = i18next.getRouteNamespaces(remixContext);
|
|
126
|
+
const slotFillContext = { fills: {} };
|
|
127
|
+
const {
|
|
128
|
+
modules: clientModules,
|
|
129
|
+
container,
|
|
130
|
+
apolloClient: client,
|
|
131
|
+
store,
|
|
132
|
+
}: IAppLoadContext = loadContext;
|
|
133
|
+
|
|
134
|
+
// First, we create a new instance of i18next so every request will have a
|
|
135
|
+
// completely unique instance and not share any state.
|
|
136
|
+
if (config.i18n.enabled) {
|
|
137
|
+
await instance
|
|
138
|
+
.use(initReactI18next) // Tell our instance to use react-i18next
|
|
139
|
+
.use(Backend) // Setup our backend.init({
|
|
140
|
+
.init({
|
|
141
|
+
fallbackLng: config.i18n.fallbackLng,
|
|
142
|
+
defaultNS: config.i18n.defaultNS,
|
|
143
|
+
react: config.i18n.react,
|
|
144
|
+
supportedLngs: config.i18n.supportedLngs,
|
|
145
|
+
lng, // The locale we detected above
|
|
146
|
+
ns, // The namespaces the routes about to render want to use
|
|
147
|
+
backend: {
|
|
148
|
+
loadPath: resolve(config.i18n.backend.loadServerPath),
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const html = renderToString(
|
|
154
|
+
<I18nextProvider i18n={instance}>
|
|
155
|
+
<CacheProvider value={defaultCache}>
|
|
156
|
+
<ApolloProvider client={client}>
|
|
157
|
+
<ReduxProvider store={store}>
|
|
158
|
+
<SlotFillProvider context={slotFillContext}>
|
|
159
|
+
<InversifyProvider container={container} modules={clientModules}>
|
|
160
|
+
<RemixServer context={remixContext} url={request.url} />
|
|
161
|
+
</InversifyProvider>
|
|
162
|
+
</SlotFillProvider>
|
|
163
|
+
</ReduxProvider>
|
|
164
|
+
</ApolloProvider>
|
|
165
|
+
</CacheProvider>
|
|
166
|
+
</I18nextProvider>,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const chunks = extractCriticalToChunks(html);
|
|
170
|
+
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
let shellRendered = false;
|
|
173
|
+
|
|
174
|
+
const { pathname, search, hash } = new URL(request.url);
|
|
175
|
+
store.dispatch({
|
|
176
|
+
type: LOCATION_CHANGE,
|
|
177
|
+
payload: { location: { pathname, search, hash }, action: 'POP' },
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const { pipe, abort } = renderToPipeableStream(
|
|
181
|
+
(
|
|
182
|
+
<I18nextProvider i18n={instance}>
|
|
183
|
+
<ServerStyleContext.Provider value={chunks.styles}>
|
|
184
|
+
<CacheProvider value={defaultCache}>
|
|
185
|
+
<ApolloProvider client={client}>
|
|
186
|
+
<ReduxProvider store={store}>
|
|
187
|
+
<SlotFillProvider context={slotFillContext}>
|
|
188
|
+
<InversifyProvider container={container} modules={clientModules}>
|
|
189
|
+
<RemixServer
|
|
190
|
+
context={remixContext}
|
|
191
|
+
url={request.url}
|
|
192
|
+
abortDelay={ABORT_DELAY}
|
|
193
|
+
/>
|
|
194
|
+
</InversifyProvider>
|
|
195
|
+
</SlotFillProvider>
|
|
196
|
+
</ReduxProvider>
|
|
197
|
+
</ApolloProvider>
|
|
198
|
+
</CacheProvider>
|
|
199
|
+
</ServerStyleContext.Provider>
|
|
200
|
+
</I18nextProvider>
|
|
201
|
+
) as any,
|
|
202
|
+
{
|
|
203
|
+
onShellReady() {
|
|
204
|
+
shellRendered = true;
|
|
205
|
+
const head = renderHeadToString({ request, remixContext, Head });
|
|
206
|
+
const body = new PassThrough();
|
|
207
|
+
const stream = createReadableStreamFromReadable(body);
|
|
208
|
+
const apolloState = { ...client.extract() };
|
|
209
|
+
const reduxState = { ...store.getState() };
|
|
210
|
+
const fills = Object.keys(slotFillContext.fills);
|
|
211
|
+
// const transform = new ConstantsTransform(fills, apolloState, reduxState);
|
|
212
|
+
|
|
213
|
+
let customHead = `<script>window.__ENV__=${JSON.stringify(publicEnv)}</script>`;
|
|
214
|
+
customHead += `<script>window.__APOLLO_STATE__=${serialize(apolloState, {
|
|
215
|
+
isJSON: true,
|
|
216
|
+
})}</script>`;
|
|
217
|
+
customHead += `<script>window.__PRELOADED_STATE__=${serialize(reduxState, {
|
|
218
|
+
isJSON: true,
|
|
219
|
+
})}</script>`;
|
|
220
|
+
customHead += `<script>window.__SLOT_FILLS__=${serialize(fills, { isJSON: true })}</script>`;
|
|
221
|
+
customHead += `<script>if (global === undefined) { var global = window; }</script>`;
|
|
222
|
+
|
|
223
|
+
responseHeaders.set('Content-Type', 'text/html');
|
|
224
|
+
|
|
225
|
+
resolve(
|
|
226
|
+
new Response(stream, {
|
|
227
|
+
headers: responseHeaders,
|
|
228
|
+
status: responseStatusCode,
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
body.write(
|
|
232
|
+
`<!DOCTYPE html><html lng=${lng}><head>${COMMON_HEAD}${customHead}${head}</head><body><div id="root">`,
|
|
233
|
+
);
|
|
234
|
+
pipe(body);
|
|
235
|
+
body.write(`</div></body></html>`);
|
|
236
|
+
// pipe(transform).pipe(renderStylesToNodeStream()).pipe(body);
|
|
237
|
+
},
|
|
238
|
+
onShellError(error: unknown) {
|
|
239
|
+
reject(error);
|
|
240
|
+
},
|
|
241
|
+
onError(error: unknown) {
|
|
242
|
+
responseStatusCode = 500;
|
|
243
|
+
// Log streaming rendering errors from inside the shell. Don't log
|
|
244
|
+
// errors encountered during initial shell rendering since they'll
|
|
245
|
+
// reject and get logged in handleDocumentRequest.
|
|
246
|
+
if (shellRendered) {
|
|
247
|
+
console.error(error);
|
|
248
|
+
}
|
|
249
|
+
reject(error);
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
setTimeout(abort, ABORT_DELAY);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData } from '@remix-run/react';
|
|
5
|
+
import { json } from '@remix-run/node';
|
|
6
|
+
import { PluginArea } from '@common-stack/client-react';
|
|
7
|
+
import { subscribeReduxRouter } from '@common-stack/remix-router-redux';
|
|
8
|
+
|
|
9
|
+
import { ApplicationErrorHandler } from '@admin-layout/chakra-ui';
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
import clientModules, { plugins } from '@app/frontend-stack-react/modules.js';
|
|
12
|
+
import { createHead } from 'remix-island';
|
|
13
|
+
import { useContext } from 'react';
|
|
14
|
+
import { ServerStyleContext, ClientStyleContext } from './context';
|
|
15
|
+
import { withEmotionCache } from '@emotion/react';
|
|
16
|
+
import { useChangeLanguage } from 'remix-i18next/react';
|
|
17
|
+
import { i18nextInstance as i18next } from '@app/frontend-stack-react/i18n-localization/i18next.server.js';
|
|
18
|
+
import { ErrorBoundary } from '@app/frontend-stack-react/entries/chakraui/components/ErrorBoundary.js';
|
|
19
|
+
|
|
20
|
+
interface DocumentProps {
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Head = createHead(() => (
|
|
25
|
+
<>
|
|
26
|
+
<Meta />
|
|
27
|
+
<Links />
|
|
28
|
+
</>
|
|
29
|
+
));
|
|
30
|
+
|
|
31
|
+
export const Document = withEmotionCache(({ children }: DocumentProps, emotionCache) => {
|
|
32
|
+
const serverStyleData = useContext(ServerStyleContext);
|
|
33
|
+
const clientStyleData = useContext(ClientStyleContext);
|
|
34
|
+
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
// re-link sheet container
|
|
37
|
+
emotionCache.sheet.container = document.head;
|
|
38
|
+
// re-inject tags
|
|
39
|
+
const tags = emotionCache.sheet.tags;
|
|
40
|
+
emotionCache.sheet.flush();
|
|
41
|
+
tags.forEach((tag) => {
|
|
42
|
+
(emotionCache.sheet as any)._insertTag(tag);
|
|
43
|
+
});
|
|
44
|
+
// reset cache to reapply global styles
|
|
45
|
+
clientStyleData?.reset();
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
<Head />
|
|
51
|
+
{serverStyleData?.map(({ key, ids, css }) => (
|
|
52
|
+
<style key={key} data-emotion={`${key} ${ids.join(' ')}`} dangerouslySetInnerHTML={{ __html: css }} />
|
|
53
|
+
))}
|
|
54
|
+
<script
|
|
55
|
+
src="https://cdnjs.cloudflare.com/ajax/libs/reflect-metadata/0.1.13/Reflect.min.js"
|
|
56
|
+
integrity="sha512-jvbPH2TH5BSZumEfOJZn9IV+5bSwwN+qG4dvthYe3KCGC3/9HmxZ4phADbt9Pfcp+XSyyfc2vGZ/RMsSUZ9tbQ=="
|
|
57
|
+
crossOrigin="anonymous"
|
|
58
|
+
referrerPolicy="no-referrer"
|
|
59
|
+
></script>
|
|
60
|
+
<PluginArea />
|
|
61
|
+
{children}
|
|
62
|
+
<ScrollRestoration />
|
|
63
|
+
<Scripts />
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export let loader = async ({ request }) => {
|
|
69
|
+
let locale = await i18next.getLocale(request);
|
|
70
|
+
return json({ locale });
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export let handle = {
|
|
74
|
+
i18n: 'common',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export function shouldRevalidate(params: any) {
|
|
78
|
+
return params.defaultShouldRevalidate && params.currentUrl.pathname !== params.nextUrl.pathname;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default function App() {
|
|
82
|
+
let { locale } = useLoaderData();
|
|
83
|
+
|
|
84
|
+
useChangeLanguage(locale);
|
|
85
|
+
|
|
86
|
+
React.useEffect(() => {
|
|
87
|
+
subscribeReduxRouter({ store: window.__remixStore, router: window.__remixRouter } as any);
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<ApplicationErrorHandler plugins={plugins}>
|
|
92
|
+
<Document>{clientModules.getWrappedRoot(<Outlet />)}</Document>
|
|
93
|
+
</ApplicationErrorHandler>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { ErrorBoundary };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createCache as createAntdCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
|
|
3
|
+
|
|
4
|
+
const antdCache = createAntdCache();
|
|
5
|
+
export const styleSheet = extractStyle(antdCache);
|
|
6
|
+
export const AntSytles = ({ children }: { children: React.ReactNode }) => (
|
|
7
|
+
<StyleProvider cache={antdCache}>{children}</StyleProvider>
|
|
8
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isEmpty } from 'lodash-es';
|
|
2
|
+
|
|
3
|
+
export function jsonToString(json) {
|
|
4
|
+
if (isEmpty(json)) {
|
|
5
|
+
return '';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (Array.isArray(json)) {
|
|
9
|
+
let arr = json.map((a) => jsonToString(a));
|
|
10
|
+
return arr.join(' ');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const str = JSON.stringify(json);
|
|
14
|
+
const arr = str.split(',');
|
|
15
|
+
return Array.isArray(arr) ? arr.join(' ') : str;
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
2
|
+
import { Tree, readProjectConfiguration } from '@nx/devkit';
|
|
3
|
+
|
|
4
|
+
import { addEntriesGenerator } from './generator';
|
|
5
|
+
import { AddEntriesGeneratorSchema } from './schema';
|
|
6
|
+
|
|
7
|
+
describe('add-entries generator', () => {
|
|
8
|
+
let tree: Tree;
|
|
9
|
+
const options: AddEntriesGeneratorSchema = { projectPath: '/' };
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
tree = createTreeWithEmptyWorkspace();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should run successfully', async () => {
|
|
16
|
+
await addEntriesGenerator(tree, options);
|
|
17
|
+
const config = readProjectConfiguration(tree, 'test');
|
|
18
|
+
expect(config).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { addProjectConfiguration, formatFiles, generateFiles, Tree } from '@nx/devkit';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { AddEntriesGeneratorSchema } from './schema';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const loadJSON = (path) => {
|
|
7
|
+
const content = fs.readFileSync(path);
|
|
8
|
+
return JSON.parse(content as any);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const getFramework = async (projectRoot) => {
|
|
12
|
+
// const mergeConfigPath = path.join(projectRoot, 'tools', 'mergeConfig.js');
|
|
13
|
+
// const webConfigPath = path.join(projectRoot, 'app', 'cde-webconfig.json');
|
|
14
|
+
const configPath = `${projectRoot}/config.json`;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const json = loadJSON(configPath);
|
|
18
|
+
return json?.uiFramework;
|
|
19
|
+
} catch(err) {
|
|
20
|
+
console.warn('Error while getting UI framework', err.message);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const resetProject = (rootDir: string) => {
|
|
26
|
+
if (fs.existsSync(`${rootDir}/project.json`)) {
|
|
27
|
+
fs.unlinkSync(`${rootDir}/project.json`);
|
|
28
|
+
}
|
|
29
|
+
if (fs.existsSync(`${rootDir}/src`)) {
|
|
30
|
+
fs.rmSync(`${rootDir}/src`, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function addEntriesGenerator(tree: Tree, options: AddEntriesGeneratorSchema) {
|
|
35
|
+
const basePath = process.cwd();
|
|
36
|
+
const projectRoot = `${basePath}/${options.projectPath}`;
|
|
37
|
+
const projectSrc = `${options.projectPath}/src`;
|
|
38
|
+
// const sourcePath = path.join(__dirname, 'entries');
|
|
39
|
+
try {
|
|
40
|
+
let framework = await getFramework(projectRoot);
|
|
41
|
+
if (!framework) {
|
|
42
|
+
framework = 'antui';
|
|
43
|
+
}
|
|
44
|
+
console.log('UI framework specified: ', framework);
|
|
45
|
+
|
|
46
|
+
resetProject(projectRoot);
|
|
47
|
+
|
|
48
|
+
// addProjectConfiguration(tree, framework, {
|
|
49
|
+
// root: projectRoot,
|
|
50
|
+
// projectType: 'library',
|
|
51
|
+
// sourceRoot: projectSrc,
|
|
52
|
+
// targets: {},
|
|
53
|
+
// });
|
|
54
|
+
|
|
55
|
+
generateFiles(tree, path.join(__dirname, 'files'), projectSrc, {});
|
|
56
|
+
generateFiles(tree, path.join(__dirname, 'entries', framework), projectSrc, options);
|
|
57
|
+
// await fs.cpSync(path.join(__dirname, 'files'), projectSrc, {recursive: true});
|
|
58
|
+
// await fs.cpSync(path.join(__dirname, 'entries', framework), projectSrc, {recursive: true});
|
|
59
|
+
await formatFiles(tree);
|
|
60
|
+
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error('Error while generating entries: ', err);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default addEntriesGenerator;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "AddEntries",
|
|
4
|
+
"title": "",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"projectPath": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "The project relative path (ex: servers/frontend-server)"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"required": ["projectPath"]
|
|
13
|
+
}
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"resolveJsonModule": true,
|
|
7
|
+
"target": "ES6",
|
|
8
|
+
"lib": ["es5","es6"],
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"experimentalDecorators": true,
|
|
12
|
+
"rootDir": "src",
|
|
13
|
+
"outDir": "lib",
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"preserveConstEnums": true,
|
|
16
|
+
"declarationDir": "lib",
|
|
17
|
+
"skipLibCheck": true
|
|
18
|
+
},
|
|
19
|
+
"include": ["src"],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"node_modules",
|
|
22
|
+
"lib",
|
|
23
|
+
"dist",
|
|
24
|
+
"rollup.config.mjs"
|
|
25
|
+
]
|
|
26
|
+
}
|