DEV Community

Cover image for Getting side-by-side preview in a terminal app Hyper
Takuya Matsuyama
Takuya Matsuyama

Posted on

Getting side-by-side preview in a terminal app Hyper

Hi, it's Takuya.
I'm publishing YouTube content, sharing my dev workflows.

Want a side-by-side preview in a terminal app

For web coding tutorials, it'd be great to show your coding and output side-by-side.
Typically, you would do that by simply arranging two windows: An editor and a browser.
But that's annoying because every time you make a tutorial, you have to align the windows side-by-side.

Hyper, a terminal app built with web standards, once had a built-in webview feature:

Hyper's built-in webview feature

As you can see above, it displays a preview on the right side of the terminal window.
It looks pretty neat.
So, you can make tutorials without having a separate window for preview.

But it looks like the feature has been removed for security reasons:

It was 3 years ago, a quite old issue.
I understand the risk of loading web pages in Electron apps.
However, webview of the recent Electron prohibits NodeJS integration by default. So, I think it's safe to embed in Hyper, especially when you understand what you do.

Hacking Hyper to get back the built-in webview feature

So, I decided to get that feature back to Hyper, and successfully did it.
Here is how it looks like:

Demo

Demo video:

How to build

I've made built-in-webview branch for the hack:

https://github.com/craftzdog/hyper/tree/built-in-webview

Clone this repo and built it yourself as following.

npm i
npm run dev
# On another terminal session
npm run app
Enter fullscreen mode Exit fullscreen mode

How to use

Split the window right by pressing Cmd-D.
Then, type echo <URL-to-open> and hit Return.
Click the URL in the terminal.
Then, the pane becomes a webview that loads the URL.

How I hacked Hyper

Check out the diffs

First, you have to allow webview tags in app/ui/window.ts:

   const winOpts: BrowserWindowConstructorOptions = {
     minWidth: 370,
     minHeight: 190,
     backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
     titleBarStyle: 'hiddenInset',
     title: 'Hyper.app',
     // we want to go frameless on Windows and Linux
     frame: process.platform === 'darwin',
     transparent: process.platform === 'darwin',
     icon,
     show: Boolean(process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev),
     acceptFirstMouse: true,
     webPreferences: {
       nodeIntegration: true,
       navigateOnDragDrop: true,
       enableRemoteModule: true,
-      contextIsolation: false
+      contextIsolation: false,
+      webviewTag: true
     },
     ...options_
   };
Enter fullscreen mode Exit fullscreen mode

Hyper partially remains the old implementations. You can reuse them.
The terminal component already has url prop.
You can display webview when the component has url prop.

In lib/components/term.tsx, change the terminal component class like so:

@@ -430,18 +436,35 @@ export default class Term extends React.PureComponent<TermProps> {
         style={{padding: this.props.padding}}
         onMouseUp={this.onMouseUp}
       >
-        {this.props.customChildrenBefore}
-        <div ref={this.onTermWrapperRef} className="term_fit term_wrapper" />
-        {this.props.customChildren}
-        {this.props.search ? (
-          <SearchBox
-            search={this.search}
-            next={this.searchNext}
-            prev={this.searchPrevious}
-            close={this.closeSearchBox}
+        {this.props.url ? (
+          <webview
+            src={this.props.url}
+            style={{
+              background: '#fff',
+              position: 'absolute',
+              top: 0,
+              left: 0,
+              display: 'inline-flex',
+              width: '100%',
+              height: '100%'
+            }}
           />
         ) : (
-          ''
+          <>
+            {this.props.customChildrenBefore}
+            <div ref={this.onTermWrapperRef} className="term_fit term_wrapper" />
+            {this.props.customChildren}
+            {this.props.search ? (
+              <SearchBox
+                search={this.search}
+                next={this.searchNext}
+                prev={this.searchPrevious}
+                close={this.closeSearchBox}
+              />
+            ) : (
+              ''
+            )}
+          </>
         )}

         <style jsx global>{`
Enter fullscreen mode Exit fullscreen mode

And, change the URL click handler to dispatch an action instead of opening up the page in an external browser:

@@ -160,7 +160,13 @@ export default class Term extends React.PureComponent<TermProps> {
       this.term.loadAddon(
         new WebLinksAddon(
           (event: MouseEvent | undefined, uri: string) => {
-            if (shallActivateWebLink(event)) void shell.openExternal(uri);
+            // if (shallActivateWebLink(event)) void shell.openExternal(uri);
+            // eslint-disable-next-line @typescript-eslint/no-unsafe-call
+            store.dispatch({
+              type: 'SESSION_URL_SET',
+              uid: props.uid,
+              url: uri
+            });
           },
           {
             // prevent default electron link handling to allow selection, e.g. via double-click
Enter fullscreen mode Exit fullscreen mode

In lib/reducers/sessions.ts, add the reducer for SESSION_URL_SET like so:

@@ -11,7 +11,8 @@ import {
   SESSION_SET_XTERM_TITLE,
   SESSION_SET_CWD,
   SESSION_SEARCH,
-  SESSION_SEARCH_CLOSE
+  SESSION_SEARCH_CLOSE,
+  SESSION_URL_SET
 } from '../constants/sessions';
 import {sessionState, session, Mutable, ISessionReducer} from '../hyper';

@@ -129,6 +130,9 @@ const reducer: ISessionReducer = (state = initialState, action) => {
       }
       return state;

+    case SESSION_URL_SET:
+      return state.setIn(['sessions', action.uid, 'url'], action.url);
+
     default:
       return state;
   }
Enter fullscreen mode Exit fullscreen mode

It works like a charm!

Follow me online

Top comments (15)

Collapse
 
astrit profile image
Astrit

Was looking for this after I saw the YouTube video and it is awesome, will do this and see how it goes, coding on terminal hard tho 😂

Collapse
 
craftzdog profile image
Takuya Matsuyama

yeah lol. hope it's helpful for your videos

Collapse
 
antonioalmeidas profile image
Antonio Almeida

Seguir seu tutorial, mas quando rodo o comando "npm run app" ou "yarn run app" recebo esse erro. Utilizo ubuntu linux.

Error launching app
Unable to find Electron app at /home/antonio/hyper/target

Cannot find module '/home/antonio/hyper/target'
Require stack:

- /home/antonio/hyper/node_modules/electron/dist/resources/default_app.asar/main.js

Collapse
 
expitc profile image

for who ever fail to compile try to disable typescript check
with put
// @ts-nocheck at the top level
to all over error file that printed when run dev, because it used old version and we just want the feature so i think is fair. Then after notice run dev got no error u safe to go with run app. Hopes help y'all have a nice day!

And for windows it should works fine or you can build by ur self it maybe just a little confusing for some newcomers but do a little bit research and it will solved.

Collapse
 
panshodev profile image
Francisco Castro A.

I have problems with the dependencies as I have 23 vulnerabilities and I tried to solve them but I could not, someone could or there is a tutorial on youtube?
Image description

Collapse
 
soumareharona profile image
SOUMARE HARONA

hello,

for windows ?

Collapse
 
yahoo100kkk profile image
yahoo100kkk

did you get it to work for windows?

Collapse
 
sathminjanuth profile image
Sathmin Januth

Hello Mr. Takuya,
please tell me how can I compile this exe for Windows?
Please help me....

Collapse
 
adhamvuejs profile image
adhamVuejs

I could compile it on mac node v: 18 everything works grea
Image descriptiont

Collapse
 
mmmarcoantonio profile image
MMMarcoAntonio

What version of node did you use?

Collapse
 
yahoo100kkk profile image
yahoo100kkk

Did you ever figure out how to get it on windows?

Collapse
 
nuopromise profile image
nuo-promise

Dev mode works, but the error "store" is not found. I also get an error trying to package the binary file.

Collapse
 
adhamvuejs profile image
adhamVuejs

(window as any).store did the trick for me

Collapse
 
fazlizekiqi profile image
Fazli Zekiqi

What was the node version when the changes were node. Cannot get it running.

Collapse
 
muinmundzir profile image
Muhammad Mu'in Mundzir

Did anyone have trying it on Ubuntu and works? Wondering how to make it work since I can't install it properly, got node-pty error