# electron-react-template **Repository Path**: mscorlib/electron-react-template ## Basic Information - **Project Name**: electron-react-template - **Description**: electron+react 手动创建模板 - **Primary Language**: TypeScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-09-19 - **Last Updated**: 2024-01-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # create react app from scratch without CRA(create-react-app) # webpack support ### init ```bash yarn init ``` ### ### create webpack.react.js ```ts import HtmlWebpackPlugin from "html-webpack-plugin"; import * as path from "path"; import { Configuration as WebpackConfiguration } from "webpack"; import { Configuration as WebpackDevServerConfiguration } from "webpack-dev-server"; const rootPath = path.resolve(__dirname, "."); interface Configuration extends WebpackConfiguration { devServer?: WebpackDevServerConfiguration; } const config: Configuration = { resolve: { extensions: [".tsx", ".ts", ".js"], mainFields: ["main", "module", "browser"], }, entry: path.resolve(rootPath, "src/renderer", "index.tsx"), target: "electron-renderer", devtool: "source-map", module: { rules: [ { test: /\.(js|ts|tsx)$/, exclude: /node_modules/, include: /src/, use: { loader: "ts-loader", }, }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: "asset/resource", } ], }, devServer: { static: { directory: path.resolve(rootPath, "dist/renderer"), publicPath: "/", }, port: 5000, historyApiFallback: true, compress: true, }, output: { path: path.resolve(rootPath, "dist/renderer"), filename: "js/[name].js", }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(rootPath, "index.html") }), ], }; export default config; ``` # install react ``` yarn add react react-dom ``` ### config plugin in webpack.config.js ```ts import HtmlWebpackPlugin from "html-webpack-plugin"; import * as path from "path"; import { Configuration as WebpackConfiguration } from "webpack"; import { Configuration as WebpackDevServerConfiguration } from "webpack-dev-server"; const rootPath = path.resolve(__dirname, "."); interface Configuration extends WebpackConfiguration { devServer?: WebpackDevServerConfiguration; } const config: Configuration = { resolve: { extensions: [".tsx", ".ts", ".js"], mainFields: ["main", "module", "browser"], }, entry: path.resolve(rootPath, "src/renderer", "index.tsx"), target: "electron-renderer", devtool: "source-map", module: { rules: [ { test: /\.(js|ts|tsx)$/, exclude: /node_modules/, include: /src/, use: { loader: "ts-loader", }, }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: "asset/resource", }, { test: /\.less$/, use: [ { loader: 'style-loader' }, { loader: 'css-loader' }, { loader: 'less-loader' } ] } ], }, devServer: { static: { directory: path.resolve(rootPath, "dist/renderer"), publicPath: "/", }, port: 5000, historyApiFallback: true, compress: true, }, output: { path: path.resolve(rootPath, "dist/renderer"), filename: "js/[name].js", }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(rootPath, "index.html") }), ], }; export default config; ``` ### create a simple react App App.tsx ```jsx import React from 'react'; export default () => { return
Hello World.
} ``` index.tsx ```jsx import React from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; const rootElement = document.getElementById("root"); const root = createRoot(rootElement!); root.render(); ``` ### config models in webpack.react.js ```js module:{ rules:[{ test:/\.js$/, exclude:/node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env','@babel/preset-react'] } } }] } ``` # Typescript Support ### add typescript ```bash yarn add -D typescript ts-loader ts-node @types/node @types/react @types/react-dom @types/jest @types/webpack-dev-server @typescript-eslint/eslint-plugin @typescript-eslint/parser ``` ### add the tsconfig.json ``` npx tsc --init ``` or create a file: tsconfig.json ```json { "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ /* Projects */ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ "target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "lib": ["DOM","ESNext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ "module": "commonjs", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ // "declarationMap": true, /* Create sourcemaps for d.ts files. */ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ "outDir": "./dist", /* Specify an output folder for all emitted files. */ // "removeComments": true, /* Disable emitting comments. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ // "newLine": "crlf", /* Set the newline character for emitting files. */ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ /* Interop Constraints */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */ "strict": true, /* Enable all strict type-checking options. */ "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ "jsx": "react", } } ``` ### update webpack.react.js ```js module: { rules: [ { test: /\.(ts|tsx)$/, loader: 'ts-loader', exclude: /node_modules/ }, { test: /\.(js|jsx)$/, use: [ { loader: 'source-map-loader', }, { loader: 'babel-loader', }, ], exclude: /node_modules/, }, ], }, resolve: { extensions: ['.ts', '.tsx', '.js'], alias: { '@': path.resolve(__dirname, './src') } } ``` ### add HTML/CSS loaders ```bash yarn add -D css-loader html-loader sass sass-loader style-loader ``` ### update webpack.react.js ```js module: { rules: [ { // look for .css or .scss files test: /\.(css|scss)$/, // in the `src` directory use: [ { loader: 'style-loader', }, { loader: 'css-loader', }, { loader: 'sass-loader', options: { sourceMap: true, }, }, ], }, { test: new RegExp('.(' + fileExtensions.join('|') + ')$'), type: 'asset/resource', exclude: /node_modules/, // loader: 'file-loader', // options: { // name: '[name].[ext]', // }, }, { test: /\.html$/, loader: 'html-loader', exclude: /node_modules/, }, ], }, ``` ### add electron ```bash yarn add -D electron electron-builder ``` ### create webpack.elecron.ts ```ts import * as path from "path"; import { Configuration } from "webpack"; const rootPath = path.resolve(__dirname, "."); const config: Configuration = { resolve: { extensions: [".tsx", ".ts", ".js"], }, devtool: "source-map", entry: path.resolve(rootPath, "src/main", "main.ts"), target: "electron-main", module: { rules: [ { test: /\.(js|ts|tsx)$/, exclude: /node_modules/, include: /src/, use: { loader: "ts-loader", }, }, ], }, node: { __dirname: false, }, output: { path: path.resolve(rootPath, "dist"), filename: "[name].js", }, }; export default config; ``` ### create entrypoint for elecron ./scr/main/main.ts ```ts import { app, BrowserWindow, Menu, ipcMain } from "electron"; import * as path from "path"; import * as url from "url"; let mainWindow: Electron.BrowserWindow | null; function createWindow() { mainWindow = new BrowserWindow({ width: 1333, height: 768, backgroundColor: "#f2f2f2", frame: false, webPreferences: { nodeIntegration: true, contextIsolation: false, devTools: process.env.NODE_ENV !== "production", }, }); if (process.env.NODE_ENV === "development") { mainWindow.loadURL("http://localhost:5000"); } else { mainWindow.loadURL( url.format({ pathname: path.join(__dirname, "renderer/index.html"), protocol: "file:", slashes: true, }) ); } //devtools //mainWindow.webContents.openDevTools(); mainWindow.on("closed", () => { mainWindow = null; }); } //display default menusinWindowenu.setApplicationMenu(null); //min,max,close event ipcMain.on('control', (event, ...args) => { if (args[0] === 'min') { mainWindow?.minimize(); } else if (args[0] === 'max') { if (mainWindow?.isMaximized()) { mainWindow?.unmaximize(); } else { mainWindow?.maximize(); } } else if (args[0] === 'close') { console.log('close event'); mainWindow?.close(); } }); // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on("ready", createWindow); // Quit when all windows are closed. app.on("window-all-closed", () => { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== "darwin") { app.quit(); } }); app.on("activate", () => { // On OS X it"s common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (mainWindow === null) { createWindow(); } }); ``` ### write dev&prod scripts to package.json ```bash yarn add -D npm-run-all wait-on ``` ```json { "scripts": { "dev": "npm-run-all -p dev:react electron:serve", "electron:serve": "wait-on http-get://localhost:5000/ && yarn dev:electron", "dev:electron": "cross-env NODE_ENV=development webpack --config webpack.electron.ts --mode=development && yarn start:electron", "dev:react": "cross-env NODE_ENV=development webpack serve --config webpack.react.ts --mode=development", "start:electron": "electron .", "build": "npm-run-all build:electron build:react", "build:run": "npm-run-all build start:electron", "build:electron": "webpack --config webpack.electron.ts --mode=production", "build:react": "webpack --config webpack.react.ts --mode=production", "package": "npm-run-all build package:dist", "package:dist": "electron-builder --dir" }, } ``` ### 构建安装代理问题 用户目录下修改`.npmrc`文件 linux:~/.npmrc Windows:C:\Users.npmrc ``` registry=https://registry.npmmirror.com/ disturl=https://registry.npmmirror.com/-/binary/node/ sass_binary_site=https://registry.npmmirror.com/node-sass phantomjs_cdnurl=https://registry.npmmirror.com/phantomjs chromedriver_cdnurl=https://registry.npmmirror.com/-/binary/chromedriver/ operadriver_cdnurl=https://registry.npmmirror.com/-/binary/operadriver/ electron_mirror=https://registry.npmmirror.com/-/binary/electron/ electron_builder_binaries_mirror=https://registry.npmmirror.com/-/binary/electron-builder-binaries/ always-auth=false ``` ### dev ```bash yarn dev ``` ### prod ```bash yarn package ```