dinou 1.1.1 → 1.3.0

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/README.md CHANGED
@@ -46,7 +46,11 @@ dinou main features are:
46
46
 
47
47
  - TypeScript or JavaScript
48
48
 
49
- - Full control and customization through the command `npm run eject` (`npx dinou eject`).
49
+ - Full control and customization through the command `npm run eject` (`npx dinou eject`)
50
+
51
+ - Support for the use of `.css`, `.module.css`, and `Tailwind.css`
52
+
53
+ - Support for the use of images in your components (`.png`, `.jpeg`, `.jpg`, `.gif`, `.svg`, `.webp`)
50
54
 
51
55
  ## Table of contents
52
56
 
@@ -96,6 +100,10 @@ dinou main features are:
96
100
 
97
101
  - [`.env` file](#env-file)
98
102
 
103
+ - [Styles (Tailwind.css, .module.css, and .css)](#styles-tailwindcss-modulecss-and-css)
104
+
105
+ - [Images (`.png`, `.jpeg`, `.jpg`, `.gif`, `.svg`, and `.webp`)](#images-png-jpeg-jpg-gif-svg-and-webp)
106
+
99
107
  - [How to run a dinou app](#how-to-run-a-dinou-app)
100
108
 
101
109
  - [Eject dinou](#eject-dinou)
@@ -895,6 +903,209 @@ dinou is ready to manage env vars in the code that runs on the Server side (Serv
895
903
  MY_VAR=my_value
896
904
  ```
897
905
 
906
+ ## Styles (Tailwind.css, .module.css, and .css)
907
+
908
+ dinou is ready to use Tailwind.css, `.module.css`, and `.css` styles. All styles will be generated in a file in `public` folder named `styles.css`. So you must include this in your `page.tsx` or `layout.tsx` file, in the `head` tag:
909
+
910
+ ```typescript
911
+ <link href="/styles.css" rel="stylesheet"></link>
912
+ ```
913
+
914
+ - Example with client components:
915
+
916
+ ```typescript
917
+ // src/layout.tsx
918
+ "use client";
919
+
920
+ import type { ReactNode } from "react";
921
+ import "./global.css";
922
+
923
+ export default function Layout({ children }: { children: ReactNode }) {
924
+ return (
925
+ <html lang="en">
926
+ <head>
927
+ <title>dinou app</title>
928
+ <link rel="icon" type="image/png" href="/favicon.ico" />
929
+ <link
930
+ rel="apple-touch-icon"
931
+ sizes="180x180"
932
+ href="/apple-touch-icon.png"
933
+ />
934
+ <link
935
+ rel="icon"
936
+ type="image/png"
937
+ sizes="32x32"
938
+ href="/favicon-32x32.png"
939
+ />
940
+ <link
941
+ rel="icon"
942
+ type="image/png"
943
+ sizes="16x16"
944
+ href="/favicon-16x16.png"
945
+ />
946
+ <link rel="manifest" href="/site.webmanifest"></link>
947
+ <link href="/styles.css" rel="stylesheet"></link>
948
+ </head>
949
+ <body>{children}</body>
950
+ </html>
951
+ );
952
+ }
953
+ ```
954
+
955
+ ```css
956
+ /* global.css */
957
+ @import "tailwindcss";
958
+
959
+ .test1 {
960
+ background-color: purple;
961
+ }
962
+ ```
963
+
964
+ ```typescript
965
+ // src/page.tsx
966
+ "use client";
967
+
968
+ import styles from "./page.module.css";
969
+
970
+ export default function Page() {
971
+ return (
972
+ <div className={`text-red-500 test1 ${styles.test2}`}>hi world!</div>
973
+ );
974
+ }
975
+ ```
976
+
977
+ ```css
978
+ /* src/page.module.css */
979
+ .test2 {
980
+ text-decoration: underline;
981
+ }
982
+ ```
983
+
984
+ ```typescript
985
+ // src/css.d.ts
986
+ declare module "*.module.css" {
987
+ const classes: { [key: string]: string };
988
+ export default classes;
989
+ }
990
+ ```
991
+
992
+ - The above will produce the text `hi world!` in red, underlined, and with a purple background color.
993
+
994
+ - **Only styles imported under `"use client"` directive will be detected by Webpack and generated in a `styles.css` in `public` folder**. This means that if you want to use server components instead of client components, then you must create an additional file (e.g. `styles.ts`) where you use the `"use client"` directive and import all the `.css` files used in server components.
995
+
996
+ - Example with server components:
997
+
998
+ ```typescript
999
+ // src/layout.tsx
1000
+ import type { ReactNode } from "react";
1001
+
1002
+ export default async function Layout({ children }: { children: ReactNode }) {
1003
+ return (
1004
+ <html lang="en">
1005
+ <head>
1006
+ <title>dinou app</title>
1007
+ <link rel="icon" type="image/png" href="/favicon.ico" />
1008
+ <link
1009
+ rel="apple-touch-icon"
1010
+ sizes="180x180"
1011
+ href="/apple-touch-icon.png"
1012
+ />
1013
+ <link
1014
+ rel="icon"
1015
+ type="image/png"
1016
+ sizes="32x32"
1017
+ href="/favicon-32x32.png"
1018
+ />
1019
+ <link
1020
+ rel="icon"
1021
+ type="image/png"
1022
+ sizes="16x16"
1023
+ href="/favicon-16x16.png"
1024
+ />
1025
+ <link rel="manifest" href="/site.webmanifest"></link>
1026
+ <link href="/styles.css" rel="stylesheet"></link>
1027
+ </head>
1028
+ <body>{children}</body>
1029
+ </html>
1030
+ );
1031
+ }
1032
+ ```
1033
+
1034
+ ```css
1035
+ /* global.css */
1036
+ @import "tailwindcss";
1037
+
1038
+ .test1 {
1039
+ background-color: purple;
1040
+ }
1041
+ ```
1042
+
1043
+ ```typescript
1044
+ // src/page.tsx
1045
+ import styles from "./page.module.css";
1046
+
1047
+ export default async function Page() {
1048
+ return (
1049
+ <div className={`text-red-500 test1 ${styles.test2}`}>hi world!</div>
1050
+ );
1051
+ }
1052
+ ```
1053
+
1054
+ ```css
1055
+ /* src/page.module.css */
1056
+ .test2 {
1057
+ text-decoration: underline;
1058
+ }
1059
+ ```
1060
+
1061
+ ```typescript
1062
+ // src/css.d.ts
1063
+ declare module "*.module.css" {
1064
+ const classes: { [key: string]: string };
1065
+ export default classes;
1066
+ }
1067
+ ```
1068
+
1069
+ ```typescript
1070
+ // src/styles.ts
1071
+ "use client"; // <-- This is key.
1072
+ import "./global.css";
1073
+ import "./page.module.css";
1074
+ ```
1075
+
1076
+ ## Images (`.png`, `.jpeg`, `.jpg`, `.gif`, `.svg`, and `.webp`)
1077
+
1078
+ dinou is ready to support the use of images in your components. Just do:
1079
+
1080
+ ```typescript
1081
+ // src/component.tsx
1082
+ "use client";
1083
+
1084
+ import image from "./image.png"; // import the image from where it is located (inside src folder)
1085
+
1086
+ export default function Component() {
1087
+ return <img src={image} alt="image" />;
1088
+ }
1089
+ ```
1090
+
1091
+ **Only images imported under `"use client"` directive will be detected by Webpack and generated in `public` folder**. If you use server components, then you must create an additional file (e.g. `images.ts`) with the `"use client"` directive and import there the images too:
1092
+
1093
+ ```typescript
1094
+ // src/images.ts
1095
+ "use client";
1096
+
1097
+ import "./image.png";
1098
+ ```
1099
+
1100
+ ```typescript
1101
+ // src/component.tsx
1102
+ import image from "./image.png"; // import the image from where it is located (inside src folder)
1103
+
1104
+ export default async function Component() {
1105
+ return <img src={image} alt="image" />;
1106
+ }
1107
+ ```
1108
+
898
1109
  ## How to run a dinou app
899
1110
 
900
1111
  Run `npm run dev` (or `npx dinou dev`) to start the dinou app in development mode. Wait for the logs of Webpack and `Listening on port <port>` to load the page on your browser.
@@ -908,3 +1119,7 @@ Run `npm run build` (or `npx dinou build`) to build the app and `npm start` (or
908
1119
  ## License
909
1120
 
910
1121
  dinou is licensed under the [MIT License](https://github.com/roggc/dinou/blob/master/LICENSE.md).
1122
+
1123
+ ```
1124
+
1125
+ ```
@@ -0,0 +1,58 @@
1
+ var fs = require("fs");
2
+ var path = require("path");
3
+ var interpolateName = require("loader-utils").interpolateName;
4
+
5
+ function compiler(name, options) {
6
+ return function compile(file) {
7
+ var content = fs.readFileSync(file);
8
+ var context = { resourcePath: file };
9
+
10
+ var resolvedName;
11
+ if (typeof name === "function") {
12
+ var localName = path.basename(file, path.extname(file));
13
+ resolvedName = name(localName, file);
14
+ } else {
15
+ resolvedName = name;
16
+ }
17
+
18
+ var result = interpolateName(context, resolvedName, {
19
+ content: content,
20
+ regExp: options.regExp,
21
+ });
22
+
23
+ if (options.publicPath) {
24
+ result =
25
+ typeof options.publicPath === "function"
26
+ ? options.publicPath(result)
27
+ : options.publicPath + result;
28
+ }
29
+
30
+ return result;
31
+ };
32
+ }
33
+
34
+ function hook(extension, compile) {
35
+ require.extensions[extension] = function (module, file) {
36
+ try {
37
+ const url = compile(file);
38
+ module._compile("module.exports = " + JSON.stringify(url), file);
39
+ } catch (err) {
40
+ console.error("Error processing file", file, err);
41
+ throw err;
42
+ }
43
+ };
44
+ }
45
+
46
+ function addHook(opts) {
47
+ opts = opts || {};
48
+ var extensions = (opts.extensions || []).map(function (ext) {
49
+ return ext.replace(".", "");
50
+ });
51
+ var comp = compiler(opts.name, opts);
52
+
53
+ extensions.forEach(function (ext) {
54
+ hook("." + ext, comp);
55
+ });
56
+ }
57
+
58
+ module.exports = addHook;
@@ -0,0 +1,8 @@
1
+ const genericNames = require("generic-names");
2
+ const path = require("path");
3
+
4
+ const generate = genericNames("[hash:base64]", {
5
+ context: path.resolve(process.cwd(), "src"),
6
+ });
7
+
8
+ module.exports = generate;
@@ -1,12 +1,23 @@
1
- require("@babel/register")({
2
- ignore: [/[\\\/](build|server|node_modules)[\\\/]/],
3
- presets: [
4
- ["@babel/preset-react", { runtime: "automatic" }],
5
- "@babel/preset-typescript",
6
- ],
7
- plugins: ["@babel/plugin-transform-modules-commonjs"],
1
+ const addHook = require("./asset-require-hook.js");
2
+ const { register } = require("esbuild-register/dist/node");
3
+ register({
4
+ target: "esnext",
5
+ format: "cjs",
8
6
  extensions: [".js", ".jsx", ".ts", ".tsx"],
9
7
  });
8
+ const createScopedName = require("./createScopedName");
9
+ require("css-modules-require-hook")({
10
+ generateScopedName: createScopedName,
11
+ });
12
+ addHook({
13
+ extensions: ["png", "jpg", "jpeg", "gif", "svg", "webp"],
14
+ name: function (localName, filepath) {
15
+ const result = createScopedName(localName, filepath);
16
+ return result + ".[ext]";
17
+ },
18
+ publicPath: "images/",
19
+ });
20
+
10
21
  const { renderToPipeableStream } = require("react-dom/server");
11
22
  const { getJSX, getSSGJSX } = require("./get-jsx");
12
23
  const { renderJSXToClientJSX } = require("./render-jsx-to-client-jsx");
@@ -26,6 +37,7 @@ async function renderToStream() {
26
37
  onError(error) {
27
38
  console.error("Render error:", error);
28
39
  process.stderr.write(JSON.stringify({ error: error.message }));
40
+ process.exit(1);
29
41
  },
30
42
  onShellReady() {
31
43
  stream.pipe(process.stdout);
@@ -38,9 +50,16 @@ async function renderToStream() {
38
50
  }
39
51
  }
40
52
 
41
- try {
42
- renderToStream();
43
- } catch (error) {
53
+ process.on("uncaughtException", (error) => {
44
54
  process.stderr.write(JSON.stringify({ error: error.message }));
45
55
  process.exit(1);
46
- }
56
+ });
57
+
58
+ process.on("unhandledRejection", (reason) => {
59
+ process.stderr.write(
60
+ JSON.stringify({ error: reason.message || "Unhandled promise rejection" })
61
+ );
62
+ process.exit(1);
63
+ });
64
+
65
+ renderToStream();
package/dinou/server.js CHANGED
@@ -1,27 +1,35 @@
1
1
  require("dotenv/config");
2
- const register = require("react-server-dom-webpack/node-register");
2
+ const webpackRegister = require("react-server-dom-webpack/node-register");
3
3
  const path = require("path");
4
4
  const { readFileSync } = require("fs");
5
5
  const { renderToPipeableStream } = require("react-server-dom-webpack/server");
6
6
  const express = require("express");
7
7
  const { spawn } = require("child_process");
8
- const babelRegister = require("@babel/register");
8
+ const { register } = require("esbuild-register/dist/node");
9
9
  const webpack = require("webpack");
10
10
  const webpackDevMiddleware = require("webpack-dev-middleware");
11
11
  const webpackHotMiddleware = require("webpack-hot-middleware");
12
12
  const webpackConfig = require(path.resolve(__dirname, "../webpack.config.js"));
13
13
  const { getSSGJSXOrJSX } = require("./get-jsx.js");
14
-
15
- register();
16
- babelRegister({
17
- ignore: [/[\\\/](build|server|node_modules)[\\\/]/],
18
- presets: [
19
- ["@babel/preset-react", { runtime: "automatic" }],
20
- "@babel/preset-typescript",
21
- ],
22
- plugins: ["@babel/transform-modules-commonjs"],
14
+ const addHook = require("./asset-require-hook.js");
15
+ webpackRegister();
16
+ register({
17
+ target: "esnext",
18
+ format: "cjs",
23
19
  extensions: [".js", ".jsx", ".ts", ".tsx"],
24
20
  });
21
+ const createScopedName = require("./createScopedName");
22
+ require("css-modules-require-hook")({
23
+ generateScopedName: createScopedName,
24
+ });
25
+ addHook({
26
+ extensions: ["png", "jpg", "jpeg", "gif", "svg", "webp"],
27
+ name: function (localName, filepath) {
28
+ const result = createScopedName(localName, filepath);
29
+ return result + ".[ext]";
30
+ },
31
+ publicPath: "images/",
32
+ });
25
33
 
26
34
  const app = express();
27
35
  const isDevelopment = process.env.NODE_ENV !== "production";
@@ -70,22 +78,27 @@ function renderAppToHtml(reqPath, paramsString) {
70
78
 
71
79
  let errorOutput = "";
72
80
  child.stderr.on("data", (data) => {
73
- errorOutput += data;
81
+ errorOutput += data.toString();
82
+ });
83
+
84
+ child.on("error", (error) => {
85
+ reject(new Error(`Failed to start child process: ${error.message}`));
86
+ });
87
+
88
+ child.on("spawn", () => {
89
+ resolve(child.stdout);
74
90
  });
75
91
 
76
92
  child.on("close", (code) => {
77
93
  if (code !== 0) {
78
94
  try {
79
95
  const errorResult = JSON.parse(errorOutput);
80
- reject(new Error(errorResult.error));
96
+ reject(new Error(errorResult.error || errorOutput));
81
97
  } catch {
82
98
  reject(new Error(`Child process failed: ${errorOutput}`));
83
99
  }
84
100
  }
85
101
  });
86
-
87
- // Resolve the stream from the child process
88
- resolve(child.stdout);
89
102
  });
90
103
  }
91
104
 
package/dinou/ssg.js CHANGED
@@ -1,14 +1,22 @@
1
- const babelRegister = require("@babel/register");
1
+ const { register } = require("esbuild-register/dist/node");
2
2
  const { buildStaticPages } = require("./build-static-pages");
3
-
4
- babelRegister({
5
- ignore: [/[\\\/](build|server|node_modules)[\\\/]/],
6
- presets: [
7
- ["@babel/preset-react", { runtime: "automatic" }],
8
- "@babel/preset-typescript",
9
- ],
10
- plugins: ["@babel/transform-modules-commonjs"],
3
+ const addHook = require("./asset-require-hook.js");
4
+ register({
5
+ target: "esnext",
6
+ format: "cjs",
11
7
  extensions: [".js", ".jsx", ".ts", ".tsx"],
12
8
  });
9
+ const createScopedName = require("./createScopedName");
10
+ require("css-modules-require-hook")({
11
+ generateScopedName: createScopedName,
12
+ });
13
+ addHook({
14
+ extensions: ["png", "jpg", "jpeg", "gif", "svg", "webp"],
15
+ name: function (localName, filepath) {
16
+ const result = createScopedName(localName, filepath);
17
+ return result + ".[ext]";
18
+ },
19
+ publicPath: "images/",
20
+ });
13
21
 
14
22
  (async () => await buildStaticPages())();
package/eject.js CHANGED
@@ -16,10 +16,18 @@ if (fs.existsSync(path.join(modulePath, "webpack.config.js"))) {
16
16
  );
17
17
  }
18
18
 
19
- fs.copyFileSync(
20
- path.join(modulePath, "LICENSE.md"),
21
- path.join(projectRoot, "dinou/LICENSE.md")
22
- );
19
+ if (fs.existsSync(path.join(modulePath, "postcss.config.js"))) {
20
+ fs.copyFileSync(
21
+ path.join(modulePath, "postcss.config.js"),
22
+ path.join(projectRoot, "postcss.config.js")
23
+ );
24
+ }
25
+
26
+ // don't copy the LICENSE.md file, as it is not needed in the project root
27
+ // fs.copyFileSync(
28
+ // path.join(modulePath, "LICENSE.md"),
29
+ // path.join(projectRoot, "dinou/LICENSE.md")
30
+ // );
23
31
 
24
32
  const pkg = require(path.join(projectRoot, "package.json"));
25
33
  pkg.scripts.dev =
package/package.json CHANGED
@@ -1,45 +1,55 @@
1
- {
2
- "name": "dinou",
3
- "version": "1.1.1",
4
- "description": "Minimal React 19 Framework",
5
- "main": "index.js",
6
- "bin": {
7
- "dinou": "./cli.js"
8
- },
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
11
- },
12
- "keywords": [
13
- "React 19",
14
- "Framework"
15
- ],
16
- "author": "Roger Gomez Castells (@roggc)",
17
- "license": "MIT",
18
- "repository": {
19
- "type": "git",
20
- "url": "https://github.com/roggc/dinou.git"
21
- },
22
- "dependencies": {
23
- "@babel/core": "^7.27.1",
24
- "@babel/plugin-syntax-import-meta": "^7.10.4",
25
- "@babel/plugin-transform-modules-commonjs": "^7.27.1",
26
- "@babel/preset-react": "^7.27.1",
27
- "@babel/preset-typescript": "^7.27.1",
28
- "@babel/register": "^7.27.1",
29
- "babel-loader": "^10.0.0",
30
- "commander": "^14.0.0",
31
- "copy-webpack-plugin": "^13.0.0",
32
- "cross-env": "^7.0.3",
33
- "dotenv": "^16.5.0",
34
- "express": "^5.1.0",
35
- "react-server-dom-webpack": "^19.1.0",
36
- "webpack": "^5.99.8",
37
- "webpack-cli": "^6.0.1",
38
- "webpack-dev-middleware": "^7.4.2",
39
- "webpack-hot-middleware": "^2.26.1"
40
- },
41
- "peerDependencies": {
42
- "react": "^19.1.0",
43
- "react-dom": "^19.1.0"
44
- }
45
- }
1
+ {
2
+ "name": "dinou",
3
+ "version": "1.3.0",
4
+ "description": "Minimal React 19 Framework",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "dinou": "./cli.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "React 19",
14
+ "Framework"
15
+ ],
16
+ "author": "Roger Gomez Castells (@roggc)",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/roggc/dinou.git"
21
+ },
22
+ "dependencies": {
23
+ "@babel/core": "^7.27.1",
24
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
25
+ "@babel/plugin-transform-modules-commonjs": "^7.27.1",
26
+ "@babel/preset-react": "^7.27.1",
27
+ "@babel/preset-typescript": "^7.27.1",
28
+ "@tailwindcss/postcss": "^4.1.10",
29
+ "autoprefixer": "^10.4.21",
30
+ "babel-loader": "^10.0.0",
31
+ "commander": "^14.0.0",
32
+ "copy-webpack-plugin": "^13.0.0",
33
+ "cross-env": "^7.0.3",
34
+ "css-loader": "^7.1.2",
35
+ "css-modules-require-hook": "^4.2.3",
36
+ "dotenv": "^16.5.0",
37
+ "esbuild-register": "^3.6.0",
38
+ "express": "^5.1.0",
39
+ "generic-names": "^4.0.0",
40
+ "loader-utils": "^3.3.1",
41
+ "mini-css-extract-plugin": "^2.9.2",
42
+ "postcss": "^8.5.5",
43
+ "postcss-loader": "^8.1.1",
44
+ "react-server-dom-webpack": "^19.1.0",
45
+ "tailwindcss": "^4.1.10",
46
+ "webpack": "^5.99.8",
47
+ "webpack-cli": "^6.0.1",
48
+ "webpack-dev-middleware": "^7.4.2",
49
+ "webpack-hot-middleware": "^2.26.1"
50
+ },
51
+ "peerDependencies": {
52
+ "react": "^19.1.0",
53
+ "react-dom": "^19.1.0"
54
+ }
55
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ autoprefixer: {},
5
+ },
6
+ };
package/webpack.config.js CHANGED
@@ -3,15 +3,19 @@ const path = require("path");
3
3
  const ReactServerWebpackPlugin = require("react-server-dom-webpack/plugin");
4
4
  const webpack = require("webpack");
5
5
  const CopyWebpackPlugin = require("copy-webpack-plugin");
6
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
7
+ const createScopedName = require("./dinou/createScopedName");
6
8
 
7
9
  const isDevelopment = process.env.NODE_ENV !== "production";
8
10
 
9
11
  module.exports = {
10
12
  mode: isDevelopment ? "development" : "production",
11
- entry: [
12
- isDevelopment && "webpack-hot-middleware/client?reload=true",
13
- path.resolve(__dirname, "./dinou/client.jsx"),
14
- ].filter(Boolean),
13
+ entry: {
14
+ main: [
15
+ isDevelopment && "webpack-hot-middleware/client?reload=true",
16
+ path.resolve(__dirname, "./dinou/client.jsx"),
17
+ ].filter(Boolean),
18
+ },
15
19
  output: {
16
20
  path: path.resolve(process.cwd(), "./public"),
17
21
  filename: "main.js",
@@ -37,6 +41,60 @@ module.exports = {
37
41
  },
38
42
  exclude: [/node_modules\/(?!dinou)/, /dist/],
39
43
  },
44
+ {
45
+ test: /\.module\.css$/,
46
+ use: [
47
+ MiniCssExtractPlugin.loader,
48
+ {
49
+ loader: "css-loader",
50
+ options: {
51
+ modules: {
52
+ getLocalIdent: (context, localIdentName, localName) => {
53
+ return createScopedName(localName, context.resourcePath);
54
+ },
55
+ },
56
+ importLoaders: 1,
57
+ },
58
+ },
59
+ "postcss-loader",
60
+ ],
61
+ },
62
+ {
63
+ test: /\.css$/,
64
+ exclude: /\.module\.css$/,
65
+ use: [
66
+ MiniCssExtractPlugin.loader,
67
+ "css-loader",
68
+ {
69
+ loader: "postcss-loader",
70
+ options: {
71
+ postcssOptions: {
72
+ config: path.resolve(__dirname, "postcss.config.js"),
73
+ },
74
+ },
75
+ },
76
+ ],
77
+ },
78
+ {
79
+ test: /\.(png|jpe?g|gif|svg|webp)$/i,
80
+ type: "asset/resource",
81
+ generator: {
82
+ filename: (pathData) => {
83
+ const resourcePath =
84
+ pathData.module.resourceResolveData?.path ||
85
+ pathData.module.resource;
86
+
87
+ const base = path.basename(
88
+ resourcePath,
89
+ path.extname(resourcePath)
90
+ );
91
+ const scoped = createScopedName(base, resourcePath);
92
+
93
+ return `images/${scoped}[ext]`;
94
+ },
95
+ publicPath: "/",
96
+ },
97
+ },
40
98
  ],
41
99
  },
42
100
  plugins: [
@@ -51,8 +109,23 @@ module.exports = {
51
109
  },
52
110
  ],
53
111
  }),
112
+ new MiniCssExtractPlugin({
113
+ filename: "[name].css",
114
+ }),
54
115
  ].filter(Boolean),
55
116
  resolve: {
56
117
  extensions: [".js", ".jsx", ".ts", ".tsx"],
57
118
  },
119
+ optimization: {
120
+ splitChunks: {
121
+ cacheGroups: {
122
+ styles: {
123
+ name: "styles",
124
+ type: "css/mini-extract",
125
+ chunks: "all",
126
+ enforce: true,
127
+ },
128
+ },
129
+ },
130
+ },
58
131
  };