Serving Assets

There is a surprising amount of variance when serving assets using the SDK. You may write your HTML/JS/CSS by hand or you may use a powerful frontend framework like React/Ember/Vue/Angular to generate it for you. These frontend frameworks may have their own build server ports and file monitors for hot module reloading. To handle all of these options, the SDK provides a handful of switches to tailor your experience to your specific use case.

Note

These all assume you have an extension named Foo that will be serving frontend assets.

Using the SDK Extension boilerplate

  • Write your assets in Foo/frontend/public

  • Foo/frontend/package.json might look something like the following. The actual values of the commands can be changed, the keys are what matter:

    // client-side rendered extensions
    {
        "scripts": {
            "build": "vite build",
            "dev": "vite build --watch --mode development"
            ...
        }
        ...
    }
    
    // server-side rendered extensions
    {
        "scripts": {
            ...
            "build": "rm -rf dist && mkdir dist && cp -r public/. dist/"
        }
    }
    
  • The build command will run as part of q2 run. The default generated command compiles, bundles, and copies all frontend files into Foo/frontend/dist

  • The dev command will be preferred to build during development, and behaves similar to build, but includes a sourcemap of your code for easier debugging. q2 run will fall back to build if dev is missing

  • On q2 run, the appropriate build command in package.json will run and assets will be served at <base_assets_url>/Foo/assets, where <base_assets_url> is calculated appropriate to your environment. The Tornado application will be the file server.

Using Hand-Rolled or a chosen Framework

  • Write your assets in Foo/frontend/ in a manner appropriate to your application or chosen framework

  • After building, your assets MUST end up in Foo/frontend/dist for deploys in the datacenter to work as expected

  • Foo/frontend/package.json might look something like the following. The actual values of the commands can be changed, the keys are what matter:

    // for a React app using react-scripts
    {
        "scripts": {
            "build": "react-scripts build && rm -rf dist && mv build dist",
            "dev": "react-scripts start"
        }
    }
    
  • For the “dev” command above, it is likely your assets will now be served on something like http://localhost:3000. The SDK backend will need to be made aware of this. To configure, you have two options:

    1. Serve all extensions at the same asset url: q2 run -c http://localhost:3000

    2. Serve each extension at a unique asset url: Update configuration/settings.py to something like the following:

      ASSET_URL_OVERRIDE = {
          'Foo': 'http://localhost:3000',
          'Bar': 'http://localhost:3001',
      }
      

Utilizing the Public Assets in your Extension

The following applies for assets that are added to the Foo/frontend/dist directory as a result of running the build command:

  • In your HTML Jinja2 templates, you can reference your assets using the Jinja2 expression:

    <script src="{{ this.base_assets_url }}/myscript.js"></script>
    
  • In a frontend framework, you can reference your assets with this.base_assets_url:

    <img src={`${this.base_assets_url}/mylogo.svg`} alt="logo" />
    
  • In your extension.py, you can reference your assets URL with self.base_assets_url:

    def default(self):
        base_assets_url = self.base_assets_url
    

In Production

Regardless of how you dev, the expectation for deployment into the Q2 datacenter is that package.json defines a build key that results in assets being built into Foo/frontend/dist. This directory is ignored by your .gitignore by default, and will be generated as part of the deployment pipeline. In the datacenter, Tornado will serve up your assets. Public asset requests will go to Q2’s CDN, which will request from the Tornado server the first time, at which point the assets will be cached. The Tornado server’s DNS will be unique to your repository’s commit hash, so there is no need to be concerned about ‘cache busting’ in the CDN, generating a unique prefix in your assets, etc.

Sharing assets between extensions

Sometimes it is nice to have a centrally located set of frontend assets but a different extension.py referencing them. To do this, your RequestHandler has a FRONTEND_OVERRIDE_EXTENSION static variable. By setting this to another extension name, it will tie the extension.py to a different frontend directory entirely. For example:

class ChildHandler(Q2TectonClientRequestHandler):
    FRONTEND_OVERRIDE_EXTENSION = 'OtherExtension'

Even though this above extension.py is located at Child/extension.py, the frontend assets from OtherExtension/frontend would be used.

Note

Currently this means OtherExtension will have to be listed in configuration/settings.py in INSTALLED_EXTENSIONS so the assets are served in the first place.