@proteinjs/server 1.0.1

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +21 -0
  3. package/dist/generated/index.d.ts +19 -0
  4. package/dist/generated/index.d.ts.map +1 -0
  5. package/dist/generated/index.js +47 -0
  6. package/dist/generated/index.js.map +1 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +18 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/jest.config.d.ts +8 -0
  12. package/dist/jest.config.d.ts.map +1 -0
  13. package/dist/jest.config.js +20 -0
  14. package/dist/jest.config.js.map +1 -0
  15. package/dist/src/NodeSessionDataStorage.d.ts +10 -0
  16. package/dist/src/NodeSessionDataStorage.d.ts.map +1 -0
  17. package/dist/src/NodeSessionDataStorage.js +42 -0
  18. package/dist/src/NodeSessionDataStorage.js.map +1 -0
  19. package/dist/src/loadRoutes.d.ts +5 -0
  20. package/dist/src/loadRoutes.d.ts.map +1 -0
  21. package/dist/src/loadRoutes.js +212 -0
  22. package/dist/src/loadRoutes.js.map +1 -0
  23. package/dist/src/routes/healthCheck.d.ts +3 -0
  24. package/dist/src/routes/healthCheck.d.ts.map +1 -0
  25. package/dist/src/routes/healthCheck.js +51 -0
  26. package/dist/src/routes/healthCheck.js.map +1 -0
  27. package/dist/src/routes/reactApp.d.ts +7 -0
  28. package/dist/src/routes/reactApp.d.ts.map +1 -0
  29. package/dist/src/routes/reactApp.js +111 -0
  30. package/dist/src/routes/reactApp.js.map +1 -0
  31. package/dist/src/startServer.d.ts +3 -0
  32. package/dist/src/startServer.d.ts.map +1 -0
  33. package/dist/src/startServer.js +238 -0
  34. package/dist/src/startServer.js.map +1 -0
  35. package/dist/webpack.config.d.ts +74 -0
  36. package/dist/webpack.config.d.ts.map +1 -0
  37. package/dist/webpack.config.js +111 -0
  38. package/dist/webpack.config.js.map +1 -0
  39. package/generated/index.ts +43 -0
  40. package/index.ts +1 -0
  41. package/jest.config.js +18 -0
  42. package/package.json +77 -0
  43. package/src/NodeSessionDataStorage.ts +40 -0
  44. package/src/loadRoutes.ts +126 -0
  45. package/src/routes/healthCheck.ts +10 -0
  46. package/src/routes/reactApp.ts +57 -0
  47. package/src/startServer.ts +184 -0
  48. package/tsconfig.json +18 -0
  49. package/webpack.config.js +112 -0
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@proteinjs/server",
3
+ "version": "1.0.1",
4
+ "description": "A server impl",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/brentbahry/server.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/brentbahry/server/issues"
14
+ },
15
+ "homepage": "https://github.com/brentbahry/server#readme",
16
+ "author": "Brent Bahry",
17
+ "license": "ISC",
18
+ "scripts": {
19
+ "clean": "rm -rf dist/ generated/ node_modules/ package-lock.json",
20
+ "build": "reflection-build && tsc",
21
+ "watch": "reflection-watch",
22
+ "dev": "DEVELOPMENT=true node dist/generated/index.js"
23
+ },
24
+ "dependencies": {
25
+ "@proteinjs/db": "1.0.3",
26
+ "@proteinjs/reflection": "1.0.2",
27
+ "@proteinjs/server-api": "1.0.2",
28
+ "@proteinjs/util": "1.0.18",
29
+ "body-parser": "1.19.0",
30
+ "compression": "1.7.4",
31
+ "cookie-parser": "1.4.5",
32
+ "express": "4.17.1",
33
+ "express-session": "1.17.1",
34
+ "passport": "0.4.1",
35
+ "passport-local": "1.0.0",
36
+ "react": "18.2.0",
37
+ "react-helmet": "6.1.0",
38
+ "webpack": "5.88.2",
39
+ "webpack-dev-middleware": "7.0.0",
40
+ "webpack-hot-middleware": "2.26.0"
41
+ },
42
+ "devDependencies": {
43
+ "@emotion/react": "11.11.1",
44
+ "@emotion/styled": "11.11.0",
45
+ "@mui/icons-material": "5.14.11",
46
+ "@mui/joy": "5.0.0-beta.8",
47
+ "@mui/material": "5.14.11",
48
+ "@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
49
+ "@proteinjs/reflection-build": "1.0.5",
50
+ "@types/body-parser": "1.19.0",
51
+ "@types/compression": "1.7.0",
52
+ "@types/cookie-parser": "1.4.2",
53
+ "@types/express": "4.17.7",
54
+ "@types/express-session": "1.17.0",
55
+ "@types/node": "14.0.13",
56
+ "@types/passport": "1.0.4",
57
+ "@types/passport-local": "1.0.33",
58
+ "@types/react": "18.2.23",
59
+ "@types/react-helmet": "6.1.0",
60
+ "@types/webpack-hot-middleware": "2.25.9",
61
+ "buffer": "6.0.3",
62
+ "crypto-browserify": "3.12.0",
63
+ "events": "3.3.0",
64
+ "file-loader": "6.2.0",
65
+ "process": "0.11.10",
66
+ "react-refresh": "0.14.0",
67
+ "react-refresh-typescript": "2.0.9",
68
+ "stream-browserify": "3.0.0",
69
+ "ts-loader": "8.0.3",
70
+ "typescript": "5.2.2",
71
+ "url": "0.11.3",
72
+ "util": "0.12.5"
73
+ },
74
+ "main": "./dist/generated/index.js",
75
+ "types": "./dist/generated/index.d.ts",
76
+ "gitHead": "7609528bc63fb92c7b528fc31795bef1e6913cbf"
77
+ }
@@ -0,0 +1,40 @@
1
+ import asyncHooks from 'async_hooks';
2
+ import { SessionData, SessionDataStorage } from '@proteinjs/server-api';
3
+
4
+ export class NodeSessionDataStorage implements SessionDataStorage {
5
+ private static HOOK_INITIALIZED = false;
6
+ private static readonly SESSION_DATA: {[id: string]: SessionData} = {};
7
+ environment = 'node' as 'node';
8
+
9
+ setData(data: SessionData) {
10
+ if (!NodeSessionDataStorage.HOOK_INITIALIZED)
11
+ this.initHook();
12
+
13
+ if (NodeSessionDataStorage.SESSION_DATA[asyncHooks.executionAsyncId()])
14
+ return;
15
+
16
+ NodeSessionDataStorage.SESSION_DATA[asyncHooks.executionAsyncId()] = data;
17
+ }
18
+
19
+ getData(): SessionData {
20
+ if (!NodeSessionDataStorage.HOOK_INITIALIZED)
21
+ this.initHook();
22
+
23
+ return NodeSessionDataStorage.SESSION_DATA[asyncHooks.executionAsyncId()];
24
+ }
25
+
26
+ private initHook() {
27
+ asyncHooks.createHook({
28
+ init: (asyncId: number, type: string, triggerAsyncId: number, resource: Object) => {
29
+ if (!NodeSessionDataStorage.SESSION_DATA[triggerAsyncId])
30
+ return;
31
+
32
+ NodeSessionDataStorage.SESSION_DATA[asyncId] = NodeSessionDataStorage.SESSION_DATA[triggerAsyncId];
33
+ },
34
+ destroy: (asyncId: number) => {
35
+ delete NodeSessionDataStorage.SESSION_DATA[asyncId];
36
+ }
37
+ }).enable();
38
+ NodeSessionDataStorage.HOOK_INITIALIZED = true;
39
+ }
40
+ }
@@ -0,0 +1,126 @@
1
+ import express from 'express';
2
+ import passport from 'passport';
3
+ import { ServerConfig, Route, getRequestListeners, Session, SessionData, getSessionDataCaches } from '@proteinjs/server-api';
4
+ import { createReactApp } from './routes/reactApp';
5
+ import { Logger } from '@proteinjs/util';
6
+
7
+ const logger = new Logger('Server');
8
+
9
+ export function loadRoutes(routes: Route[], server: express.Express, config: ServerConfig) {
10
+ let starRoute: Route|null = null;
11
+ const wildcardRoutes: Route[] = [];
12
+ for (const route of routes) {
13
+ logger.info(`Loading route: ${route.path}`);
14
+ if (route.path == '*') {
15
+ starRoute = route;
16
+ continue;
17
+ }
18
+
19
+ if (route.path.includes('*')) {
20
+ wildcardRoutes.push(route);
21
+ continue;
22
+ }
23
+
24
+ server[route.method](getPath(route.path), wrapRoute(route.onRequest.bind(route), config));
25
+ }
26
+
27
+ for (const wildcardRoute of wildcardRoutes)
28
+ server[wildcardRoute.method](getPath(wildcardRoute.path), wrapRoute(wildcardRoute.onRequest.bind(wildcardRoute), config));
29
+
30
+ if (starRoute)
31
+ server[starRoute.method](starRoute.path, wrapRoute(starRoute.onRequest.bind(starRoute), config));
32
+ }
33
+
34
+ export function loadDefaultStarRoute(routes: Route[], server: express.Express, config: ServerConfig) {
35
+ let starRouteSpecified = false;
36
+ for (const route of routes) {
37
+ if (route.path == '*') {
38
+ starRouteSpecified = true;
39
+ break;
40
+ }
41
+ }
42
+
43
+ if (!starRouteSpecified && config.staticContent?.bundlePaths) {
44
+ const reactApp = createReactApp(config);
45
+ server[reactApp.method](reactApp.path, wrapRoute(reactApp.onRequest, config));
46
+ }
47
+ }
48
+
49
+ function getPath(path: string) {
50
+ return path.startsWith('/') ? path : `/${path}`;
51
+ }
52
+
53
+ function wrapRoute(route: (request: express.Request, response: express.Response) => Promise<void>, config: ServerConfig) {
54
+ return async function (request: express.Request, response: express.Response, next: express.NextFunction) {
55
+ if (response.locals['responseHandled']) {
56
+ next();
57
+ return;
58
+ }
59
+
60
+ if (config.authenticate) {
61
+ await new Promise<void>((resolve, reject) => {
62
+ passport.authenticate('local', function (err, user, info) {
63
+ if (err)
64
+ reject(err);
65
+
66
+ resolve();
67
+ })(request, response, next);
68
+ });
69
+ }
70
+
71
+ const sessionData: SessionData = { sessionId: request.sessionID, user: request.user as string, data: {} };
72
+ for (const sessionDataCache of getSessionDataCaches())
73
+ sessionData.data[sessionDataCache.key] = await sessionDataCache.create(sessionData.sessionId, sessionData.user);
74
+ Session.setData(sessionData);
75
+
76
+ const requestListeners = getRequestListeners();
77
+ for (const listener of requestListeners) {
78
+ if (!listener.beforeRequest)
79
+ continue;
80
+
81
+ try {
82
+ await listener.beforeRequest(request, response);
83
+ } catch (error: any) {
84
+ logger.error(`Caught error when running listener before request`, error);
85
+ }
86
+ }
87
+
88
+ const sixtyMinutes = 1000 * 60 * 60;
89
+ const timeout = typeof config.request?.timeoutMs !== 'undefined' ? config.request.timeoutMs : sixtyMinutes;
90
+ request.setTimeout(timeout, () => {
91
+ if (response.locals.requestNumber)
92
+ logger.warn(`[#${response.locals.requestNumber}] Timed out ${request.originalUrl}`);
93
+ else
94
+ logger.warn(`Timed out ${request.originalUrl}`);
95
+ });
96
+
97
+ try {
98
+ await route(request, response);
99
+ } catch(error) {
100
+ console.error(error);
101
+ }
102
+ response.locals['responseHandled'] = true;
103
+
104
+ for (const listener of requestListeners) {
105
+ if (!listener.afterRequest)
106
+ continue;
107
+
108
+ try {
109
+ await listener.afterRequest(request, response);
110
+ } catch (error: any) {
111
+ logger.error(`Caught error when running listener after request`, error);
112
+ }
113
+ }
114
+
115
+ next();
116
+ };
117
+ }
118
+
119
+ function basicAuthCredentials(request: express.Request): { username: string, password: string } | null {
120
+ const b64auth = (request.headers.authorization || '').split(' ')[1] || '';
121
+ const [username, password] = Buffer.from(b64auth, 'base64').toString().split(':');
122
+ if (!username || !password)
123
+ return null;
124
+
125
+ return { username, password };
126
+ }
@@ -0,0 +1,10 @@
1
+ import { Route } from '@proteinjs/server-api';
2
+
3
+ export const healthCheck: Route = {
4
+ path: 'health-check',
5
+ method: 'get',
6
+ useHttp: true,
7
+ onRequest: async (request, response): Promise<void> => {
8
+ response.send();
9
+ }
10
+ }
@@ -0,0 +1,57 @@
1
+ import path from 'path';
2
+ import ReactHelmet from 'react-helmet';
3
+ import { ServerConfig, getServerRenderedScripts } from '@proteinjs/server-api';
4
+
5
+ export const createReactApp = (serverConfig: ServerConfig) => {
6
+ return {
7
+ path: '*',
8
+ method: 'get' as 'get',
9
+ onRequest: async (request: any, response: any): Promise<void> => {
10
+ if (request.path.startsWith('/static'))
11
+ return;
12
+
13
+ if (!serverConfig.staticContent?.bundlePaths)
14
+ throw new Error(`ServerConfig.bundlePath must be provided to serve react app`);
15
+
16
+ const helmet = ReactHelmet.renderStatic();
17
+ response.send(`<!DOCTYPE html>
18
+ <html ${helmet.htmlAttributes}>
19
+ <head>
20
+ <meta charset='utf-8' />
21
+ <meta name='viewport' content='width=device-width, initial-scale=1.0'>
22
+ <link href='${serverConfig.staticContent?.faviconPath ? path.join('/static/', serverConfig.staticContent.faviconPath) : ''}' rel='icon' type='image/x-icon' />
23
+ ${helmet.title.toString()}
24
+ ${helmet.meta.toString()}
25
+ ${helmet.link.toString()}
26
+ </head>
27
+ <body ${helmet.bodyAttributes.toString()}>
28
+ <div id='app'></div>
29
+ <script>proteinjs = {};</script>
30
+ ${await serverRenderedScriptTags()}
31
+ ${bundleScriptTags(serverConfig)}
32
+ </body>
33
+ </html>`
34
+ );
35
+ }
36
+ }
37
+ }
38
+
39
+ function bundleScriptTags(serverConfig: ServerConfig) {
40
+ if (!serverConfig.staticContent?.bundlePaths)
41
+ return;
42
+
43
+ const scriptTags: string[] = [];
44
+ for (const bundlePath of serverConfig.staticContent.bundlePaths)
45
+ scriptTags.push(`<script src='${path.join('/static/', bundlePath)}'></script>`);
46
+
47
+ return scriptTags.join('\n');
48
+ }
49
+
50
+ async function serverRenderedScriptTags() {
51
+ const scripts = getServerRenderedScripts();
52
+ const scriptTags: string[] = [];
53
+ for (const script of scripts)
54
+ scriptTags.push(`<script>${await script.script()}</script>`);
55
+
56
+ return scriptTags.join('\n');
57
+ }
@@ -0,0 +1,184 @@
1
+ import express from 'express';
2
+ import expressSession from 'express-session';
3
+ import compression from 'compression';
4
+ import cookieParser from 'cookie-parser';
5
+ import bodyParser from 'body-parser';
6
+ import passport from 'passport';
7
+ import passportLocal from 'passport-local';
8
+ import webpack from 'webpack';
9
+ import webpackDevMiddleware from 'webpack-dev-middleware';
10
+ import webpackHotMiddleware from 'webpack-hot-middleware';
11
+ import { Db } from '@proteinjs/db';
12
+ import { ServerConfig, getRoutes } from '@proteinjs/server-api';
13
+ import { loadRoutes, loadDefaultStarRoute } from './loadRoutes';
14
+ import { Logger } from '@proteinjs/util';
15
+
16
+ const webpackConfig = require('../webpack.config');
17
+ const staticContentPath = '/static/';
18
+ const logger = new Logger('Server');
19
+
20
+ export async function startServer(config: ServerConfig) {
21
+ const routes = getRoutes();
22
+ await runStartupEvents(config);
23
+ const server = express();
24
+ configureRequests(server);
25
+ initializeHotReloading(server, config);
26
+ beforeRequest(server, config);
27
+ loadRoutes(routes.filter(route => route.useHttp), server, config);
28
+ configureHttps(server); // registering here forces static content to be redirected to https
29
+ configureStaticContentRouter(server, config); // registering here prevents sessions from being created on static content requests
30
+ configureSession(server, config);
31
+ loadRoutes(routes.filter(route => !route.useHttp), server, config);
32
+ loadDefaultStarRoute(routes, server, config);
33
+ afterRequest(server, config);
34
+ start(server, config);
35
+ }
36
+
37
+ async function runStartupEvents(config: ServerConfig) {
38
+ await new Db().init();
39
+
40
+ if (config.onStartup)
41
+ await config.onStartup();
42
+ }
43
+
44
+ function configureRequests(server: express.Express) {
45
+ server.use(compression());
46
+ server.use(cookieParser());
47
+ server.use(bodyParser.json({ limit: '100mb' }));
48
+ server.use(bodyParser.urlencoded({
49
+ extended: true,
50
+ limit: '100mb'
51
+ }));
52
+ server.disable('x-powered-by');
53
+ }
54
+
55
+ function initializeHotReloading(server: express.Express, config: ServerConfig) {
56
+ if (!process.env.DEVELOPMENT || process.env.DISABLE_HOT_CLIENT_BUILDS || config.disableHotClientBuilds || !config.staticContent?.staticContentDir || !config.staticContent?.appEntryPath)
57
+ return;
58
+
59
+ let wpConfig = Object.assign({}, webpackConfig);
60
+ wpConfig['entry'] = { app: ['webpack-hot-middleware/client', config.staticContent.appEntryPath] };
61
+ wpConfig['output']['path'] = config.staticContent.staticContentDir;
62
+ wpConfig['output']['publicPath'] = staticContentPath;
63
+ const webpackCompiler = webpack(wpConfig);
64
+ server.use(webpackDevMiddleware(webpackCompiler, {
65
+ publicPath: staticContentPath,
66
+ }));
67
+ server.use(webpackHotMiddleware(webpackCompiler));
68
+ }
69
+
70
+ function configureHttps(server: express.Express) {
71
+ server.use((request: express.Request, response: express.Response, next: express.NextFunction) => {
72
+ if (request.protocol == 'https' || response.headersSent || process.env.DEVELOPMENT) {
73
+ next();
74
+ return;
75
+ }
76
+
77
+ logger.debug(`Redirecting to https: ${request.headers.host + request.url}`);
78
+ response.redirect('https://' + request.headers.host + request.url);
79
+ });
80
+ }
81
+
82
+ function configureStaticContentRouter(server: express.Express, config: ServerConfig) {
83
+ if (!config.staticContent?.staticContentDir)
84
+ return;
85
+
86
+ server.use(staticContentPath, express.static(config.staticContent.staticContentDir));
87
+ logger.info(`Serving static content on path: ${staticContentPath}, serving from directory: ${config.staticContent.staticContentDir}`);
88
+ }
89
+
90
+ function configureSession(server: express.Express, config: ServerConfig) {
91
+ const sixtyDays = 1000 * 60 * 60 * 24 * 60;
92
+ let sessionOptions: expressSession.SessionOptions = {
93
+ secret: config.session.secret,
94
+ store: config.session.store,
95
+ resave: false,
96
+ saveUninitialized: false,
97
+ cookie: {
98
+ maxAge: sixtyDays
99
+ },
100
+ rolling: true
101
+ };
102
+
103
+ if (!process.env.DEVELOPMENT) {
104
+ server.set('trust proxy', 1);
105
+ if (!sessionOptions.cookie)
106
+ sessionOptions.cookie = {};
107
+ sessionOptions.cookie.secure = true;
108
+ }
109
+
110
+ if (config.session)
111
+ sessionOptions = Object.assign(sessionOptions, config.session);
112
+
113
+ server.use(expressSession(sessionOptions));
114
+ server.use(passport.initialize());
115
+ server.use(passport.session());
116
+ if (config.authenticate)
117
+ initializeAuthentication(config.authenticate);
118
+ }
119
+
120
+ function initializeAuthentication(authenticate: (username: string, password: string) => Promise<true | string>) {
121
+ passport.use(new passportLocal.Strategy(async function (username, password, done) {
122
+ logger.info(`Authenticating`);
123
+ const result = await authenticate(username, password);
124
+ if (result === true)
125
+ return done(null, { username });
126
+
127
+ return done(new Error(result));
128
+ }));
129
+
130
+ passport.serializeUser(function (user, done) {
131
+ done(null, user);
132
+ });
133
+
134
+ passport.deserializeUser(function (id, done) {
135
+ done(null, id);
136
+ });
137
+ }
138
+
139
+ function beforeRequest(server: express.Express, config: ServerConfig) {
140
+ let requestCounter: number = 0;
141
+ if (config.request?.disableRequestLogging == false || typeof config.request?.disableRequestLogging === 'undefined') {
142
+ server.use((request: express.Request, response: express.Response, next: express.NextFunction) => {
143
+ if (request.path.startsWith('/static') || request.path.startsWith('/favicon.ico')) {
144
+ next();
145
+ return;
146
+ }
147
+
148
+ const requestNumber = ++requestCounter;
149
+ logger.info(`[#${requestNumber}] Started ${request.originalUrl}`);
150
+ response.locals = { requestNumber };
151
+ next();
152
+ });
153
+ }
154
+
155
+ if (config.request?.beforeRequest)
156
+ server.use(config.request.beforeRequest);
157
+ }
158
+
159
+ function afterRequest(server: express.Express, config: ServerConfig) {
160
+ if (config.request?.afterRequest)
161
+ server.use(config.request.afterRequest);
162
+
163
+ if (config.request?.disableRequestLogging == false || typeof config.request?.disableRequestLogging === 'undefined') {
164
+ server.use((request: express.Request, response: express.Response, next: express.NextFunction) => {
165
+ if (request.path.startsWith('/static') || request.path.startsWith('/favicon.ico')) {
166
+ next();
167
+ return;
168
+ }
169
+
170
+ logger.info(`[#${response.locals.requestNumber}] Finished ${request.originalUrl}`);
171
+ next();
172
+ });
173
+ }
174
+ }
175
+
176
+ function start(server: express.Express, config: ServerConfig) {
177
+ const port = config.port ? config.port : 3000;
178
+ server.listen(port, () => {
179
+ if (process.env.DEVELOPMENT)
180
+ logger.info(`Starting in development mode`);
181
+
182
+ logger.info(`Server listening on port: ${port}`);
183
+ });
184
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "./",
4
+ "target": "es5",
5
+ "module": "commonjs",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "outDir": "./dist/",
10
+ "strict": true,
11
+ "noImplicitAny": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "allowJs": true
17
+ }
18
+ }
@@ -0,0 +1,112 @@
1
+ var path = require('path');
2
+ var webpack = require('webpack');
3
+ const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
4
+ const ReactRefreshTypeScript = require('react-refresh-typescript');
5
+
6
+ // if there are module resolution issues, verify assumptions about pathing
7
+ // console.log(`__dirname in webpack.config: ${__dirname}`);
8
+
9
+ // see https://github.com/webpack/webpack/issues/11467#issuecomment-808618999/
10
+ // for details
11
+ const webpack5esmInteropRule = {
12
+ test: /\.m?js/,
13
+ resolve: {
14
+ fullySpecified: false
15
+ }
16
+ };
17
+
18
+ module.exports = {
19
+ mode: 'development',
20
+ devtool: 'inline-source-map',
21
+ // entry provided dynamically in startServer.initializeHotReloading
22
+ // entry: {
23
+ // app: ['webpack-hot-middleware/client', './generated/index.ts'],
24
+ // },
25
+ output: {
26
+ filename: '[name].js',
27
+ // path and publicPath provided in startServer.initializeHotReloading
28
+ // path: path.join(__dirname, 'dist'),
29
+ library: '[name]'
30
+ },
31
+ resolve: {
32
+ extensions: ['.tsx', '.ts', '.js', '.json'],
33
+ alias: {
34
+ 'react': path.join(__dirname, '../node_modules/react'),
35
+ 'process': 'process/browser',
36
+ '@mui/joy': path.join(__dirname, '../node_modules/@mui/joy'),
37
+ '@mui/material': path.join(__dirname, '../node_modules/@mui/material'),
38
+ '@mui/icons-material': path.join(__dirname, '../node_modules/@mui/icons-material'),
39
+ 'webpack-hot-middleware': path.join(__dirname, '../node_modules/webpack-hot-middleware'),
40
+ },
41
+ // provide shims for node libraries for webpack >= 5
42
+ fallback: {
43
+ 'crypto': require.resolve('crypto-browserify'),
44
+ 'util': require.resolve('util/'),
45
+ 'events': require.resolve('events/'),
46
+ 'url': require.resolve('url/'),
47
+ 'buffer': require.resolve('buffer/'),
48
+ 'stream': require.resolve('stream-browserify'),
49
+ 'process/browser': require.resolve('process/browser'),
50
+ },
51
+ },
52
+ // since this webpack.config is invoked via webpack-dev-middleware in the process of the consuming server package (not this package)
53
+ // we need to let webpack know where to look for loaders like: file-loader and ts-loader
54
+ // it's ideal to keep those dependencies within this package instead of requiring them installed by the consumer
55
+ resolveLoader: {
56
+ modules: [path.resolve(__dirname, '../node_modules'), 'node_modules'],
57
+ },
58
+ module: {
59
+ rules: [
60
+ webpack5esmInteropRule,
61
+ // all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
62
+ // this will only transpile (to integrate with babel via ReactRefreshTypeScript); rely on the ide for compiler checks
63
+ {
64
+ test: /\.[jt]sx?$/,
65
+ exclude: /node_modules/,
66
+ use: [
67
+ {
68
+ loader: require.resolve('ts-loader'),
69
+ options: {
70
+ getCustomTransformers: () => ({
71
+ before: [ReactRefreshTypeScript()],
72
+ }),
73
+ transpileOnly: true,
74
+ },
75
+ },
76
+ ],
77
+ },
78
+ // image files
79
+ {
80
+ test: /\.(png|jpe?g|gif|svg)$/i,
81
+ use: [
82
+ {
83
+ loader: 'file-loader',
84
+ options: {
85
+ name: '[path][name].[ext]', // This line will keep the original path and filename
86
+ },
87
+ },
88
+ ],
89
+ },
90
+ ],
91
+ },
92
+ optimization: {
93
+ splitChunks: {
94
+ cacheGroups: {
95
+ vendor: {
96
+ test: /[\\/]node_modules[\\/]/, // Matches node_modules folder
97
+ name: 'vendor',
98
+ chunks: 'all',
99
+ priority: -10
100
+ }
101
+ }
102
+ }
103
+ },
104
+ plugins: [
105
+ new webpack.ProvidePlugin({
106
+ process: 'process/browser',
107
+ Buffer: ['buffer', 'Buffer'],
108
+ }),
109
+ new webpack.HotModuleReplacementPlugin(),
110
+ new ReactRefreshWebpackPlugin(),
111
+ ],
112
+ };