rebuiltron 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-present Arkellys
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # Rebuiltron
2
+
3
+ A tool made to easily build an offline **React/Electron** app using webpack.
4
+
5
+ ---
6
+
7
+ ## Why
8
+
9
+ The idea behind Rebuiltron was to migrate one of my project initially created with the deprecated [CRA](https://create-react-app.dev/) to a maintained tool configured with Electron in mind. As such, it has been developed using [react-scripts](https://github.com/facebook/create-react-app/tree/main/packages/react-scripts) as a base, but heavily edited and stripped of a lot of features.
10
+
11
+ > [!IMPORTANT]
12
+ > Since I made Rebuiltron specifically for one of my own projects, I only kept in the configuration what *this* project needed and nothing more. Except for the entry points and some SASS options, **Rebuiltron doesn't offer any configurable options**. If you are looking to create a new Electron/React app, or even migrating an existing CRA app, you should probably take a look at [electron-vite](https://electron-vite.org/) instead.
13
+
14
+
15
+ ## Features
16
+
17
+ Rebuiltron uses webpack with [SWC](https://swc.rs/) to compile JavaScript instead of [Babel](https://babeljs.io/).
18
+
19
+ - Support for multiple windows and preloads
20
+ - Development server taking care of starting Electron
21
+ - Production bundler for React and Electron code
22
+ - Support for React, JSX, SASS, ICSS
23
+
24
+ > [!WARNING]
25
+ > Rebuiltron **doesn't support**: Typescript, Flow, CSS Modules, Jest, and proxying.
26
+
27
+
28
+ ## Installation
29
+
30
+ ```shell
31
+ yarn add rebuiltron -D
32
+ ```
33
+
34
+ [**React**](https://react.dev/) and [**Electron**](https://www.electronjs.org/) are required dependencies you must install separately.
35
+
36
+ ## Configuration
37
+
38
+ The following documentation assume you already have a basic knowledge of how to use React and Electron.
39
+
40
+ ### Folder structure
41
+
42
+ Rebuiltron expects the following folder structure:
43
+
44
+ ```js
45
+ my-app
46
+ ├── electron/
47
+ │ └── // Main and preload files
48
+ ├── public/
49
+ │ └── // Renderer HTML file(s)
50
+ ├── src/
51
+ │ └── // Renderer JS file(s)
52
+ └── package.json
53
+ ```
54
+
55
+ ### Main entry
56
+ Set the main Electron entry in `package.json`:
57
+
58
+ ```json
59
+ {
60
+ "main": "electron/main.js",
61
+ }
62
+ ```
63
+
64
+ ### Scripts
65
+ Add Rebuiltron scripts:
66
+
67
+ ```json
68
+ {
69
+ "start": "rebuiltron start",
70
+ "build": "rebuiltron build",
71
+ }
72
+ ```
73
+
74
+ The `build` command will bundle your code so it's ready for production, but **it will not package it into a working app**. For this task, you need to use a tool such as [Electron Forge](https://www.electronforge.io/) or [electron-builder](https://www.electron.build/index.html).
75
+
76
+ ### Browserslist
77
+
78
+ Add your desired [browserslist](https://github.com/browserslist/browserslist) in `package.json`:
79
+
80
+ ```json
81
+ {
82
+ "browserslist": "last 2 electron major versions",
83
+ }
84
+ ```
85
+
86
+ ### Configuration file
87
+
88
+ At the root of your project, create a `rebuiltron.config.js` file.
89
+
90
+ #### Options:
91
+
92
+ | Option | Type | Required | Description |
93
+ | --- | :---: | :---: | --- |
94
+ | `renderers` | `object` | ✔️ | Renderer entries. It takes the name of the entries as keys and their paths as values. |
95
+ | `preloads` | `object` | ✔️ | Preload entries. It takes the name of the entries as keys and their paths as values. |
96
+ | `sassOptions` | `object` | ❌ | Custom SASS options for `sass-loader`. |
97
+ | `sassOptions.additionalData` | `object` | ❌ | Configuration of `additionalData`. |
98
+ | `sassOptions.additionalData.data` | `string` | ❌ | Data to prepend to SASS files. |
99
+ | `sassOptions.additionalData.exclude` | `Regex` | ❌ | Regex matching the files to exclude from `additionalData`. This is necessary to prevent an `@import loop` error. |
100
+
101
+ #### Example:
102
+
103
+ ```js
104
+ module.exports = {
105
+ renderers: {
106
+ index: "./src/index.js",
107
+ worker: "./src/worker.js"
108
+ },
109
+ preloads: {
110
+ worker: "./electron/preloads/worker.js"
111
+ },
112
+ sassOptions: {
113
+ additionalData: {
114
+ data: "@use \"styles/settings\" as *;",
115
+ exclude: /^src\\styles\\.*/
116
+ }
117
+ }
118
+ };
119
+ ```
120
+
121
+ > [!NOTE]
122
+ > Your renderers entry file(s) will be automatically injected into your HTML file(s). Thus, make sure you have a corresponding HTML file in the `public` folder for each entry (using the same name).
123
+
124
+ ### Environment variables
125
+
126
+ When the development server is running, Rebuiltron exposes a `DEV_LOCAL_URL` variable that you can access on your main process using `process.env.DEV_LOCAL_URL`.
127
+
128
+ ```js
129
+ if(!app.isPackaged) {
130
+ appWindow.loadURL(`${process.env.DEV_LOCAL_URL}/index.html`);
131
+ }
132
+ ```
133
+
134
+ ## Usage
135
+
136
+ To run the development server:
137
+
138
+ ```shell
139
+ yarn start
140
+ ```
141
+
142
+ To create the production build:
143
+
144
+ ```shell
145
+ yarn build
146
+ ```
147
+
148
+ ## Contributing and feature requests
149
+
150
+ I made Rebuiltron specifically for a project of mine, and for that reason, **I have no plans to add new features nor to accept contributions**, unless required for this project in particular. If you wish to use Rebuiltron but need something that is not available in the current configuration, please feel free to create a fork and change the code to your needs.
151
+
152
+ ## License
153
+ MIT
package/bin/index.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { exitProcessWithError } = require("../helpers/utils.js");
4
+
5
+
6
+ const args = process.argv.slice(2);
7
+ const [script] = args;
8
+
9
+ const scripts = {
10
+ start: () => require("../scripts/start.js"),
11
+ build: () => require("../scripts/build.js")
12
+ };
13
+
14
+ scripts[script]?.() || exitProcessWithError(`Unknown command: \`${script}\``);
package/buildApp.js ADDED
@@ -0,0 +1,33 @@
1
+ const fsExtra = require("fs-extra");
2
+ const clearConsole = require("react-dev-utils/clearConsole");
3
+ const webpack = require("webpack");
4
+ const formatWebpackMessages = require("react-dev-utils/formatWebpackMessages");
5
+ const { isEmpty, head } = require("lodash");
6
+
7
+ const paths = require("./helpers/paths");
8
+ const webpackConfig = require("./webpack.config");
9
+ const spinnies = require("./helpers/spinnies");
10
+
11
+
12
+ module.exports = (previousFileSizes) => {
13
+ fsExtra.emptyDirSync(paths.appBuild);
14
+
15
+ clearConsole();
16
+ spinnies.add("build", { text: "Creating production build" });
17
+
18
+ const compiler = webpack(webpackConfig);
19
+
20
+ return new Promise((resolve, reject) => {
21
+ compiler.run((error, stats) => {
22
+ const statsData = error
23
+ ? { errors: [error?.message || error], warnings: [] }
24
+ : stats.toJson({ all: false, warnings: true, errors: true });
25
+
26
+ const { errors, warnings } = formatWebpackMessages(statsData);
27
+
28
+ return !isEmpty(errors)
29
+ ? reject(["Failed to compile.\n", head(errors)])
30
+ : resolve({ stats, previousFileSizes, warnings });
31
+ });
32
+ });
33
+ };
package/checkSetup.js ADDED
@@ -0,0 +1,63 @@
1
+ const fs = require("fs");
2
+
3
+ const detect = require("detect-port");
4
+
5
+ const { isEnvProduction } = require("./helpers/environment");
6
+ const rebuiltronConfig = require("./rebuiltronConfig");
7
+ const { resolveApp } = require("./helpers/utils");
8
+
9
+ /**
10
+ * Checks whether the default port is available, otherwise resolves with the next available port.
11
+ */
12
+
13
+ const checkPort = new Promise(async (resolve) => {
14
+ if (isEnvProduction) resolve();
15
+
16
+ const defaultPort = 3000;
17
+ const nextAvailablePort = await detect(defaultPort);
18
+
19
+ resolve(nextAvailablePort);
20
+ });
21
+
22
+ /**
23
+ * Checks whether Electron's entry point is set.
24
+ */
25
+
26
+ const checkEntryPoint = new Promise((resolve, reject) => {
27
+ const packageJsonPath = resolveApp("package.json");
28
+ const { main } = require(packageJsonPath);
29
+
30
+ if (!main) {
31
+ reject([
32
+ "No entry point found for Electron.",
33
+ "Please add a `main` field in `package.json`."
34
+ ]);
35
+ }
36
+
37
+ resolve();
38
+ });
39
+
40
+ /**
41
+ * Checks whether Rebuiltron's config file exists and contains the required fields.
42
+ */
43
+
44
+ const checkAppConfig = new Promise((resolve, reject) => {
45
+ const configPath = resolveApp(rebuiltronConfig.configFileName);
46
+
47
+ if (!fs.existsSync(configPath)) {
48
+ reject([
49
+ "Not configuration file found.",
50
+ `Please create a \`${rebuiltronConfig.configFileName}\` file.`
51
+ ]);
52
+ }
53
+
54
+ const { preloads, renderers } = require(configPath);
55
+
56
+ if (!preloads) reject("Configuration error: `preloads` field is required.");
57
+ if (!renderers) reject("Configuration error: `renderers` field is required.");
58
+
59
+ resolve();
60
+ });
61
+
62
+
63
+ module.exports = Promise.all([checkPort, checkEntryPoint, checkAppConfig]);
@@ -0,0 +1,121 @@
1
+ const { createHash } = require("crypto");
2
+
3
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
4
+ const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
5
+ const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");
6
+ const TerserPlugin = require("terser-webpack-plugin");
7
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
8
+ const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
9
+ const ModuleNotFoundPlugin = require("react-dev-utils/ModuleNotFoundPlugin");
10
+ const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
11
+ const CopyPlugin = require("copy-webpack-plugin");
12
+
13
+ const paths = require("../helpers/paths");
14
+ const { shouldUseSourceMap, isEnvDevelopment, isEnvProduction } = require("../helpers/environment");
15
+ const { emptyOr } = require("../helpers/utils");
16
+ const imageLoaders = require("../loaders/images");
17
+ const styleLoaders = require("../loaders/style");
18
+ const javascriptLoaders = require("../loaders/javascript");
19
+ const rebuiltronConfig = require("../rebuiltronConfig");
20
+
21
+
22
+ module.exports = {
23
+ stats: "errors-warnings",
24
+ bail: isEnvProduction,
25
+ output: {
26
+ path: paths.appBuild,
27
+ pathinfo: isEnvDevelopment,
28
+ publicPath: paths.basePath
29
+ },
30
+ cache: {
31
+ type: "filesystem",
32
+ version: createHash("md5").update(process.env.NODE_ENV.toString()).digest("hex"), // I don't know what I'm doing...
33
+ cacheDirectory: paths.appWebpackCache,
34
+ store: "pack",
35
+ buildDependencies: {
36
+ defaultWebpack: ["webpack/lib/"],
37
+ config: [__filename]
38
+ }
39
+ },
40
+ infrastructureLogging: {
41
+ level: "none"
42
+ },
43
+ resolve: {
44
+ modules: ["node_modules", paths.appNodeModules, paths.appSrc],
45
+ extensions: [".web.js", ".js", ".json", ".jsx", ".node"],
46
+ alias: {
47
+ src: paths.appSrc
48
+ }
49
+ },
50
+ optimization: {
51
+ minimize: isEnvProduction,
52
+ minimizer: [
53
+ new TerserPlugin({
54
+ terserOptions: {
55
+ parse: {
56
+ ecma: 8
57
+ },
58
+ compress: {
59
+ ecma: 5,
60
+ warnings: false,
61
+ comparisons: false,
62
+ inline: 2
63
+ },
64
+ output: {
65
+ ecma: 5,
66
+ comments: false
67
+ }
68
+ }
69
+ }),
70
+ new CssMinimizerPlugin()
71
+ ]
72
+ },
73
+ module: {
74
+ strictExportPresence: true,
75
+ rules: [
76
+ // Handles `node_modules` packages that contain sourcemaps
77
+ ...emptyOr(shouldUseSourceMap, [{
78
+ enforce: "pre",
79
+ exclude: /@babel(?:\/|\\{1,2})runtime/,
80
+ test: /\.(js|mjs|jsx|ts|tsx|css)$/,
81
+ loader: require.resolve("source-map-loader")
82
+ }]),
83
+ {
84
+ oneOf: [
85
+ ...imageLoaders,
86
+ ...javascriptLoaders,
87
+ ...styleLoaders,
88
+ {
89
+ // Makes sure the assets get served by `WebpackDevServer`
90
+ exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
91
+ type: "asset/resource"
92
+ }
93
+ ]
94
+ }
95
+ ]
96
+ },
97
+ plugins: [
98
+ ...emptyOr(isEnvProduction, [
99
+ new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]), // Injects scripts into HTML
100
+ new MiniCssExtractPlugin({
101
+ filename: `${rebuiltronConfig.buildDirs.css}/[name].[contenthash:8].css`,
102
+ chunkFilename: `${rebuiltronConfig.buildDirs.css}/[name].[contenthash:8].chunk.css`
103
+ }),
104
+ new CopyPlugin({
105
+ patterns: [{
106
+ from: paths.appPublic,
107
+ to: paths.appBuild,
108
+ globOptions: {
109
+ ignore: ["**/*.html"]
110
+ }
111
+ }]
112
+ })
113
+ ]),
114
+ ...emptyOr(isEnvDevelopment, [
115
+ new ReactRefreshWebpackPlugin({ overlay: false }),
116
+ new CaseSensitivePathsPlugin() // Detects case errors in import paths
117
+ ]),
118
+ new ModuleNotFoundPlugin(paths.appPath) // Gives some necessary context to module not found errors
119
+ ],
120
+ performance: false // Performance processing is already handled via `FileSizeReporter`
121
+ };
@@ -0,0 +1,17 @@
1
+ const paths = require("../helpers/paths");
2
+ const rebuiltronConfig = require("../rebuiltronConfig");
3
+ const baseConfig = require("./base");
4
+
5
+
6
+ module.exports = {
7
+ ...baseConfig,
8
+ mode: "production",
9
+ target: "electron-main",
10
+ entry: {
11
+ [rebuiltronConfig.buildFileNames.main]: paths.electronMain
12
+ },
13
+ output: {
14
+ ...baseConfig.output,
15
+ filename: `${rebuiltronConfig.buildDirs.js}/[name].js`
16
+ }
17
+ };
@@ -0,0 +1,22 @@
1
+ const { mapKeys } = require("lodash");
2
+
3
+ const paths = require("../helpers/paths");
4
+ const rebuiltronConfig = require("../rebuiltronConfig");
5
+ const baseConfig = require("./base");
6
+
7
+
8
+ const { preloads } = require(paths.appConfig);
9
+
10
+
11
+ module.exports = {
12
+ ...baseConfig,
13
+ mode: "production",
14
+ target: "electron-preload",
15
+ entry: mapKeys(preloads, (_value, entryName) => (
16
+ `${rebuiltronConfig.buildFileNames.preload}.${entryName}`)
17
+ ),
18
+ output: {
19
+ ...baseConfig.output,
20
+ filename: `${rebuiltronConfig.buildDirs.js}/[name].js`
21
+ }
22
+ };
@@ -0,0 +1,53 @@
1
+ const path = require("path");
2
+
3
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
4
+ const { keys } = require("lodash");
5
+
6
+ const paths = require("../helpers/paths");
7
+ const { isEnvProduction, shouldUseSourceMap } = require("../helpers/environment");
8
+ const { emptyOr } = require("../helpers/utils");
9
+ const rebuiltronConfig = require("../rebuiltronConfig");
10
+ const baseConfig = require("./base");
11
+
12
+
13
+ const { renderers } = require(paths.appConfig);
14
+
15
+
16
+ module.exports = {
17
+ ...baseConfig,
18
+ mode: isEnvProduction ? "production" : "development",
19
+ target: "browserslist",
20
+ devtool: shouldUseSourceMap && (isEnvProduction ? "source-map" : "cheap-module-source-map"),
21
+ entry: renderers,
22
+ output: {
23
+ ...baseConfig.output,
24
+ filename: `${rebuiltronConfig.buildDirs.js}/${isEnvProduction ? "[name].[contenthash:8].js" : "[name].bundle.js"}`,
25
+ chunkFilename: `${rebuiltronConfig.buildDirs.js}/${isEnvProduction ? "[name].[contenthash:8].chunk.js" : "[name].chunk.js"}`,
26
+ assetModuleFilename: `${rebuiltronConfig.buildDirs.media}/[name].[hash][ext]`
27
+ },
28
+ plugins: [
29
+ ...baseConfig.plugins,
30
+ ...keys(renderers).map(renderer => (
31
+ new HtmlWebpackPlugin({
32
+ inject: true,
33
+ template: path.join(paths.appPublic, `${renderer}.html`),
34
+ filename: `${renderer}.html`,
35
+ chunks: [renderer],
36
+ ...emptyOr(isEnvProduction, {
37
+ minify: {
38
+ removeComments: true,
39
+ collapseWhitespace: true,
40
+ removeRedundantAttributes: true,
41
+ useShortDoctype: true,
42
+ removeEmptyAttributes: true,
43
+ removeStyleLinkTypeAttributes: true,
44
+ keepClosingSlash: true,
45
+ minifyJS: true,
46
+ minifyCSS: true,
47
+ minifyURLs: true
48
+ }
49
+ })
50
+ })
51
+ ))
52
+ ]
53
+ };
@@ -0,0 +1,75 @@
1
+ const { spawn } = require("child_process");
2
+ const path = require("path");
3
+ const fs = require("fs");
4
+
5
+ const webpack = require("webpack");
6
+ const WebpackDevServer = require("webpack-dev-server");
7
+ const clearConsole = require("react-dev-utils/clearConsole");
8
+ const { bold } = require("colorette");
9
+
10
+ const webpackConfig = require("./webpack.config");
11
+ const devServerConfig = require("./webpackDevServer.config");
12
+ const { exitProcessWithError } = require("./helpers/utils");
13
+ const spinnies = require("./helpers/spinnies");
14
+
15
+
16
+ let isElectronStarted = false;
17
+
18
+
19
+ const _startElectron = (port) => {
20
+ spinnies.add("electron", { text: "Starting Electron" });
21
+
22
+ try {
23
+ const electronPath = require.resolve("electron");
24
+ const electronModulePath = path.dirname(electronPath);
25
+ const pathFile = path.join(electronModulePath, "path.txt");
26
+ const executablePath = fs.readFileSync(pathFile, "utf-8");
27
+ const electronExtPath = path.join(electronModulePath, "dist", executablePath);
28
+
29
+ const electronProcess = spawn(electronExtPath, ["."], {
30
+ stdio: "inherit",
31
+ env: {
32
+ ...process.env,
33
+ DEV_LOCAL_URL: `http://localhost:${port}`
34
+ }
35
+ });
36
+
37
+ electronProcess.on("close", process.exit);
38
+
39
+ spinnies.succeed("devServer", { text: `Development server running on port ${bold(port)}` });
40
+ spinnies.succeed("electron", { text: "Electron started\n" });
41
+
42
+ isElectronStarted = true;
43
+
44
+ } catch (error) {
45
+ exitProcessWithError({
46
+ message: [
47
+ "An error occured while starting Electron.",
48
+ "Please make sure the dependencies `electron` is installed."
49
+ ],
50
+ stack: error.stack || error
51
+ });
52
+ }
53
+ };
54
+
55
+ module.exports = (port) => {
56
+ const compiler = webpack(webpackConfig);
57
+
58
+ // Starting (re)compiling
59
+
60
+ compiler.hooks.invalid.tap("invalid", () => {
61
+ clearConsole();
62
+ spinnies.add("compile", { text: "Compiling..." });
63
+ });
64
+
65
+ // Finished (re)compiling
66
+
67
+ compiler.hooks.done.tap("done", () => {
68
+ clearConsole();
69
+ spinnies.remove("compile");
70
+
71
+ if (!isElectronStarted) _startElectron(port);
72
+ });
73
+
74
+ return new WebpackDevServer({ ...devServerConfig, port }, compiler);
75
+ };
@@ -0,0 +1,6 @@
1
+ const isEnvDevelopment = process.env.NODE_ENV === "development";
2
+ const isEnvProduction = process.env.NODE_ENV === "production";
3
+ const shouldUseSourceMap = isEnvDevelopment;
4
+
5
+
6
+ module.exports = { isEnvDevelopment, isEnvProduction, shouldUseSourceMap };
@@ -0,0 +1,55 @@
1
+ const { red, green, yellow, black } = require("colorette");
2
+ const { isString } = require("lodash");
3
+
4
+
5
+ /**
6
+ * @typedef {(string|string[])} LogMessage
7
+ */
8
+
9
+ /**
10
+ * Formats a message into a single string.
11
+ * @param {LogMessage} message - Message to format
12
+ * @returns {string} Formatted message
13
+ */
14
+
15
+ const _formatMessage = (message) => {
16
+ if (isString(message)) return message;
17
+ return message.join("\n");
18
+ };
19
+
20
+
21
+ /**
22
+ * Logs a message in red.
23
+ * @param {LogMessage} message - Message to log
24
+ */
25
+
26
+ const logError = (message) => console.log(red(`\n${_formatMessage(message)}\n`));
27
+
28
+ /**
29
+ * Logs a message in green.
30
+ * @param {LogMessage} message - Message to log
31
+ */
32
+
33
+ const logSuccess = (message) => console.log(green(`\n${_formatMessage(message)}\n`));
34
+
35
+ /**
36
+ * Logs a message in yellow.
37
+ * @param {LogMessage} message - Message to log
38
+ */
39
+
40
+ const logWarning = (message) => console.log(yellow(`\n${_formatMessage(message)}\n`));
41
+
42
+ /**
43
+ * Logs a message in black.
44
+ * @param {string} message - Message to log
45
+ */
46
+
47
+ const logInfo = (message) => console.log(black(`${message}\n`));
48
+
49
+
50
+ module.exports = {
51
+ error: logError,
52
+ success: logSuccess,
53
+ warning: logWarning,
54
+ info: logInfo
55
+ };
@@ -0,0 +1,22 @@
1
+ const rebuiltronConfig = require("../rebuiltronConfig.js");
2
+ const { isEnvDevelopment } = require("./environment.js");
3
+ const { resolveApp } = require("./utils.js");
4
+
5
+
6
+ const packageJsonPath = resolveApp("package.json");
7
+ const packageJson = require(packageJsonPath);
8
+
9
+
10
+ module.exports = {
11
+ appConfig: resolveApp(rebuiltronConfig.configFileName),
12
+ electronMain: resolveApp(packageJson.main),
13
+ appPath: resolveApp("."),
14
+ appBuild: resolveApp(rebuiltronConfig.appDirs.build),
15
+ appPublic: resolveApp(rebuiltronConfig.appDirs.public),
16
+ appPackageJson: packageJsonPath,
17
+ appSrc: resolveApp(rebuiltronConfig.appDirs.appSrc),
18
+ electronSrc: resolveApp(rebuiltronConfig.appDirs.electronSrc),
19
+ appNodeModules: resolveApp("node_modules"),
20
+ appWebpackCache: resolveApp("node_modules/.cache"),
21
+ basePath: isEnvDevelopment ? "/" : "./"
22
+ };
@@ -0,0 +1,7 @@
1
+ const Spinnies = require("spinnies");
2
+
3
+
4
+ let spinnies = null;
5
+ spinnies ??= new Spinnies({ spinnerColor: "cyan", color: "blue", succeedColor: "white" });
6
+
7
+ module.exports = spinnies;
@@ -0,0 +1,48 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+
4
+ const { isArray } = require("lodash");
5
+
6
+ const log = require("./logger");
7
+ const spinnies = require("./spinnies");
8
+
9
+
10
+ const _appDirectory = fs.realpathSync(process.cwd());
11
+
12
+
13
+ /**
14
+ * Resolves a path relative to the app directory.
15
+ * @param {string} relativePath - Path to resolve
16
+ * @returns {string} Resolved path
17
+ */
18
+
19
+ const resolveApp = (relativePath) => path.resolve(_appDirectory, relativePath);
20
+
21
+ /**
22
+ * Logs error message and stack (if available) and exit the running process.
23
+ * @param {(Error|import("./logger").LogMessage)} error - Error to log
24
+ */
25
+
26
+ const exitProcessWithError = (error) => {
27
+ spinnies.stopAll("fail");
28
+
29
+ log.error(error?.message || error);
30
+ if (error.stack) log.info(error.stack);
31
+
32
+ process.exit(1);
33
+ };
34
+
35
+ /**
36
+ * Returns the given value when the condition is truthy, otherwise an empty element of the same type.
37
+ * @param {any} condition - Condition determining if the value should be returned
38
+ * @param {(object|Array)} value - Value to return
39
+ * @returns {(object|Array)} Value or empty element
40
+ */
41
+
42
+ const emptyOr = (condition, value) => {
43
+ const empty = isArray(value) ? [] : {};
44
+ return condition ? value : empty;
45
+ };
46
+
47
+
48
+ module.exports = { resolveApp, exitProcessWithError, emptyOr };
@@ -0,0 +1,24 @@
1
+ module.exports = [
2
+ {
3
+ test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
4
+ type: "asset",
5
+ parser: {
6
+ dataUrlCondition: {
7
+ maxSize: 10000
8
+ }
9
+ }
10
+ },
11
+ {
12
+ test: /\.svg$/,
13
+ issuer: /\.jsx?$/,
14
+ use: [{
15
+ loader: require.resolve("@svgr/webpack"),
16
+ options: {
17
+ prettier: false,
18
+ svgo: false,
19
+ titleProp: true,
20
+ ref: true
21
+ }
22
+ }]
23
+ }
24
+ ];
@@ -0,0 +1,28 @@
1
+ const paths = require("../helpers/paths");
2
+ const { shouldUseSourceMap, isEnvProduction } = require("../helpers/environment");
3
+
4
+
5
+ module.exports = [
6
+ {
7
+ test: /\.(js|jsx)$/,
8
+ exclude: [paths.appNodeModules],
9
+ use: {
10
+ loader: require.resolve("swc-loader"),
11
+ options: {
12
+ jsc: {
13
+ parser: {
14
+ jsx: true
15
+ },
16
+ transform: {
17
+ react: {
18
+ refresh: true,
19
+ runtime: "automatic"
20
+ }
21
+ }
22
+ },
23
+ minify: isEnvProduction,
24
+ sourceMaps: shouldUseSourceMap
25
+ }
26
+ }
27
+ }
28
+ ];
@@ -0,0 +1,98 @@
1
+ const path = require("path");
2
+
3
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
4
+
5
+ const paths = require("../helpers/paths");
6
+ const { shouldUseSourceMap, isEnvDevelopment, isEnvProduction } = require("../helpers/environment");
7
+ const { emptyOr } = require("../helpers/utils");
8
+ const rebuiltronConfig = require("../rebuiltronConfig");
9
+
10
+
11
+ const { sassOptions } = require(paths.appConfig);
12
+
13
+
14
+ const _publicPath = rebuiltronConfig.buildDirs.css
15
+ .split("/")
16
+ .reduce(path => (path + "../"), "");
17
+
18
+ const _getBaseStyleLoaders = () => ([
19
+ ...emptyOr(isEnvDevelopment, [require.resolve("style-loader")]),
20
+ ...emptyOr(isEnvProduction, [{
21
+ loader: MiniCssExtractPlugin.loader,
22
+ options: { publicPath: _publicPath } // Locates HTML files from bundled `.css`
23
+ }]),
24
+ {
25
+ loader: require.resolve("css-loader"),
26
+ options: {
27
+ importLoaders: 3,
28
+ sourceMap: shouldUseSourceMap,
29
+ modules: {
30
+ mode: "icss"
31
+ }
32
+ }
33
+ },
34
+ {
35
+ loader: require.resolve("postcss-loader"),
36
+ options: {
37
+ postcssOptions: {
38
+ ident: "postcss",
39
+ config: false,
40
+ plugins: [
41
+ "postcss-flexbugs-fixes",
42
+ [
43
+ "postcss-preset-env",
44
+ {
45
+ autoprefixer: {
46
+ flexbox: "no-2009"
47
+ },
48
+ stage: 3
49
+ }
50
+ ],
51
+ "postcss-normalize"
52
+ ]
53
+ },
54
+ sourceMap: shouldUseSourceMap
55
+ }
56
+ }
57
+ ]);
58
+
59
+
60
+ module.exports = [
61
+ {
62
+ test: /\.css$/,
63
+ use: _getBaseStyleLoaders({ importLoaders: 1 }),
64
+ sideEffects: true // https://github.com/webpack/webpack/issues/6571
65
+ },
66
+ {
67
+ test: /\.(scss|sass)$/,
68
+ use: [
69
+ ..._getBaseStyleLoaders({ importLoaders: 3 }),
70
+ {
71
+ loader: require.resolve("resolve-url-loader"),
72
+ options: {
73
+ sourceMap: shouldUseSourceMap,
74
+ root: paths.appSrc
75
+ }
76
+ },
77
+ {
78
+ loader: require.resolve("sass-loader"),
79
+ options: {
80
+ sourceMap: true,
81
+ ...emptyOr(sassOptions?.additionalData, {
82
+ additionalData: (content, loaderContext) => {
83
+ const { data, exclude } = sassOptions.additionalData;
84
+ const { resourcePath, rootContext } = loaderContext;
85
+
86
+ const relativeFilePath = path.relative(rootContext, resourcePath);
87
+ const isExcluded = relativeFilePath.match(exclude);
88
+
89
+ // Add `additionalData` on non-excluded files
90
+ return isExcluded ? content : data + content;
91
+ }
92
+ })
93
+ }
94
+ }
95
+ ],
96
+ sideEffects: true // https://github.com/webpack/webpack/issues/6571
97
+ }
98
+ ];
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "rebuiltron",
3
+ "version": "1.0.0",
4
+ "author": "Arkellys",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "rebuiltron": "bin/index.js"
8
+ },
9
+ "scripts": {
10
+ "lint": "eslint \"**/*.js\""
11
+ },
12
+ "devDependencies": {
13
+ "eslint-config-arklint": "^1.0.2"
14
+ },
15
+ "dependencies": {
16
+ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
17
+ "@svgr/webpack": "^8.0.1",
18
+ "@swc/core": "^1.3.76",
19
+ "browserslist": "^4.18.1",
20
+ "case-sensitive-paths-webpack-plugin": "^2.4.0",
21
+ "colorette": "^2.0.20",
22
+ "copy-webpack-plugin": "^11.0.0",
23
+ "css-loader": "^6.5.1",
24
+ "css-minimizer-webpack-plugin": "^5.0.1",
25
+ "detect-port": "^1.5.1",
26
+ "fs-extra": "^11.1.1",
27
+ "html-webpack-plugin": "^5.5.0",
28
+ "lodash": "^4.17.21",
29
+ "mini-css-extract-plugin": "^2.4.5",
30
+ "postcss": "^8.4.4",
31
+ "postcss-flexbugs-fixes": "^5.0.2",
32
+ "postcss-loader": "^7.3.3",
33
+ "postcss-normalize": "^10.0.1",
34
+ "postcss-preset-env": "^9.1.1",
35
+ "react-dev-utils": "^12.0.1",
36
+ "react-refresh": "^0.14.0",
37
+ "resolve-url-loader": "^5.0.0",
38
+ "sass-loader": "^13.3.2",
39
+ "source-map-loader": "^4.0.1",
40
+ "spinnies": "^0.5.1",
41
+ "style-loader": "^3.3.1",
42
+ "swc-loader": "^0.2.3",
43
+ "terser-webpack-plugin": "^5.2.5",
44
+ "webpack": "^5.64.4",
45
+ "webpack-dev-server": "^4.6.0"
46
+ },
47
+ "peerDependencies": {
48
+ "electron": ">=25.0.0",
49
+ "react": ">=18.0.0",
50
+ "react-dom": ">=18.0.0"
51
+ },
52
+ "eslintConfig": {
53
+ "extends": "arklint"
54
+ },
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/Arkellys/rebuiltron.git"
58
+ },
59
+ "homepage": "https://github.com/Arkellys/rebuiltron#readme"
60
+ }
@@ -0,0 +1,18 @@
1
+ module.exports = {
2
+ configFileName: "rebuiltron.config.js",
3
+ buildDirs: {
4
+ js: "static/js",
5
+ media: "static/media",
6
+ css: "static/css"
7
+ },
8
+ buildFileNames: {
9
+ main: "electron.main",
10
+ preload: "electron.preload"
11
+ },
12
+ appDirs: {
13
+ public: "public",
14
+ electronSrc: "electron",
15
+ appSrc: "src",
16
+ build: "build"
17
+ }
18
+ };
@@ -0,0 +1,48 @@
1
+ process.env.BABEL_ENV = "production";
2
+ process.env.NODE_ENV = "production";
3
+
4
+
5
+ const { measureFileSizesBeforeBuild, printFileSizesAfterBuild } = require("react-dev-utils/FileSizeReporter");
6
+ const { isEmpty } = require("lodash");
7
+ const clearConsole = require("react-dev-utils/clearConsole");
8
+
9
+ const checkSetup = require("../checkSetup");
10
+ const paths = require("../helpers/paths");
11
+ const { exitProcessWithError } = require("../helpers/utils");
12
+ const log = require("../helpers/logger");
13
+ const spinnies = require("../helpers/spinnies");
14
+
15
+
16
+ process.on("unhandledRejection", exitProcessWithError);
17
+
18
+ const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
19
+ const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
20
+
21
+ checkSetup
22
+ .then(() => measureFileSizesBeforeBuild(paths.appBuild))
23
+ .then(previousFileSizes => {
24
+ const buildApp = require("../buildApp");
25
+ return buildApp(previousFileSizes);
26
+ })
27
+ .then(({ stats, previousFileSizes, warnings }) => {
28
+ spinnies.remove("build");
29
+ clearConsole();
30
+
31
+ isEmpty(warnings)
32
+ ? log.success("Compiled successfully!")
33
+ : log.warning(["Compiled with warnings.\n", warnings.join("\n\n")]);
34
+
35
+ console.log("File sizes after gzip:\n");
36
+
37
+ printFileSizesAfterBuild(
38
+ stats,
39
+ previousFileSizes,
40
+ paths.appBuild,
41
+ WARN_AFTER_BUNDLE_GZIP_SIZE,
42
+ WARN_AFTER_CHUNK_GZIP_SIZE
43
+ );
44
+
45
+ console.log();
46
+ process.exit(0);
47
+ })
48
+ .catch(exitProcessWithError);
@@ -0,0 +1,29 @@
1
+ process.env.BABEL_ENV = "development";
2
+ process.env.NODE_ENV = "development";
3
+
4
+
5
+ const clearConsole = require("react-dev-utils/clearConsole");
6
+
7
+ const checkSetup = require("../checkSetup");
8
+ const { exitProcessWithError } = require("../helpers/utils");
9
+ const spinnies = require("../helpers/spinnies");
10
+
11
+
12
+ process.on("unhandledRejection", exitProcessWithError);
13
+
14
+ checkSetup.then(([port]) => {
15
+ const createDevServer = require("../createDevServer");
16
+ const devServer = createDevServer(port);
17
+
18
+ clearConsole();
19
+ spinnies.add("devServer", { text: "Starting the development server" });
20
+
21
+ devServer.start();
22
+
23
+ ["SIGINT", "SIGTERM"].forEach((sig) => {
24
+ process.on(sig, () => {
25
+ devServer.close();
26
+ process.exit();
27
+ });
28
+ });
29
+ }).catch(exitProcessWithError);
@@ -0,0 +1,7 @@
1
+ const { isEnvDevelopment } = require("./helpers/environment");
2
+ const rendererConfig = require("./configurations/renderers");
3
+ const preloadConfig = require("./configurations/preloads");
4
+ const mainConfig = require("./configurations/main");
5
+
6
+
7
+ module.exports = isEnvDevelopment ? rendererConfig : [rendererConfig, mainConfig, preloadConfig];
@@ -0,0 +1,32 @@
1
+ const evalSourceMapMiddleware = require("react-dev-utils/evalSourceMapMiddleware");
2
+ const ignoredFiles = require("react-dev-utils/ignoredFiles");
3
+ const redirectServedPath = require("react-dev-utils/redirectServedPathMiddleware");
4
+
5
+ const paths = require("./helpers/paths");
6
+
7
+
8
+ module.exports = {
9
+ compress: true,
10
+ static: {
11
+ directory: paths.appPublic,
12
+ publicPath: [paths.basePath],
13
+ watch: {
14
+ ignored: ignoredFiles(paths.appSrc)
15
+ }
16
+ },
17
+ client: {
18
+ overlay: {
19
+ errors: true,
20
+ warnings: false
21
+ }
22
+ },
23
+ historyApiFallback: {
24
+ disableDotRule: true, // Supports route with dots
25
+ index: paths.basePath
26
+ },
27
+ setupMiddlewares: (middlewares, devServer) => ([
28
+ ...middlewares,
29
+ evalSourceMapMiddleware(devServer), // Fetches source contents from webpack for the error overlay
30
+ redirectServedPath(paths.basePath) // Redirects to `basePath` if url not match
31
+ ])
32
+ };