Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doubts and possible Features #2799

Closed
tiagomta opened this issue Jan 6, 2023 · 4 comments
Closed

Doubts and possible Features #2799

tiagomta opened this issue Jan 6, 2023 · 4 comments

Comments

@tiagomta
Copy link

tiagomta commented Jan 6, 2023

Hey,

Quick summary I'm a senior Nodejs Developer/Architect, and currently, for my own entertainment I'm building a kind of Framework for React with Typescript support, I was using webpack for bundling, but since I was using esbuild transform API for the typescript and jsx, I thought of giving esbuild build API a shot.

First, one thing that I cannot find and do in esbuild, without using regex on the output code, is transforming imports to window references, this was something that I was able to configure in webpack without having a post-processor, it would be great if this was an option for the resolver plugin, like you have currently for the external option, something like global.

And finally, my last "issue" is performance when I'm running two different instances of Build API for different files, at basically the same time, this is required by my project since I want an active file watch system. What I am observing is that the different builds wait for each other, they all end at the same time...

image

@evanw
Copy link
Owner

evanw commented Jan 6, 2023

transforming imports to window references

See here for an example: #337 (comment)

What I am observing is that the different builds wait for each other, they all end at the same time...

image

It’s unclear what is going on here. This screenshot doesn’t look like esbuild’s output.

@tiagomta
Copy link
Author

tiagomta commented Jan 6, 2023

import { build } from "esbuild";
import { isBuiltin } from "module";
import { pathToFileURL } from "url";
import { fs, encode as toBuffer } from "../utils/index.js";
import resolver from "./resolver.js";
import css from "./extensions/css.js";
import html from "./extensions/html.js";
import json from "./extensions/json.js";
import file from "./extensions/file.js";

class Bundler {
  cache = {};
  config = {
    bundle: true,
    write: false,
    outdir: "out",
    legalComments: "none",
    keepNames: true,
    treeShaking: true,
    incremental: true,
    define: (() => {
      const define = { "process.argv": JSON.stringify(process.argv) };
      const keys = Object.keys(process.env);
      for (let i = 0, len = keys.length; i < len; i++) {
        define[`process.env.${keys[i]}`] = JSON.stringify(process.env[keys[i]]);
      }
      define[`process.env`] = JSON.stringify({});
      return define;
    })(),
  };

  constructor() {
    this.cache = {};
  }

  init(filename, module, mode = process.env.NODE_ENV) {
    const files = {};
    const resources = {};
    const config = Object.assign({}, this.config, {
      entryPoints: [filename],
      minify: mode === "production",
      format: module ? "esm" : "iife",
      plugins: [
        {
          name: "vision",
          setup: (build) => {
            const filter = /.*/;
            build.onResolve({ filter }, (args) => this.resolver(args, files));
            build.onLoad({ filter, namespace: "ignore" }, () => ({
              contents: "",
            }));
            build.onLoad({ filter }, (args) => this.loader(args, resources));
          },
        },
      ],
    });
    return { config, resources, files };
  }

  async run(filename, { module = true, mode, encode = false, rebuild } = {}) {
    console.time(filename);
    const { config, resources, files } = this.init(filename, module, mode);
    const { outputFiles, metafile } = await build(config);
    if (rebuild) delete this.cache[rebuild];
    let code = outputFiles?.[0]?.text || "";
    if (encode && code != "") code = `data:text/javascript;base64,${toBuffer(code)}`;
    console.timeEnd(filename, `Page ${filename} compiled and ready for connection`);
    return { code, resources, files: Object.values(files).map((c) => c?.path) };
  }

  resolver(args, cache) {
    switch (args.path) {
      case "@vision/visionjs":
        return { path: "window.vision", external: true };
      case "@app":
        return { path: "app", external: true };
      case "@socket":
        return { path: "socket", external: true };
      default:
        break;
    }
    if (!cache[args.path])
      cache[args.path] = ((path) => {
        if (fs.isAbsolute(path)) return { path: path };
        if (isBuiltin(path)) return { path: path, namespace: "ignore" };
        return {};
      })(resolver(args.path, { cwd: args.resolveDir }));
    return cache[args.path];
  }

  loader(args, bundleResources) {
    const extension = fs.extname(args.path).slice(1);
    if (["js", "mjs", "cjs"].includes(extension)) return { loader: "js" };
    if (["jsx"].includes(extension)) return { loader: "jsx" };
    if (["tsx", "ts", "mts", "cts"].includes(extension)) return { loader: "tsx" };
    if (!this.cache[args.path])
      this.cache[args.path] = ((source, transformer) =>
        transformer(source, {
          url: pathToFileURL(args.path),
          extension,
          isBundler: true,
        }))(
        ...(() => {
          if (["css", "sass", "scss"].includes(extension)) return [fs.readFileSync(args.path, "utf8"), css];
          else if ("html" === extension) return [fs.readFileSync(args.path, "utf8"), html];
          else if ("json" === extension) return [fs.readFileSync(args.path, "utf8"), json];
          return [args.path, file];
        })()
      );
    const { code, resources } = this.cache[args.path];
    resources?.forEach(({ path, source, hash }) => (bundleResources[hash] = path || source));
    return { contents: code, loader: "js" };
  }

  formatter(code, module) {
    if (module)
      return code.replace(/import[^;]*from\s?"window\.\w*";/g, (importer) => {
        importer = importer.replace(/^import/, "const");
        importer = importer.replace(/from\s?"window\./, this.mode === "production" ? "=window." : "= window.");
        importer = importer.replace(/";$/, ";");
        importer = importer.replace(/\bas\b/g, ":");
        return importer;
      });
    return code.replace(/\w*\("window\.\w*"\)/g, (r) => r.match(/"(.*?)"/)?.[1] || "window");
  }
}

export const bundle = ((b) => b.run.bind(b))(new Bundler());
export default Bundler;

Yeah, the output is custom made

@evanw
Copy link
Owner

evanw commented Jan 7, 2023

Yeah, the output is custom made

You're going to have to debug this for yourself, sorry. One guess could be that node's single main thread remains blocked until the end of the build so the callbacks that run at the end of the build can only be run when the main thread becomes free. You'd have to debug that hypothesis on your end though.

@evanw
Copy link
Owner

evanw commented Jan 10, 2023

I'm closing this issue because this question was answered.

@evanw evanw closed this as not planned Won't fix, can't repro, duplicate, stale Jan 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants