diff --git a/web/build.sh b/web/build.sh index 179102705b..3db55449a0 100755 --- a/web/build.sh +++ b/web/build.sh @@ -29,6 +29,7 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \ "configure" \ "build" \ "test" \ + ":app/browser The form of Keyman Engine for Web for use on websites" \ ":app/webview A puppetable version of KMW designed for use in a host app's WebView" \ ":engine/attachment Subset used for detecting valid page contexts for use in text editing " \ ":engine/device-detect Subset used for device-detection " \ @@ -95,12 +96,13 @@ builder_run_child_actions build:engine/package-cache # Uses engine/paths, engine/device-detect, engine/package-cache, & engine/osk builder_run_child_actions build:engine/main -# Uses all but engine/element-wrappers +# Uses all but engine/element-wrappers and engine/attachment builder_run_child_actions build:app/webview # Uses literally everything `engine/` above -# Is not yet compilable due to unmodularized components. -# builder_run_child_actions build:app/browser +builder_run_child_actions build:app/browser + +builder_run_child_actions test if builder_has_action build:app/browser; then builder_die "Modularization work is not yet complete; builds dependent on this will fail." @@ -110,8 +112,6 @@ if builder_has_action build:app/ui; then builder_die "Modularization work is not yet complete; builds dependent on this will fail." fi -builder_run_child_actions test - if builder_start_action test; then ./test.sh :engine diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 8da047207a..786f30a0db 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -7,6 +7,28 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; +import fs from 'fs'; + +/* + * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - + * the `@class` emit comment-annotation is designed to facilitate tree-shaking for ES5-targeted + * down-level emits. `esbuild` doesn't look for it by default... but we can override that with + * this plugin. + */ +let es5ClassAnnotationAsPurePlugin = { + name: '@class -> __PURE__', + setup(build) { + build.onLoad({filter: /\.js$/ }, async (args) => { + let source = await fs.promises.readFile(args.path, 'utf8'); + return { + // Marks any classes compiled by TS (as per the /** @class */ annotation) + // as __PURE__ in order to facilitate tree-shaking. + contents: source.replace('/** @class */', '/* @__PURE__ */ /** @class */'), + loader: 'js' + } + }); + } +} await esbuild.build({ bundle: true, @@ -14,9 +36,29 @@ await esbuild.build({ format: "iife", nodePaths: ['../../../../node_modules'], entryPoints: { - 'index': '../../../build/app/webview/obj/main.js', + 'index': '../../../build/app/browser/obj/debug-main.js', }, - outdir: '../../../build/app/webview/lib/', - tsconfig: './tsconfig.json', - target: "es5" + outfile: '../../../build/app/browser/debug/keymanweb.js', + plugins: [ es5ClassAnnotationAsPurePlugin ], + target: "es5", + treeShaking: true, + tsconfig: './tsconfig.json' +}); + +await esbuild.build({ + bundle: true, + sourcemap: true, + minifyWhitespace: true, + minifySyntax: true, + minifyIdentifiers: false, + format: "iife", + nodePaths: ['../../../../node_modules'], + entryPoints: { + 'index': '../../../build/app/browser/obj/release-main.js', + }, + outfile: '../../../build/app/browser/release/keymanweb.js', + plugins: [ es5ClassAnnotationAsPurePlugin ], + target: "es5", + treeShaking: true, + tsconfig: './tsconfig.json' }); diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index 672e64dcfe..a88e76c896 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -38,11 +38,16 @@ builder_describe "Builds the Keyman Engine for Web's website-integrating version # Possible TODO?s # "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \ +builder_parse "$@" + +config=release +if builder_is_debug_build; then + config=debug +fi + builder_describe_outputs \ configure /node_modules \ - build /web/build/$SUBPROJECT_NAME/lib/index.js - -builder_parse "$@" + build /web/build/$SUBPROJECT_NAME/$config/keymanweb.js #### Build action definitions #### diff --git a/web/src/app/browser/src/context/pageIntegrationHandlers.ts b/web/src/app/browser/src/context/pageIntegrationHandlers.ts index 579cc99a9f..98367eb422 100644 --- a/web/src/app/browser/src/context/pageIntegrationHandlers.ts +++ b/web/src/app/browser/src/context/pageIntegrationHandlers.ts @@ -1,6 +1,6 @@ import { DomEventTracker } from 'keyman/engine/events'; -import { KeymanEngine } from "../keymanEngine.js"; +import KeymanEngine from "../keymanEngine.js"; import { FocusAssistant } from './focusAssistant.js'; // Note: in the future, it'd probably be best to have an instance per iframe window as diff --git a/web/src/app/browser/src/debug-main.ts b/web/src/app/browser/src/debug-main.ts new file mode 100644 index 0000000000..f8331e36de --- /dev/null +++ b/web/src/app/browser/src/debug-main.ts @@ -0,0 +1,15 @@ +import KeymanEngine from './keymanEngine.js' +import { SourcemappedWorker } from '@keymanapp/lexical-model-layer/web' + + /** + * Determine path and protocol of executing script, setting them as + * construction defaults. + * + * This can only be done during load when the active script will be the + * last script loaded. Otherwise the script must be identified by name. + */ + var scripts = document.getElementsByTagName('script'); + var ss = scripts[scripts.length-1].src; + var sPath = ss.substr(0,ss.lastIndexOf('/')+1); + +window['keyman'] = new KeymanEngine(SourcemappedWorker.constructInstance(), sPath); \ No newline at end of file diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 98e1ef857e..8b945dbc56 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -14,7 +14,7 @@ import { PageIntegrationHandlers } from './context/pageIntegrationHandlers.js'; import { LanguageMenu } from './languageMenu.js'; import { setupOskListeners } from './oskConfiguration.js'; -export class KeymanEngine extends KeymanEngineBase { +export default class KeymanEngine extends KeymanEngineBase { touchLanguageMenu?: LanguageMenu; private pageIntegration: PageIntegrationHandlers; diff --git a/web/src/app/browser/src/languageMenu.ts b/web/src/app/browser/src/languageMenu.ts index 4c543989fd..5493752280 100644 --- a/web/src/app/browser/src/languageMenu.ts +++ b/web/src/app/browser/src/languageMenu.ts @@ -2,7 +2,7 @@ import { getAbsoluteX, landscapeView } from "keyman/engine/dom-utils"; import { KeyboardStub } from "keyman/engine/package-cache"; -import { KeymanEngine } from "./keymanEngine.js"; +import KeymanEngine from "./keymanEngine.js"; import * as util from "./utils/index.js"; // Used by 'native'-mode KMW only - the Android and iOS embedding apps implement their own menus. diff --git a/web/src/app/browser/src/main.ts b/web/src/app/browser/src/main.ts deleted file mode 100644 index e2a56806ca..0000000000 --- a/web/src/app/browser/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -import ContextManager from './contextManager.js'; \ No newline at end of file diff --git a/web/src/app/browser/src/oskConfiguration.ts b/web/src/app/browser/src/oskConfiguration.ts index ecde1f1120..831583421e 100644 --- a/web/src/app/browser/src/oskConfiguration.ts +++ b/web/src/app/browser/src/oskConfiguration.ts @@ -1,7 +1,7 @@ import { type KeyElement, OSKView, VisualKeyboard } from "keyman/engine/osk"; import { KEYMAN_VERSION } from "@keymanapp/keyman-version"; import ContextManager from "./contextManager.js"; -import { KeymanEngine } from "./keymanEngine.js"; +import KeymanEngine from "./keymanEngine.js"; import { LanguageMenu } from "./languageMenu.js"; export function setupOskListeners(engine: KeymanEngine, osk: OSKView, contextManager: ContextManager) { diff --git a/web/src/app/browser/src/release-main.ts b/web/src/app/browser/src/release-main.ts new file mode 100644 index 0000000000..8fba3bb293 --- /dev/null +++ b/web/src/app/browser/src/release-main.ts @@ -0,0 +1,15 @@ +import KeymanEngine from './keymanEngine.js' +import { Worker } from '@keymanapp/lexical-model-layer/web' + + /** + * Determine path and protocol of executing script, setting them as + * construction defaults. + * + * This can only be done during load when the active script will be the + * last script loaded. Otherwise the script must be identified by name. + */ + var scripts = document.getElementsByTagName('script'); + var ss = scripts[scripts.length-1].src; + var sPath = ss.substr(0,ss.lastIndexOf('/')+1); + +window['keyman'] = new KeymanEngine(Worker.constructInstance(), sPath); \ No newline at end of file