Modern assets pipeline for Flask.
- Asset bundling using esbuild.
- Live reloading
- Support for tailwind
- Compatible with #nobuild leveraging import maps
- Extract inline scripts and styles from templates and bundle them properly
- CDN support
Using pip:
pip install flask-assets-pipeline
You will need to install esbuild:
npm install esbuild
Initialize the AssetsPipeline extension and list your JS and CSS that need bundling, located in the app's static directory:
from flask import Flask
from flask_assets_pipeline import AssetsPipeline
app = Flask(__name__)
assets = AssetsPipeline(app, bundles=[
'app.js',
'app.css'
])
Tip
You can also import css in your js files directly. Related stylesheets will be automatically included when their corresponding js is included.
In your template, use the asset_tags
directive to render the needed tags to include your assets.
<head>
{% asset_tags %}
</head>
While in development, use the flask assets dev
command to launch esbuild (and optionnaly tailwind). This will watch your files and expose a live reloading endpoint.
Source maps are automatically enabled in development mode.
Bundles are the files that will be used as inputs in the esbuild command.
Filenames are relative to the assets folder unless it's an absolute path. Bundles can also contain URLs that will be ignored during the build process but that will be used in includes.
Bundles can either be a list of files or a dict of named bundles:
assets = AssetsPipeline(app, bundles={
"@default": ['base.js'],
"@home": ['home.js', 'home.css']
}, include=["@default"])
@app.route("/")
def home():
assets.include("@home")
return render_template("home.html")
When using named bundles, its files are not concatanated together. It is simply a way to reference multiple files at once. Files from all named bundles will be provided as input in the esbuild command.
Note
Named bundles do not need to start their name with @ but it's a good convention to follow.
Code splitting is enabled by default.
The bundle()
and blueprint_bundle()
methods allow you to register bundle with more options.
By default, all bundled assets are included in your pages. You can change this behavior and manually select what asset to include on a given page.
assets = AssetsPipeline(app, bundles=[
'base.js',
'home.js'
], include=["base.js"])
@app.route("/")
def home():
assets.include("home.js")
return render_template("home.html")
The include_asset()
function is also available in templates. It does not return anything, only includes assets for {% asset_tags %}
to take into account.
In the include list, you can also add paths for any files in the static folder and urls.
assets = AssetsPipeline(app, bundles={
'base.js',
'home.js'
}, include=[
"base.js",
"https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
])
Warning
By default, included assets that are not bundles won't be included as modules (<script type="module">
). Change that by prefixing them with "import ".
assets = AssetsPipeline(app, bundles={
"base.js"
}, include=[
"base.js",
"import other.js"
])
For assets other than scripts and stylesheets, use asset_url(filename)
in your templates to get the url.
<img src="{{ asset_url('cat.jpg') }}">
Use the following command to build assets for production:
$ flask assets build
This will:
- build your bundles using esbuild
- build your css with tailwind if enabled
- minify files and not produce source maps
- generate a mapping file (default: assets.json)
Files built using esbuild and tailwind will be generated in a dist folder in the static directory.
Important
Make sure to ship all generated files and the mapping file to production
Tip
It is not recommended to build in production. You should build in a separate environment (eg: CI), then generate a tarball (or any other deliverable, eg: docker image) that you ship to production.
Flask-Assets-Pipeline can launch tailwind in the background at the same time as esbuild and build your css file. The output css file will be automatically included.
assets = AssetsPipeline(app, bundles=[
'base.js'
], tailwind='main.css')
If the tailwind config or the input file is missing, they will be automatically generated. Make sure your tailwind config includes all the needed content paths to watch for.
Example config:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./templates/**/*.html",
"./static/**/*.js"
],
theme: {
extend: {},
},
plugins: [],
}
You can use a separate assets folder than your app static folder. This folder won't be exposed in production so raw assets are safe.
When using a separate assets folder:
- Always use
asset_url()
for your assets' urls - In development, the assets folder will be exposed and assets served directly from it
- For production, you must call the build command
- During the build, assets will be copied to the static folder with a hash in their filename (for cache busting)
assets = AssetsPipeline(app, bundles=['app.js'], assets_folder='assets')
Flask-Assets-Pipeline allows to define scripts and styles directly in your templates. Their content will be extracted and bundled using the previously mentionned process.
To enable inline assets, set the inline option to true.
assets = AssetsPipeline(app, ..., inline=True)
Add the bundle
attribute to your script and style tags to extract and bundle them.
Example datatable.html template:
<table class="datatable">
<!-- ... -->
</table>
<script bundle>
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".datatable").forEach(table => {
// ...
});
});
</script>
<style bundle>
.datatable {
/* ... */
}
</style>
When this template is used as part of your request, its associated assets will be automatically included.
Important
No jinja directives can be used inside the bundled script and style tags
Note
Extracted assets will be stored in your assets folder.
You can customize the name of the extracted files using a value for the bundle attribute: <script bundle="filename.js">
If your are building a Single Page App (SPA), you will use a frontend router. To make sure these routes exist on the backend, you can register them:
assets.add_route("login", "/login")
assets.add_route("account", "/account", decorators=[login_required]) # apply decorators
These routes simply return a rendered template defined using ASSETS_ROUTE_TEMPLATE
(default is frontend_route.html)
If you're not planning to use any feature that require preprocessing (eg. typescript) or are using an externel build system, you can skip defining bundles.
assets = AssetsPipeline(app, include=['app.js', 'app.css'])
Warning
Script mentionned in include are not included as module by default. If you wish to do so, prefix them with "import ":
assets = AssetsPipeline(app, include=['import app.js', 'app.css'])
Warning
If you are using a separate assets folder, you will still need to build for production as assets need to be copied to the static folder
When using no build, it may be needed to edit the import map:
assets = AssetsPipeline(app, ..., import_map={"mypkg": "https://..."})
assets.map_import("myotherpkg", "https://...")
The import map will be rendered as part of {% asset_tags %}
.
You may want to expose some package from your node_modules directory to import them in your scripts. Exposed packages will be bundled using esbuild and added to the import map.
assets = AssetsPipeline(app, ..., expose_node_packages=["mypkg"])
assets.expose_node_package("myotherpkg")
You can then import them as usual in your scripts.
You can include assets and urls for preloading and/or prefetching.
Similarly to how "import" can be specified, use "preload" in front of your included asset. The preload content type will be determined based on the file extension. You can specify it manually using preload as TYPE URL
.
For prefetching, use "prefetch" as the prefix.
assets = AssetsPipeline(app, include=["preload dependency.js", "preload as image cat.jpg", "prefetch page.js"])
Any URLs can be used in the include list.
It is possible to provide additionnal metadata like crossorigin and integrity hash.
assets = AssetsPipeline(app, include=["http://example.com/external.js#integrity=sha384-XXXX"])
Any parameter in the fragment part will be transformed to attributes of the html element used to include the asset.
By default, crossorigin="anonymous"
is automatically applied to any url used.
Set the cdn host in your configuration to use a CDN when debug mode is not enabled.
assets = AssetsPipeline(app, ..., cdn_host="https://xxx.cloudfront.net")
You can control when the cdn is used or not using the ASSETS_CDN_ENABLED config option (it will override the default behavior).
Config key | Extension argument | Description | Default |
---|---|---|---|
ASSETS_BUNDLES | bundles | List of assets to build | |
ASSETS_INCLUDE | include | List of assets to include in your page | All assets in bundles |
ASSETS_ROUTE_TEMPLATE | route_template | The template to use for frontend routes | frontend_route.html |
ASSETS_INLINE | inline | Whether to extract assets from templates | False |
ASSETS_IMPORT_MAP | import_map | ES Modules import map | {} |
ASSETS_EXPOSE_NODE_PACKAGES | expose_node_packages | Node packages to expose to the frontend via the import map | [] |
ASSETS_FOLDER | assets_folder | Search path for asset files | The app static_folder |
ASSETS_URL_PATH | assets_url_path | Base URL for the assets endpoint when in debug mode | /static/assets |
ASSETS_STAMP_ASSETS | stamp_assets | Whether to stamp filenames with the file's hash when copying files from the assets folder to the static folder | True |
ASSETS_OUTPUT_FOLDER | output_folder | The output folder for bundled files relative to the static folder | dist |
ASSETS_OUTPUT_URL | output_url | Base url for outputted file | /static/dist |
ASSETS_MAPPING_FILE | mapping_file | Location of the mapping file to resolve assets filename to their built equivalent | assets.json |
ASSETS_ESBUILD_ARGS | esbuild_args | Additional esbuild arguments | [] |
ASSETS_ESBUILD_BIN | esbuild_bin | esbuild binary location | npx esbuild |
ASSETS_ESBUILD_SPLITTING | esbuild_splitting | Use the --splitting option | True |
ASSETS_ESBUILD_TARGET | esbuild_target | Set the target option for esbuild (use a list) | [] |
ASSETS_LIVERELOAD_PORT | livereload_port | Port onto which to start the livereloading server | 8000 |
ASSETS_TAILWIND | tailwind | Tailwind input css file | |
ASSETS_TAILWIND_ARGS | tailwind_args | Additional tailwind arguments | [] |
ASSETS_TAILWIND_BIN | tailwind_bin | tailwindcss binary location | npx tailwindcss |
ASSETS_NODE_MODULES_PATH | node_modules_path | Path to the node_modules directory | node_modules |
ASSETS_COPY_FILES_FROM_NODE_MODULES | copy_files_from_node_modules | Mapping of src/dest files to copy from node modules | {} |
Command | Description |
---|---|
assets dev | Start the build process in development |
assets build | Build your assets for production |
assets extract | Extract assets from templates |
assets init-tailwind | Create the tailwind config |