electron-userland / electron-compile

DEPRECATED: Electron supporting package to compile JS and CSS in Electron applications
1.01k stars 98 forks source link

Hot-Reloading / Auto-Refresh Ideas #144

Closed flexzuu closed 7 years ago

flexzuu commented 7 years ago

In the talk about this package on the Github Universe Conference, it was mentioned that you are thinking about adding "Hot-Reloading". I like the idea of this package, but without "Hot-Reloading" other Build-Pipelines are simply more productive. I am interested of spending some time adding "Hot-Reloading" or even first only just Auto-Refresh to this package. Can you give me some information how to start? My first idea was so simply call something like browserWindow.refresh() on every rebuilding or file change detection. This would not add Hot-Reloading but "Auto-refresh".

Mike-Dax commented 7 years ago

Auto refresh is pretty trivial:

https://www.npmjs.com/package/electron-reload

The real magic is when we can refresh whilst maintaining state.

iErik commented 7 years ago

Does electron-reload works with electron-compile though?

Mike-Dax commented 7 years ago

Just did a quick test, yeah it works.

I'm think about having a crack at this in the next few weeks when I get some time off. First gotta fix the 'unaware if dependencies change' problem.

anaisbetts commented 7 years ago

This is On My List, specifically for React.

Mike-Dax commented 7 years ago

Couldn't sleep, decided to fix the 'unaware of dependencies thing' ahah, I'll make a PRs against electron-compile and electron-compilers ~tomorrow~ now here and here. mnquintana is a massive champion having done almost all the work already.

So far got ES6, SASS, SCSS, Stylus, and LESS all working, which leaves TypeScript and CoffeeScript. If I get more free time I'll probably have a crack at a hot reload solution instead of doing the other languages, then if I get even more free time, I'll tackle those languages.

Mike-Dax commented 7 years ago

I've got a prototype of hot-reloading going, should this be put into a separate module for the same reasoning as electron-compilers?

MarshallOfSound commented 7 years ago

@Mike-Dax Should probably defer to @paulcbetts but I think an electron-compile HMR tool should be separate to electron-compile itself so that it isn't pulled into the production dependencies.

Something like electron-compile-hmr would be a good fit possibly.

Also depends on how big the code is IMO, if it's not much code then modularizing it simply increases complexity. :man_shrugging: Do what you think works and we can discuss it 😆

Mike-Dax commented 7 years ago

Seems to be running out of electron-forge well.

JSX Hot Reloading

Going to try and work out how react-proxy works so this can be useful in a stateful application instead of being just a party trick.

Things that compile down to css all seem to work well.

I think I'll go the route of electron-compile checking to see if the package is installed, and if so bootstrap it up so the user needs as little boilerplate as possible. I think no matter what though I'll need a reference to the root node used by React in order to force a reload.

MarshallOfSound commented 7 years ago

@Mike-Dax This looks super cool, if you need a reference to the root node you probably need to follow the pattern from react HMR AppContainer

https://github.com/gaearon/redux-devtools/blob/master/examples/todomvc/index.js#L11

Mike-Dax commented 7 years ago

@paulcbetts

So the way I've been doing hmr in my prototype is to inject into the end of the index.html that Electron uses to boot, much like the 'magic' electron-compile file.

I would then scrape from the dom the list of script and link elements, extract the details of the files in there.

For each of those files I would build a dependency tree using the functions created earlier (in electron-compilers).

I would then attach an fs.watch, much like you have, but to the files individually.

From my testing in the current implementation in 6.0.0, it doesn't have the functionality to detect nested changes with the compilers that I put my fingers in, however it works with babel? (I don't know if I'm understanding the babel implementation correctly, does that work because of the 'globalish' file watcher + the fact that babel will traverse those dependencies implicitly? If so why doesn't that work with everything else) If you've got the time I'd love an explanation from your perspective.

If one of these watchers detects a change within one of the individual trees, it would traverse up the tree deleting the node cache then re-require the root node, or reset the dom link in the case of an scss file.

For a react component I'd just re-render the root node, throwing away state. I'm still trying to parse how all the react-hmr implementations work (and how they keep state) so I can do one for this properly, with all the fun that @gaearon does in React Hot Loader 3.

Rough pre-react earlier code snippet available here, does it as a flat list instead of a tree:

https://github.com/Mike-Dax/electron-compile-hmr/blob/master/simplesnippet.js

Just wanted to be transparent in what I was working on, this is the first real contribution I've done to an OSS project.

Mike-Dax commented 7 years ago

@MarshallOfSound So if we can land #183, I've got code 80% of the way there for css based hot reloading. It's just a bit messy at the moment, I've discovered rxjs and it's changed my life.

MarshallOfSound commented 7 years ago

@Mike-Dax Sounds awesome 👍 🎉

mavajee commented 7 years ago

@Mike-Dax, @MarshallOfSound. I'am new in electron, plz get me a work prototype or boilerplate, to get work electron and react-hot-reload.

Mike-Dax commented 7 years ago

@mavajee we'll write up documentation after its landed and the api settles, this will probably find its way into an electron-forge template for react specifically.

mavajee commented 7 years ago

@Mike-Dax, all template I have saw, very hard for me, and I don't get find electron compile with react-hot-reload.

anaisbetts commented 7 years ago

@Mike-Dax Over the weekend @MarshallOfSound was able to get this working and I published a new electron-compile version. Check out https://github.com/electron/electron-compile#react-hot-module-loading and see if you can get it working for your app

@Mike-Dax Thanks a ton for the work you put into this, this was a huge contribution!

JakeDluhy commented 7 years ago

Hey guys, thanks so much for doing this! I wasn't able to get it working unfortunately... Here's my index.js (which is called from index.html).

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';

import routes from 'app/routes';
import Root from 'containers/Root';

const appEl = document.getElementById('app');

ReactDOM.render(
    <AppContainer>
        <Root routes={routes} />
    </AppContainer>,
    appEl
);

// Hot Module Replacement API
console.log(module);
if (module.hot) {
    module.hot.accept([
        './containers/Root.js',
        './routes.js',
    ], () => {
        // TODO: HMR+Epics? https://redux-observable.js.org/docs/recipes/HotModuleReplacement.html /*
        console.log('module.hot.accept');

        const nextRoutes = require('app/routes').default;
        const hotReloadRoutes = require('utils/hotReloadRoutes').default;

        hotReloadRoutes(routes, nextRoutes);

        ReactDOM.render(
            <AppContainer>
                <Root routes={routes} />
            </AppContainer>,
            appEl
        );
    });
}

Note this setup works for hot reloading through webpack on our web build. The problem is that module.hot is undefined. Tbh I don't really know where that property would get added. Note I did try using the code specified in the docs (with the render function) and was unable to get that to work (it also broke my web hot loading ☚ī¸ ).

index.html

<!DOCTYPE html>
<!--[if lt IE 7 ]> <html lang="en" class="ie6" > <![endif]-->
<!--[if IE 7 ]>    <html lang="en" class="ie7" > <![endif]-->
<!--[if IE 8 ]>    <html lang="en" class="ie8" > <![endif]-->
<!--[if IE 9 ]>    <html lang="en" class="ie9" > <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="" > <!--<![endif]-->
    <head>
        <title>React Demo</title>

        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="stylesheet" type='text/sass' href="../styles/main.scss">
    </head>
    <body>        
        <div id="app"></div>

        <script type="application/javascript">
            require('../index.js');
        </script>
    </body>
</html>

main-process.js (which is the entry to electron-forge

const { app, BrowserWindow } = require('electron');
const path = require('path');
const enableLiveReload = require('electron-compile').enableLiveReload;

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;

// Hot reloading
const isDevMode = process.execPath.match(/[\\/]electron/);
if(isDevMode) enableLiveReload({ strategy: 'react-hmr' });

function addDevTools() {

    // Open the DevTools.
    mainWindow.webContents.openDevTools();
    require('devtron').install();
    require('electron-debug')();

    const installer = require('electron-devtools-installer');

    const extensions = [
        'REACT_DEVELOPER_TOOLS',
        'REDUX_DEVTOOLS',
        'REACT_PERF',
    ];

    for(const name of extensions) {
        installer.default(installer[name], true)
            .then((name) => console.log(`Added Extension:  ${name}`))
            .catch((err) => console.log(`An error occurred: ${err}`));
    }
}

function createWindow() {
    // Create the browser window.
    mainWindow = new BrowserWindow();
    mainWindow.maximize();

    // and load the index.html of the app.
    mainWindow.loadURL(`file://${__dirname}/index.html`);

    if (process.env.NODE_ENV === 'development') {
        addDevTools();
    }

    // Emitted when the window is closed.
    mainWindow.on('closed', () => {
        // Dereference the window object, usually you would store windows
        // in an array if your app supports multi windows, this is the time
        // when you should delete the corresponding element.
        mainWindow = null;
    });
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);

// Quit when all windows are closed.
app.on('window-all-closed', () => {
    // On OS X it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

app.on('activate', () => {
    // On OS X it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (mainWindow === null) {
        createWindow();
    }
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

Any insights or help would be great! I'm taking over the electron stuff from a team member who left and I'm pretty new to it.

EDIT: Note I also just installed a new electron-forge app with electron-forge init test --template=react and then ran it and HMR is not working for it either. Same issue, where module.hot is undefined.

MarshallOfSound commented 7 years ago

@JakeDluhy Woops, looks like a bug got introduced at some point with enable hot reloading 👍

Fix incoming 👍

MarshallOfSound commented 7 years ago

@JakeDluhy PR up #216 👍

JakeDluhy commented 7 years ago

Worked, thanks!