Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

What's the best setup for building Roll20 scripts out of multiple Typescript files?

February 14 (4 years ago)

Edited February 14 (4 years ago)

EDIT to put some information I gathered at the top for future readers:

  • For tsconfig.json, I used target == "ES2019", and module == "None".
  • For Webpack, I used globalObject == "this", and libraryTarget == "umd".
  • I think a roll20.d.ts file should declare all of roll20's names using "declare global { }". Individual Typescript files should not import anything to account for this file, but should just use the global declarations. It seems like the roll20.d.ts file can then be included like any other source file. It was really hard to find information about this exact situation online (i.e. the situation where the runtime environment provides global symbols).
  • I got this done with one monolithic tsconfig.json that built everything; I should have done it that way first!
  • I separated Typescript compilation and Webpack packing into two different steps, by telling Webpack to build using the Javascript outputs instead of the Typescript files. This was simpler than fighting with ts-loader errors.
  • I think that Webpack is including its own module setup when the Javascript code doesn't have one. Fortunately this doesn't make Roll20 complain and script loading is not noticeably slower than a typical script.

Original question:

I'm looking for a way to set up a Typescript project, optionally with other tools like Webpack, to build multiple Typescript source files in multiple directories into a single script that I can copy-paste into the Roll20 editor. I'm a complete novice to front-end development and this is my first Javascript and my first Typescript project. EDIT: I think that was a mis-statement in terms of the question context.

There are two problems that crop up:

  1. As far as I can see, Roll20 doesn't support any of the typical module systems, so I need configs for Typescript and the surrounding tools that don't produce *any* module code. (Is there a module system that Roll20 supports, or at least that it will ignore?)
  2. When an output script is broken, I typically get a SyntaxError with no context of any kind (e.g. "SyntaxError: Unexpected token '.'"). So I really don't have any way to fix it other than blindly changing configs and hoping for the best.

Ideas?

Thanks!


February 14 (4 years ago)
timmaugh
Forum Champion
API Scripter

I don't have experience setting up a Typescript library so I can't help directly on that, but in the Roll20 environment, as I understand it, you have access to Node.js, vanilla JS, and the underscore library. I've pinged Aaron to see if he can offer more insight. In the meantime, I wonder if an online typescript => javascript converter would help...?

https://extendsclass.com/typescript-to-javascript.html


February 14 (4 years ago)

Edited February 14 (4 years ago)
The Aaron
Roll20 Production Team
API Scripter

Just a correction on the above. The API runs on Node, but there isn't any of Node's environment exposed to the API. All code is executed in a Sandbox, and only has access to Javascript (around ES2019), the Underscore.js library, and Roll20's interface. 

I have to say that tackling TypeScript, Webpack, and the API all in one go as a first timer seems like a bit much. I'd suggest starting with just writing some vanilla Javascript API scripts, then convert those to their TypeScript equivalents, then subdivide that and get wrbpack working. 

February 14 (4 years ago)

Edited February 14 (4 years ago)

I appreciate the responses. I'm not a first-timer to development, just to Typescript and Webpack and the ecosystem. The build systems all seem pretty weird compared to what you'd expect out of Java, or a typical C++ build environment.

I didn't know what version of Javascript to target, so I will check what the Typescript projects are set to generate.

February 14 (4 years ago)

Edited February 14 (4 years ago)

Update:

After Aaron's post, I changed the Javascript target version of my tsconfig from "esnext" to "es2019" and got a different syntax error (the runtime didn't know what the symbol "self" was). I dug around and found that Webpack was assuming a global variable "self" because it assumed an environment with window.self etc. available. So I switched it to a global variable of my choice and added a var X = {}; for it.

At that point I got a "cannot find module" error, and since my packed script has require() statements, I am thinking that require() is available, but just doesn't do anything. So there's a little chunk of Node.js that is exposed. I may be able to do something with module setup to fix it.

EDIT: I also have the option of using Typescript namespaces. I switched away from them because I thought Webpack was getting confused by them.

February 14 (4 years ago)
timmaugh
Forum Champion
API Scripter

A couple more notes on the environment...

...it isn't true "front-end" development as API scripts won't have access to the window, or any of the DOM. All of the exposed functionality is to the firebase db collating all of the changes that happen in the game, behind the scenes. There is no ability to require(), as you're working in a sandbox with a limited interface.

...all of the scripts are compiled into one long file, making error chasing sometimes difficult. See Aaron's method for helping to track those down.

...if this is your first foray into javascript (coming from a more formal language), you should investigate hoisting, the global namespace, and the revealing module pattern. Aaron compiled some other helpful links in this post.

But, also, welcome to the wonderful world of scripting!

February 14 (4 years ago)

I think I understated my experience too much, sorry. I've been working on this code for months in Javascript, and I think it was the last week or so that I rewrote it in Typescript after work. It's all of the surrounding infrastructure (building, deployment, Web apps, the window, useful npm libraries) that I have not worked with much.

Roll20 supports classes and #-privacy, so I've been using that in place of Revealing Module. (I think the community here would benefit *a lot* from using the class support, since Revealing Module adds a lot of weird boilerplate.)

My idea was to either stub out require() to "do the right thing" without formal Node.js support, or to find something in Node.js that allows you to dynamically add code as a loadable module (i.e. sort of like a Java class loader). Those are pretty hacky and I am probably going back to Typescript namespaces.

I have not strongly investigated hoisting, I've been limiting myself to use after declaration. That kind of thing is one reason I'm converting my stuff to Typescript.

February 14 (4 years ago)

I've gotten it to work. Thanks to Aaron for pointing out the importance of targeting the right JS version; once I fixed that Roll20's error output was much easier to understand.

Changes I made, which I'll record for the benefit of others:

  • For tsconfig.json, I used target == "ES2019", and module == "None".
  • For Webpack, I used globalObject == "this", and libraryTarget == "umd".
  • I think a roll20.d.ts file should declare all of roll20's names using "declare global { }". Individual Typescript files should not import anything to account for this file, but should just use the global declarations. It seems like the roll20.d.ts file can then be included like any other source file. It was really hard to find information about this exact situation online (i.e. the situation where the runtime environment provides global symbols).
  • I got this done with one monolithic tsconfig.json that built everything; I should have done it that way first!
  • I separated Typescript compilation and Webpack packing into two different steps, by telling Webpack to build using the Javascript outputs instead of the Typescript files. This was simpler than fighting with ts-loader errors.
  • I think that Webpack is including its own module setup when the Javascript code doesn't have one. Fortunately this doesn't make Roll20 complain and script loading is not noticeably slower than a typical script.

Webpack is putting a lot of symbols into the global namespace; I haven't figured out how to avoid that.

February 14 (4 years ago)
timmaugh
Forum Champion
API Scripter

I had not realized R20 had implemented the # privacy declaration. I thought I'd just tested that a month or so ago.

Perhaps I only dreamed it.

February 14 (4 years ago)


timmaugh said:

I had not realized R20 had implemented the # privacy declaration. I thought I'd just tested that a month or so ago.

Perhaps I only dreamed it.

After your post I got nervous of my own memories and decided to test it:

class TestClass {
    #nameString = "test_class_debug_string";
    #intValue = 0;
    
    returnDebugString() {
        return this.#nameString + this.#intValue;
} } log("new TestClass().returnDebugString() => " + new TestClass().returnDebugString()); log("new TestClass().#intValue) => " + new TestClass().#intValue);
with the result
SyntaxError: Private field '#intValue' must be declared in an enclosing class
I tried again with that line commented:
class TestClass {
    #nameString = "test_class_debug_string";
#intValue = 0;
returnDebugString() { return this.#nameString + this.#intValue;
} } log("new TestClass().returnDebugString() => " + new TestClass().returnDebugString()); //log("new TestClass().#intValue) => " + new TestClass().#intValue);
and got a positive result:
"new TestClass().returnDebugString() => test_class_debug_string0"
This makes me think that the Revealing Module pattern is obsolete even for Roll20.