Asset Bundling
A guide to understanding how static asset bundling works in Frappe Framework.
Frappe ships with a Rich Admin UI accessible at
/app
which is an SPA written
in modern JavaScript syntax and styling which is written in SASS (
.scss
)
files. These files are not directly understandable by the browser and hence need
to be compiled before they are sent to the browser to parse and execute.
Frappe ships with an asset bundler that can compile client side assets like:
-
.js
(Modern syntax withimport
andexport
) -
.ts
(TypeScript files) -
.vue
(Vue single file components) -
.css
(CSS processed using PostCSS) -
.scss
(SASS files) -
.sass
(SASS files with indentation syntax) -
.styl
(Stylus files) -
.less
(Less files)
These files are compiled to
.js
or
.css
depending on the type and are sent
to the browser.
Building assets
To compile assets using the asset bundler, you run the following command from
the
frappe-bench
folder:
$ bench build
You can also run it for specific apps by giving it the
--apps
option.
# build only frappe assets
$ bench build --apps frappe
# build only frappe and erpnext assets
$ bench build --apps frappe,erpnext
Watch mode
When you are working with bundled files you need the build command to run every time you make a change to your source files. The asset bundler comes with a watch mode where it will listen to changes in the file system and rebuild whenever a file changes.
Running the following command will start a long-running process that watches your files and rebuilds them as they change. It will log a line with the text "Compiled changes..." every time it does a rebuild.
$ bench watch
Watching for changes...
1:17:28 PM: Compiled changes...
You can also run it for specific apps by giving it the
--apps
option.
# watch only erpnext assets
$ bench watch --apps erpnext
Starting with Version 14,
Desk
will be automatically reloaded if assets get rebuilt in watch mode. This behavior can be toggled by setting the
LIVE_RELOAD
environment variable, or changing the value for
live_reload
in
common_site_config.json
.
Bundle files
A bundle file is an entry point of an asset that is picked up by the bundler for
compilation. For e.g., if there is a file named
main.bundle.js
in the public
folder of your app it will be automatically picked up by the bundler and
compiled at
/assets/[app]/dist/js/main.bundle.[hash].js
. A unique hash
computed from the contents of the output is also appended to the file name which
is useful for cache-busting in browsers.
Similarly, if there is a file named
style.bundle.scss
in the public folder, it
will be compiled to
/assets/[app]/dist/css/style.bundle.css
. Notice, the
extension changed from
.scss
to
.css
because browsers can understand CSS
files but not SASS files. Bundle files can exist at any nesting level in the
public
folder, but they will always be compiled in either
dist/js
or
dist/css
depending upon their type. This means if there is a file at
public/main.bundle.js
and another file at
public/src/main.bundle.js
the
compiled output of the latter will override. The bundler will also print a
warning for such collisions.
Some more examples of bundle inputs and their outputs:
Input | Output |
---|---|
[app]/public/main.bundle.js
|
/assets/dist/[app]/js/main.bundle.[hash].js
|
[app]/public/src/main.bundle.js
|
/assets/dist/[app]/js/main.bundle.[hash].js
|
[app]/public/src/utils/utils.bundle.js
|
/assets/dist/[app]/js/utils.bundle.[hash].js
|
[app]/public/main.bundle.ts
|
/assets/dist/[app]/js/main.bundle.[hash].js
|
[app]/public/main.bundle.css
|
/assets/dist/[app]/css/main.bundle.[hash].css
|
[app]/public/styles/main.bundle.css
|
/assets/dist/[app]/css/main.bundle.[hash].css
|
[app]/public/main.bundle.scss
|
/assets/dist/[app]/css/main.bundle.[hash].css
|
[app]/public/main.bundle.sass
|
/assets/dist/[app]/css/main.bundle.[hash].css
|
[app]/public/main.bundle.styl
|
/assets/dist/[app]/css/main.bundle.[hash].css
|
[app]/public/main.bundle.less
|
/assets/dist/[app]/css/main.bundle.[hash].css
|
Importing libraries from npm
If you are familiar with modern web development, you might need to install 3rd-party libraries from npm and use it in your project.
Let's say you want to use the
dayjs
library for working with Date and Time in
your app. You first install it using
yarn
by running the following command
from the root of your apps folder.
$ cd frappe-bench/apps/myapp
$ yarn add dayjs
Now, you can import it in your source files like so:
myapp/public/main.bundle.js
import * as dayjs from 'dayjs';
console.log(dayjs())
Including bundled assets in HTML
When a bundle file is compiled, the output file contains a unique hash. So, you cannot hardcode the path of the file because the next time you make a change to that file the hash will change. Frappe provides a couple of helpers to do this.
Including assets in custom HTML files
The Jinja methods
include_script
and
include_style
will output the correct
path of the file including the HTML markup for
.js
and
.css
files respectively.
index.html
<meta charset="utf-8"/>
<meta content="IE=edge"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>My App</title>
{{ include_style('style.bundle.css') }}
<div id="myapp"></div>
{{ include_script('main.bundle.js') }}
index.html (Rendered)
<meta charset="utf-8"/>
<meta content="IE=edge"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>My App</title>
<link href="/assets/myapp/dist/css/style.bundle.SYKETW5P.css" rel="stylesheet" type="text/css"/>
<div id="myapp"></div>
<script type="text/javascript" src="/assets/myapp/dist/js/main.bundle.BYJXV4LB.js"></script>
Including assets in app.html
If you want to include bundled assets from your app in
/app
you can use the
app_include_js
and
app_include_css
to load them into app.html.
[app]/hooks.py
app_include_js = ['main.bundle.js']
app_include_css = ['style.bundle.css']
Get bundled asset path
If for some reason you need only the path of the bundled asset, you can use the
bundled_asset
Jinja method to generate it.
Jinja
{{ bundled_asset('main.bundle.js') }}
Rendered
/assets/myapp/dist/js/main.bundle.BYJXV4LB.js
Python API
These APIs are also available in python. You can import them from jinja_globals.py.
from frappe.utils.jinja_globals import bundled_asset, include_script, include_style
bundled_asset('main.bundle.js')
Including bundled assets lazily in
/app
If you want to lazy load bundled assets inside the Admin UI (
/app
) you can
use the
frappe.require
method.
frappe.require('main.bundle.js').then(() => {
// main.bundle.js is now loaded
})
This approach is useful when you want to load your code based on some condition. The first page load won't be impacted and is better for performance.
Production Mode
When deploying your app to production, you can build your assets in production mode. In this mode, the bundler will minify the final output of your bundle which results in smaller file sizes.
To build your assets in production mode, run the following command:
$ bench build --production