Hi folks, having delved into the rabbit hole that is home rolled import mechanisms before (in JavaScript for Automation, aka JXA), I’d thought I would share an improved version of @sylumer’s solution. This is basically a poor man’s version of the CommonJS module system (which the NodeJS module logic built and expanded on).
You will need this snippet of code in your script:
function require (path) {
try { var fm = FileManager.iCloud() } catch (e) { var fm = FileManager.local() }
let code = fm.readString(fm.joinPath(fm.documentsDirectory(), path))
if (code == null) throw new Error(`Module '${path}' not found.`)
return Function(`${code}; return exports`)()
}
Now, given a module script test-module
that declares a top level variable exports
like this:
const exports = {
foo: function () { console.log('foo') },
bar: 123,
Baz: class Baz {
constructor (qux) {
this.out = () => { console.log(qux) }
}
}
}
you can do:
const mod = require('test-module.js')
mod.foo() // logs 'foo'
console.log(mod.bar) // logs 123
new mod.Baz('qux').out() // logs 'qux'
Even better, you can use JavaScript’s object destructuring syntax to only import selected parts of the module into your script scope. For instance, if you only need the Baz
class, do:
const {Baz} = require('test-module.js')
new Baz('qux').out() // logs 'qux'
What are the advantages of this over the original solution? Well:
-
Function
is faster and safer thaneval()
– in fact, it is the recommended alternative toeval()
on MDN; - the defined module structure allows for exporting constant literals and classes defined via the
class
keyword, which is not possible witheval()
, and it allows for partial imports (see above); - error handling and proper fallback to local storage if iCloud is not active (assuming that Scriptable does the same; I haven’t checked).