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



我正在尝试运行我的 Electon 应用程序,我正在从 ES6 迁移到 Typescript。使用我的配置,我可以成功构建 dll 和 main,但在尝试运行时出现错误(语法错误:意外的令牌...... 目前,项目文件是打字稿和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:toolnode_modulespirateslibindex.js:99:24)
at Module._extensions..js (internal/modules/cjs/loader.js:798:10)
at Object.newLoader [as .js] (C:toolnode_modulespirateslibindex.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:toolnode_moduleselectrondistresourcesdefault_app.asarmain.js:109:16)
at Object.<anonymous> (C:toolnode_moduleselectrondistresourcesdefault_app.asarmain.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 我已经用 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模块(一些很好的链接总结了这一点:Link,Link,Link,Link(,因此它无法在运行时解释import语句。

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

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

我的猜测是没有对 CommonJS 的转换,因为您将 Electron6.0.2指定为目标,它映射到支持 ES 模块的非常不错的 Chromium 版本 - 有关详细信息,请参阅设置modules: auto及其含义。

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

webpack.config.renderer.dev.jsbabel-loader

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

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

溶液

我们必须让 Babel 将你的主进程.js文件转换为 CommonJS 语法。最简单的方法是更改模块选项:

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

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

呵呵,这比预期的要长一些。希望你仍然醒着,并且有帮助。祝你好运!

最新更新