|
- let path = require("path");
- let fs = require("fs");
- let glob = require("fast-glob");
- let esbuild = require("esbuild");
- let vue = require("esbuild-vue");
- let yargs = require("yargs");
- let cliui = require("cliui")();
- let chalk = require("chalk");
- let html_plugin = require("./frappe-html");
- let postCssPlugin = require("esbuild-plugin-postcss2").default;
- let ignore_assets = require("./ignore-assets");
- let sass_options = require("./sass_options");
- let {
- app_list,
- assets_path,
- apps_path,
- sites_path,
- get_app_path,
- get_public_path,
- log,
- log_warn,
- log_error,
- bench_path
- } = require("./utils");
- let { get_redis_subscriber } = require("../node_utils");
-
- let argv = yargs
- .usage("Usage: node esbuild [options]")
- .option("apps", {
- type: "string",
- description: "Run build for specific apps"
- })
- .option("watch", {
- type: "boolean",
- description: "Run in watch mode and rebuild on file changes"
- })
- .option("production", {
- type: "boolean",
- description: "Run build in production mode"
- })
- .example(
- "node esbuild --apps frappe,erpnext",
- "Run build only for frappe and erpnext"
- )
- .version(false).argv;
-
- const APPS = !argv.apps ? app_list : argv.apps.split(",");
- const WATCH_MODE = Boolean(argv.watch);
- const PRODUCTION = Boolean(argv.production);
- const TOTAL_BUILD_TIME = `${chalk.black.bgGreen(" DONE ")} Total Build Time`;
- const NODE_PATHS = [].concat(
- // node_modules of apps directly importable
- app_list
- .map(app => path.resolve(get_app_path(app), "../node_modules"))
- .filter(fs.existsSync),
- // 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)
- );
-
- execute();
-
- async function execute() {
- console.time(TOTAL_BUILD_TIME);
- await clean_dist_folders(APPS);
-
- let result;
- try {
- result = await build_assets_for_apps(APPS);
- } catch (e) {
- log_error("There were some problems during build");
- log();
- log(chalk.dim(e.stack));
- }
-
- if (!WATCH_MODE) {
- log_built_assets(result.metafile);
- console.timeEnd(TOTAL_BUILD_TIME);
- log();
- } else {
- log("Watching for changes...");
- }
- await write_meta_file(result.metafile);
- }
-
- function build_assets_for_apps(apps) {
- let { include_patterns, ignore_patterns } = get_files_to_build(apps);
-
- return glob(include_patterns, { ignore: ignore_patterns }).then(files => {
- let output_path = assets_path;
-
- let file_map = {};
- for (let file of files) {
- let relative_app_path = path.relative(apps_path, file);
- let app = relative_app_path.split(path.sep)[0];
-
- let extension = path.extname(file);
- let output_name = path.basename(file, extension);
- if (
- [".css", ".scss", ".less", ".sass", ".styl"].includes(extension)
- ) {
- output_name = path.join("css", output_name);
- } else if ([".js", ".ts"].includes(extension)) {
- output_name = path.join("js", output_name);
- }
- output_name = path.join(app, "dist", output_name);
-
- if (Object.keys(file_map).includes(output_name)) {
- log_warn(
- `Duplicate output file ${output_name} generated from ${file}`
- );
- }
-
- file_map[output_name] = file;
- }
-
- return build_files({
- files: file_map,
- outdir: output_path
- });
- });
- }
-
- function get_files_to_build(apps) {
- let include_patterns = [];
- let ignore_patterns = [];
-
- for (let app of apps) {
- let public_path = get_public_path(app);
- include_patterns.push(
- path.resolve(
- public_path,
- "**",
- "*.bundle.{js,ts,css,sass,scss,less,styl}"
- )
- );
- ignore_patterns.push(
- path.resolve(public_path, "node_modules"),
- path.resolve(public_path, "dist")
- );
- }
-
- return {
- include_patterns,
- ignore_patterns
- };
- }
-
- function build_files({ files, outdir }) {
- return esbuild.build({
- entryPoints: files,
- entryNames: "[dir]/[name].[hash]",
- outdir,
- sourcemap: true,
- bundle: true,
- metafile: true,
- minify: PRODUCTION,
- nodePaths: NODE_PATHS,
- define: {
- "process.env.NODE_ENV": JSON.stringify(
- PRODUCTION ? "production" : "development"
- )
- },
- plugins: [
- html_plugin,
- ignore_assets,
- vue(),
- postCssPlugin({
- plugins: [require("autoprefixer")],
- sassOptions: sass_options
- })
- ],
-
- watch: WATCH_MODE
- ? {
- onRebuild(error, result) {
- if (error) {
- log_error(
- "There was an error during rebuilding changes."
- );
- log();
- log(chalk.dim(error.stack));
- notify_redis({ error });
- } else {
- console.log(
- `${new Date().toLocaleTimeString()}: Compiled changes...`
- );
- write_meta_file(result.metafile);
- notify_redis({ success: true });
- }
- }
- }
- : 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_built_assets(metafile) {
- let column_widths = [60, 20];
- cliui.div(
- {
- text: chalk.cyan.bold("File"),
- width: column_widths[0]
- },
- {
- text: chalk.cyan.bold("Size"),
- width: column_widths[1]
- }
- );
- cliui.div("");
-
- let output_by_dist_path = {};
- for (let outfile in metafile.outputs) {
- if (outfile.endsWith(".map")) continue;
- let data = metafile.outputs[outfile];
- outfile = path.resolve(outfile);
- outfile = path.relative(assets_path, outfile);
- let filename = path.basename(outfile);
- let dist_path = outfile.replace(filename, "");
- output_by_dist_path[dist_path] = output_by_dist_path[dist_path] || [];
- output_by_dist_path[dist_path].push({
- name: filename,
- size: (data.bytes / 1000).toFixed(2) + " Kb"
- });
- }
-
- for (let dist_path in output_by_dist_path) {
- let files = output_by_dist_path[dist_path];
- cliui.div({
- text: dist_path,
- width: column_widths[0]
- });
-
- for (let i in files) {
- let file = files[i];
- let branch = "";
- if (i < files.length - 1) {
- branch = "├─ ";
- } else {
- branch = "└─ ";
- }
- let color = file.name.endsWith(".js") ? "green" : "blue";
- cliui.div(
- {
- text: branch + chalk[color]("" + file.name),
- width: column_widths[0]
- },
- {
- text: file.size,
- width: column_widths[1]
- }
- );
- }
- cliui.div("");
- }
- 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;
- }
- }
-
- let assets_json = JSON.stringify(out, null, 4);
- return fs.promises
- .writeFile(
- path.resolve(assets_path, "frappe", "dist", "assets.json"),
- assets_json
- )
- .then(() => {
- let client = get_redis_subscriber("redis_cache");
- // update assets_json cache in redis, so that it can be read directly by python
- return client.set("assets_json", assets_json);
- });
- }
-
- async function notify_redis({ error, success }) {
- let subscriber = get_redis_subscriber("redis_socketio");
- // notify redis which in turns tells socketio to publish this to browser
-
- let payload = null;
-
- if (error) {
- let formatted = await esbuild.formatMessages(error.errors, {
- kind: "error",
- terminalWidth: 100
- });
- let stack = error.stack.replace(new RegExp(bench_path, "g"), "");
- payload = {
- error,
- formatted,
- stack
- };
- }
- if (success) {
- payload = {
- success: true
- };
- }
-
- subscriber.publish(
- "events",
- JSON.stringify({
- event: "build_event",
- message: payload
- })
- );
- }
-
- function open_in_editor() {
- let subscriber = get_redis_subscriber("redis_socketio");
- subscriber.on("message", (event, file) => {
- if (event === "open_in_editor") {
- file = JSON.parse(file);
- let file_path = path.resolve(file.file);
- console.log("Opening file in editor:", file_path);
- let launch = require("launch-editor");
- launch(`${file_path}:${file.line}:${file.column}`);
- }
- });
- subscriber.subscribe("open_in_editor");
- }
-
- open_in_editor();
|