javascript - 设置和运行 Electron + React + Typescript + Webpack 应用程序时出现问题

标签 javascript reactjs typescript webpack electron

我正在尝试运行我正在从 ES6 迁移到 Typescript 的 Electon 应用程序。通过我的配置,我可以成功构建 dll 和 main,但当我尝试运行时出现错误(SyntaxError:意外的标记...)。 目前项目文件是 typescript 和 javascript 文件的混合。

我使用以下依赖项(其中主要是):

"@babel/core": "^7.2.2", "@babel/register": "^7.0.0" and others @babel.
"electron": "6.0.2"
"react": "^16.9.0",
"typescript": "^3.5.3",
"webpack": "^4.39.2"
  1. 在 package.json 中运行的脚本
"build": "concurrently \"npm run build-main\" \"npm run build-renderer\"",
"build-dll": "cross-env NODE_ENV=development node --trace-warnings -r @babel/register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.dev.dll.js --colors",
"build-e2e": "cross-env E2E_BUILD=true npm run build",
"build-main": "cross-env NODE_ENV=production node --trace-warnings -r @babel/register ./node_modules/webpack/bin/webpack --config webpack.config.main.prod.js --colors",
"build-renderer": "cross-env NODE_ENV=production node --trace-warnings -r @babel/register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.prod.js --colors",
"electron-rebuild": "electron-rebuild --parallel --force --types prod,dev,optional --module-dir app",
"dev": "cross-env START_HOT=1 node -r @babel/register ./scripts/CheckPortInUse.js && cross-env START_HOT=1 npm run start-renderer-dev",
"start-main-dev": "cross-env HOT=1 NODE_ENV=development electron -r @babel/register ./app/main.dev.js",
"start-renderer-dev": "cross-env NODE_ENV=development node --trace-warnings -r @babel/register ./node_modules/webpack-dev-server/bin/webpack-dev-server --config webpack.config.renderer.dev.js",
  • tsconfig.json
  • {
      "compilerOptions": {
        "target": "es5",
        "lib": [
          "dom",
          "dom.iterable",
          "esnext"
        ],
        "outDir": "./dist",
        "allowJs": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "strictNullChecks": false,
        "forceConsistentCasingInFileNames": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "sourceMap": true,
        "noImplicitAny": false,
        "jsx": "react",
      },
      "include": [
        "./app"
      ],
      "exclude": [
        "node_modules",
        "**/node_modules/*",
        "dist"
      ]
    }
    
  • .babelrc
  • {
      "presets": [
        ["@babel/preset-env", {
          "targets": { "electron": "6.0.2" },
          "corejs": "2",
          "useBuiltIns": "usage"
        }],
        "@babel/preset-typescript",
        "@babel/preset-react"
      ],
      "plugins": [
        // Stage 0
        "@babel/plugin-proposal-function-bind",
    
        // Stage 1
        "@babel/plugin-proposal-export-default-from",
        "@babel/plugin-proposal-logical-assignment-operators",
        ["@babel/plugin-proposal-optional-chaining", {
          "loose": false
        }],
        ["@babel/plugin-proposal-pipeline-operator", {
          "proposal": "minimal"
        }],
        ["@babel/plugin-proposal-nullish-coalescing-operator", {
          "loose": false
        }],
        "@babel/plugin-proposal-do-expressions",
    
        // Stage 2
        ["@babel/plugin-proposal-decorators", {
          "legacy": true
        }],
        "@babel/plugin-proposal-function-sent",
        "@babel/plugin-proposal-export-namespace-from",
        "@babel/plugin-proposal-numeric-separator",
        "@babel/plugin-proposal-throw-expressions",
    
        // Stage 3
        "@babel/plugin-syntax-dynamic-import",
        "@babel/plugin-syntax-import-meta",
        ["@babel/plugin-proposal-class-properties", {
          "loose": true
        }],
        "@babel/plugin-proposal-json-strings",
        "@babel/plugin-proposal-object-rest-spread"
      ],
      "env": {
        "production": {
          "plugins": [
            "babel-plugin-dev-expression",
            "@babel/plugin-transform-react-constant-elements",
            "@babel/plugin-transform-react-inline-elements",
            "babel-plugin-transform-react-remove-prop-types"
          ]
        },
        "development": {
          "plugins": [
            "@babel/plugin-syntax-typescript",
            "react-hot-loader/babel"
          ]
        }
      }
    }
    
  • webpack.config.base.js
  • import path from 'path';
    import webpack from 'webpack';
    import fs from 'fs';
    import { dependencies as externals } from './app/package.json';
    import { dependencies as possibleExternals } from './package.json';
    
    export default {
      externals: [
        ...Object.keys(externals || {}),
        ...Object.keys(possibleExternals ||{})
      ],
    
      module: {
        rules: [
          {
            test: /\.(js|jsx|tsx|ts)$/,
            exclude: /node_modules/,
            use: [
              {
                loader: 'babel-loader',
                options: {
                  cacheDirectory: true
                }
              },
              'ts-loader'
            ]
          }
        ]
      },
    
      output: {
        path: path.join(__dirname, 'app'),
        // https://github.com/webpack/webpack/issues/1114
        libraryTarget: 'commonjs2'
      },
    
      resolve: {
        extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
        modules: [path.join(__dirname, 'app'), 'node_modules']
      },
    
      plugins: [
        new webpack.EnvironmentPlugin({
          NODE_ENV: 'production'
        }),
    
        new webpack.NamedModulesPlugin()
      ]
    };
    
    
  • webpack.config.renderer.dev.dll.js
  • /**
     * Builds the DLL for development electron renderer process
     */
    import webpack from 'webpack';
    import path from 'path';
    import merge from 'webpack-merge';
    import baseConfig from './webpack.config.base';
    import { dependencies } from './package.json';
    import CheckNodeEnv from './scripts/CheckNodeEnv';
    
    CheckNodeEnv('development');
    
    const dist = path.resolve(process.cwd(), 'dll');
    
    export default merge.smart(baseConfig, {
    
      devtool: 'eval',
    
      mode: 'development',
    
      target: 'electron-renderer',
    
      externals: ['fsevents', 'crypto-browserify'],
    
      module: require('./webpack.config.renderer.dev').default.module,
    
      entry: {
        renderer: Object.keys(dependencies || {}).filter(
          dependency => dependency !== 'font-awesome'
        )
      },
    
      output: {
        library: 'renderer',
        path: dist,
        filename: '[name].dev.dll.js',
        libraryTarget: 'var'
      },
    
      plugins: [
        new webpack.DllPlugin({
          path: path.join(dist, '[name].json'),
          name: '[name]'
        }),
    
        new webpack.EnvironmentPlugin({
          NODE_ENV: 'development'
        }),
    
        new webpack.LoaderOptionsPlugin({
          debug: true,
          options: {
            context: path.resolve(process.cwd(), 'app'),
            output: {
              path: path.resolve(process.cwd(), 'dll')
            }
          }
        })
      ]
    });
    
    
  • webpack.config.renderer.dev.js
  • import path from 'path';
    import fs from 'fs';
    import webpack from 'webpack';
    import chalk from 'chalk';
    import merge from 'webpack-merge';
    import { spawn, execSync } from 'child_process';
    import ExtractTextPlugin from 'extract-text-webpack-plugin';
    import baseConfig from './webpack.config.base';
    import CheckNodeEnv from './scripts/CheckNodeEnv';
    
    CheckNodeEnv('development');
    
    const port = process.env.PORT || 1212;
    const publicPath = `http://localhost:${port}/dist`;
    const dll = path.resolve(process.cwd(), 'dll');
    const manifest = path.resolve(dll, 'renderer.json');
    const requiredByDLLConfig = module.parent.filename.includes(
      'webpack.config.renderer.dev.dll'
    );
    
    if (!requiredByDLLConfig && !(fs.existsSync(dll) && fs.existsSync(manifest))) {
      execSync('npm run build-dll');
    }
    
    export default merge.smart(baseConfig, {
      devtool: 'inline-source-map',
    
      mode: 'development',
    
      target: 'electron-renderer',
    
      entry: [
        'react-hot-loader/patch',
        `webpack-dev-server/client?http://localhost:${port}/`,
        'webpack/hot/only-dev-server',
        path.join(__dirname, 'app/index.js')
      ],
    
      output: {
        publicPath: `http://localhost:${port}/dist/`,
        filename: 'renderer.dev.js'
      },
    
      module: {
        rules: [
          {
            test: /\.[jt]sx?$/,
            exclude: /node_modules/,
            use: {
              loader: 'babel-loader',
              options: {
                cacheDirectory: true,
                presets: [
                  '@babel/preset-env',
                  '@babel/preset-typescript',
                  '@babel/preset-react'
                ],
                plugins: [
                  '@babel/plugin-transform-runtime',
                  ["@babel/plugin-proposal-class-properties", { "loose": true }],
                  'react-hot-loader/babel'
                ]
              }
            }
          },
          {
            test: /\.global\.css$/,
            use: [
              {
                loader: 'style-loader'
              },
              {
                loader: 'css-loader',
                options: {
                  sourceMap: true
                }
              }
            ]
          },
          {
            test: /^((?!\.global).)*\.css$/,
            use: [
              {
                loader: 'style-loader'
              },
              {
                loader: 'css-loader',
                options: {
                  modules: true,
                  sourceMap: true,
                  importLoaders: 1,
                  localIdentName: '[name]__[local]__[hash:base64:5]'
                }
              }
            ]
          },
          // SASS support - compile all .global.scss files and pipe it to style.css
          {
            test: /\.global\.(scss|sass)$/,
            use: [
              {
                loader: 'style-loader'
              },
              {
                loader: 'css-loader',
                options: {
                  sourceMap: true
                }
              },
              {
                loader: 'sass-loader'
              }
            ]
          },
          // SASS support - compile all other .scss files and pipe it to style.css
          {
            test: /^((?!\.global).)*\.(scss|sass)$/,
            use: [
              {
                loader: 'style-loader'
              },
              {
                loader: 'css-loader',
                options: {
                  modules: true,
                  sourceMap: true,
                  importLoaders: 1,
                  localIdentName: '[name]__[local]__[hash:base64:5]'
                }
              },
              {
                loader: 'sass-loader'
              }
            ]
          },
          // WOFF Font
          {
            test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
            use: {
              loader: 'url-loader',
              options: {
                limit: 10000,
                mimetype: 'application/font-woff'
              }
            }
          },
          // WOFF2 Font
          {
            test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
            use: {
              loader: 'url-loader',
              options: {
                limit: 10000,
                mimetype: 'application/font-woff'
              }
            }
          },
          // TTF Font
          {
            test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
            use: {
              loader: 'url-loader',
              options: {
                limit: 10000,
                mimetype: 'application/octet-stream'
              }
            }
          },
          // EOT Font
          {
            test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
            use: 'file-loader'
          },
          // SVG Font
          {
            test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
            use: {
              loader: 'url-loader',
              options: {
                limit: 10000,
                mimetype: 'image/svg+xml'
              }
            }
          },
          // Common Image Formats
          {
            test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
            use: 'url-loader'
          },
          // All files with a ".ts" or ".tsx" extension will be handled by "awesome-typescript-loader".
          {
            test: /\.tsx?$/,
            loader: "awesome-typescript-loader"
          },
          // All output ".js" files will have any sourcemaps re-processed by "source-map-loader".
          {
            test: /\.js$/,
            enforce: "pre",
            loader: "source-map-loader" }
        ]
      },
    
      plugins: [
        requiredByDLLConfig
          ? null
          : new webpack.DllReferencePlugin({
              context: process.cwd(),
              // eslint-disable-next-line global-require
              manifest: require(manifest),
              sourceType: 'var'
            }),
    
        new webpack.HotModuleReplacementPlugin({
          multiStep: true
        }),
    
        new webpack.NoEmitOnErrorsPlugin(),
    
        new webpack.EnvironmentPlugin({
          NODE_ENV: 'development'
        }),
    
        new webpack.LoaderOptionsPlugin({
          debug: true
        }),
    
        new ExtractTextPlugin({
          filename: '[name].css'
        })
      ],
    
      node: {
        __dirname: false,
        __filename: false
      },
    
      devServer: {
        port,
        publicPath,
        compress: true,
        noInfo: true,
        stats: 'errors-only',
        inline: true,
        lazy: false,
        hot: true,
        headers: { 'Access-Control-Allow-Origin': '*' },
        contentBase: path.join(__dirname, 'dist'),
        watchOptions: {
          aggregateTimeout: 300,
          ignored: /node_modules/,
          poll: 100
        },
        historyApiFallback: {
          verbose: true,
          disableDotRule: false
        },
        before() {
          if (process.env.START_HOT) {
            console.log('Starting Main Process...');
            spawn('npm', ['run', 'start-main-dev'], {
              shell: true,
              env: process.env,
              stdio: 'inherit'
            })
              .on('close', code => process.exit(code))
              .on('error', spawnError => console.error(spawnError));
          }
        }
      }
    });
    
    

    错误消息:

    SyntaxError: Unexpected token {
    
    import { app, BrowserWindow } from 'electron';
           ^
    
        at Module._compile (internal/modules/cjs/loader.js:722:23)
        at Module._compile (C:\tool\node_modules\pirates\lib\index.js:99:24)
        at Module._extensions..js (internal/modules/cjs/loader.js:798:10)
        at Object.newLoader [as .js] (C:\tool\node_modules\pirates\lib\index.js:104:7)
        at Module.load (internal/modules/cjs/loader.js:645:32)
        at Function.Module._load (internal/modules/cjs/loader.js:560:12)
        at loadApplicationPackage (C:\tool\node_modules\electron\dist\resources\default_app.asar\main.js:109:16)
        at Object.<anonymous> (C:\tool\node_modules\electron\dist\resources\default_app.asar\main.js:155:9)
        at Module._compile (internal/modules/cjs/loader.js:786:30)
        at Object.Module._extensions..js (internal/modules/cjs/loader.js:798:10)
    

    更新:

    正如ford04所说,需要使用commonjs或es2015,因为Electron仅适用于es2015标准。还需要在 .babelrc 文件或 webpack 配置中设置 babel 预设/插件。

    tsconfig.js

    {
      "compilerOptions": {
        "target": "es5",
        "lib": [
          "dom",
          "dom.iterable",
          "es2015",
          "es6",
        ],
        "outDir": "./dist",
        "allowJs": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "strictNullChecks": false,
        "forceConsistentCasingInFileNames": true,
        "module": "es2015",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "sourceMap": true,
        "noImplicitAny": false,
        "jsx": "react"
      },
      "include": [
        "./app"
      ],
      "exclude": [
        "node_modules",
        "**/node_modules/*",
        "dist"
      ]
    }
    

    .babelrc 我已根据 electron-typescript 替换为 babel.config.js

    const developmentEnvironments = ['development', 'test'];
    
    const developmentPlugins = [
      require('react-hot-loader/babel'),
      require('@babel/plugin-syntax-typescript')
    ];
    
    const productionPlugins = [
      require('babel-plugin-dev-expression'),
    
      // babel-preset-react-optimize
      require('@babel/plugin-transform-react-constant-elements'),
      require('@babel/plugin-transform-react-inline-elements'),
      require('babel-plugin-transform-react-remove-prop-types')
    ];
    
    module.exports = api => {
    
      const development = api.env(developmentEnvironments);
    
      return {
        presets: [
          [
            require('@babel/preset-env'),
            {
              targets: { electron: require('electron/package.json').version },
              useBuiltIns: 'usage',
              corejs: 2
            }
          ],
          require('@babel/preset-typescript'),
          [require('@babel/preset-react'), { development }]
        ],
        plugins: [
    
          // Stage 0
          require('@babel/plugin-proposal-function-bind'),
    
          // Stage 1
          require('@babel/plugin-proposal-export-default-from'),
          require('@babel/plugin-proposal-logical-assignment-operators'),
          [require('@babel/plugin-proposal-optional-chaining'), { loose: false }],
          [
            require('@babel/plugin-proposal-pipeline-operator'),
            { proposal: 'minimal' }
          ],
          [
            require('@babel/plugin-proposal-nullish-coalescing-operator'),
            { loose: false }
          ],
          require('@babel/plugin-proposal-do-expressions'),
    
          // Stage 2
          [require('@babel/plugin-proposal-decorators'), { legacy: true }],
          require('@babel/plugin-proposal-function-sent'),
          require('@babel/plugin-proposal-export-namespace-from'),
          require('@babel/plugin-proposal-numeric-separator'),
          require('@babel/plugin-proposal-throw-expressions'),
    
          // Stage 3
          require('@babel/plugin-syntax-dynamic-import'),
          require('@babel/plugin-syntax-import-meta'),
          [require('@babel/plugin-proposal-class-properties'), { loose: true }],
          require('@babel/plugin-proposal-json-strings'),
          require('@babel/plugin-transform-runtime'),
    
          ...(development ? developmentPlugins : productionPlugins)
        ]
      };
    };
    

    webpack.config.base.js

    import path from 'path';
    import webpack from 'webpack';
    import fs from 'fs';
    import { dependencies as externals } from './app/package.json';
    
    export default {
      externals: [
        ...Object.keys(externals || {})
      ],
    
      module: {
        rules: [
          {
            test: /\.(js|jsx|tsx|ts)$/,
            exclude: /node_modules/,
            use: [
              {
                loader: 'babel-loader',
                options: {
                  cacheDirectory: true
                }
              },
              'ts-loader'
            ]
          }
        ]
      },
    
      output: {
        path: path.join(__dirname, 'app'),
        libraryTarget: 'commonjs2'
      },
    
      resolve: {
        extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
        modules: [path.join(__dirname, 'app'), 'node_modules']
      },
    
      plugins: [
        new webpack.EnvironmentPlugin({
          NODE_ENV: 'production'
        }),
    
        new webpack.NamedModulesPlugin()
      ]
    };
    

    最佳答案

    您的错误表明您的构建系统未将 ES 模块语法转换为 CommonJS 语法。 Electron 并不容易以原生方式支持 ES 模块(一些很好的链接总结了这一点: LinkLinkLinkLink ),因此它无法解释 import 语句运行时。

    带有 ES 模块的旧 .js 文件似乎是这里的症结所在,因为您根据 tsconfig.json 将 .ts 文件转换为 "target": "es5" 。在 .babelrc 中,您配置了 @babel/preset-env像这样:

    [
      "@babel/preset-env", {
        "targets": { "electron": "6.0.2" },
        "corejs": "2",
        "useBuiltIns": "usage"
      }
    ]
    

    我的猜测是没有转换为 CommonJS,因为您指定了 Electron 6.0.2 作为目标,它映射到支持 ES 模块的非常不错的 Chromium 版本 - 请参阅设置 modules: autowhat it means了解更多信息。

    当您为渲染器设置自定义 Babel 设置时,我会进一步将您的问题隔离到主进程:

    webpack.config.renderer.dev.jsbabel-loader:

    use: {
      loader: 'babel-loader',
      options: {
        ...
      }
    }
    

    每当你设置这些 options 时,.babelrc 将被完全忽略,并且在 webpack 构建期间获取 options 中的所有内容,包括 babel 转换。因此,对于渲染器配置,Babel 应该采用默认选项,其中包括转换为 CommonJS 语法,从而有效消除渲染器过程中的导入错误。

    解决方案

    我们必须使用 Babel 将主流程 .js 文件转换为 CommonJS 语法。最简单的方法是更改​​ modules选项:

    [
      "@babel/env", {
        ...
        "modules": "cjs"
      }
    ],
    

    您可以在主进程的 babel 配置中添加此设置,或者直接在主 webpack 配置中的 babel-loader 选项中添加此设置(类似于渲染器 webpack.config)。请记住,如果您执行后者,则必须在选项中添加 .babelrc 中的每个设置。作为 modules 选项的替代方案,您还可以尝试 @babel/plugin-transform-modules-commonjs插件。

    呃,这比预期的要长一些。希望你还醒着,这对你有帮助。祝你好运!

    关于javascript - 设置和运行 Electron + React + Typescript + Webpack 应用程序时出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57526892/

    相关文章:

    javascript - javascript 如何找到按下了哪些按钮?

    javascript - 在 React 中操作 DOM

    typescript - 为什么 typescript 允许使用 any 作为接口(interface)

    javascript - 如何从列表中过滤数据并以 Angular 从数据中删除现有房间

    javascript - 如何让变量采用 typescript 中的某些预定义值之一?

    javascript - 将文本的字体颜色更改为绿色?

    javascript - 如何知道 ModelState 是否包含错误

    javascript - 如何基于 bool 属性有条件地在 ReactJS 中应用嵌套的 SCSS 类?

    reactjs - reducer 中存储的 ref 对象,没有 offsetTop 属性

    angular - "MapboxGeocoder is not a constructor"当尝试在我的组件的 ts 内初始化 MapboxGeocoder 时