- For better HTTP caching and cache busting - assets.json is created under [app]/dist folder which contains the map of input file and output file name, this is used to get the correct path for bundled assetsversion-14
@@ -13,6 +13,7 @@ let { | |||
app_list, | |||
assets_path, | |||
apps_path, | |||
sites_path, | |||
get_app_path, | |||
get_public_path, | |||
get_cli_arg | |||
@@ -39,18 +40,26 @@ const NODE_PATHS = [].concat( | |||
} | |||
console.time(TOTAL_BUILD_TIME); | |||
build_assets_for_apps(apps) | |||
.then(() => { | |||
console.timeEnd(TOTAL_BUILD_TIME); | |||
console.log(); | |||
if (WATCH_MODE) { | |||
console.log('Watching for changes...') | |||
} | |||
}) | |||
.catch(() => { | |||
let error = chalk.white.bgRed(" ERROR "); | |||
console.error(`${error} There were some problems during build`); | |||
}); | |||
await clean_dist_folders(apps); | |||
let result; | |||
try { | |||
result = await build_assets_for_apps(apps); | |||
} catch (e) { | |||
let error = chalk.white.bgRed(" ERROR "); | |||
console.error(`${error} There were some problems during build`); | |||
console.log(); | |||
console.log(chalk.dim(e.stack)); | |||
} | |||
if (!WATCH_MODE) { | |||
log_built_assets(result.metafile); | |||
console.timeEnd(TOTAL_BUILD_TIME); | |||
console.log(); | |||
await write_meta_file(result.metafile); | |||
} else { | |||
console.log("Watching for changes..."); | |||
} | |||
})(); | |||
function build_assets_for_apps(apps) { | |||
@@ -117,48 +126,57 @@ function get_files_to_build(apps) { | |||
} | |||
function build_files({ files, outdir }) { | |||
return esbuild | |||
.build({ | |||
entryPoints: files, | |||
outdir, | |||
sourcemap: true, | |||
bundle: true, | |||
metafile: true, | |||
// minify: true, | |||
nodePaths: NODE_PATHS, | |||
define: { | |||
"process.env.NODE_ENV": "'development'" | |||
}, | |||
plugins: [ | |||
html_plugin, | |||
ignore_assets, | |||
vue(), | |||
postCssPlugin({ | |||
plugins: [require("autoprefixer")], | |||
sassOptions: sass_options | |||
}) | |||
], | |||
watch: WATCH_MODE | |||
? { | |||
onRebuild(error, result) { | |||
if (error) | |||
console.error("watch build failed:", error); | |||
else { | |||
console.log(`${new Date().toLocaleTimeString()}: Compiled changes...`) | |||
// log_build_meta(result.metafile); | |||
} | |||
return esbuild.build({ | |||
entryPoints: files, | |||
entryNames: "[dir]/[name].[hash]", | |||
outdir, | |||
sourcemap: true, | |||
bundle: true, | |||
metafile: true, | |||
// minify: true, | |||
nodePaths: NODE_PATHS, | |||
define: { | |||
"process.env.NODE_ENV": "'development'" | |||
}, | |||
plugins: [ | |||
html_plugin, | |||
ignore_assets, | |||
vue(), | |||
postCssPlugin({ | |||
plugins: [require("autoprefixer")], | |||
sassOptions: sass_options | |||
}) | |||
], | |||
watch: WATCH_MODE | |||
? { | |||
onRebuild(error, result) { | |||
if (error) console.error("watch build failed:", error); | |||
else { | |||
console.log( | |||
`${new Date().toLocaleTimeString()}: Compiled changes...` | |||
); | |||
} | |||
} | |||
: null | |||
}) | |||
.then(result => { | |||
log_build_meta(result.metafile); | |||
} | |||
} | |||
: null | |||
}); | |||
} | |||
async function clean_dist_folders(apps) { | |||
for (let app of apps) { | |||
let public_path = get_public_path(app); | |||
await fs.promises.rmdir(path.resolve(public_path, "dist", "js"), { | |||
recursive: true | |||
}); | |||
await fs.promises.rmdir(path.resolve(public_path, "dist", "css"), { | |||
recursive: true | |||
}); | |||
} | |||
} | |||
function log_build_meta(metafile) { | |||
let column_widths = [40, 20]; | |||
function log_built_assets(metafile) { | |||
let column_widths = [60, 20]; | |||
cliui.div( | |||
{ | |||
text: chalk.cyan.bold("File"), | |||
@@ -217,3 +235,19 @@ function log_build_meta(metafile) { | |||
} | |||
console.log(cliui.toString()); | |||
} | |||
function write_meta_file(metafile) { | |||
let out = {}; | |||
for (let output in metafile.outputs) { | |||
let info = metafile.outputs[output]; | |||
let asset_path = "/" + path.relative(sites_path, output); | |||
if (info.entryPoint) { | |||
out[path.basename(info.entryPoint)] = asset_path; | |||
} | |||
} | |||
return fs.promises.writeFile( | |||
path.resolve(assets_path, "frappe", "dist", "assets.json"), | |||
JSON.stringify(out, null, 4) | |||
); | |||
} |
@@ -13,6 +13,8 @@ common_default_keys = ["__default", "__global"] | |||
doctype_map_keys = ('energy_point_rule_map', 'assignment_rule_map', | |||
'milestone_tracker_map', 'event_consumer_document_type_map') | |||
bench_cache_keys = ('assets_json',) | |||
global_cache_keys = ("app_hooks", "installed_apps", 'all_apps', | |||
"app_modules", "module_app", "system_settings", | |||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains', | |||
@@ -58,6 +60,7 @@ def clear_global_cache(): | |||
clear_doctype_cache() | |||
clear_website_cache() | |||
frappe.cache().delete_value(global_cache_keys) | |||
frappe.cache().delete_value(bench_cache_keys) | |||
frappe.setup_module_map() | |||
def clear_defaults_cache(user=None): | |||
@@ -29,16 +29,16 @@ page_js = { | |||
# website | |||
app_include_js = [ | |||
"/assets/frappe/dist/js/libs.bundle.js", | |||
"/assets/frappe/dist/js/desk.bundle.js", | |||
"/assets/frappe/dist/js/list.bundle.js", | |||
"/assets/frappe/dist/js/form.bundle.js", | |||
"/assets/frappe/dist/js/controls.bundle.js", | |||
"/assets/frappe/dist/js/report.bundle.js", | |||
"libs.bundle.js", | |||
"desk.bundle.js", | |||
"list.bundle.js", | |||
"form.bundle.js", | |||
"controls.bundle.js", | |||
"report.bundle.js", | |||
] | |||
app_include_css = [ | |||
"/assets/frappe/dist/css/desk.bundle.css", | |||
"/assets/frappe/dist/css/report.bundle.css", | |||
"desk.bundle.css", | |||
"report.bundle.css", | |||
] | |||
doctype_js = { | |||
@@ -30,11 +30,11 @@ | |||
{%- if theme.name != 'Standard' -%} | |||
<link type="text/css" rel="stylesheet" href="{{ theme.theme_url }}"> | |||
{%- else -%} | |||
<link type="text/css" rel="stylesheet" href="/assets/frappe/dist/css/website.bundle.css"> | |||
{{ include_style('website.bundle.css') }} | |||
{%- endif -%} | |||
{%- for link in web_include_css %} | |||
<link type="text/css" rel="stylesheet" href="{{ link|abs_url }}"> | |||
{{ include_style(link) }} | |||
{%- endfor -%} | |||
{%- endblock -%} | |||
@@ -96,11 +96,11 @@ | |||
{% block base_scripts %} | |||
<!-- js should be loaded in body! --> | |||
<script type="text/javascript" src="/assets/frappe/dist/js/frappe-web.bundle.js"></script> | |||
{{ include_script('frappe-web.bundle.js') }} | |||
{% endblock %} | |||
{%- for link in web_include_js %} | |||
<script type="text/javascript" src="{{ link | abs_url }}?ver={{ build_version }}"></script> | |||
{{ include_script(link) }} | |||
{%- endfor -%} | |||
{%- block script %} | |||
@@ -765,6 +765,22 @@ def get_build_version(): | |||
# this is not a major problem so send fallback | |||
return frappe.utils.random_string(8) | |||
def get_assets_json(): | |||
if not hasattr(frappe.local, "assets_json"): | |||
assets_json = frappe.cache().get_value("assets_json", shared=True) | |||
if not assets_json: | |||
import json | |||
assets_json = json.loads( | |||
frappe.read_file("assets/frappe/dist/assets.json") | |||
) | |||
frappe.cache().set_value("assets_json", assets_json, shared=True) | |||
frappe.local.assets_json = assets_json | |||
return frappe.local.assets_json | |||
def get_bench_relative_path(file_path): | |||
"""Fixes paths relative to the bench root directory if exists and returns the absolute path | |||
@@ -69,23 +69,21 @@ def web_blocks(blocks): | |||
return html | |||
def script(path): | |||
path = assets_url(path) | |||
if '/public/' in path: | |||
path = path.replace('/public/', '/dist/') | |||
def include_script(path): | |||
if not path.startswith("/assets") and ".bundle." in path: | |||
path = bundled_asset_path(path) | |||
return f'<script type="text/javascript" src="{path}"></script>' | |||
def style(path): | |||
path = assets_url(path) | |||
if '/public/' in path: | |||
path = path.replace('/public/', '/dist/') | |||
if path.endswith(('.scss', '.sass', '.less', '.styl')): | |||
path = path.rsplit('.', 1)[0] + '.css' | |||
def include_style(path): | |||
if not path.startswith("/assets") and ".bundle." in path: | |||
path = bundled_asset_path(path) | |||
return f'<link type="text/css" rel="stylesheet" href="{path}">' | |||
def assets_url(path): | |||
if not path.startswith('/'): | |||
path = '/' + path | |||
if not path.startswith('/assets'): | |||
path = '/assets' + path | |||
return path | |||
def bundled_asset_path(path): | |||
from frappe.utils import get_assets_json | |||
bundled_assets = get_assets_json() | |||
return bundled_assets.get(path) |
@@ -28,7 +28,7 @@ class RedisWrapper(redis.Redis): | |||
return "{0}|{1}".format(frappe.conf.db_name, key).encode('utf-8') | |||
def set_value(self, key, val, user=None, expires_in_sec=None): | |||
def set_value(self, key, val, user=None, expires_in_sec=None, shared=False): | |||
"""Sets cache value. | |||
:param key: Cache key | |||
@@ -36,7 +36,7 @@ class RedisWrapper(redis.Redis): | |||
:param user: Prepends key with User | |||
:param expires_in_sec: Expire value of this key in X seconds | |||
""" | |||
key = self.make_key(key, user) | |||
key = self.make_key(key, user, shared) | |||
if not expires_in_sec: | |||
frappe.local.cache[key] = val | |||
@@ -50,7 +50,7 @@ class RedisWrapper(redis.Redis): | |||
except redis.exceptions.ConnectionError: | |||
return None | |||
def get_value(self, key, generator=None, user=None, expires=False): | |||
def get_value(self, key, generator=None, user=None, expires=False, shared=False): | |||
"""Returns cache value. If not found and generator function is | |||
given, it will call the generator. | |||
@@ -59,7 +59,7 @@ class RedisWrapper(redis.Redis): | |||
:param expires: If the key is supposed to be with an expiry, don't store it in frappe.local | |||
""" | |||
original_key = key | |||
key = self.make_key(key, user) | |||
key = self.make_key(key, user, shared) | |||
if key in frappe.local.cache: | |||
val = frappe.local.cache[key] | |||
@@ -21,7 +21,7 @@ | |||
<link rel="icon" | |||
href="{{ favicon or "/assets/frappe/images/frappe-favicon.svg" }}" type="image/x-icon"> | |||
{% for include in include_css -%} | |||
{{ style(include) }} | |||
{{ include_style(include) }} | |||
{%- endfor -%} | |||
</head> | |||
<body> | |||
@@ -51,7 +51,7 @@ | |||
</script> | |||
{% for include in include_js %} | |||
{{ script(include) }} | |||
{{ include_script(include) }} | |||
{% endfor %} | |||
{% include "templates/includes/app_analytics/google_analytics.html" %} | |||
@@ -53,7 +53,7 @@ | |||
{% endmacro %} | |||
{% block head_include %} | |||
<link type="text/css" rel="stylesheet" href="/assets/frappe/dist/css/login.bundle.css"> | |||
{{ include_style('login.bundle.css') }} | |||
{% endblock %} | |||
{% macro logo_section() %} | |||