Typescene Framework: Quick Start

Jelmer

When I published my first article about the dependency-less JavaScript framework I’ve worked on for 5 years, I knew I was facing an uphill battle. No matter how many stars on GitHub, claps on Medium, or followers on Twitter there were going to be, persuading developers to actually use a new framework that’s not made by one of the Internet Giants is nearly impossible.

In a time when a ‘new JavaScript framework’ is a sad meme rather than a sign of progress in our amazing community, the To Do App example doesn’t inspire anyone anymore. How can I still get you, dear reader, to dip your toes in the strongly typed water and actually give Typescene a try?


My Pitch: Here’s a simple but practical project that’s perfect as a Quick Start introduction, and also isn’t totally useless in real life.

In this article I’ll take you through a ‘feedback widget’ implemented in Typescene, that you can add to any existing website and use as a springboard for other interactive elements on the same site. It should work for static HTML websites as well as those generated by a CMS. I’ll use TypeScript, but only where necessary so it’s easy to understand if you’re coming from vanilla JS (ES6).

Now, is this the best use for a full-scope framework like Typescene?
No, not really. But it’s definitely not a bad way to get some experience outside your current toolset, learn something new, and see if you enjoy working with Typescene as much as I do without totally wasting your time. (To all would-be haters: please skip to The Big Picture at the bottom of this article immediately).

I’ve made a repository available that contains all the code, but you should be able to follow along as well — so I’ll link to it only at the bottom of this article. Let’s 👏 get 👏 started! 👏


The Setup

Since this isn’t a full-fledged application, we’ll be adding our widget to an existing HTML page. Let’s say we have a placeholder like this:

<p id="rating-root"><span>Was this page helpful?</span></p>

And we want to turn it into this:

Amazing! 🙌 What’s the plan of attack here?

  • Add our Typescene ‘view’ within the <p> element to show the two buttons,
  • Handle button click events (and as a bonus we’ll show a dialog when the user clicks the No button),
  • Submit the user’s rating somewhere, like Google Analytics or your CMS backend.

All of this is going to live in an NPM package within the website code, in a separate folder, and output one JS file that must be included from your HTML file. Nice and clean.


Updated: getting started is now really easy!
You’ll need a recent version of Node JS installed on your computer. After that, it’s smooth sailing. Run the following command from within your website project, which creates an app sub folder and installs all dependencies. You can change the folder name, of course.

npx create-typescene-webapp app

Now that we have the directory structure, we can add our source code.
Typescene helps you to split your program into a few major architectural components, each with a different purpose:

  • Service(s), for interacting with your data and/or back end, independently of the current UI state.
  • Activity component(s) to represent the application state, and
  • View(s) to describe the UI, including modals, menus, etc.

Let’s go through these one by one.

1. Service

Services in Typescene are singletons: each service is a class, but they’renew'ed only once. The real feature of services is that they can be accessed by name from any other component, as and when they are registered. (This facilitates all kinds of interesting architectural patterns, which is a topic for another day).

Open the src folder within your new package folder, and create a file named FeedbackService.ts. Paste the following code (notes in square brackets referenced below) —

Things to note here:

  1. We’re creating a class called FeedbackService which is a Service simply because it extends ManagedService. All Services need a valid name property, which is usually something like ‘Scope.Name’, to avoid name clashes for Services that you might be copy-pasting from other projects or 3rd parties.
  2. The submitFeedback method changes the submitted property and then emits a change event. This event is going to help the View later, because the View isn’t able to observe properties more than 1 level deep (this will make sense later, I promise…).

Note: The GitHub repo linked below actually has code to submit an event to Google Analytics, in case you’ve already connected GA and want to use the widget on your own site.

2. Activity

Activities in Typescene represent the current application state. They act mostly as ‘controllers’ (if you’re familiar with MVC), ‘components’ (if you’re familiar with most other JS frameworks), ‘beans’, ‘forms’ (Swing/WinForms), but also the ‘view model’ (WPF, MVVM) since they encapsulate the current state using their own property values which can be observed by the view.

If you’re coming from Android development, Activities should look pretty familiar to you: they are ‘activated’ using a life cycle model (new, active, inactive, destroyed). There’s a twist, though, because Typescene Activities can be nested, and multiple Activities can be active at the same time — which is very useful for implementing master/detail UIs.

Create a file named activity.ts and paste the following code:

  1. We’re creating a MainActivity class, which extends the ‘page view activity’ class, with reference to the view (which we’re going to add in a separate file). The ‘with’ method is explained in more detail over at the Typescene documentation website, for now all you’ll need to know is that the view is automatically injected into the activity instance when needed.
  2. We’re referencing the FeedbackService that we created above. The @service notation is called a ‘decorator’, and it turns the feedbackService property into a magical 🦄 property that automatically references the correct service in real time, and is able to receive the change event that we’re emitting from the Service.
  3. We’re defining event handlers for use with the view. These are normal methods, nothing special about them.

3. View

Views are made up of UI components, like rows, columns, buttons, and text fields, which can be nested and combined to build a complete UI.

Technically, you could create a view programmatically, using new UIRow(...), new UIButton("Label"), etc., and adjust properties one by one to put the UI together by hand — but that would become cumbersome really quickly.

Instead, we use the static ‘with’ method on UI components to describe the View just once, and then reference the resulting constructor from an Activity class. The Activity creates the View when needed.

Create a file named view.ts and paste the following code:

Try reading this code out loud:

For the View, we export a default value, which is
a ‘cover cell’ (to cover the entire placeholder area), with
- A row, with [a hidden property that’s bound to submitted]
an outlined button with label “Yes”, and yesClicked() event handler.
…and so on.

The trick here is in the static ‘with’ method, which is called on a component constructor (class), and returns a derived component constructor that automatically adds given properties and child components when the component is constructed: new UIRow creates a new empty row, while new (UIRow.with({...}, ...)) creates a new row with whatever content we pass to ‘with’.

Bindings and event handlers provide ways to update the UI. The former does its job automatically by observing the Activity, while the latter lets you write any code you want.


Why did we need to fire a change event in the Service, if we’re binding to its property directly in the View?
While bindings in the View can read nested properties such as feedbackService.submitted, they aren’t notified of value changes more than one level deep for performance reasons. However, if we also emit a change event directly from the Service object, it triggers the observer to fire for feedbackService as if we changed the property itself, and the binding gets updated.

Where’s the CSS?
Everything that’s rendered by Typescene (technically, by the webapp package which contains the renderer) is ‘normalized’ so your page’s CSS doesn’t affect the output from Typescene at all. That doesn’t mean you can’t change what it looks like, though. Styles get applied using a CSS-in-JS theming system, which makes it easy to define and re-use your own color scheme, default look and feel, and additional styles to match your own style guide for example.


Build

The last thing we’ll need is an entry point. Change the file called app.ts to the following code (by default, create-typescene-webapp assumes you want to build a full-page app):

  1. First off, we’re creating and registering the Service singleton.
  2. Next, we’re injecting the Activity into a new Application (constructor), which we then use to instantiate and start the application pointed at our root DOM element.

Now, with this file as a starting point, Webpack can bundle everything into a single JS file. This includes Typescene itself, but you can also separate the framework from your own code using some Webpack configuration.

We’ll need to modify src/webpack.config.js to remove the full-page HTML output, and just output a JavaScript file instead. Use the following configuration (update the target path for the JS file if needed):

After that, you can build a production version of the JS bundle:

npm run build

If you want to build a development version instead and re-build whenever you make a change, navigate to the src folder and run npx webpack -d -w instead.

Add the following HTML to a file, and we can see the widget in action (fix the path to the JS file here too if needed):

<article>
<h1>Test</h1>
<p>This is a test page.</p>
<p id="rating-root"><span>Was this page helpful?</span></p>
<style>
#rating-root {
position: relative;
height: 6rem;
}
</style>
</article>
<script src="/js/app.bundle.js"></script>

That’s a lot of overhead for two buttons!
Yes it is… These are certainly the most over-engineered buttons I’ve ever seen. But we’re not in the business of shipping buttons, are we? 😆

Our widget ‘app’ already includes everything you need to scale up to a full size app, without adding more overhead at all:

  • Add more Services for everything your app needs to do under the hood: API connections, storage, login sessions, interacting with service workers, etc.
  • Add more Activities for every page or dialog in your app. Simple routing is free (see that path property?), but Activities can also be nested or added dynamically (yes, even using the new Promise-based import(…) API) for super flexible routing scenarios.
  • Add a View for every Activity, or create reusable View-components that you can use throughout your app.

Let’s add an Activity with a slightly more complex View to scale things up just a notch.

Adding a Modal Dialog

First, some housekeeping. Move the activity.ts and view.ts files to their own folder. You can overwrite the files in activities/main/, for example, which we didn’t use until now. You’ll need to fix the import statements, or if you’re using VS Code you can let that fix it you (thanks, TypeScript language service!).

View

Now, add another View file, this time activities/form/view.ts:

  1. This file exports a UIFormContextController constructor. This is a wrapper (controller) that doesn’t output any rendered elements by itself, but can be used to inject a ‘form context’. The parent form context is used by form controls such as UIToggle and UITextField.
  2. Within the form wrapper, the actual output consists of a ‘cell’ (basically a div) that’s positioned in the middle of the screen and made to look like a pop-up dialog box.
    Tip: If we had more dialogs like this, we could save the result of .with(...) to a constant and reuse it instead of UIFlowCell here.
  3. The UIToggle components read and write values from the current form context, using the given property name.

The view above suggests that we need an Activity with two event handlers (i.e. close() and submit()) and a formInput property to contain the form context.

Activity

Create activities/form/activity.ts and add the following content:

Instead of the page view activity we used before, this is a dialog view activity, and our form context is a ‘managed record’ (basically a fancy object that can also emit events, look this up in the Typescene docs if you’re interested), but otherwise this looks very familiar!

So how do we get this Activity to become ‘active’ and create and show its View?
We’re not using any routes here, so we’ll need to create an instance of the activity, and add it to the Application manually. Don’t worry, this is just a single line of code — replace our old noClicked() function code with the following:

this.getApplication()!.showViewActivityAsync(new FormActivity());

Note how we’re using the main Activity (this) to get back to the referring Application instance, because Typescene allows multiple Application instances to exist simultaneously so there is no global reference to the running app.

Build the JS bundle again, and admire the result! 😎


Repository

The repository that contains all of the code above lives on GitHub:

Tip: For a larger-scale example of a Typescene application, and an example of proper page routing, check out the RealWorld sample. The code is structured in the exact same way as the example above, there’s just… more of it:

Screenshot of the Typescene RealWorld sample code

The Big Picture

OK— disclaimer: please don’t get all worked up about this article because you can build the same widget in a few lines of vanilla JavaScript. Yes, I know. This is a contrived example, and Typescene is a relatively ‘expensive’ framework, but for a good reason. Hear me out. 👀

Comparing JavaScript frameworks

Typescene comes at a cost, size-wise (~40kB gzipped overhead) because it’s a full-scope framework. Vue creator Evan You recently gave a very good talk that explains the concept of framework ‘scope’: the larger the scope, the more standardized and scalable your app code; but the higher the overhead and steeper the learning curve.

When considering a framework for your project, it’s super important to understand these trade-offs, and Evan hits the nail on the head in his talk. Nobody can make the framework decision for you, and just because React is the most popular framework doesn’t mean it’s always the best for your application. Don’t believe content marketers on the Internet (sound advice in any situation, I guess).

When considering a framework for your project, it’s super important to understand these trade-offs.

‘Full-scope’ doesn’t mean ‘does-it-all’, though. What I love about the Web, is that new innovations are constantly blurring the lines between all kinds of user experiences: from reading an article to submitting a form, from typing a chat message to managing email in a desktop-grade application. It’s a wonderful world out there. But here’s where the problem starts: it’s always been a struggle for a single tool to support development of all kinds of ‘dynamic’ content (remember Microsoft FrontPage?).

Typescene is no exception, and I think that’s OK.

On the one hand, if your application is best represented in a page-like format, such as Medium or Amazon.com, then it makes a lot of sense to keep working in HTML and use a high-powered, small-scope renderer such as React or a more complete framework such as Vue (don’t @ me please 😅).

On the other hand, if you’re creating relatively stand-alone apps that mimic native desktop/mobile user experiences — whether within a page such as our example widget, or replacing page-based navigation entirely — and you’re looking for long-term benefits of a standardized architecture and zero-dependency maintenance, then Typescene might just be the perfect choice for you.

Now ⭐️ star, 👏 clap, and 🐦 tweet the word! ~ Thanks 🙏

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade