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 ofq2 run
. The default generated command compiles, bundles, and copies all frontend files into Foo/frontend/distThe
dev
command will be preferred tobuild
during development, and behaves similar tobuild
, but includes a sourcemap of your code for easier debugging.q2 run
will fall back tobuild
ifdev
is missingOn
q2 run
, the appropriate build command inpackage.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:Serve all extensions at the same asset url:
q2 run -c http://localhost:3000
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.