Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch from electron-protocol-serve to file: URLs #476

Merged
merged 4 commits into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 21 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,29 @@ module.exports = {
'ember/avoid-leaking-state-in-ember-objects': 'off',
})
},
// forge template files
// test runner
{
files: [
'forge/files/**/*.js'
'lib/test-runner.js'
],
env: {
browser: false,
node: true
},
plugins: ['node'],
rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
'node/no-missing-require': ['error', {
'allowModules': [
'ember-electron'
],
}]
})
},
// Electon runtime files
{
files: [
'forge/files/**/*.js',
'lib/test-support/**/*.js'
],
env: {
browser: false,
Expand Down
2 changes: 1 addition & 1 deletion blueprints/ember-electron/files/testem-electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module.exports = {
test_page: 'tests/index.html?hidepassed',
disable_watching: true,
launchers: {
Electron: require('ember-electron/lib/test-support/test-runner'),
Electron: require('ember-electron/lib/test-runner'),
},
launch_in_ci: [
'Electron',
Expand Down
61 changes: 46 additions & 15 deletions blueprints/ember-electron/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ const fs = require('fs');
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const YAWN = require('yawn-yaml/cjs');
const SilentError = require('silent-error');
const {
upgradingUrl,
routingAndAssetLoadingUrl,
ciUrl
} = require('../../lib/utils/documentation-urls');

Expand All @@ -24,31 +24,62 @@ module.exports = class EmberElectronBlueprint extends Blueprint {
return entityName;
}

beforeInstall() {
if (fs.existsSync(electronProjectPath)) {
return Promise.reject(
new SilentError([
`Cannot create electron-forge project at './${electronProjectPath}'`,
`because a file or directory already exists there. Please remove/rename`,
`it and run the blueprint again: 'ember generate ember-electron'.`
].join(' '))
);
}

async afterInstall() {
if (fs.existsSync('ember-electron')) {
this.ui.writeLine(chalk.yellow([
`\n'ember-electron' directory detected -- this looks like an ember-electron`,
`v2 project. Setting up an updated project will not be destructive, but you`,
`should read the upgrading documentation at ${upgradingUrl}.\n`
].join(' ')));
}
}

async afterInstall() {
await this.updateEnvironmentConfig();
await this.updateTravisYml();
await this.updateEslintIgnore();
await this.updateEslintRc();
await this.createElectronProject();

if (!fs.existsSync(electronProjectPath)) {
await this.createElectronProject();
} else {
this.ui.writeLine(chalk.yellow([
`An electron-forge project already exists at './${electronProjectPath}'.`,
`If you're running the blueprint manually as part of an ember-electron`,
`upgrade, make sure to check for upgrade instructions relevant to your`,
`version upgrade at ${upgradingUrl}.\n`
].join(' ')));
}
}

async updateEnvironmentConfig() {
this.ui.writeLine(chalk.green('Updating config/environment.js'));

let contents = (await readFile('config/environment.js')).toString();

const rootURLRegex = /(\srootURL\s*:)/;
if (rootURLRegex.test(contents)) {
contents = contents.replace(rootURLRegex, `$1 process.env.EMBER_CLI_ELECTRON ? '' :`);
} else {
this.ui.writeLine(chalk.yellow([
`\nUnable to update rootURL setting to`,
`\`process.env.EMBER_CLI_ELECTRON ? '' : <previous value>\`,`,
`which is needed for your Ember app to load assets under Electron.`,
`See ${routingAndAssetLoadingUrl} for more information.`
].join(' ')));
}

const locationTypeRegex = /(\slocationType\s*:)/;
if (locationTypeRegex.test(contents)) {
contents = contents.replace(locationTypeRegex, `$1 process.env.EMBER_CLI_ELECTRON ? 'hash' :`);
} else {
this.ui.writeLine(chalk.yellow([
`\nUnable to update locationType setting to`,
`\`process.env.EMBER_CLI_ELECTRON ? 'hash' : <previous value>\`,`,
`which is needed for your Ember app's routing to work under Electron.`,
`See ${routingAndAssetLoadingUrl} for more information.`
].join(' ')));
}

await writeFile('config/environment.js', contents);
}

async updateTravisYml() {
Expand Down
29 changes: 29 additions & 0 deletions forge/files/src/handle-file-urls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const { protocol } = require('electron');
const { fileURLToPath } = require('url');
const path = require('path');
const fs = require('fs');
const { promisify } = require('util');

const access = promisify(fs.access);
Copy link
Collaborator

@jacobq jacobq May 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remind me again why we can't use fsPromises... Need to support Node < 10.x?

I see this in package.json:

  "engines": {
    "node": "10.* || >= 12"
  },

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, yeah, we kinda recently dropped node 8 support, so that's why we were avoiding it before. However, this is a different case, because this code runs in the Electron main process, not the user's Node environment, so the engines in package.json isn't relevant here, just the version of Electron that the user is running.

But Electron has been on Node 10+ since 3.x, and I'm quite confident that there are other reasons this code would break on Electron <=2, not the least of which is the usage of fileURLToPath in this same file, which wasn't introduced until Node 10.12/Electron 5 😆

I don't think we have an official policy on which versions of Electron we do and don't target, but since:

  1. This is all blueprint-generated code that the user could modify/replace if they need to
  2. We're in the midst of a major version bump

I think we're fine keeping the usage of fileURLToPath and using fs.promises. I'll make the change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should maybe support Electron 5+ or whatever we had to update Electron Forge for?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW the Electron team only supports the latest 3 stable branches. So now that 9.0.0 is out I think that means versions older than 7.x are no longer supported. I am fine with 5+, but my gut would be to target actively supported versions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be fine with Electron 7+, and I would honestly also be fine with bumping node minimum to 12, but ember-cli supports 10 until April 2021.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is kinda silly because eslint is warning us about code that runs in Electron, not the environment that the engines field is describing. So I don't think bumping our minimum required node version makes any sense because I don't think we use any fs.promises in code that actually runs in the user's node environment, so we'd be dropping support just to make the linter happy.

That being said, I do think it makes sense to get more specific about what Electron versions we support, but haven't put any thought into really how to accomplish that -- I guess it would just be documentation and then testing against all supported (major) versions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think we would mainly list in the documentation that we don't support earlier Electron versions, but that it may still work.


//
// Patch asset loading -- Ember apps use absolute paths to reference their
// assets, e.g. `<img src="/images/foo.jpg">`. When the current URL is a `file:`
// URL, that ends up resolving to the absolute filesystem path `/images/foo.jpg`
// rather than being relative to the root of the Ember app. So, we intercept
// `file:` URL request and look to see if they point to an asset when
// interpreted as being relative to the root of the Ember app. If so, we return
// that path, and if not we leave them as-is, as their absolute path.
//
module.exports = function handleFileURLs(emberAppDir) {
protocol.interceptFileProtocol('file', async ({ url }, callback) => {
let urlPath = fileURLToPath(url);
let appPath = path.join(emberAppDir, urlPath);
try {
await access(appPath);
callback(appPath);
} catch (e) {
callback(urlPath);
}
});
};
58 changes: 24 additions & 34 deletions forge/files/src/index.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@
/* eslint-disable no-console */
const { app, BrowserWindow, protocol } = require('electron');
const { dirname, join, resolve } = require('path');
const protocolServe = require('electron-protocol-serve');
const isDev = require('electron-is-dev');
const { default: installExtension, EMBER_INSPECTOR } = require('electron-devtools-installer');
const { pathToFileURL } = require('url');
const { app, BrowserWindow } = require('electron');
const path = require('path');
const isDev = require('electron-is-dev');
const handleFileUrls = require('./handle-file-urls');

let mainWindow = null;
const emberAppDir = path.resolve(__dirname, '..', 'ember-dist');
const emberAppURL = pathToFileURL(path.join(emberAppDir, 'index.html')).toString();
jacobq marked this conversation as resolved.
Show resolved Hide resolved

// Registering a protocol & schema to serve our Ember application
if (typeof protocol.registerSchemesAsPrivileged === 'function') {
// Available in Electron >= 5
protocol.registerSchemesAsPrivileged([{
scheme: 'serve',
privileges: {
secure: true,
standard: true
}
}]);
}
else {
// For compatibility with Electron < 5
protocol.registerStandardSchemes(['serve'], { secure: true });
}
protocolServe({
cwd: join(__dirname || resolve(dirname('')), '..', 'ember-dist'),
app,
protocol,
});
let mainWindow = null;

// Uncomment the lines below to enable Electron's crash reporter
// For more information, see http://electron.atom.io/docs/api/crash-reporter/
Expand All @@ -43,13 +26,22 @@ app.on('window-all-closed', () => {
}
});

app.on('ready', () => {
app.on('ready', async () => {
if (isDev) {
require('devtron').install();
installExtension(EMBER_INSPECTOR)
.catch((err) => console.log('An error occurred: ', err));
try {
jacobq marked this conversation as resolved.
Show resolved Hide resolved
require('devtron').install();
} catch (err) {
console.log('Failed to install Devtron: ', err);
}
try {
await installExtension(EMBER_INSPECTOR);
} catch (err) {
console.log('Failed to install Ember Inspector: ', err);
}
}

await handleFileUrls(emberAppDir);

mainWindow = new BrowserWindow({
width: 800,
height: 600,
Expand All @@ -58,15 +50,13 @@ app.on('ready', () => {
// If you want to open up dev tools programmatically, call
// mainWindow.openDevTools();

const emberAppLocation = 'serve://dist';

// Load the ember application using our custom protocol/scheme
mainWindow.loadURL(emberAppLocation);
// Load the ember application
mainWindow.loadURL(emberAppURL);

// If a loading operation goes wrong, we'll send Electron back to
// Ember App entry point
mainWindow.webContents.on('did-fail-load', () => {
mainWindow.loadURL(emberAppLocation);
mainWindow.loadURL(emberAppURL);
});

mainWindow.webContents.on('crashed', () => {
Expand Down
31 changes: 26 additions & 5 deletions forge/files/tests/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
const { default: installExtension, EMBER_INSPECTOR } = require('electron-devtools-installer');
const path = require('path');
const { app } = require('electron');
const handleFileUrls = require('../src/handle-file-urls');
const { setupTestem, openTestWindow } = require('ember-electron/lib/test-support');

require('electron').app.on('ready', function onReady() {
require('devtron').install();
installExtension(EMBER_INSPECTOR)
.catch((err) => console.log('An error occurred: ', err));
const emberAppDir = path.resolve(__dirname, '..', 'ember-test');

app.on('ready', async function onReady() {
try {
require('devtron').install();
} catch (err) {
console.log('Failed to install Devtron: ', err);
}
try {
await installExtension(EMBER_INSPECTOR);
} catch (err) {
console.log('Failed to install Ember Inspector: ', err);
}

await handleFileUrls(emberAppDir);
setupTestem();
openTestWindow(emberAppDir);
});

require('ember-electron/lib/test-support/test-index');
app.on('window-all-closed', function onWindowAllClosed() {
if (process.platform !== 'darwin') {
app.quit();
}
});
1 change: 0 additions & 1 deletion forge/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ class EmberElectronTemplates extends BaseTemplate {
return [
'electron-devtools-installer',
'electron-is-dev',
'electron-protocol-serve'
];
}

Expand Down
17 changes: 14 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,20 @@ module.exports = {
node = replace(node, {
files: [ 'tests/index.html' ],
pattern: {
match: /src="[^"]*testem\.js"/,
replacement: 'src="http://testemserver/testem.js"',
},
match: /(src|href)="([^"]+)"/g,
replacement(match, attr, value) {
if (value.endsWith('testem.js')) {
// Replace testem script source so our test main process code can
// recognize and redirect requests to the testem server
value = 'http://testemserver/testem.js';
} else if (!value.includes(':/')) {
// Since we're loading from the filesystem, asset URLs in
// tests/index.html need to be prepended with '../'
value = `../${value}`;
}
return `${attr}="${value}"`;
}
}
});
}
return node;
Expand Down
34 changes: 0 additions & 34 deletions lib/resources/shim-head.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,6 @@
((win) => {
win.ELECTRON = true;

// On linux the renderer process doesn't inherit the main process'
// environment, so we need to fall back to using the remote module.
let serveIndex;
// If nodeIntegration is disabled, this will throw and serveIndex will remain
// undefined, which is fine because we only need it to fix module search paths
// that aren't relevant if node integration is disabled.
try {
serveIndex = process.env.ELECTRON_PROTOCOL_SERVE_INDEX || require('electron').remote.process.env.ELECTRON_PROTOCOL_SERVE_INDEX;
} catch (e) {
// When nodeIntegration is false we expect process to be undefined. Don't swallow the exception if something else is wrong
if (e.message !== "process is not defined") {
throw e;
}
}

if (serveIndex && window.location.protocol !== 'file:') {
// Using electron-protocol-serve to load index.html via a 'serve:' URL
// prevents electron's renderer/init.js from setting the module search
// paths correctly. So this is basically a copy of that code, but using an
// environment variable set by electron-protocol-serve containing the
// filesystem path to index.html instead of window.location.
const path = require('path');
const Module = require('module');

global.__filename = path.normalize(serveIndex);
global.__dirname = path.dirname(serveIndex);

// Set module's filename so relative require can work as expected.
module.filename = global.__filename;

// Also search for module under the html file.
module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname));
}

// Store electrons node environment injections for later usage
win.moduleNode = win.module;
win.processNode = win.process;
Expand Down