On the frontend, Submitty uses JavaScript to allow for interactivity with the site. Historically, the JS code was written
and imported globally and that lives in the site/public/js
folder. However, embracing modern standards, we are increasingly
looking to write all new frontend code as ES6 modules.
ES6 Modules allow JavaScript code to be split up
into “modules” that can allow other modules to import functions and variables. Modules in Submitty are loaded the
same way as normal JavaScript files using the PHP function $this->output->addInternalJs(...)
. Unlike normal
JavaScript, modules are “bundled” together by esbuild. This process takes all files imported and creates a single,
minified JavaScript file that is served to the user.
Unlike regular JS files, ES6 modules are not included into the global JS scope, and are self-contained. When a module is
executed, it only has access to the things that it imports, and we cannot call functions defined within modules directly from
the DOM. As such, to hook a function defined in a module to something in the DOM, such as a click event listener, you will need to
use the addEventListener API. This separation of regular JS and modules also extends to HTML, attributes such as onclick
and other inline event handlers cannot call functions defined in modules even if they are exported.
JavaScript modules are placed in the Submitty/site/ts
directory and running the install script will launch esbuild
and bundle files, the output will be placed in the Submitty/site/mjs
directory. You can run the command
npm run build
to manually bundle files after running npm install
from the Submitty/site
directory.
npm run build
uses node to run the Submitty/site/.build.js
file, this contains the logic for searching which files
to transpile and bundle, along with the configuration for esbuild.
Creating new modules
Any new module written should be a .ts
file to take advantage the type safety of TypeScript. Top levels files
under the ts
directory are considered entry points and once bundled will create a corresponding .js
file under
the public/mjs
directory along with a sourcemap. Support files for your module can be created under a new
directory in the ts
area, like the utils
directory. On the PHP side, you will then only need
to include the single corresponding mjs
file to your top level entry point file. This has the advantage of
reducing the number of files sent to the client.
TypeScript
TypeScript is a superset of JavaScript that allows strongly typed syntax which is
then transpiled into normal JavaScript. Any file with the .ts
file type will be transpiled by esbuild as part of the
npm run build
command and Submitty install script.
Loading JavaScript files
Once a module or regular JavaScript file is written it needs to be served to the user and included by the HTML. This is
done in PHP through the addInternalModuleJs
and addInternalJS
. Each function takes a target filename which is then
sent to the user and timestamped to prevent browsers from caching old versions. These functions are defined in the
site/app/libraries/Output.php
.
Both functions work similarly but the addInternalJS
searches the site/public/js
directory and addInternalModuleJs
searches the site/public/mjs
directory.
E.g:
// searches the site/public/mjs area, its source file would be site/ts/foo.ts
$this->output->addInternalModuleJs('foo.js');
// searches the site/public/js area
$this->output->addInternalJS('foo.js');