Electron

Electron、webpack、react、typescrip

2020-05-20  本文已影响0人  野鸡没名

完整代码 https://github.com/caoxiemeihao/electron-react-tsx
vue3+vite 版本

注:此教程以 windows 为例

准备材料

{
  "dependencies": {
    "@ant-design/icons": "^4.1.0",
    "antd": "^4.2.2",
    "core-js": "^3.6.5",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-router-dom": "^5.1.2"
  },
  "devDependencies": {
    "@babel/core": "^7.9.6",
    "@babel/plugin-proposal-class-properties": "^7.8.3",
    "@babel/plugin-transform-runtime": "^7.9.6",
    "@babel/preset-env": "^7.9.6",
    "@babel/preset-react": "^7.9.4",
    "@babel/preset-typescript": "^7.9.0",
    "@types/react": "^16.9.34",
    "@types/react-dom": "^16.9.7",
    "@types/react-router-dom": "^5.1.5",
    "babel-loader": "^8.1.0",
    "babel-plugin-import": "^1.13.0",
    "chalk": "^4.0.0",
    "chokidar": "^3.4.0",
    "clean-webpack-plugin": "^3.0.0",
    "concurrently": "^5.2.0",
    "copy-webpack-plugin": "^5.1.1",
    "css-loader": "^3.5.3",
    "dotenv": "^8.2.0",
    "electron": "^9.0.0-beta.24",
    "electron-builder": "^22.6.0",
    "electron-connect": "^0.6.3",
    "electron-is-dev": "^1.2.0",
    "eslint": "^7.0.0",
    "eslint-plugin-react": "^7.19.0",
    "eslint-plugin-react-hooks": "^4.0.0",
    "file-loader": "^6.0.0",
    "html-webpack-plugin": "^4.3.0",
    "less": "^3.11.1",
    "less-loader": "^6.1.0",
    "minimist": "^1.2.5",
    "ora": "^4.0.4",
    "style-loader": "^1.2.1",
    "wait-on": "^5.0.0",
    "webpack": "^4.43.0",
    "webpack-dev-server": "^3.11.0",
    "webpack-merge": "^4.2.2"
  }
}

大体目录结构

.
├─config/
│  ├─webpack.config.js    # 基本 webpack 配置
│  ├─webpack.main.js      # electron 主进程配置
│  └─webpack.render.js    # electron 渲染进程配置(react、tsx)
│
├─dist/                   # React 打包后的文件
├─eslint-rules/
├─node_modules/
├─script/
│  ├─render-build.js      # React 打包脚本
│  ├─render-start.js      # React 开发脚本
│  ├─main-pack.js         # Electron electron-builder
│  └─main-build.js        # Electron 打包脚本
│
├─src-main/               # Electron 主进程目录
│  ├─main.js              # Electron 开发入口
│  └─bundle.js            # Electron 运行入口
│
├─src-render/             # Electron 渲染进程目录
│  │
│  ├─static/              # **** 静态文件夹,直接搬运到 dist/static
│  │                      # **** 在样式文件里面用法:background: url(./static/image/xxx.png)
│  │
│  └─main.tsx             # React 入口文件

编写 webpack 配置

渲染进程、主进程都需要 webpack 打包

渲染进程使用 react、tsx 编写,所以需要编译才能运行

为什么 Electron 的主进程需要 webpack 打包?
根目录新建 config 文件夹,新建如下三个文件
webpack.config.js (主进程主、渲染进程公用部分)
webpack.main.js
webpack.render.js
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const baseConfig = require('./webpack.config');

const resolve = dir => path.join(__dirname, dir);

const lessRegex = /\.less$/;
const lessModuleRegex = /\.(mod|module).less$/;
// antd@4 下报错
// const lessNormalRegex = new RegExp(`(\\.normal\\.less$)|(ode_modules\\${path.sep}antd)`);
const getStyleLoaders = (mod = false) => [
  'style-loader',
  {
    loader: 'css-loader',
    options: {
      modules: mod ? { localIdentName: '[path][name]__[local]' } : undefined,
    }
  },
  {
    loader: 'less-loader',
    options: {
      lessOptions: { javascriptEnabled: true },
    },
  },
];

module.exports = function (env) {
  const isDev = env === 'development';

  return merge(baseConfig(env), {
    target: 'electron-renderer',
    entry: resolve('../src/render/main.tsx'),
    output: {
      path: resolve('../src/dist'),
      filename: isDev ? 'bundle.js' : 'bundle.[hash:9].js',
    },
    module: {
      rules: [
        {
          test: /\.(js|jsx|ts|tsx)$/,
          exclude: /node_modules/,
          loader: 'babel-loader',
          options: {
            // presets 是 plugins 的集合,把很多需要转换的ES6的语法插件集合在一起,避免各种配置
            // presets 加载顺序和一般理解不一样 ,是倒序的
            presets: [
              ["@babel/preset-env", {
                // targets 用来指定 是转换 需要支持哪些浏览器的的支持,这个语法是参照 browserslist,
                // 如果设置 browserslist 可以不设置 target
                "targets": "> 0.25%, not dead",
                // 这个是非常重要的一个属性,主要是用来配合@babel/polyfill ,
                // 这里简单讲下,在 transform-runtime 和 polyfill 差别的环节重点讲, 
                // 有 false,entry,usage,默认是 false 啥子也不干,
                // 为 entry,项目中 main.js 主动引入 @babel/polyfill , 会把所有的 polyfill 都引入,
                // 为 usage main.js 主动引入 @babel/polyfill, 只会把用到的 polyfill 引入,
                "useBuiltIns": "usage",
                "corejs": 3,
                // 默认是 false 开启后控制台会看到 哪些语法做了转换,Babel的日志信息,开发的时候强烈建议开启
                // "debug": isDev,
              }
              ],
              "@babel/preset-react",
              "@babel/preset-typescript",
            ],
            // plugins 加载顺序是正序的
            plugins: [
              // "@babel/plugin-syntax-dynamic-import",       // preset-env 中已经集成
              // "@babel/plugin-proposal-object-rest-spread", // preset-env 中已经集成
              "@babel/plugin-transform-runtime",
              ["@babel/plugin-proposal-class-properties", { "loose": true }],
              ["import", {
                "libraryName": "antd",
                "style": true, // or 'css'
              }],
            ],
            cacheDirectory: true,
          },
        },
        {
          test: /\.css$/,
          use: ['style-loader', 'css-loader'],
        },
        {
          test: lessRegex,
          exclude: lessModuleRegex,
          use: getStyleLoaders(),
        },
        {
          test: lessModuleRegex,
          use: getStyleLoaders(true),
        },
        {
          test: /\.(jpe?g|png|svg|gif)$/,
          loader: 'file-loader',
        },
      ],
    },
    resolve: {
      alias: {
        '@render': resolve('../src/render'),
      },
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: resolve('../src/render/index.html')
      }),
      new CopyWebpackPlugin([
        { from: resolve('../src/render/index.html'), to: resolve('../src/dist'), },
        { from: resolve('../src/render/static'), to: resolve('../src/dist/static'), },
      ]),
      ...(isDev
        ? [
          // This is necessary to emit hot updates (currently CSS only):
          new webpack.HotModuleReplacementPlugin(),
        ]
        : [
          new CleanWebpackPlugin(),
        ]),
    ],
    devServer: {
      // port: 4100, 放在 .env 中设置
      // 请注意,当前只有对CSS的更改是热重加载的。JS更改将刷新浏览器。
      hot: true,
      contentBase: resolve('../src/dist'), // 静态文件服务器地址
      stats: 'minimal', // 'none' | 'errors-only' | 'minimal' | 'normal' | 'verbose' object
    },
  });
};

以上就是所有的 webpack 配置了

编写启动、构建脚本

根目录新建 scripts 文件夹,新建如下三个文件
scripts/main-build.js
scripts/render-start.js
scripts/render-build.js
/**
 * 渲染进程构建脚本
 */
process.env.NODE_ENV = 'production';

const path = require('path');
const webpack = require('webpack');
const chalk = require('chalk');
const ora = require('ora');
const configFactory = require('../config/webpack.render');

const config = configFactory(process.env.NODE_ENV);
const compiler = webpack(config);
const spinner = ora('React webpack 构建...');
const TAG = '[scripts/render-build.js]';

compiler.hooks.beforeCompile.tap('start', () => spinner.start());

compiler.run(compileHandle);

function compileHandle(err, stats) {
  // 20-02-25 构建日志控制,还木搞定 = =
  // console.log(stats.compilation.records);
  spinner.stop();

  if (err) {
    // err 对象将只包含与webpack相关的问题,例如错误配置等
    console.log(TAG, chalk.red('💥 webpack 相关报错'));
  } else if (stats.hasErrors()) {
    // webpack 编译报错
    const json = stats.toJson('errors-only');
    // fs.writeFileSync(path.join(__dirname, './\.tmp/errors.json'), JSON.stringify(json, null, 2));
    console.log(TAG, filterLogs(json.errors)().join('\n'));
    console.log(TAG, chalk.red('💥 编译报错'));
  } else {
    console.log(TAG, chalk.green('React webpack 构建成功'));
  }
}

/**
 * webpack 日志过滤
 */
function filterLogs(errors) {
  let tmp = [];
  return function (filter = true) {
    if (filter) {
      errors.forEach(err => {
        if (err.includes('Error: Child compilation failed:')) {
          // 忽略 webpack 内部调用错误栈
          return;
        }
        if (!tmp.find(_ => _.split('\n')[1] === err.split('\n')[1])) {
          // 一个错误,可能会被爆出多次,做下报错去重
          // 比如一个 loader 报错,那么 n 个文件经过 loader 就会报出 n 个错误
          tmp.push(err);
        }
      });
    } else {
      tmp = errors;
    }
    return tmp;
  }
}

至此所有构建脚本已经写好,下面开始配置 scripts 命令

{
  "scripts": {
    "start": "concurrently -n=react,electron -c=blue,green \"npm run dev:react\" \"npm run dev:electron\"",
    "dev:react": "node scripts/render-start",
    "dev:electron": "node scripts/main-build --env=development --watch",
    "build-win": "node scripts/render-build && node scripts/main-build --env=production && electron-builder -w"
  }
}
main-window.png
electron-builder 构建配置
image setup.png image install.png finish.png

大功告成啦!!!(ง •_•)ง

上一篇 下一篇

猜你喜欢

热点阅读