Permalink
Browse files

build: use typescript for internal Electron JS code (#16441)

  • Loading branch information...
MarshallOfSound committed Feb 6, 2019
1 parent 858781b commit 26df9992cfa77953582412d51170f8ad69067c01
@@ -1,12 +1,21 @@
{
"extends": "standard",
"parser": "typescript-eslint-parser",
"plugins": ["typescript"],
"env": {
"browser": true
},
"rules": {
"no-var": "error",
"no-unused-vars": 0,
"no-global-assign": 0,
"typescript/no-unused-vars": "error",
"prefer-const": ["error", {
"destructuring": "all"
}]
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
}
}
@@ -59,3 +59,6 @@ spec/.hash

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

# If someone runs tsc this is where stuff will end up
ts-gen
@@ -11,8 +11,10 @@ import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
import("//v8/snapshot_toolchain.gni")
import("build/asar.gni")
import("build/npm.gni")
import("build/tsc.gni")
import("buildflags/buildflags.gni")
import("electron_paks.gni")
import("filenames.auto.gni")
import("filenames.gni")

if (is_mac) {
@@ -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") {
script = "browserify"

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

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

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

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

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

copy("default_app_js") {
sources = filenames.default_app_sources
typescript_build("default_app_js") {
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 = [
"$target_gen_default_app_js/{{source}}",
]
@@ -197,10 +230,12 @@ asar("default_app_asar") {
deps = [
":default_app_js",
":default_app_octicon_deps",
":default_app_static",
]

root = "$target_gen_default_app_js/electron/default_app"
sources = get_target_outputs(":default_app_js") +
get_target_outputs(":default_app_static") +
get_target_outputs(":default_app_octicon_deps")
outputs = [
"$root_out_dir/resources/default_app.asar",
@@ -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" ]
}
}
}
@@ -1,19 +1,17 @@
'use strict'
import { app, BrowserWindow, BrowserWindowConstructorOptions } from 'electron'
import * as path from 'path'

const { app, BrowserWindow } = require('electron')
const path = require('path')

let mainWindow = null
let mainWindow: BrowserWindow | null = null

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

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

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

mainWindow = new BrowserWindow(options)

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

mainWindow.loadURL(appUrl)
mainWindow.focus()
@@ -1,20 +1,31 @@
'use strict'

const { app, dialog } = require('electron')
import { app, dialog } from '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 path = require('path')
const url = require('url')

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

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

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

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

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

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

function startRepl () {
@@ -1,9 +1,7 @@
'use strict'

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

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

for (const link of document.querySelectorAll('a[href]')) {
for (const link of document.querySelectorAll<HTMLLinkElement>('a[href]')) {
// 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' }
const url = URL.format(parsedUrl)

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

document.querySelector('.electron-version').innerText = `Electron v${process.versions.electron}`
document.querySelector('.chrome-version').innerText = `Chromium v${process.versions.chrome}`
document.querySelector('.node-version').innerText = `Node v${process.versions.node}`
document.querySelector('.v8-version').innerText = `v8 v${process.versions.v8}`
document.querySelector('.command-example').innerText = `${electronPath} path-to-app`
document.querySelector<HTMLAnchorElement>('.electron-version')!.innerText = `Electron v${process.versions.electron}`
document.querySelector<HTMLAnchorElement>('.chrome-version')!.innerText = `Chromium v${process.versions.chrome}`
document.querySelector<HTMLAnchorElement>('.node-version')!.innerText = `Node v${process.versions.node}`
document.querySelector<HTMLAnchorElement>('.v8-version')!.innerText = `v8 v${process.versions.v8}`
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`)
if (fs.existsSync(octiconPath)) {
const content = fs.readFileSync(octiconPath, 'utf8')
@@ -45,21 +43,23 @@ function initialize () {
return null
}

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

for (const element of document.querySelectorAll('.octicon')) {
for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) {
loadSVG(element)
}
}
Oops, something went wrong.

0 comments on commit 26df999

Please sign in to comment.