├── example
├── babel.config.js
├── electron-webpack.js
├── .gitignore
├── index.js
└── package.json
├── src
├── Electron.ts
├── Webpack.ts
└── Config.ts
├── .gitattributes
├── .eslintrc.js
├── template
├── electron
│ ├── webpack.config.js
│ └── main
│ │ └── index.js
└── electron-webpack.js
├── scripts
├── start.js
└── customize.js
├── tsconfig.json
├── .gitignore
├── LICENSE
├── README.md
├── package.json
└── bin
└── expo-electron.js
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['expo'],
3 | };
4 |
--------------------------------------------------------------------------------
/src/Electron.ts:
--------------------------------------------------------------------------------
1 | export * from './Config';
2 | export * from './Webpack';
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['universe/node', 'universe/web'],
3 | rules: {
4 | 'react/jsx-fragments': 'off',
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/example/electron-webpack.js:
--------------------------------------------------------------------------------
1 | const { withExpoAdapter } = require('../');
2 |
3 | module.exports = withExpoAdapter({
4 | projectRoot: __dirname,
5 | });
6 |
--------------------------------------------------------------------------------
/template/electron/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { withExpoWebpack } = require('@expo/electron-adapter');
2 |
3 | module.exports = config => {
4 | return withExpoWebpack(config);
5 | };
6 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p12
6 | *.key
7 | *.mobileprovision
8 | *.orig.*
9 | web-build/
10 | web-report/
11 | electron-build/
12 | dist/
--------------------------------------------------------------------------------
/scripts/start.js:
--------------------------------------------------------------------------------
1 | const { ensureElectronConfig } = require('../');
2 |
3 | ensureElectronConfig(process.cwd());
4 |
5 | process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 1;
6 |
7 | require('electron-webpack/out/dev/dev-runner');
8 |
--------------------------------------------------------------------------------
/template/electron-webpack.js:
--------------------------------------------------------------------------------
1 | const { withExpoAdapter } = require('@expo/electron-adapter');
2 |
3 | module.exports = withExpoAdapter({
4 | projectRoot: __dirname,
5 | // Provide any overrides for electron-webpack: https://github.com/electron-userland/electron-webpack/blob/master/docs/en/configuration.md
6 | });
7 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import { registerRootComponent } from 'expo';
2 | import React from 'react';
3 | import { StyleSheet, Text, View } from 'react-native';
4 |
5 | const styles = StyleSheet.create({
6 | container: {
7 | flex: 1,
8 | alignItems: 'center',
9 | justifyContent: 'center',
10 | },
11 | });
12 |
13 | function App() {
14 | return (
15 |
16 | Open up App.js to start working on your app!
17 |
18 | );
19 | }
20 |
21 | registerRootComponent(App);
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*.ts"],
3 | "compilerOptions": {
4 | "outDir": "build",
5 | "lib": ["ES2017", "dom"],
6 | "rootDir": "src",
7 | "declaration": true,
8 | "composite": true,
9 | "esModuleInterop": true,
10 | "inlineSources": true,
11 | "module": "CommonJS",
12 | "moduleResolution": "node",
13 | "noImplicitReturns": true,
14 | "resolveJsonModule": true,
15 | "sourceMap": true,
16 | "strict": true,
17 | "target": "ES2017",
18 | "skipLibCheck": true,
19 | "typeRoots": ["node_modules/@types"]
20 | },
21 | "exclude": ["**/__mocks__/*", "**/__tests__/*"]
22 | }
23 |
--------------------------------------------------------------------------------
/scripts/customize.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 | const fs = require('fs-extra');
3 |
4 | const { copyTemplateToProject, ensureMinProjectSetupAsync } = require('../');
5 | process.on('unhandledRejection', err => {
6 | throw err;
7 | });
8 |
9 | const projectRoot = fs.realpathSync(process.cwd());
10 |
11 | console.log();
12 | console.log(chalk.magenta('\u203A Copying Expo Electron main process to local project...'));
13 |
14 | copyTemplateToProject(projectRoot);
15 | ensureMinProjectSetupAsync(projectRoot);
16 |
17 | console.log(
18 | chalk.magenta(
19 | '\u203A You can now edit the Electron main process in the `electron/main` folder of your project...'
20 | )
21 | );
22 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-example",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "start": "ELECTRON_DISABLE_SECURITY_WARNINGS=1 electron-webpack dev",
6 | "build": "electron-webpack && electron-builder --dir -c.compression=store",
7 | "test:build": "electron-webpack && electron-builder --dir -c.compression=store -c.mac.identity=null"
8 | },
9 | "dependencies": {
10 | "expo": "^41.0.1",
11 | "react": "16.13.1",
12 | "react-dom": "16.13.1",
13 | "react-native-web": "~0.13.7",
14 | "source-map-support": "^0.5.16"
15 | },
16 | "devDependencies": {
17 | "electron": "5.0.6",
18 | "electron-builder": "^21.0.11",
19 | "electron-webpack": "^2.7.4"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | /build
58 |
59 | /*.tsbuildinfo
60 |
61 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019-present 650 Industries, Inc. (aka Expo)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 👋 Welcome to
@expo/electron-adapter
4 |
5 |
6 |
7 |
8 |
9 | Create Desktop apps with Expo and Electron!
10 |
11 |
12 | ---
13 |
14 | > Warning: This package is no longer maintained.
15 |
16 | This package wraps `electron-webpack` and adds support for Expo web and other universal React packages.
17 |
18 | ## Documentation
19 |
20 | To learn more about Electron usage with Expo, check out the blog post: [Making Desktop apps with Electron, React Native, and Expo][docs].
21 |
22 | ## License
23 |
24 | The Expo source code is made available under the [MIT license](LICENSE). Some of the dependencies are licensed differently, with the BSD license, for example.
25 |
26 |
27 |
28 | ---
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | [docs]: https://dev.to/evanbacon/making-desktop-apps-with-electron-react-native-and-expo-5e36
40 | [expo-cli-issues]: https://github.com/expo/expo-cli/issues
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@expo/electron-adapter",
3 | "version": "0.0.55",
4 | "description": "Use Electron with Expo",
5 | "main": "build/Electron.js",
6 | "types": "build/Electron.d.ts",
7 | "scripts": {
8 | "watch": "tsc --watch",
9 | "build": "tsc",
10 | "test": "cd example && yarn && yarn test:build",
11 | "prepare": "yarn run clean && yarn build",
12 | "clean": "rimraf ./tsconfig.tsbuildinfo",
13 | "lint": "eslint ."
14 | },
15 | "bin": {
16 | "expo-electron": "./bin/expo-electron.js"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/expo/expo-electron-adapter.git"
21 | },
22 | "keywords": [
23 | "expo",
24 | "expo-web",
25 | "json",
26 | "electron",
27 | "pwa",
28 | "react",
29 | "react-native",
30 | "react-dom",
31 | "react-native-web"
32 | ],
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/expo/expo-electron-adapter/issues"
36 | },
37 | "homepage": "https://github.com/expo/expo-electron-adapter",
38 | "files": [
39 | "build",
40 | "scripts",
41 | "bin",
42 | "template"
43 | ],
44 | "devDependencies": {
45 | "@expo/babel-preset-cli": "0.2.20",
46 | "@expo/webpack-config": "0.12.68",
47 | "@types/fs-extra": "^9.0.11",
48 | "eslint-config-universe": "^7.0.1",
49 | "rimraf": "^3.0.2"
50 | },
51 | "publishConfig": {
52 | "access": "public"
53 | },
54 | "peerDependencies": {
55 | "@expo/webpack-config": "^0.10.1",
56 | "electron": "^6.0.12"
57 | },
58 | "dependencies": {
59 | "@expo/config": "3.3.38",
60 | "@expo/package-manager": "0.0.41",
61 | "@expo/spawn-async": "^1.5.0",
62 | "chalk": "^4.0.0",
63 | "electron-webpack": "^2.7.4",
64 | "resolve-from": "^5.0.0",
65 | "typescript": "^4.2.4"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/template/electron/main/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { app, BrowserWindow } from 'electron';
4 | import * as path from 'path';
5 | import { format as formatUrl } from 'url';
6 |
7 | const isDevelopment = process.env.NODE_ENV !== 'production';
8 |
9 | // global reference to mainWindow (necessary to prevent window from being garbage collected)
10 | let mainWindow;
11 |
12 | function createMainWindow() {
13 | const browserWindow = new BrowserWindow({ webPreferences: { nodeIntegration: true } });
14 |
15 | if (isDevelopment) {
16 | browserWindow.webContents.openDevTools();
17 | }
18 |
19 | if (isDevelopment) {
20 | browserWindow.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
21 | } else {
22 | browserWindow.loadURL(
23 | formatUrl({
24 | pathname: path.join(__dirname, 'index.html'),
25 | protocol: 'file',
26 | slashes: true,
27 | })
28 | );
29 | }
30 |
31 | browserWindow.on('closed', () => {
32 | mainWindow = null;
33 | });
34 |
35 | browserWindow.webContents.on('devtools-opened', () => {
36 | browserWindow.focus();
37 | setImmediate(() => {
38 | browserWindow.focus();
39 | });
40 | });
41 |
42 | return browserWindow;
43 | }
44 |
45 | // quit application when all windows are closed
46 | app.on('window-all-closed', () => {
47 | // on macOS it is common for applications to stay open until the user explicitly quits
48 | if (process.platform !== 'darwin') {
49 | app.quit();
50 | }
51 | });
52 |
53 | app.on('activate', () => {
54 | // on macOS it is common to re-create a window even after all windows have been closed
55 | if (mainWindow === null) {
56 | mainWindow = createMainWindow();
57 | }
58 | });
59 |
60 | // create main BrowserWindow when electron is ready
61 | app.on('ready', () => {
62 | mainWindow = createMainWindow();
63 | });
64 |
--------------------------------------------------------------------------------
/bin/expo-electron.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /**
3 | * Copyright (c) 2019-present, Expo
4 | * Copyright (c) 2015-present, Facebook, Inc.
5 | *
6 | * This source code is licensed under the MIT license found in the
7 | * LICENSE file in the root directory of this source tree.
8 | */
9 |
10 | 'use strict';
11 |
12 | // Makes the script crash on unhandled rejections instead of silently
13 | // ignoring them. In the future, promise rejections that are not handled will
14 | // terminate the Node.js process with a non-zero exit code.
15 | process.on('unhandledRejection', err => {
16 | throw err;
17 | });
18 | const spawnAsync = require('@expo/spawn-async');
19 | const { realpathSync } = require('fs-extra');
20 |
21 | const { ensureMinProjectSetupAsync } = require('../build/Config');
22 |
23 | const args = process.argv.slice(2);
24 |
25 | const scriptIndex = args.findIndex(x => x === 'start' || x === 'customize');
26 | const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
27 | const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];
28 |
29 | if (['start', 'customize'].includes(script)) {
30 | spawnAsync(
31 | 'node',
32 | nodeArgs.concat(require.resolve('../scripts/' + script)).concat(args.slice(scriptIndex + 1)),
33 | { stdio: 'inherit' }
34 | ).then(result => {
35 | if (result.signal) {
36 | if (result.signal === 'SIGKILL') {
37 | console.log(
38 | 'The build failed because the process exited too early. ' +
39 | 'This probably means the system ran out of memory or someone called ' +
40 | '`kill -9` on the process.'
41 | );
42 | } else if (result.signal === 'SIGTERM') {
43 | console.log(
44 | 'The build failed because the process exited too early. ' +
45 | 'Someone might have called `kill` or `killall`, or the system could ' +
46 | 'be shutting down.'
47 | );
48 | }
49 | process.exit(1);
50 | }
51 | process.exit(result.status);
52 | });
53 | } else if (script === undefined) {
54 | const projectRoot = realpathSync(process.cwd());
55 |
56 | ensureMinProjectSetupAsync(projectRoot);
57 | } else {
58 | console.log('Invalid command "' + script + '".');
59 | }
60 |
--------------------------------------------------------------------------------
/src/Webpack.ts:
--------------------------------------------------------------------------------
1 | import { withAlias } from '@expo/webpack-config/addons';
2 | import { getAliases, getConfig, getModuleFileExtensions, getPaths } from '@expo/webpack-config/env';
3 | import { createBabelLoaderFromEnvironment } from '@expo/webpack-config/loaders';
4 | import { ExpoDefinePlugin, ExpoInterpolateHtmlPlugin } from '@expo/webpack-config/plugins';
5 | import {
6 | getPluginsByName,
7 | getRulesByMatchingFiles,
8 | resolveEntryAsync,
9 | } from '@expo/webpack-config/utils';
10 | import { AnyConfiguration } from '@expo/webpack-config/webpack/types';
11 | import HtmlWebpackPlugin from 'html-webpack-plugin';
12 | import * as path from 'path';
13 |
14 | // skipEntry defaults to false
15 | export function withExpoWebpack(
16 | config: AnyConfiguration,
17 | options: { projectRoot?: string; skipEntry?: boolean } = {}
18 | ) {
19 | const projectRoot = options.projectRoot || process.cwd();
20 |
21 | // Support react-native-web aliases
22 | // @ts-ignore: webpack version mismatch
23 | config = withAlias(config, getAliases(projectRoot));
24 |
25 | const env: any = {
26 | platform: 'electron',
27 | projectRoot,
28 | locations: getPaths(projectRoot),
29 | };
30 | if (!config.plugins) config.plugins = [];
31 | if (!config.resolve) config.resolve = {};
32 |
33 | env.config = getConfig(env);
34 |
35 | const [plugin] = getPluginsByName(config, 'HtmlWebpackPlugin');
36 | if (plugin) {
37 | const { options } = plugin.plugin as any;
38 | // Replace HTML Webpack Plugin so we can interpolate it
39 | // @ts-ignore: webpack version mismatch
40 | config.plugins.splice(plugin.index, 1, new HtmlWebpackPlugin(options));
41 | config.plugins.splice(
42 | plugin.index + 1,
43 | 0,
44 | // Add variables to the `index.html`
45 | // @ts-ignore
46 | ExpoInterpolateHtmlPlugin.fromEnv(env, HtmlWebpackPlugin)
47 | );
48 | }
49 |
50 | // Add support for expo-constants
51 | // @ts-ignore: webpack version mismatch
52 | config.plugins.push(ExpoDefinePlugin.fromEnv(env));
53 |
54 | // Support platform extensions
55 | config.resolve.extensions = getModuleFileExtensions('electron', 'web');
56 | config.resolve.extensions.push('.node');
57 |
58 | if (!options.skipEntry) {
59 | if (!env.locations.appMain) {
60 | throw new Error(
61 | `The entry point for your project couldn't be found. Please install \`expo\`, or define it in the package.json main field`
62 | );
63 | }
64 |
65 | const electronWebpackDefaultEntryPoints = [
66 | path.resolve(env.projectRoot, 'index'),
67 | path.resolve(env.projectRoot, 'app'),
68 | ];
69 | const expoEntry = config.entry;
70 | config.entry = async () => {
71 | const entries = await resolveEntryAsync(expoEntry);
72 |
73 | const expoEntryPointPath = env.locations.appMain;
74 |
75 | if (entries.renderer && !entries.renderer.includes(expoEntryPointPath)) {
76 | if (!Array.isArray(entries.renderer)) {
77 | entries.renderer = [entries.renderer];
78 | }
79 |
80 | entries.renderer = entries.renderer.filter(
81 | inputPath =>
82 | !electronWebpackDefaultEntryPoints.some(possibleEntryPoint =>
83 | inputPath.includes(possibleEntryPoint)
84 | )
85 | );
86 |
87 | entries.renderer.push(expoEntryPointPath);
88 |
89 | if (entries.renderer.length > 2) {
90 | throw new Error(
91 | `electron-adapter app entry hack doesn't work with this version of electron-webpack. The expected entry length should be 2, instead got: [${entries.renderer.join(
92 | ', '
93 | )}]`
94 | );
95 | } else {
96 | console.log(` Using custom entry point > ${expoEntryPointPath}`);
97 | }
98 | }
99 | return entries;
100 | };
101 | }
102 |
103 | const babelConfig = createBabelLoaderFromEnvironment(env);
104 |
105 | // Modify externals https://github.com/electron-userland/electron-webpack/issues/81
106 | const includeFunc = babelConfig.include as (path: string) => boolean;
107 | if (config.externals) {
108 | config.externals = (config.externals as any)
109 | .map((external: any) => {
110 | if (typeof external !== 'function') {
111 | const relPath = path.join('node_modules', external);
112 | if (!includeFunc(relPath)) return external;
113 | return null;
114 | }
115 | return (ctx: any, req: any, cb: any) => {
116 | const relPath = path.join('node_modules', req);
117 | return includeFunc(relPath) ? cb() : external(ctx, req, cb);
118 | };
119 | })
120 | .filter(Boolean);
121 | }
122 |
123 | // Replace JS babel loaders with Expo loaders that can handle RN libraries
124 | const rules = getRulesByMatchingFiles(config, [env.locations.appMain]);
125 |
126 | for (const filename of Object.keys(rules)) {
127 | for (const loaderItem of rules[filename]) {
128 | (config.module || { rules: [] }).rules.splice(loaderItem.index, 0, babelConfig);
129 | return config;
130 | }
131 | }
132 |
133 | return config;
134 | }
135 |
--------------------------------------------------------------------------------
/src/Config.ts:
--------------------------------------------------------------------------------
1 | import { createForProject } from '@expo/package-manager';
2 | import chalk from 'chalk';
3 | import fs from 'fs-extra';
4 | import * as path from 'path';
5 | import resolveFrom from 'resolve-from';
6 | // const PACKAGE_MAIN_PROCESS_PATH = '../template/main';
7 | // const PACKAGE_TEMPLATE_PATH = '../../webpack-config/web-default/index.html';
8 | // const PACKAGE_RENDER_WEBPACK_CONFIG_PATH = '../template/webpack.config';
9 | const PACKAGE_MAIN_PROCESS_PATH = '@expo/electron-adapter/template/electron/main';
10 | const PACKAGE_TEMPLATE_PATH = '@expo/webpack-config/web-default/index.html';
11 | const PACKAGE_RENDER_WEBPACK_CONFIG_PATH =
12 | '@expo/electron-adapter/template/electron/webpack.config';
13 |
14 | const LOCAL_ELECTRON_PATH = './electron';
15 | const LOCAL_MAIN_PROCESS_PATH = './electron/main';
16 | const LOCAL_TEMPLATE_PATH = './electron/template/index.html';
17 | const LOCAL_RENDER_WEBPACK_CONFIG_PATH = './electron/webpack.config';
18 |
19 | function getFolder(projectRoot: string, localPath: string, packagePath: string): string {
20 | const resolvedFullPath =
21 | resolveFrom.silent(projectRoot, localPath) || resolveFrom.silent(projectRoot, packagePath);
22 |
23 | if (!resolvedFullPath) throw new Error(`Failed to resolve path for file: ${packagePath}`);
24 | return path.relative(projectRoot, path.dirname(resolvedFullPath));
25 | }
26 |
27 | function getFile(projectRoot: string, localPath: string, packagePath: string): string {
28 | const localTemplate = path.join(projectRoot, localPath);
29 | if (fs.pathExistsSync(localTemplate)) {
30 | return localTemplate;
31 | }
32 | const resolvedFullPath =
33 | resolveFrom.silent(projectRoot, packagePath) || path.resolve(projectRoot, packagePath);
34 | return path.relative(projectRoot, resolvedFullPath);
35 | }
36 |
37 | function resolveFile(projectRoot: string, localPath: string, packagePath: string): string {
38 | const localTemplate = resolveFrom.silent(projectRoot, localPath);
39 | if (localTemplate) {
40 | return path.relative(projectRoot, localTemplate);
41 | }
42 | return path.relative(projectRoot, resolveFrom(projectRoot, packagePath));
43 | }
44 |
45 | export function withExpoAdapter({
46 | projectRoot,
47 | ...config
48 | }: {
49 | projectRoot?: string;
50 | [key: string]: any;
51 | }): { [key: string]: any } {
52 | projectRoot = projectRoot || fs.realpathSync(process.cwd());
53 |
54 | const webpackConfig = resolveFile(
55 | projectRoot,
56 | LOCAL_RENDER_WEBPACK_CONFIG_PATH,
57 | PACKAGE_RENDER_WEBPACK_CONFIG_PATH
58 | );
59 |
60 | const mainSourceDirectory = getFolder(
61 | projectRoot,
62 | LOCAL_MAIN_PROCESS_PATH,
63 | PACKAGE_MAIN_PROCESS_PATH
64 | );
65 |
66 | return {
67 | commonSourceDirectory: './electron/common',
68 | staticSourceDirectory: './electron/static',
69 | ...config,
70 | main: {
71 | sourceDirectory: mainSourceDirectory,
72 | ...((config || {}).main || {}),
73 | },
74 | renderer: {
75 | sourceDirectory: './',
76 | template: getFile(projectRoot, LOCAL_TEMPLATE_PATH, PACKAGE_TEMPLATE_PATH),
77 | webpackConfig,
78 | ...((config || {}).renderer || {}),
79 | },
80 | };
81 | }
82 |
83 | export function copyTemplateToProject(projectPath: string) {
84 | const outputPath = path.resolve(projectPath, LOCAL_ELECTRON_PATH);
85 | if (!fs.pathExistsSync(outputPath)) {
86 | fs.ensureDirSync(path.dirname(outputPath));
87 | fs.copy(path.resolve(__dirname, '../template/electron'), outputPath);
88 | }
89 | }
90 |
91 | export function ensureElectronConfig(projectPath: string) {
92 | const outputPath = path.resolve(projectPath, 'electron-webpack.js');
93 | if (!fs.pathExistsSync(outputPath)) {
94 | fs.copy(path.resolve(__dirname, '../template/electron-webpack.js'), outputPath);
95 | }
96 | }
97 |
98 | export async function ensureMinProjectSetupAsync(projectRoot: string): Promise {
99 | ensureElectronConfig(projectRoot);
100 | await ensureGitIgnoreAsync(projectRoot);
101 | await ensureDependenciesAreInstalledAsync(projectRoot);
102 | }
103 |
104 | const generatedTag = `@generated: @expo/electron-adapter@${
105 | require('@expo/electron-adapter/package.json').version
106 | }`;
107 |
108 | function createBashTag(): string {
109 | return `# ${generatedTag}`;
110 | }
111 |
112 | export async function ensureGitIgnoreAsync(projectRoot: string): Promise {
113 | const destinationPath = path.resolve(projectRoot, '.gitignore');
114 |
115 | // Ensure a default expo .gitignore exists
116 | if (!(await fs.pathExists(destinationPath))) {
117 | return;
118 | }
119 |
120 | // Ensure the .gitignore has the required fields
121 | let contents = await fs.readFile(destinationPath, 'utf8');
122 |
123 | const tag = createBashTag();
124 | if (contents.includes(tag)) {
125 | console.warn(
126 | chalk.yellow(
127 | `\u203A The .gitignore already appears to contain expo generated files. To rengerate the code, delete everything from "# ${generatedTag}" to "# @end @expo/electron-adapter" and try again.`
128 | )
129 | );
130 | return;
131 | }
132 |
133 | console.log(chalk.magenta(`\u203A Adding the generated folders to your .gitignore`));
134 |
135 | const ignore = [
136 | '',
137 | tag,
138 | '/.expo/*',
139 | '# Expo Web',
140 | '/web-build/*',
141 | '# electron-webpack',
142 | '/dist',
143 | '# @end @expo/electron-adapter',
144 | '',
145 | ];
146 |
147 | contents += ignore.join('\n');
148 | await fs.writeFile(destinationPath, contents);
149 | }
150 |
151 | function getDependencies(
152 | projectRoot: string
153 | ): { dependencies: string[]; devDependencies: string[] } {
154 | const dependencies = ['react-native-web', 'electron@^6.0.12'].filter(
155 | dependency => !resolveFrom.silent(projectRoot, dependency.split('@^').shift()!)
156 | );
157 | const devDependencies = ['@expo/electron-adapter', '@expo/webpack-config'].filter(
158 | dependency => !resolveFrom.silent(projectRoot, dependency)
159 | );
160 |
161 | return { dependencies, devDependencies };
162 | }
163 |
164 | async function ensureDependenciesAreInstalledAsync(projectRoot: string): Promise {
165 | const { dependencies, devDependencies } = getDependencies(projectRoot);
166 | const all = [...dependencies, ...devDependencies];
167 | if (!all.length) {
168 | console.log(chalk.yellow(`\u203A All of the required dependencies are installed already.`));
169 | return;
170 | } else {
171 | console.log(chalk.magenta(`\u203A Installing the missing dependencies: ${all.join(', ')}.`));
172 | }
173 |
174 | const packageManager = createForProject(projectRoot);
175 |
176 | if (dependencies.length) await packageManager.addAsync(...dependencies);
177 | if (devDependencies.length) await packageManager.addDevAsync(...devDependencies);
178 | }
179 |
--------------------------------------------------------------------------------