Lesson 08-Electron Project Architecture and Engineering

Project Architecture Design

Modular and Component-Based Design Principles

Modular Architecture Design

src/
├── modules/                # Feature modules directory
│   ├── auth/               # Authentication module
│   │   ├── actions.ts      # Redux actions
│   │   ├── reducer.ts      # Redux reducer
│   │   ├── api.ts          # API encapsulation
│   │   └── components/     # Module components
│   ├── settings/           # Settings module
│   └── ...                 # Other modules
├── core/                   # Core functionality
│   ├── ipc/                # IPC communication encapsulation
│   ├── window/             # Window management
│   └── utils/              # Utility functions
└── shared/                 # Shared code
    ├── components/         # Global components
    ├── hooks/              # Custom hooks
    └── styles/             # Global styles

Component-Based Design Specifications

  1. UI Component Categories:
    • Container Components: Connect state and UI
    • Presentational Components: Pure UI rendering
    • Layout Components: Page structure components
  2. Component Design Principles:
    • Single Responsibility Principle
    • Controlled Component Pattern
    • Explicitly Defined Props Interfaces
    • Default Props and Type Checking

Separation of Responsibilities Between Main and Renderer Processes

Responsibility Matrix

Functional AreaMain Process ResponsibilitiesRenderer Process Responsibilities
Application LifecycleManage app startup/shutdownHandle UI events
Window ManagementCreate/destroy windowsUI interactions within windows
System IntegrationFile system access, native API callsDisplay system status
Data PersistenceDatabase operations, file read/writeForm data collection
Network CommunicationWebSocket connection managementInitiate API requests
Security ControlSensitive operation validationCollect user input

Communication Protocol Design

// ipcProtocol.ts - Communication protocol definition
export interface IPCProtocol {
  // Main process → Renderer process
  'notification:show': (payload: { title: string; body: string }) => void;

  // Renderer process → Main process
  'file:open': (payload: { path: string }) => Promise<{ content: string }>;

  // Bidirectional communication
  'data:sync': {
    request: (payload: { version: number }) => void;
    response: (payload: { data: any; version: number }) => void;
  };
}

State Management Architecture Design

Redux Integration Solution

// store/configureStore.ts
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import authReducer from '../modules/authSlice';
import api from '../services/api';

export const store = configureStore({
  reducer: {
    auth: authReducer,
    [api.reducerPath]: api.reducer
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(api.middleware)
});

setupListeners(store.dispatch);

// Typed hooks
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

MobX Integration Solution

// stores/AuthStore.ts
import { makeAutoObservable } from 'mobx';

class AuthStore {
  user = null;
  token = '';

  constructor() {
    makeAutoObservable(this);
  }

  login(credentials: { username: string; password: string }) {
    // Async login logic
  }

  logout() {
    this.user = null;
    this.token = '';
  }
}

export const authStore = new AuthStore();

Routing Architecture Design

Frontend Routing Configuration

// routers/AppRouter.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import MainLayout from '../layouts/MainLayout';
import Home from '../pages/Home';
import Settings from '../pages/Settings';
import AuthGuard from '../guards/AuthGuard';

export default function AppRouter() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<MainLayout />}>
          <Route index element={<Home />} />
          <Route 
            path="settings" 
            element={
              <AuthGuard>
                <Settings />
              </AuthGuard>
            } 
          />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

Route Guard Implementation

// guards/AuthGuard.tsx
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../stores/useAuth';

export default function AuthGuard() {
  const { isAuthenticated } = useAuth();

  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }

  return <Outlet />;
}

Project Directory Structure and Specifications

electron-app/
├── .github/                # CI/CD configuration
│   └── workflows/          # GitHub Actions
├── src/                    # Source code
│   ├── main/               # Main process code
│   │   ├── index.ts        # Main entry point
│   │   └── window/         # Window management
│   ├── preload/            # Preload scripts
│   ├── renderer/           # Renderer process code
│   │   ├── assets/         # Static assets
│   │   ├── components/     # Components
│   │   ├── pages/          # Pages
│   │   └── App.tsx         # Renderer entry point
│   └── shared/             # Shared code
├── tests/                  # Test code
├── .eslintrc.js            # ESLint configuration
├── .prettierrc             # Prettier configuration
├── tsconfig.json           # TypeScript configuration
└── package.json            # Project configuration

Code Specification Key Points

  1. Naming Conventions:
    • Variables/Functions: camelCase
    • Classes/Types: PascalCase
    • Constants: UPPER_CASE
    • Filenames: kebab-case
  2. Code Organization:
    • Organize by feature, not type
    • Group related functionality together
    • Group third-party libraries separately

Engineering Toolchain

Electron CLI Advanced Configuration

Custom Script Configuration

// package.json
{
  "scripts": {
    "start": "electron .",
    "dev": "concurrently \"npm run start-renderer\" \"npm run start-main\"",
    "start-main": "electron --inspect=5858 .",
    "start-renderer": "webpack serve --config webpack.renderer.config.js",
    "build": "npm run build-main && npm run build-renderer",
    "build-main": "tsc -p tsconfig.main.json",
    "build-renderer": "webpack --config webpack.renderer.config.js",
    "package": "electron-builder --win --mac --linux"
  }
}

Environment Variable Management

// config/env.ts
type Env = 'development' | 'production' | 'test';

const getEnv = (): Env => {
  return process.env.NODE_ENV as Env || 'development';
};

export const config = {
  apiBaseUrl: process.env.API_BASE_URL || 
    (getEnv() === 'production' ? 'https://api.prod.com' : 'http://localhost:3000'),
  isDev: getEnv() === 'development',
  // Other configurations...
};

Webpack Advanced Configuration

Multi-Process Build Configuration

// webpack.renderer.config.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = {
  // ...
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true, // Enable multi-process compression
        terserOptions: {
          compress: { drop_console: true }
        }
      }),
      new CssMinimizerPlugin()
    ],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        common: {
          minChunks: 2,
          priority: -20
        }
      }
    }
  },
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: process.env.ANALYZE ? 'server' : 'disabled'
    })
  ]
};

Code Splitting Strategy

// Dynamic import example
const SettingsModal = React.lazy(() => import('../components/SettingsModal'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <SettingsModal />
    </Suspense>
  );
}

// Webpack magic comments
const HeavyComponent = React.lazy(() => import(
  /* webpackChunkName: "heavy-component" */
  /* webpackPrefetch: true */
  '../components/HeavyComponent'
));

TypeScript Integration

Type-Safe Configuration

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["DOM", "ES2020"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true, // Handled by Babel
    "jsx": "react-jsx"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

// tsconfig.main.json (Main process specific)
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "target": "ES2019",
    "module": "CommonJS",
    "outDir": "dist/main"
  },
  "include": ["src/main/**/*"]
}

Type Definition Extensions

// types/electron.d.ts
declare namespace NodeJS {
  interface Global {
    ipcRenderer: Electron.IpcRenderer;
  }
}

// Extend Window interface
interface Window {
  electronAPI: {
    readFile: (path: string) => Promise<string>;
    writeFile: (path: string, content: string) => Promise<void>;
  };
}

Code Linting Tools

ESLint Configuration

// .eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2020: true
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier' // Must be last
  ],
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint', 'import'],
  rules: {
    'react/react-in-jsx-scope': 'off', // Not needed in React 17+
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    'import/order': [
      'error',
      {
        groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
        'newlines-between': 'always'
      }
    ]
  },
  settings: {
    react: {
      version: 'detect'
    }
  }
};

Prettier Configuration

// .prettierrc
{
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "quoteProps": "as-needed",
  "jsxSingleQuote": false,
  "trailingComma": "all",
  "bracketSpacing": true,
  "bracketSameLine": false,
  "arrowParens": "always",
  "endOfLine": "lf"
}

Automated Testing Framework

Jest Configuration

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  moduleNameMapper: {
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest'
  },
  globals: {
    'ts-jest': {
      tsconfig: 'tsconfig.test.json'
    }
  }
};

Spectron Testing Example

// tests/app.test.ts
import { Application } from 'spectron';
import electronPath from 'electron';
import path from 'path';

describe('Application launch', () => {
  let app: Application;

  beforeEach(async () => {
    app = new Application({
      path: electronPath,
      args: [path.join(__dirname, '..')]
    });
    await app.start();
  });

  afterEach(async () => {
    if (app && app.isRunning()) {
      await app.stop();
    }
  });

  it('shows an initial window', async () => {
    const count = await app.client.getWindowCount();
    expect(count).toEqual(1);

    const title = await app.client.getTitle();
    expect(title).toContain('My App');
  });
});

Performance Optimization and Deployment

Code Splitting and Lazy Loading

Dynamic Import Strategy

// Route-level code splitting
const Home = React.lazy(() => import('../pages/Home'));
const Settings = React.lazy(() => import('../pages/Settings'));

// Component-level lazy loading
const HeavyChart = React.lazy(() => import('../components/HeavyChart'));

// API request splitting
const fetchUserData = () => import('../api/user').then(mod => mod.fetchUser);

Preloading Critical Resources

<!-- Preload critical resources in HTML -->
<link rel="preload" href="/critical.css" as="style">
<link rel="preload" href="/main.js" as="script">
<link rel="prefetch" href="/settings.js" as="script">

Resource Compression and Caching

Webpack Resource Optimization

// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|webp)$/i,
        use: [
          {
            loader: 'image-webpack-loader',
            options: {
              mozjpeg: { progressive: true, quality: 65 },
              optipng: { enabled: false },
              pngquant: { quality: [0.65, 0.9], speed: 4 },
              gifsicle: { interlaced: false }
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new CompressionPlugin({
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 10240 // Compress files larger than 10KB
    })
  ]
};

Caching Strategy Configuration

// Service Worker caching strategy
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // Cache hit
        if (response) return response;

        // Network request
        return fetch(event.request)
          .then(networkResponse => {
            // Dynamic caching strategy
            if (event.request.url.match(/\.(js|css|png|jpg|json)$/)) {
              const clone = networkResponse.clone();
              caches.open('dynamic-cache')
                .then(cache => cache.put(event.request, clone));
            }
            return networkResponse;
          });
      })
  );
});

Application Packaging and Release

Auto-Update Configuration

// main.js - Auto-update implementation
const { autoUpdater } = require('electron');
const server = 'https://update.example.com';
const feed = `${server}/update/${process.platform}/${app.getVersion()}`;

autoUpdater.setFeedURL({ feed });

autoUpdater.on('checking-for-update', () => {
  sendStatusToWindow('Checking for update...');
});

autoUpdater.on('update-available', (info) => {
  sendStatusToWindow('Update available');
});

autoUpdater.on('update-downloaded', (info) => {
  sendStatusToWindow('Update downloaded');
  autoUpdater.quitAndInstall();
});

// Check for updates every hour
setInterval(() => autoUpdater.checkForUpdatesAndNotify(), 3600000);

Version Management Strategy

// package.json
{
  "version": "1.0.0",
  "build": {
    "appId": "com.example.app",
    "version": "1.0.0",
    "directories": {
      "output": "dist"
    },
    "win": {
      "target": "nsis",
      "artifactName": "${productName}-${version}-${platform}-${arch}.${ext}"
    },
    "mac": {
      "target": "dmg",
      "artifactName": "${productName}-${version}-${platform}-${arch}.${ext}"
    }
  }
}

Performance Monitoring and Analysis

Sentry Integration

// main.js - Error monitoring
const Sentry = require('@sentry/electron');

Sentry.init({
  dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0',
  release: app.getVersion(),
  environment: process.env.NODE_ENV
});

// Capture unhandled exceptions
process.on('uncaughtException', (error) => {
  Sentry.captureException(error);
});

// Renderer process error monitoring
window.addEventListener('error', (event) => {
  Sentry.captureException(event.error);
});

Performance Analysis Tools

// Performance measurement example
const { performance, PerformanceObserver } = require('perf_hooks');

// Create performance observer
const obs = new PerformanceObserver((items) => {
  items.getEntries().forEach((entry) => {
    console.log(`${entry.name}: ${entry.duration}ms`);
  });
});
obs.observe({ entryTypes: ['measure'] });

// Mark performance critical points
performance.mark('start-render');
// ...rendering logic...
performance.mark('end-render');
performance.measure('render-time', 'start-render', 'end-render');

Multi-Platform Deployment and CI/CD

GitHub Actions Configuration

# .github/workflows/build.yml
name: Build and Release

on:
  push:
    tags: ['v*']

jobs:
  build:
    strategy:
      matrix:
        platform: [macos-latest, windows-latest, ubuntu-latest]
    runs-on: ${{ matrix.platform }}

    steps:
      - uses: actions/checkout@v2

      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '16'

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Package
        run: npm run package

      - name: Upload Artifact
        uses: actions/upload-artifact@v2
        with:
          name: release-${{ matrix.platform }}
          path: dist/

Automated Release Process

# Release script example
#!/bin/bash

# 1. Run tests
npm test

# 2. Build application
npm run build

# 3. Generate changelog
standard-version

# 4. Push to GitHub
git push --follow-tags

# 5. Trigger GitHub Actions for auto-release
Share your love