1

I'm trying to run my Electon application that I'm migrating from ES6 to Typescript. With my configuration I can build dll and main successfully but get an error (SyntaxError: Unexpected token ...) when I'm trying to run. For now project files are mix of typescript and javascript files.

I use the following dependencies (main of them are):

"@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. Scripts for run in 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));
      }
    }
  }
});

Error message:

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)

Update:

As ford04 said, need to use commonjs or es2015, because electron is worked only with es2015 standard. Need to set up babel presets/plugins either in .babelrc file or webpack config as well.

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 I've replaced with babel.config.js according to electron-typescript

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()
  ]
};
serj026
  • 13
  • 1
  • 4

1 Answers1

1

Your error indicates that your build system somehow did not transpile ES modules syntax to CommonJS syntax. Electron does not support ES modules in a native way easily (some good links summarizing this: Link, Link, Link, Link), so it cannot interpret the import statement at runtime.

Your old .js files with ES modules seem to be the crux here, as you transpile .ts files to "target": "es5" according to tsconfig.json. In .babelrc, you configured @babel/preset-env like this:

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

My guess is there is no transpilation to CommonJS, because you specified Electron 6.0.2 as target, which maps to a very decent Chromium version supporting ES modules - see the setting modules: auto and what it means for further infos.

I would further isolate your problem to the main process, as you set custom Babel settings for renderer:

webpack.config.renderer.dev.js, babel-loader:

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

Whenever you set these options, .babelrc will be completely ignored and everything from options taken during the webpack build including babel transformations. So for the renderer config, Babel should take its default options, which includes converting to CommonJS syntax, effectively eliminating import errors in the renderer process.

Solution

We have to bring Babel to transpile your main process .js files to CommonJS syntax. Easiest way is to change modules option:

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

You could add this setting in a babel config for your main process or directly in the options of babel-loader in your main webpack config (similar to renderer webpack.config). Remember, if you do the latter, you have to add every setting from .babelrc in options. As an alternative to modules option, you could also try the @babel/plugin-transform-modules-commonjs plugin.

Puh, that was a bit longer than expected. Hope you are still awake and that it helps out. Good luck!

ford04
  • 66,267
  • 20
  • 199
  • 171
  • 1
    Yep, this is my fault with options/.babelrc overriding. I will try with cjs module and @babel/plugin-transform-modules-commonjs. Thank you! – serj026 Aug 19 '19 at 07:34