From f0e712e45c538a9a2e63b2cb9a1bdb4b64a01edb Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Mon, 10 May 2021 19:23:22 +0200 Subject: [PATCH] Correctly interpret string arguments as booleans in electron arguments. (#1539) --- .prettierrc.yaml | 2 +- CHANGELOG.md | 5 + src/js/lib/content/package.js | 4 + src/js/lib/content/src/{index.js => index.ts} | 407 +++++++++++------- src/js/lib/content/tsconfig.json | 7 + src/js/lib/content/webpack.config.js | 12 +- 6 files changed, 269 insertions(+), 168 deletions(-) rename src/js/lib/content/src/{index.js => index.ts} (52%) create mode 100644 src/js/lib/content/tsconfig.json diff --git a/.prettierrc.yaml b/.prettierrc.yaml index c0cf095109..4c6d313b53 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -1,6 +1,6 @@ overrides: - - files: "*.js" + - files: "*.[j|t]s" options: printWidth: 100 tabWidth: 4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9120c369d7..80df8bc008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,10 @@ you can find their release notes
![Bug Fixes](/docs/assets/tags/bug_fixes.svg) +- [Fix some internal settings not being applied correctly in the IDE][1539]. + Some arguments were not passed correctly to the IDE leading to erroneous + behaviour in the electron app. This is now fixed. + #### Visual Environment - [Some command line arguments were not applied correctly in the IDE][1536]. @@ -59,6 +63,7 @@ you can find their release notes [1511]: https://github.com/enso-org/ide/pull/1511 [1536]: https://github.com/enso-org/ide/pull/1536 [1531]: https://github.com/enso-org/ide/pull/1531 +[1531]: https://github.com/enso-org/ide/pull/1539
diff --git a/src/js/lib/content/package.js b/src/js/lib/content/package.js index 64bb53e05d..4492acabf3 100644 --- a/src/js/lib/content/package.js +++ b/src/js/lib/content/package.js @@ -14,6 +14,10 @@ let config = { "compression-webpack-plugin": "^3.1.0", "copy-webpack-plugin": "^5.1.1", "yaml-loader": "^0.6.0", + "ts-loader": "^8.0.3", + "typescript": "^4.0.2", + "webpack": "^4.44.1", + "webpack-cli": "^3.3.12" } } diff --git a/src/js/lib/content/src/index.js b/src/js/lib/content/src/index.ts similarity index 52% rename from src/js/lib/content/src/index.js rename to src/js/lib/content/src/index.ts index bd239b9d05..ef7620e872 100644 --- a/src/js/lib/content/src/index.js +++ b/src/js/lib/content/src/index.ts @@ -2,14 +2,16 @@ /// user with a visual representation of this process (welcome screen). It also implements a view /// allowing to choose a debug rendering test from. +// @ts-ignore import * as loader_module from 'enso-studio-common/src/loader' -import * as html_utils from 'enso-studio-common/src/html_utils' -import * as animation from 'enso-studio-common/src/animation' -import * as globalConfig from '../../../../config.yaml' -import cfg from '../../../config' -import assert from "assert"; - - +// @ts-ignore +import * as html_utils from 'enso-studio-common/src/html_utils' +// @ts-ignore +import * as globalConfig from '../../../../config.yaml' +// @ts-ignore +import cfg from '../../../config' +// @ts-ignore +import assert from 'assert' // ================= // === Constants === @@ -17,16 +19,31 @@ import assert from "assert"; const ALIVE_LOG_INTERVAL = 1000 * 60 - - // ================== // === Global API === // ================== -let API = {} -window[globalConfig.windowAppScopeName] = API +class ContentApi { + main: (inputConfig: any) => Promise + private logger: MixpanelLogger + + initLogging(config: Config) { + assert(typeof config.no_data_gathering == 'boolean') + if (!config.no_data_gathering) { + this.logger = new MixpanelLogger() + } + } + remoteLog(event: string, data?: any) { + if (this.logger) { + this.logger.log(event, data) + } + } +} +const API = new ContentApi() +// @ts-ignore +window[globalConfig.windowAppScopeName] = API // ======================== // === Content Download === @@ -37,27 +54,29 @@ let incorrect_mime_type_warning = ` 'application/wasm' MIME type. Falling back to 'WebAssembly.instantiate' which is slower. ` -function wasm_instantiate_streaming(resource,imports) { - return WebAssembly.instantiateStreaming(resource,imports).catch(e => { - return wasm_fetch.then(r => { - if (r.headers.get('Content-Type') != 'application/wasm') { - console.warn(`${incorrect_mime_type_warning} Original error:\n`, e) - return r.arrayBuffer() - } else { - throw("Server not configured to serve WASM with 'application/wasm' mime type.") - } - }).then(bytes => WebAssembly.instantiate(bytes,imports)) - }) +async function wasm_instantiate_streaming( + resource: Response, + imports: WebAssembly.Imports +): Promise { + try { + return WebAssembly.instantiateStreaming(resource, imports) + } catch (e) { + const r = await resource + if (r.headers.get('Content-Type') != 'application/wasm') { + console.warn(`${incorrect_mime_type_warning} Original error:\n`, e) + return r.arrayBuffer() + } else { + throw "Server not configured to serve WASM with 'application/wasm' mime type." + } + } } - /// Downloads the WASM binary and its dependencies. Displays loading progress bar unless provided /// with `{use_loader:false}` option. -async function download_content(config) { +async function download_content(config: { wasm_glue_url: RequestInfo; wasm_url: RequestInfo }) { let wasm_glue_fetch = await fetch(config.wasm_glue_url) - let wasm_fetch = await fetch(config.wasm_url) - let loader = - new loader_module.Loader([wasm_glue_fetch,wasm_fetch], config) + let wasm_fetch = await fetch(config.wasm_url) + let loader = new loader_module.Loader([wasm_glue_fetch, wasm_fetch], config) // TODO [mwu] // Progress indication for WASM loading is hereby capped at 30%. @@ -66,38 +85,38 @@ async function download_content(config) { // See https://github.com/enso-org/ide/issues/1237 for an immediate reason. // See https://github.com/enso-org/ide/issues/1105 for a broader context. loader.cap_progress_at = 0.3 - + loader.done.then(() => { console.groupEnd() - console.log("Download finished. Finishing WASM compilation.") + console.log('Download finished. Finishing WASM compilation.') }) let download_size = loader.show_total_bytes() let download_info = `Downloading WASM binary and its dependencies (${download_size}).` - let wasm_loader = html_utils.log_group_collapsed(download_info, async () => { + let wasm_loader = html_utils.log_group_collapsed(download_info, async () => { let wasm_glue_js = await wasm_glue_fetch.text() - let wasm_glue = Function("let exports = {};" + wasm_glue_js + "; return exports")() - let imports = wasm_glue.wasm_imports() - console.log("WASM dependencies loaded.") - console.log("Starting online WASM compilation.") - let wasm_loader = await wasm_instantiate_streaming(wasm_fetch,imports) + let wasm_glue = Function('let exports = {};' + wasm_glue_js + '; return exports')() + let imports = wasm_glue.wasm_imports() + console.log('WASM dependencies loaded.') + console.log('Starting online WASM compilation.') + let wasm_loader = await wasm_instantiate_streaming(wasm_fetch, imports) + // @ts-ignore wasm_loader.wasm_glue = wasm_glue return wasm_loader }) - let wasm = await wasm_loader.then(({instance,module,wasm_glue}) => { + // @ts-ignore + let wasm = await wasm_loader.then(({ instance, module, wasm_glue }) => { let wasm = instance.exports - wasm_glue.after_load(wasm,module) + wasm_glue.after_load(wasm, module) return wasm }) - console.log("WASM Compiled.") + console.log('WASM Compiled.') await loader.initialized - return {wasm,loader} + return { wasm, loader } } - - // ==================== // === Debug Screen === // ==================== @@ -106,46 +125,44 @@ async function download_content(config) { let main_entry_point = 'ide' /// Prefix name of each scene defined in the WASM binary. -let wasm_entry_point_pfx = "entry_point_" - +let wasm_entry_point_pfx = 'entry_point_' /// Displays a debug screen which allows the user to run one of predefined debug examples. -function show_debug_screen(wasm,msg) { - API.remoteLog("show_debug_screen") +function show_debug_screen(wasm: any, msg: string) { + API.remoteLog('show_debug_screen') let names = [] for (let fn of Object.getOwnPropertyNames(wasm)) { if (fn.startsWith(wasm_entry_point_pfx)) { - let name = fn.replace(wasm_entry_point_pfx,"") + let name = fn.replace(wasm_entry_point_pfx, '') names.push(name) } } - if(msg==="" || msg===null || msg===undefined) { msg = "" } + if (msg === '' || msg === null || msg === undefined) { + msg = '' + } let debug_screen_div = html_utils.new_top_level_div() - let newDiv = document.createElement("div") - let newContent = document.createTextNode(msg + "Available entry points:") - let currentDiv = document.getElementById("app") - let ul = document.createElement('ul') + let newDiv = document.createElement('div') + let newContent = document.createTextNode(msg + 'Available entry points:') + let ul = document.createElement('ul') debug_screen_div.style.position = 'absolute' - debug_screen_div.style.zIndex = 1 + debug_screen_div.style.zIndex = 1 newDiv.appendChild(newContent) debug_screen_div.appendChild(newDiv) newDiv.appendChild(ul) for (let name of names) { - let li = document.createElement('li') - let a = document.createElement('a') + let li = document.createElement('li') + let a = document.createElement('a') let linkText = document.createTextNode(name) ul.appendChild(li) a.appendChild(linkText) - a.title = name - a.href = "?entry="+name + a.title = name + a.href = '?entry=' + name li.appendChild(a) } } - - // ==================== // === Scam Warning === // ==================== @@ -159,52 +176,58 @@ function printScamWarning() { font-weight : bold; padding: 10px 20px 10px 20px; ` - let headerCSS1 = headerCSS + "font-size : 46px;" - let headerCSS2 = headerCSS + "font-size : 20px;" - let msgCSS = "font-size:16px;" - - let msg1 = "This is a browser feature intended for developers. If someone told you to " + - "copy-paste something here, it is a scam and will give them access to your " + - "account and data." - let msg2 = "See https://github.com/enso-org/ide/blob/main/docs/security/selfxss.md for more " + - "information." - console.log("%cStop!",headerCSS1) - console.log("%cYou may be victim of a scam!",headerCSS2) - console.log("%c"+msg1,msgCSS) - console.log("%c"+msg2,msgCSS) + let headerCSS1 = headerCSS + 'font-size : 46px;' + let headerCSS2 = headerCSS + 'font-size : 20px;' + let msgCSS = 'font-size:16px;' + + let msg1 = + 'This is a browser feature intended for developers. If someone told you to ' + + 'copy-paste something here, it is a scam and will give them access to your ' + + 'account and data.' + let msg2 = + 'See https://github.com/enso-org/ide/blob/main/docs/security/selfxss.md for more ' + + 'information.' + console.log('%cStop!', headerCSS1) + console.log('%cYou may be victim of a scam!', headerCSS2) + console.log('%c' + msg1, msgCSS) + console.log('%c' + msg2, msgCSS) } - - // ====================== // === Remote Logging === // ====================== class MixpanelLogger { + private readonly mixpanel: any + constructor() { - this.mixpanel = require('mixpanel-browser'); - this.mixpanel.init("5b541aeab5e08f313cdc1d1bbebc12ac", { "api_host": "https://api-eu.mixpanel.com" }, ""); + this.mixpanel = require('mixpanel-browser') + this.mixpanel.init( + '5b541aeab5e08f313cdc1d1bbebc12ac', + { api_host: 'https://api-eu.mixpanel.com' }, + '' + ) } - log(event,data) { + log(event: string, data: any) { if (this.mixpanel) { event = MixpanelLogger.trim_message(event) if (data !== undefined && data !== null) { data = MixpanelLogger.trim_message(JSON.stringify(data)) - this.mixpanel.track(event,{data}); + this.mixpanel.track(event, { data }) } else { - this.mixpanel.track(event); + this.mixpanel.track(event) } } else { console.warn(`Failed to log the event '${event}'.`) } } - static trim_message(message) { - const MAX_MESSAGE_LENGTH = 500; - let trimmed = message.substr(0,MAX_MESSAGE_LENGTH) + static trim_message(message: string) { + const MAX_MESSAGE_LENGTH = 500 + let trimmed = message.substr(0, MAX_MESSAGE_LENGTH) if (trimmed.length < message.length) { - trimmed += "..." + trimmed += '...' } return trimmed } @@ -214,36 +237,46 @@ class MixpanelLogger { // === Logs Buffering === // ====================== -const logsFns = ['log','info','debug','warn','error','group','groupCollapsed','groupEnd'] +const logsFns = ['log', 'info', 'debug', 'warn', 'error', 'group', 'groupCollapsed', 'groupEnd'] class LogRouter { + private buffer: any[] + private readonly raw: {} + autoFlush: boolean + constructor() { - this.buffer = [] - this.raw = {} + this.buffer = [] + this.raw = {} this.autoFlush = true + // @ts-ignore console.autoFlush = true for (let name of logsFns) { + // @ts-ignore this.raw[name] = console[name] + // @ts-ignore console[name] = (...args) => { - this.handle(name,args) + this.handle(name, args) } } } auto_flush_on() { this.autoFlush = true + // @ts-ignore console.autoFlush = true - for (let {name,args} of this.buffer) { + for (let { name, args } of this.buffer) { + // @ts-ignore this.raw[name](...args) } this.buffer = [] } - handle(name,args) { + handle(name: string, args: any[]) { if (this.autoFlush) { + // @ts-ignore this.raw[name](...args) } else { - this.buffer.push({name,args}) + this.buffer.push({ name, args }) } // The following code is just a hack to discover if the logs start with `[E]` which @@ -271,8 +304,8 @@ class LogRouter { } } - handleError(...args) { - API.remoteLog("error", args) + handleError(...args: any[]) { + API.remoteLog('error', args) } } @@ -281,6 +314,7 @@ let logRouter = new LogRouter() function hideLogs() { console.log('All subsequent logs will be hidden. Eval `showLogs()` to reveal them.') logRouter.autoFlush = false + // @ts-ignore console.autoFlush = false } @@ -288,10 +322,9 @@ function showLogs() { logRouter.auto_flush_on() } +// @ts-ignore window.showLogs = showLogs - - // ====================== // === Crash Handling === // ====================== @@ -304,7 +337,7 @@ function initCrashHandling() { } } -const crashMessageStorageKey = "crash-message" +const crashMessageStorageKey = 'crash-message' function previousCrashMessageExists() { return sessionStorage.getItem(crashMessageStorageKey) !== null @@ -314,7 +347,7 @@ function getPreviousCrashMessage() { return sessionStorage.getItem(crashMessageStorageKey) } -function storeLastCrashMessage(message) { +function storeLastCrashMessage(message: string) { sessionStorage.setItem(crashMessageStorageKey, message) } @@ -322,7 +355,6 @@ function clearPreviousCrashMessage() { sessionStorage.removeItem(crashMessageStorageKey) } - // === Crash detection === function setupCrashDetection() { @@ -341,38 +373,42 @@ function setupCrashDetection() { window.addEventListener('unhandledrejection', function (event) { // As above, we prefer stack traces. // But here, `event.reason` is not even guaranteed to be an `Error`. - handleCrash(event.reason.stack || event.reason.message || "Unhandled rejection") + handleCrash(event.reason.stack || event.reason.message || 'Unhandled rejection') }) } -function handleCrash(message) { - API.remoteLog("crash", message) +function handleCrash(message: string) { + API.remoteLog('crash', message) if (document.getElementById(crashBannerId) === null) { storeLastCrashMessage(message) location.reload() } else { - for (let element of [... document.body.childNodes]) { + // @ts-ignore + for (let element of [...document.body.childNodes]) { + // @ts-ignore if (element.id !== crashBannerId) { element.remove() } } - document.getElementById(crashBannerContentId).insertAdjacentHTML("beforeend", + document.getElementById(crashBannerContentId).insertAdjacentHTML( + 'beforeend', `
-
A second error occurred. This time, the IDE will not automatically restart.
`) +
A second error occurred. This time, the IDE will not automatically restart.
` + ) } } - // === Crash recovery === // Those IDs should be the same that are used in index.html. -const crashBannerId = "crash-banner" -const crashBannerContentId = "crash-banner-content" -const crashReportButtonId = "crash-report-button" -const crashBannerCloseButtonId = "crash-banner-close-button" - -function showCrashBanner(message) { - document.body.insertAdjacentHTML('afterbegin', +const crashBannerId = 'crash-banner' +const crashBannerContentId = 'crash-banner-content' +const crashReportButtonId = 'crash-report-button' +const crashBannerCloseButtonId = 'crash-banner-close-button' + +function showCrashBanner(message: string) { + document.body.insertAdjacentHTML( + 'afterbegin', `
@@ -390,9 +426,9 @@ function showCrashBanner(message) { report_button.onclick = async _event => { try { await reportCrash(message) - content.textContent = "Thank you, the crash was reported." + content.textContent = 'Thank you, the crash was reported.' } catch (e) { - content.textContent = "The crash could not be reported." + content.textContent = 'The crash could not be reported.' } } close_button.onclick = () => { @@ -400,20 +436,19 @@ function showCrashBanner(message) { } } -async function reportCrash(message) { +async function reportCrash(message: string) { + // @ts-ignore const crashReportHost = API[globalConfig.windowAppScopeConfigName].crash_report_host await fetch(`http://${crashReportHost}/`, { method: 'POST', mode: 'no-cors', headers: { - 'Content-Type': 'text/plain' + 'Content-Type': 'text/plain', }, - body: message - }) + body: message, + }) } - - // ======================== // === Main Entry Point === // ======================== @@ -426,6 +461,7 @@ function style_root() { /// Waits for the window to finish its show animation. It is used when the website is run in /// Electron. Please note that it returns immediately in the web browser. async function windowShowAnimation() { + // @ts-ignore await window.showAnimation } @@ -435,63 +471,104 @@ function disableContextMenu() { }) } -function ok(value) { +function ok(value: any) { return value !== null && value !== undefined } +class Config { + public use_loader: boolean + public wasm_url: string + public wasm_glue_url: string + public crash_report_host: string + public no_data_gathering: boolean + public is_in_cloud: boolean + public entry: string + + static default() { + let config = new Config() + config.use_loader = true + config.wasm_url = '/assets/ide.wasm' + config.wasm_glue_url = '/assets/wasm_imports.js' + config.crash_report_host = cfg.defaultLogServerHost + config.no_data_gathering = false + config.is_in_cloud = false + config.entry = null + return config + } + + updateFromObject(other: any) { + if (!ok(other)) { + return + } + this.use_loader = ok(other.use_loader) ? tryAsBoolean(other.use_loader) : this.use_loader + this.no_data_gathering = ok(other.no_data_gathering) + ? tryAsBoolean(other.no_data_gathering) + : this.no_data_gathering + this.is_in_cloud = ok(other.is_in_cloud) + ? tryAsBoolean(other.is_in_cloud) + : this.is_in_cloud + this.wasm_url = ok(other.wasm_url) ? tryAsString(other.wasm_url) : this.wasm_url + this.wasm_glue_url = ok(other.wasm_glue_url) + ? tryAsString(other.wasm_glue_url) + : this.wasm_glue_url + this.crash_report_host = ok(other.crash_report_host) + ? tryAsString(other.crash_report_host) + : this.crash_report_host + this.entry = ok(other.entry) ? tryAsString(other.entry) : this.entry + } +} + /// Check whether the value is a string with value `"true"`/`"false"`, if so, return the // appropriate boolean instead. Otherwise, return the original value. -function parseBooleanOrLeaveAsIs(value) { - if (value === "true"){ +function parseBooleanOrLeaveAsIs(value: any): any { + if (value === 'true') { return true } - if (value === "false"){ + if (value === 'false') { return false } return value } -/// Turn all values that have a boolean in string representation (`"true"`/`"false"`) into actual -/// booleans (`true/`false``). -function parseAllBooleans(config) { - for (const key in config) { - config[key] = parseBooleanOrLeaveAsIs(config[key]) - } +function tryAsBoolean(value: any): boolean { + value = parseBooleanOrLeaveAsIs(value) + assert(typeof value == 'boolean') + return value } -function initLogging(config) { - assert(typeof config.no_data_gathering == "boolean") - if (config.no_data_gathering ) { - API.remoteLog = function (_event, _data) {} - } else { - let logger = new MixpanelLogger - API.remoteLog = function (event,data) {logger.log(event,data)} - } +function tryAsString(value: any): string { + return value.toString() } /// Main entry point. Loads WASM, initializes it, chooses the scene to run. -API.main = async function (inputConfig) { - let defaultConfig = { - use_loader : true, - wasm_url : '/assets/ide.wasm', - wasm_glue_url : '/assets/wasm_imports.js', - crash_report_host : cfg.defaultLogServerHost, - no_data_gathering : false, - is_in_cloud : false, - } - let urlParams = new URLSearchParams(window.location.search); - let urlConfig = Object.fromEntries(urlParams.entries()) - let config = Object.assign(defaultConfig,inputConfig,urlConfig) - parseAllBooleans(config) +API.main = async function (inputConfig: any) { + const urlParams = new URLSearchParams(window.location.search) + // @ts-ignore + const urlConfig = Object.fromEntries(urlParams.entries()) + + const config = Config.default() + config.updateFromObject(inputConfig) + config.updateFromObject(urlConfig) + + // @ts-ignore API[globalConfig.windowAppScopeConfigName] = config - initLogging(config) + API.initLogging(config) + + window.setInterval(() => { + API.remoteLog('alive') + }, ALIVE_LOG_INTERVAL) - window.setInterval(() =>{API.remoteLog("alive");}, ALIVE_LOG_INTERVAL) - //Build data injected during the build process. See `webpack.config.js` for the source. - API.remoteLog("git_hash", {hash: GIT_HASH}) - API.remoteLog("build_information", BUILD_INFO) - API.remoteLog("git_status", {satus: GIT_STATUS}) + // Build data injected during the build process. See `webpack.config.js` for the source. + // @ts-ignore + const hash = GIT_HASH + API.remoteLog('git_hash', { hash }) + // @ts-ignore + const buildInfo = BUILD_INFO + API.remoteLog('build_information', buildInfo) + // @ts-ignore + const status = GIT_STATUS + API.remoteLog('git_status', { status }) //initCrashHandling() style_root() @@ -500,21 +577,23 @@ API.main = async function (inputConfig) { disableContextMenu() let entryTarget = ok(config.entry) ? config.entry : main_entry_point - config.use_loader = config.use_loader && (entryTarget === main_entry_point) + config.use_loader = config.use_loader && entryTarget === main_entry_point - API.remoteLog("window_show_animation") + API.remoteLog('window_show_animation') await windowShowAnimation() - API.remoteLog("download_content") - let {wasm,loader} = await download_content(config) - API.remoteLog("wasm_loaded") + API.remoteLog('download_content') + let { wasm, loader } = await download_content(config) + API.remoteLog('wasm_loaded') if (entryTarget) { let fn_name = wasm_entry_point_pfx + entryTarget - let fn = wasm[fn_name] - if (fn) { fn() } else { + let fn = wasm[fn_name] + if (fn) { + fn() + } else { loader.destroy() - show_debug_screen(wasm,"Unknown entry point '" + entryTarget + "'. ") + show_debug_screen(wasm, "Unknown entry point '" + entryTarget + "'. ") } } else { - show_debug_screen(wasm) + show_debug_screen(wasm, '') } } diff --git a/src/js/lib/content/tsconfig.json b/src/js/lib/content/tsconfig.json new file mode 100644 index 0000000000..4d28ded9be --- /dev/null +++ b/src/js/lib/content/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "noImplicitAny": true, + "target": "ES5", + "module": "ES2015" + } +} \ No newline at end of file diff --git a/src/js/lib/content/webpack.config.js b/src/js/lib/content/webpack.config.js index 1f5d9c5945..44b5c1c967 100644 --- a/src/js/lib/content/webpack.config.js +++ b/src/js/lib/content/webpack.config.js @@ -18,7 +18,7 @@ const BUILD_INFO = JSON.parse(require('fs').readFileSync(buildPath, 'utf8')); module.exports = { entry: { - index: path.resolve(thisPath,'src','index.js'), + index: path.resolve(thisPath,'src','index.ts'), wasm_imports: './src/wasm_imports.js', }, output: { @@ -51,8 +51,9 @@ module.exports = { }, resolve: { alias: { - wasm_rust_glue$: path.resolve(wasmPath,'ide.js') - } + wasm_rust_glue$: path.resolve(wasmPath,'ide.js'), + }, + extensions: [ '.ts', '.js' ], }, performance: { hints: false, @@ -65,6 +66,11 @@ module.exports = { test: /\.ya?ml$/, type: 'json', use: 'yaml-loader' + }, + { + test: /\.tsx?/, + use: 'ts-loader', + exclude: /node_modules/, } ] }