安装和运行Electron + React + Typescript + Webpack应用程序时出现问题

时间:2019-08-16 14:59:47

标签: javascript reactjs typescript webpack electron

我正在尝试运行从ES6迁移到Typescript的Electon应用程序。通过我的配置,我可以成功构建dll和main,但是在尝试运行时出现错误(SyntaxError:意外令牌...)。 目前,项目文件是打字稿和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",
  1. 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"
  ]
}
  1. .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"
      ]
    }
  }
}
  1. 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()
  ]
};

  1. 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')
        }
      }
    })
  ]
});

  1. 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,因为电子仅适用于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()
  ]
};

1 个答案:

答案 0 :(得分:0)

您的错误表明,您的生成系统未将ES模块语法转换为CommonJS语法。 Electron不容易以本机方式支持ES模块(一些不错的链接总结如下:LinkLinkLinkLink),因此它无法解释{{ 1}}语句。

当您根据tsconfig.json将.ts文件转换为import时,带有ES模块的旧.js文件似乎是关键所在。在.babelrc中,您像这样配置@babel/preset-env

"target": "es5"

我的猜测是没有移植到CommonJS,因为您指定了Electron [ "@babel/preset-env", { "targets": { "electron": "6.0.2" }, "corejs": "2", "useBuiltIns": "usage" } ] 作为目标,它映射到非常不错的支持ES模块的Chromium版本-请参见设置modules: auto和{{3} }以获取更多信息。

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

6.0.2webpack.config.renderer.dev.js

babel-loader

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

解决方案

我们必须带Babel才能将您的主要流程.js文件转换为CommonJS语法。最简单的方法是更改​​what it means选项:

options

您可以在主进程的babel配置中添加此设置,也可以直接在主webpack配置中的[ "@babel/env", { ... "modules": "cjs" } ], 选项中添加此设置(类似于渲染器webpack.config)。请记住,如果要进行后者,则必须在选项中添加babel-loader中的所有设置。作为.babelrc选项的替代方法,您也可以尝试使用modules插件。

P,那比预期的要长一些。希望您仍然清醒,并能有所帮助。祝你好运!