Skip to content

Commit

Permalink
build: use typescript for internal Electron JS code (#16441)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarshallOfSound committed Feb 6, 2019
1 parent 858781b commit 26df999
Show file tree
Hide file tree
Showing 19 changed files with 683 additions and 199 deletions.
9 changes: 9 additions & 0 deletions .eslintrc.json
@@ -1,12 +1,21 @@
{ {
"extends": "standard", "extends": "standard",
"parser": "typescript-eslint-parser",
"plugins": ["typescript"],
"env": { "env": {
"browser": true "browser": true
}, },
"rules": { "rules": {
"no-var": "error", "no-var": "error",
"no-unused-vars": 0,
"no-global-assign": 0,
"typescript/no-unused-vars": "error",
"prefer-const": ["error", { "prefer-const": ["error", {
"destructuring": "all" "destructuring": "all"
}] }]
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
} }
} }
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -59,3 +59,6 @@ spec/.hash


# Generated native addon files # Generated native addon files
/spec/fixtures/native-addon/echo/build/ /spec/fixtures/native-addon/echo/build/

# If someone runs tsc this is where stuff will end up
ts-gen
51 changes: 43 additions & 8 deletions BUILD.gn
Expand Up @@ -11,8 +11,10 @@ import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
import("//v8/snapshot_toolchain.gni") import("//v8/snapshot_toolchain.gni")
import("build/asar.gni") import("build/asar.gni")
import("build/npm.gni") import("build/npm.gni")
import("build/tsc.gni")
import("buildflags/buildflags.gni") import("buildflags/buildflags.gni")
import("electron_paks.gni") import("electron_paks.gni")
import("filenames.auto.gni")
import("filenames.gni") import("filenames.gni")


if (is_mac) { if (is_mac) {
Expand Down Expand Up @@ -53,6 +55,21 @@ config("branding") {
] ]
} }


# We geneate the definitions twice here, once in //electron/electron.d.ts
# and once in $target_gen_dir
# The one in $target_gen_dir is used for the actual TSC build later one
# and the one in //electron/electron.d.ts is used by your IDE (vscode)
# for typescript prompting
npm_action("build_electron_definitions") {
script = "gn-typescript-definitions"
args = [ rebase_path("$target_gen_dir/tsc/typings/electron.d.ts") ]
inputs = auto_filenames.api_docs + [ "package-lock.json" ]

outputs = [
"$target_gen_dir/tsc/typings/electron.d.ts",
]
}

npm_action("atom_browserify_sandbox") { npm_action("atom_browserify_sandbox") {
script = "browserify" script = "browserify"


Expand Down Expand Up @@ -140,9 +157,12 @@ action("atom_js2c") {
target_gen_electron_js = "$target_gen_dir/js/electron" target_gen_electron_js = "$target_gen_dir/js/electron"
target_gen_default_app_js = "$target_gen_dir/js/default_app" target_gen_default_app_js = "$target_gen_dir/js/default_app"


# TODO(MarshallOfSound) typescript_build("lib_js") {
# This copy will be replaced by a call to tsc in the future deps = [
copy("lib_js") { ":build_electron_definitions",
]
type_root = rebase_path("$target_gen_dir/tsc/electron/typings")

sources = filenames.js_sources sources = filenames.js_sources
if (enable_desktop_capturer) { if (enable_desktop_capturer) {
sources += [ sources += [
Expand All @@ -162,9 +182,9 @@ copy("lib_js") {
] ]
} }


outputs = [ output_gen_dir = target_gen_electron_js
"$target_gen_electron_js/{{source}}", output_dir_name = "lib"
] tsconfig = "tsconfig.electron.json"
} }


asar("electron_asar") { asar("electron_asar") {
Expand All @@ -179,8 +199,21 @@ asar("electron_asar") {
] ]
} }


copy("default_app_js") { typescript_build("default_app_js") {
sources = filenames.default_app_sources deps = [
":build_electron_definitions",
]
type_root = rebase_path("$target_gen_dir/tsc/electron/typings")

sources = filenames.default_app_ts_sources

output_gen_dir = target_gen_default_app_js
output_dir_name = "default_app"
tsconfig = "tsconfig.default_app.json"
}

copy("default_app_static") {
sources = filenames.default_app_static_sources
outputs = [ outputs = [
"$target_gen_default_app_js/{{source}}", "$target_gen_default_app_js/{{source}}",
] ]
Expand All @@ -197,10 +230,12 @@ asar("default_app_asar") {
deps = [ deps = [
":default_app_js", ":default_app_js",
":default_app_octicon_deps", ":default_app_octicon_deps",
":default_app_static",
] ]


root = "$target_gen_default_app_js/electron/default_app" root = "$target_gen_default_app_js/electron/default_app"
sources = get_target_outputs(":default_app_js") + sources = get_target_outputs(":default_app_js") +
get_target_outputs(":default_app_static") +
get_target_outputs(":default_app_octicon_deps") get_target_outputs(":default_app_octicon_deps")
outputs = [ outputs = [
"$root_out_dir/resources/default_app.asar", "$root_out_dir/resources/default_app.asar",
Expand Down
50 changes: 50 additions & 0 deletions build/tsc.gni
@@ -0,0 +1,50 @@
import("npm.gni")

template("typescript_build") {
assert(defined(invoker.tsconfig), "Need tsconfig name to run")
assert(defined(invoker.sources), "Need tsc sources to run")
assert(defined(invoker.output_dir_name),
"Need output_dir_name to run, should be 'lib' or other top level dir")
assert(defined(invoker.output_gen_dir),
"Need output_gen_dir to run, should be relative to the root gen dir")

npm_action(target_name) {
forward_variables_from(invoker,
[
"deps",
"public_deps",
"outputs",
])
script = "tsc"

sources = invoker.sources
inputs = [
invoker.tsconfig,
"//electron/tsconfig.json",
"//electron/package-lock.json",
]

type_roots = "node_modules/@types,typings"
if (defined(invoker.type_root)) {
type_roots += "," + invoker.type_root
}

base_out_path = invoker.output_gen_dir + "/electron/"
args = [
"-p",
rebase_path(invoker.tsconfig),
"--outDir",
rebase_path("$base_out_path" + invoker.output_dir_name),
"--typeRoots",
type_roots,
]

outputs = []

foreach(invoker_source, invoker.sources) {
# The output of TSC is all inputs but with JS instead of TS as the extension
outputs += [ "$base_out_path" + get_path_info(invoker_source, "dir") +
"/" + get_path_info(invoker_source, "name") + ".js" ]
}
}
}
14 changes: 6 additions & 8 deletions default_app/default_app.js → default_app/default_app.ts
@@ -1,19 +1,17 @@
'use strict' import { app, BrowserWindow, BrowserWindowConstructorOptions } from 'electron'
import * as path from 'path'


const { app, BrowserWindow } = require('electron') let mainWindow: BrowserWindow | null = null
const path = require('path')

let mainWindow = null


// Quit when all windows are closed. // Quit when all windows are closed.
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
app.quit() app.quit()
}) })


exports.load = async (appUrl) => { export const load = async (appUrl: string) => {
await app.whenReady() await app.whenReady()


const options = { const options: BrowserWindowConstructorOptions = {
width: 900, width: 900,
height: 600, height: 600,
autoHideMenuBar: true, autoHideMenuBar: true,
Expand All @@ -33,7 +31,7 @@ exports.load = async (appUrl) => {


mainWindow = new BrowserWindow(options) mainWindow = new BrowserWindow(options)


mainWindow.on('ready-to-show', () => mainWindow.show()) mainWindow.on('ready-to-show', () => mainWindow!.show())


mainWindow.loadURL(appUrl) mainWindow.loadURL(appUrl)
mainWindow.focus() mainWindow.focus()
Expand Down
38 changes: 25 additions & 13 deletions default_app/main.js → default_app/main.ts
@@ -1,20 +1,31 @@
'use strict' import { app, dialog } from 'electron'


const { app, dialog } = require('electron') import * as fs from 'fs'
import * as path from 'path'
import * as url from 'url'

type DefaultAppOptions = {
file: null | string;
noHelp: boolean;
version: boolean;
webdriver: boolean;
interactive: boolean;
abi: boolean;
modules: string[];
}


const fs = require('fs')
const Module = require('module') const Module = require('module')
const path = require('path')
const url = require('url')


// Parse command line options. // Parse command line options.
const argv = process.argv.slice(1) const argv = process.argv.slice(1)


const option = { const option: DefaultAppOptions = {
file: null, file: null,
noHelp: Boolean(process.env.ELECTRON_NO_HELP), noHelp: Boolean(process.env.ELECTRON_NO_HELP),
version: null, version: false,
webdriver: null, webdriver: false,
interactive: false,
abi: false,
modules: [] modules: []
} }


Expand Down Expand Up @@ -62,7 +73,7 @@ if (option.modules.length > 0) {
Module._preloadModules(option.modules) Module._preloadModules(option.modules)
} }


function loadApplicationPackage (packagePath) { function loadApplicationPackage (packagePath: string) {
// Add a flag indicating app is started from default app. // Add a flag indicating app is started from default app.
Object.defineProperty(process, 'defaultApp', { Object.defineProperty(process, 'defaultApp', {
configurable: false, configurable: false,
Expand Down Expand Up @@ -112,14 +123,15 @@ function loadApplicationPackage (packagePath) {
} }
} }


function showErrorMessage (message) { function showErrorMessage (message: string) {
app.focus() app.focus()
dialog.showErrorBox('Error launching app', message) dialog.showErrorBox('Error launching app', message)
process.exit(1) process.exit(1)
} }


function loadApplicationByUrl (appUrl) { async function loadApplicationByUrl (appUrl: string) {
require('./default_app').load(appUrl) const { load } = await import('./default_app')
load(appUrl)
} }


function startRepl () { function startRepl () {
Expand Down
38 changes: 19 additions & 19 deletions default_app/renderer.js → default_app/renderer.ts
@@ -1,9 +1,7 @@
'use strict' import { remote, shell } from 'electron'

import * as fs from 'fs'
const { remote, shell } = require('electron') import * as path from 'path'
const fs = require('fs') import * as URL from 'url'
const path = require('path')
const URL = require('url')


function initialize () { function initialize () {
// Find the shortest path to the electron binary // Find the shortest path to the electron binary
Expand All @@ -13,13 +11,13 @@ function initialize () {
? absoluteElectronPath ? absoluteElectronPath
: relativeElectronPath : relativeElectronPath


for (const link of document.querySelectorAll('a[href]')) { for (const link of document.querySelectorAll<HTMLLinkElement>('a[href]')) {
// safely add `?utm_source=default_app // safely add `?utm_source=default_app
const parsedUrl = URL.parse(link.getAttribute('href'), true) const parsedUrl = URL.parse(link.getAttribute('href')!, true)
parsedUrl.query = { ...parsedUrl.query, utm_source: 'default_app' } parsedUrl.query = { ...parsedUrl.query, utm_source: 'default_app' }
const url = URL.format(parsedUrl) const url = URL.format(parsedUrl)


const openLinkExternally = (e) => { const openLinkExternally = (e: Event) => {
e.preventDefault() e.preventDefault()
shell.openExternalSync(url) shell.openExternalSync(url)
} }
Expand All @@ -28,13 +26,13 @@ function initialize () {
link.addEventListener('auxclick', openLinkExternally) link.addEventListener('auxclick', openLinkExternally)
} }


document.querySelector('.electron-version').innerText = `Electron v${process.versions.electron}` document.querySelector<HTMLAnchorElement>('.electron-version')!.innerText = `Electron v${process.versions.electron}`
document.querySelector('.chrome-version').innerText = `Chromium v${process.versions.chrome}` document.querySelector<HTMLAnchorElement>('.chrome-version')!.innerText = `Chromium v${process.versions.chrome}`
document.querySelector('.node-version').innerText = `Node v${process.versions.node}` document.querySelector<HTMLAnchorElement>('.node-version')!.innerText = `Node v${process.versions.node}`
document.querySelector('.v8-version').innerText = `v8 v${process.versions.v8}` document.querySelector<HTMLAnchorElement>('.v8-version')!.innerText = `v8 v${process.versions.v8}`
document.querySelector('.command-example').innerText = `${electronPath} path-to-app` document.querySelector<HTMLAnchorElement>('.command-example')!.innerText = `${electronPath} path-to-app`


function getOcticonSvg (name) { function getOcticonSvg (name: string) {
const octiconPath = path.resolve(__dirname, 'octicon', `${name}.svg`) const octiconPath = path.resolve(__dirname, 'octicon', `${name}.svg`)
if (fs.existsSync(octiconPath)) { if (fs.existsSync(octiconPath)) {
const content = fs.readFileSync(octiconPath, 'utf8') const content = fs.readFileSync(octiconPath, 'utf8')
Expand All @@ -45,21 +43,23 @@ function initialize () {
return null return null
} }


function loadSVG (element) { function loadSVG (element: HTMLSpanElement) {
for (const cssClass of element.classList) { for (const cssClass of element.classList) {
if (cssClass.startsWith('octicon-')) { if (cssClass.startsWith('octicon-')) {
const icon = getOcticonSvg(cssClass.substr(8)) const icon = getOcticonSvg(cssClass.substr(8))
if (icon) { if (icon) {
icon.classList = element.classList for (const elemClass of element.classList) {
element.parentNode.insertBefore(icon, element) icon.classList.add(elemClass)
}
element.before(icon)
element.remove() element.remove()
break break
} }
} }
} }
} }


for (const element of document.querySelectorAll('.octicon')) { for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) {
loadSVG(element) loadSVG(element)
} }
} }
Expand Down

0 comments on commit 26df999

Please sign in to comment.