diff --git a/esbuild/esbuild.js b/esbuild/esbuild.js index e22613e0e9..581a4ed2b0 100644 --- a/esbuild/esbuild.js +++ b/esbuild/esbuild.js @@ -64,6 +64,11 @@ const argv = yargs description: "Saves esbuild metafiles for built assets. Useful for analyzing bundle size. More info: https://esbuild.github.io/api/#metafile", }) + .option("using-cached", { + type: "boolean", + description: + "Skips build and uses cached build artifacts to update assets.json (used by Bench)", + }) .example("node esbuild --apps frappe,erpnext", "Run build only for frappe and erpnext") .example( "node esbuild --files frappe/website.bundle.js,frappe/desk.bundle.js", @@ -88,6 +93,7 @@ const NODE_PATHS = [].concat( // import js file of any app if you provide the full path app_list.map((app) => path.resolve(get_app_path(app), "..")).filter(fs.existsSync) ); +const USING_CACHED = Boolean(argv["using-cached"]); execute().catch((e) => { console.error(e); @@ -101,6 +107,12 @@ if (WATCH_MODE) { async function execute() { console.time(TOTAL_BUILD_TIME); + if (USING_CACHED) { + await update_assets_json_from_built_assets(APPS); + await update_assets_json_in_cache(); + console.timeEnd(TOTAL_BUILD_TIME); + process.exit(0); + } let results; try { @@ -131,6 +143,44 @@ async function execute() { } } +async function update_assets_json_from_built_assets(apps) { + const assets = await get_assets_json_path_and_obj(false); + const assets_rtl = await get_assets_json_path_and_obj(true); + + for (const app in apps) { + await update_assets_obj(app, assets.obj, assets_rtl.obj); + } + + for (const { obj, path } of [assets, assets_rtl]) { + const data = JSON.stringify(obj, null, 4); + await fs.promises.writeFile(path, data); + } +} + +async function update_assets_obj(app, assets, assets_rtl) { + const app_path = get_app_path(app); + const dist_path = path.join(app_path, "public, dist"); + const files = await glob("**/*.bundle.*.{js,css}", { cwd: dist_path }); + const prefix = path.join("/", "assets", app, "dist"); + + // eg: "js/marketplace.bundle.6SCSPSGQ.js" + for (const file of files) { + // eg: [ "marketplace", "bundle", "6SCSPSGQ", "js" ] + const parts = path.parse(file).base.split("."); + + // eg: "marketplace.bundle.js" + const key = [...parts.slice(0, -2), parts.at(-1)].join("."); + + // eg: "js/marketplace.bundle.6SCSPSGQ.js" + const value = path.join(prefix, file); + if (file.includes("-rtl")) { + assets_rtl[`rtl_${key}`] = value; + } else { + assets[key] = value; + } + } +} + function build_assets_for_apps(apps, files) { let { include_patterns, ignore_patterns } = files.length ? get_files_to_build(files) @@ -393,14 +443,7 @@ async function write_assets_json(metafile) { } } - let assets_json_path = path.resolve(assets_path, `assets${rtl ? "-rtl" : ""}.json`); - let assets_json; - try { - assets_json = await fs.promises.readFile(assets_json_path, "utf-8"); - } catch (error) { - assets_json = "{}"; - } - assets_json = JSON.parse(assets_json); + let { obj: assets_json, path: assets_json_path } = await get_assets_json_path_and_obj(rtl); // update with new values let new_assets_json = Object.assign({}, assets_json, out); curr_assets_json = new_assets_json; @@ -434,6 +477,19 @@ async function update_assets_json_in_cache() { }); } +async function get_assets_json_path_and_obj(is_rtl) { + const file_name = is_rtl ? "assets-rtl.json" : "assets.json"; + const assets_json_path = path.resolve(assets_path, file_name); + let assets_json; + try { + assets_json = await fs.promises.readFile(assets_json_path, "utf-8"); + } catch (error) { + assets_json = "{}"; + } + assets_json = JSON.parse(assets_json); + return { obj: assets_json, path: assets_json_path }; +} + function run_build_command_for_apps(apps) { let cwd = process.cwd(); let { execSync } = require("child_process"); diff --git a/frappe/build.py b/frappe/build.py index 03b830f0cb..49021ae6bb 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -229,6 +229,7 @@ def bundle( skip_frappe=False, files=None, save_metafiles=False, + using_cached=False, ): """concat / minify js files""" setup() @@ -246,7 +247,10 @@ def bundle( if files: command += " --files {files}".format(files=",".join(files)) - command += " --run-build-command" + if using_cached: + command += " --using-cached" + else: + command += " --run-build-command" if save_metafiles: command += " --save-metafiles" diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 8f6cb70d17..7d997e012d 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -36,6 +36,12 @@ EXTRA_ARGS_CTX = {"ignore_unknown_options": True, "allow_extra_args": True} default=False, help="Saves esbuild metafiles for built assets. Useful for analyzing bundle size. More info: https://esbuild.github.io/api/#metafile", ) +@click.option( + "--using-cached", + is_flag=True, + default=False, + help="Skips build and uses cached build artifacts (cache is set by Bench). Ignored if developer_mode enabled.", +) def build( app=None, apps=None, @@ -44,6 +50,7 @@ def build( verbose=False, force=False, save_metafiles=False, + using_cached=False, ): "Compile JS and CSS source files" from frappe.build import bundle, download_frappe_assets @@ -69,6 +76,9 @@ def build( if production: mode = "production" + if development: + using_cached = False + bundle( mode, apps=apps, @@ -76,6 +86,7 @@ def build( verbose=verbose, skip_frappe=skip_frappe, save_metafiles=save_metafiles, + using_cached=using_cached, ) if apps and isinstance(apps, str):