Sfoglia il codice sorgente

fix: Hash based file naming

- 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 assets
version-14
Faris Ansari 4 anni fa
parent
commit
dd69f1ab43
9 ha cambiato i file con 136 aggiunte e 85 eliminazioni
  1. +84
    -50
      esbuild/index.js
  2. +3
    -0
      frappe/cache_manager.py
  3. +8
    -8
      frappe/hooks.py
  4. +4
    -4
      frappe/templates/base.html
  5. +16
    -0
      frappe/utils/__init__.py
  6. +14
    -16
      frappe/utils/jinja_globals.py
  7. +4
    -4
      frappe/utils/redis_wrapper.py
  8. +2
    -2
      frappe/www/app.html
  9. +1
    -1
      frappe/www/login.html

+ 84
- 50
esbuild/index.js Vedi File

@@ -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)
);
}

+ 3
- 0
frappe/cache_manager.py Vedi File

@@ -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):


+ 8
- 8
frappe/hooks.py Vedi File

@@ -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 = {


+ 4
- 4
frappe/templates/base.html Vedi File

@@ -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 %}


+ 16
- 0
frappe/utils/__init__.py Vedi File

@@ -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



+ 14
- 16
frappe/utils/jinja_globals.py Vedi File

@@ -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)

+ 4
- 4
frappe/utils/redis_wrapper.py Vedi File

@@ -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]


+ 2
- 2
frappe/www/app.html Vedi File

@@ -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" %}


+ 1
- 1
frappe/www/login.html Vedi File

@@ -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() %}


Caricamento…
Annulla
Salva