Yeah, I thought about that and my first implementation actually had a very naive stab at it, but I eventually opted out of it. I‘ll explain my thinking:
The semantics of the module path in my hack are fundamentally different from those used by CommonJS / NodeJS. For technical reasons (both iOS sandbox and Scriptable API limitations), the module path is always anchored at Scriptable’s document directory, while the big players anchor it at the call point’s location in the file system. require(‘../lib/module.js’) will not work the way you’d expect it to coming from Node. In such cases, I find it best to not paper over differences by trying to match syntax.
Now you might object that even then, the convenience of not having to type three characters (“.js”) could trump semantics, but consider this:
you can’t simply slap on the extension unconditionally because that would just shift the annoyance from always having to type the extension to always having to omit the extension, while also precluding people from using any extension but “.js”;
you can’t say “OK, just slap it on if there is no extension already” because there is no easy way to say what actually constitutes an extension if the file name contains a dot anywhere (it’s 2018, even Windows allows extensions of arbitrary length by now, and Apple systems have for ages), which, by the way, they do if you use the Reverse Domain Notation naming scheme common to Apple’s OS. In short, you would end up either dictating a module naming scheme orthogonal to platform conventions (notice anything about the Node module naming conventions?) or having to maintain a list of “allowed alternate extensions” you’d have to update inline in all scripts whenever it changes.
All of this is solvable, but not within six lines of inlined code in a way that scales safely. I’m not opposed to “magic” functionality as such, but in this case, I feel the convenience of not adding the extension to the call is not worth the hassle. Feel free to try to change my mind, of course!
I agree, there’s really a lot more to it.
My line of thought is to actually to only replace path with (path.replace(/\.js$/,'') + '.js'). This handles both having/not having the extension and reverse domain name notation.
But yes, this would be limited to only using .js as the extension.
That actually was exactly my first naive implementation … however, as I stated, all this achieves is creating a restriction on file naming, all at the cost of one extra line of code in a snippet that needs to be inlined everywhere. I also happen to think that using the extension more clearly communicates intent, i.e. that you require a file, not some abstract module.
Anyway, lots of words to say I won’t be adding this into my reference snippet. You are of course free to modify it for your own usage!
CAVEAT: the following is just a technical tidbit without practical usage. For a module importing system that actually works, see my require() code above.
For those among you interested in the wider context, the whole thing may end up a non-issue in the foreseeable future: the dynamic import() spec is currently in stage 3 of the TC39 process, meaning it’s pretty much slated for inclusion in the JS language spec. In fact, import() has been in Chrome since build 64 and, more to the point, in WebKit and hence JavaScript Core, since January of, hold onto your hats, 2017.
@kopischke Thanks for sharing your thoughts about this. I expect to add support for importing other scripts in a future update and I think your implementation and the thoughts surrounding it is a great inspiration.
Long version: you are trying to import() a module, which returns an empty object because, as I wrote, the import() function exists but is not actually doing anything – as of the iOS APIs Scriptable is currently using – but returning a Promise that always fails. Promises are asynchronous, and as you don’t await the resolution, you get an empty placeholder object (if you did wait, you’d get an error). Hence your second log line, the empty placeholder object, and your third one, because all properties of empty objects are undefined by definition.
Even if import() did anything, your code would still fail, because you pass it a Function object, which is not executed and would not return anything even if it was, and that is not a path import() can work with. However, you do successfully define a function foo while doing so, meaning you can call it, hence your first log line.
I might have muddied the waters with my digression on import(). If you want to import an external module file, you currently should stick to the code and module format I posted above.
Importing / requiring is meant to pull code stored outside your current script into it. If you just want to group related functions into a namespace like “module”, put them into an object:
Sorry to bump an old thread, but I’m just now resurfacing Scriptable.
I’m trying out the fm.readString() function and it works okay, but Scriptable is injecting some comments at the top of my scripts that appear in the output.
I can regex my way around, but it would be nice if I didn’t have to. Any thoughts?
Output:
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: teal; icon-glyph: magic;
theThingIAmInterestedIn