This ArcOS wiki is still under active development, and contains errors and unfinished pages.

Import and export

The environment that third-party applications run in is far from ordinary, mostly because we're dealing with the ArcOS filesystem instead of regular old web assets. Unfortunately this means that importing and exporting between JS files is also a little bit... unorthodox. You can't use a regular require call or import keyword.

What's different from normal JS?

  • You have access to top-level await and return statements

  • There is no import, export or require

  • TPA javascript has a lot more global variables, classes and properties, which you've undoubtedly encountered already.

Importing and exporting

Say you have a class in tray.js that you want to make use of in main.js. This is quite simple! First, at the bottom of tray.js, export the class by "returning" it:

tray.js
class MyClass {
  // some code
}

return { MyClass };

Then, inside main.js, destructure the result of an awaited call to the load function to get the class:

main.js
const { MyClass } = await load("tray.js");

The load function is asynchronous: it has to be awaited.

Assuming none of the files in the "import chain" had syntactic errors, your class is now available in main.js.

The Working Directory

In regular JS, if you want to import a file from another directory on the same level, you'll use .. to get to the parent directory, like so:

import { MyClass } from "../js/tray.js";

Paths in ArcOS third-party apps are a little different. No matter in what child directory your file is located, your working directory (or __dirname if you're a NodeJS person) will always be equal to the root of your project. So, if js/main.js wants to import that MyClass, it'll have to import js/tray.js instead of tray.js. This example might seem simple, but it gets a lot more complicated the more files and folders you intertwine.

The workingDirectory is determined by calculating the parent directory of the initial .tpa file from which your application was opened.

Behind the scenes

Internally, when the text of your script file is loaded, it is wrapped in a export default async function before being sent off to the backend, which returns a link that's loaded using a dynamic import. This extra step is put in place to give the wrapped version of your code a clear filename. Without this step, I'm forced to load the JS using Data URLs, which makes stack traces virtually unreadable. The wrap function takes an object that contains all variables, functions, classes and other globals made accessible to third-party apps. Observe this function that I just copied from the User Daemon:

const wrap = (c: string) =>
        `export default async function({ ${Object.keys(props).join(",")} }) { \nconst global = arguments;\n ${c}\n }`;

This wrapper takes your code and wraps it into a function. This is exactly the reason why top-level awaits work and why you have to return stuff instead of exporting them. The props variable here is the Third-party app props object assembled before this function is first called. I'm automatically destructuring the props to make them easily accessible to you. You're welcome.

Last updated