profile
viewpoint
If you are wondering where the data of this site comes from, please visit https://api.github.com/users/threepointone/events. GitMemory does not store any data, but only uses NGINX to cache data for a period of time. The idea behind GitMemory is simply to give users a better reading experience.
Sunil Pai threepointone London, England

emotion-js/emotion 12598

👩‍🎤 CSS-in-JS library designed for high performance style composition

TheLarkInn/unity-component-specification 212

This is a WIP draft of the Unity (Single File Web Component) Specification

threepointone/bs-nice 185

css-in-reason

jpmorganchase/iff 17

Feature Flags: The Next Generation

threepointone/babel-macros 12

macros for babel. I think.

threepointone/asyncstorage-mock 9

a mock for react-native's asyncstorage api. useful for testing.

paramaggarwal/Rubber 2

iOS apps in Javascript like React Native.

threepointone/bus 2

global event bus

threepointone/async-act-test-21-11 1

Created with CodeSandbox

threepointone/backbone-mongodb 1

Backbone extensions to support local MongoDB back-end storage

issue commentreactjs/react-codemod

'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.

We ran into this also. You can fix it by setting the jsx compiler option: https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#react-17-jsx-factories

ok

"jsx": "preserve",
Jairwin-L

comment created time in 4 hours

issue commentevanw/esbuild

collect performance timings about plugin cost

@evanw ,it seems esbuild-wasm's plugin does not running concurrently, I do some profile on my application(https://github.com/evanw/esbuild/files/6095377/browser.cpuprofile.zip), It seems that handleRequest's cost is a little too high(the cost is mostly in the js-to-wasm function, and the cost is much slower on node #922 ), image it seems that can do some perf metrics on esbuild's js side https://github.com/evanw/esbuild/blob/899414ec00b92e069060f049084d061b534c634f/lib/common.ts#L632-L658, since it already await esbuild plugin's resolve&load hooks.

hardfist

comment created time in 4 hours

IssuesEvent

issue openedevanw/esbuild

[Feature]: lib/main.js should target to node:10 or higher node version

since lib/main.js always runs on node, and node 10+(node@8 already reaches ends-of-life) supports most esnext features, I think it should transformed target to node10+ other than es2015( for performance and debugging), especially the transformed async/await is not easy to debug.

created time in 5 hours

issue commentevanw/esbuild

Summory option in `.build()`

Good to know. Thanks for calling this out. As you can tell I don't use yarn myself. It looks like this only happens with yarn 1 but not with yarn 2? Technically I could just not print the time if the npm_config_user_agent environment variable contains yarn/1.. Maybe I'll try that.

I’m on Yarn 1.22 and I always see this footer when running commands such as yarn esbuild. I don’t know whether Yarn 2 also does this, but I would expect 90%+ of your Yarn-based users to be on 1.x.

Yarn’s decorative text gets annoying when you are relying on terminal output for debugging, so I’ve started doing ./node_modules/.bin/esbuild and more recently I just alias esbuild as alias esbuild=node_modules/.bin/esbuild in my .bash_profile. I think there’s a silent flag for yarn but it’s tripped me up in the past so I don’t bother with it anymore.

General example:

Screen Shot 2021-03-06 at 9 29 36 PM

Yarn + esbuild example: (I’m on a Mac FWIW)

Screen Shot 2021-03-06 at 9 30 14 PM

I don't think there is a number you can assign to each file that would mean something useful.

Anyway, I do think it’s important for your users to understand how fast esbuild is without the need for time esbuild ....

Somewhat related: Create React App does some interesting profiling when building (i.e. react-scripts build) where it compares your previous build against your current build (not time measured, but bundle sizes). I found this image online so you can see what I mean. This might be a nice enhancement to summary output, reporting time measured and bundle size deltas. (Just an idea, not requesting this)

One interesting direction you could take this is emitting some kind of stats file (a little like your metafile idea, but for bundle statistics / performance metrics). One way I’ve done this in the past (not related to esbuild) is by running a small script on build so every release includes some metadata. If you were to support something like this for esbuild with normal builds (maybe a stats flag?) then esbuild would be able to compare stats from previous builds assuming it relates to the same build.

Sorry for getting off-topic. I just think terminal UIs are generally under-appreciated but is actually critical to adopting new users.

rogemus

comment created time in 5 hours

push eventevanw/esbuild

Evan Wallace

commit sha 05cb1a01bbecedba376081ebfdc76e4638e640df

remove "startService" from uglify and terser tests

view details

push time in 5 hours

issue openedevanw/esbuild

Idea: “dev-serverless” JS

I wonder if ESBuild WASM could enable “dev-serverless” JS

Specifically the flow would be:

  • Go to special (remote) webpage
  • Drag and drop code folder into page with index.html
  • Using the new Filesystem API, contents of page are replaced with index.html, but esbuild runs in web worker and live bundles app / assets.
  • NPM imports replaced with Skypack/JSPM/whatever
  • URL changes to match directory name and the directory handle is persisted to IndexedDB. Next time you refresh it reloads the folder and bundles again (remembers)
  • Now you have an in-browser “dev-server” without installing any software or running Node.js and with modern JS tooling
  • Unlike CodeSandbox/etc this lets you use your own local and comfortable development environment/IDE.
  • maybe could do deploys this way too. With a literal one-click in-app deploy button
  • with a lot of work, could probably make it Next.js compatible via intercepting fetch requests in service workers & magic

if the bundler is fast enough to run inside the webpage and works with the local dev environment, why would people still use and install a CLI?

The 2nd order effects would be interesting too.

Will the next generation of frontend engineers know how to use terminals? If you only use a terminal to run “git pull”, “git clone”, “git commit” and “git push” then a git GUI makes more sense.

If you’re not running npm install, will web apps still have a package.json?

created time in 6 hours

push eventevanw/esbuild

Evan Wallace

commit sha b2d208933fd230b67f2aaad9b59561e4e6377d0e

fix typescript type error

view details

Evan Wallace

commit sha 17fc69e999761f864b6f8e8575396acb1330f146

avoid run time in summary for yarn 1

view details

push time in 6 hours

issue commentevanw/esbuild

Summory option in `.build()`

Edit: I now see that --summary has been supported since 0.8.28 so you probably won’t want to change it. 😅

I'm fine with changing it. People shouldn't be parsing it (e.g. it's truncated over a certain number of entries) so it should only be for humans.

My only concern is that when using esbuild with yarn for example, is that the output is going to get very noisy since yarn also decorates outputs with its own Done in X seconds footer. I’m not sure it actually makes sense for esbuild output to be shaped by yarn, but I felt I should bring attention to this.

Good to know. Thanks for calling this out. As you can tell I don't use yarn myself. It looks like this only happens with yarn 1 but not with yarn 2? Technically I could just not print the time if the npm_config_user_agent environment variable contains yarn/1.. Maybe I'll try that.

That way users can see, whether for one file or for many, how long each file took to process.

I don't think there is a number you can assign to each file that would mean something useful. Go has a preemptable task scheduler so just measuring the time difference between the start and the end of a task doesn't mean the task was running for that whole time. And also esbuild is heavily multithreaded so all of the times would add up to much longer than the actual running time, which is misleading IMO.

rogemus

comment created time in 6 hours

push eventevanw/esbuild

Evan Wallace

commit sha 5f161e43660a2c47e58f831a4346d58119552050

add node unref tests to common tests

view details

Evan Wallace

commit sha 826e791bd357850f27347f903f5208b5bf3adcd0

remove "startService" in unref tests

view details

push time in 6 hours

push eventevanw/esbuild

Evan Wallace

commit sha bdc21687092cc7ff3a0a0353635c8dd136a28f21

remove the "startService" API

view details

Evan Wallace

commit sha 1ed4fce9918928bee1564ea2d5c110521496651a

types: consolidate the "ImportKind" type

view details

Evan Wallace

commit sha e796c45b215e270119dfc924f31f0b057c59a864

enforce that "transform" input is a string

view details

push time in 6 hours

issue closedevanw/esbuild

[Question] Extending esbuild-style logs in TypeScript

I really enjoy how esbuild logs are printed. I have a very clear sense of what went wrong and why, and the colors help draw attention to the important bits.

I’m poking around in internal and found:

// ...
if terminalInfo.UseColorEscapes {
  if d.Suggestion != "" {
    return fmt.Sprintf("%s%s%s:%d:%d: %s%s: %s%s\n%s%s%s%s%s%s\n%s%s%s%s%s\n%s%s%s%s%s%s%s\n",
      textColor, textIndent, d.Path, d.Line, d.Column,
      kindColor, kind.String(),
      textResetColor, d.Message,
      colorResetDim, d.SourceBefore, colorGreen, d.SourceMarked, colorResetDim, d.SourceAfter,
      emptyMarginText(maxMargin, false), d.Indent, colorGreen, d.Marker, colorResetDim,
      emptyMarginText(maxMargin, true), d.Indent, colorGreen, d.Suggestion, colorResetDim,
      d.ContentAfter, colorReset)
  }

  return fmt.Sprintf("%s%s%s:%d:%d: %s%s: %s%s\n%s%s%s%s%s%s\n%s%s%s%s%s%s%s\n",
    textColor, textIndent, d.Path, d.Line, d.Column,
    kindColor, kind.String(),
    textResetColor, d.Message,
    colorResetDim, d.SourceBefore, colorGreen, d.SourceMarked, colorResetDim, d.SourceAfter,
    emptyMarginText(maxMargin, true), d.Indent, colorGreen, d.Marker, colorResetDim,
    d.ContentAfter, colorReset)
}
// ...

My question is, does this cover all esbuild-style logs? It would be helpful to better understand this so as I attack this in JS, I have a complete picture of how logs should appear generally.

I want to faithfully recreate these logs for JavaScript since esbuild cannot be used to run JavaScript (as talked about in #903). So I know I can use logLevel to get esbuild to emit errors for bundling / building; I simply want to extend this terminal UI to errors generally as I’ve found them to be so useful.

Previously I drafted something like this:

export function format(msg: esbuild.Message, color: (...args: unknown[]) => void): string {
  const meta = msg.location!

  const namespace = `${meta.file}:${meta.line}:${meta.column}`
  const error = `esbuild: ${msg.text}`

  let code = ""
  code += `${meta.lineText.slice(0, meta.column)}`
  code += `${color(meta.lineText.slice(meta.column, meta.column + meta.length))}`
  code += `${meta.lineText.slice(meta.column + meta.length)}`

  return `${namespace}: ${error}

  ${meta.line} ${terminal.dim("|")} ${code}
  ${" ".repeat(String(meta.line).length)} \x20 ${" ".repeat(meta.column)}${color("~".repeat(meta.length))}`
}

closed time in 7 hours

zaydek

issue commentevanw/esbuild

[Question] Extending esbuild-style logs in TypeScript

I understand Unicode and runes in the abstract but implementation is another story. I previously wrote / forked some code and created this to help me distinguish between ASCII / BMP / astral code points (for a previous project) but using regex for this kind of thing seems crude and offensive: https://github.com/codex-src/codex-wysiwyg/blob/master/src/lib/UTF8/testAlphanum/index.js.

because different terminals have different levels of Unicode support so there is no one code point width table algorithm that works for all terminals

I hadn’t considered that, but yes, terminal encoding is another story. In my own experience I haven’t noticed any hiccups with caret or tilde alignment in terminal output but I’ll be sure to report them if I come across them in the wild.

Yes. And esbuild also indicates where the plugin was registered as well. That looks like this:

Ah. I think I now understand why you are splitting stacks beyond the first stack trace in common.ts:

let lines = (e.stack + '').split('\n', 4)
// ...
location = parseStackLinesV8(streamIn, (e.stack + '').split('\n', 3), '')

It looks like you’re propagating relevant stack traces to the user in this case. Thanks for pointing that out. I wasn’t sure if you were always omitting the stack trace from the user, but now I can see that it depends.

I’m gonna close this since you’ve more than answered my questions. Thank you for taking the time to do that.

I’ll also link this since it’s somewhat relevant to terminal UI / UX: https://github.com/evanw/esbuild/issues/704#issuecomment-791907616.

zaydek

comment created time in 7 hours

issue commentevanw/esbuild

Summory option in `.build()`

I wanted to get a sense of what the summary options feels like so I took a screenshot for myself and or others:

Screen Shot 2021-03-06 at 6 57 53 PM

My only concern is that when using esbuild with yarn for example, is that the output is going to get very noisy since yarn also decorates outputs with its own Done in X seconds footer.

Given the you have outdir and outbase options, would it make more sense to put milliseconds next to every file instead of as a footer? That way users can see, whether for one file or for many, how long each file took to process. This may not make perfect sense with how esbuild works, given it uses concurrency, etc., but you can also measure the differences between processing file A from file B.

In my own project (which uses esbuild), output looks like this. Maybe there is some inspiration to be shared?

<details> <summary>Implementation here:</summary>

import * as esbuild from "esbuild"
import * as path from "path"
import * as terminal from "../shared/terminal"
import * as types from "./types"
import * as utils from "./utils"

const TERM_WIDTH = 40

function formatMS(ms: number): string {
  switch (true) {
    case ms < 250:
      // 250ms
      return `${ms}ms`
    default:
      // 0.25ms
      return `${(ms / 1e3).toFixed(2)}s`
  }
}

export function export_(runtime: types.Runtime, meta: types.RouteMeta, start: number): void {
  const dur = formatMS(Date.now() - start)

  const l1 = runtime.directories.srcPagesDirectory.length
  const l2 = runtime.directories.exportDirectory.length

  let color = terminal.white
  if (meta.routeInfo.type === "dynamic") {
    color = terminal.cyan
  }

  let dimColor = terminal.dim.white
  if (meta.routeInfo.type === "dynamic") {
    dimColor = terminal.dim.cyan
  }

  const src = meta.routeInfo.src.slice(l1)
  const src_ext = path.extname(src)
  const src_name = src.slice(1, -src_ext.length)

  const dst = meta.routeInfo.dst.slice(l2)
  const dst_ext = path.extname(dst)
  const dst_name = dst.slice(1, -dst_ext.length)

  const sep = "-".repeat(Math.max(0, TERM_WIDTH - `/${src_name}${src_ext}\x20`.length))

  console.log(
    `\x20${terminal.dim(utils.timestamp())}\x20\x20` +
      `${dimColor("/")}${color(src_name)}${dimColor(src_ext)} ${dimColor(sep)} ${dimColor("/")}${color(dst_name)}${
        start === 0 ? "" : ` ${dimColor(`(${dur})`)}`
      }`,
  )
}

export function serve(args: esbuild.ServeOnRequestArgs): void {
  const dur = formatMS(args.timeInMS)

  let color = terminal.normal
  if (args.status < 200 || args.status >= 300) {
    color = terminal.red
  }

  let dimColor = terminal.dim
  if (args.status < 200 || args.status >= 300) {
    dimColor = terminal.dim.red
  }

  let logger = (...args: unknown[]): void => console.log(...args)
  if (args.status < 200 || args.status >= 300) {
    logger = (...args) => console.error(...args) // eslint-disable-line
  }

  const path_ = args.path
  const path_ext = path.extname(path_)
  const path_name = path_.slice(1, -path_ext.length)

  const sep = "-".repeat(Math.max(0, TERM_WIDTH - `/${path_name}${path_ext}\x20`.length))

  logger(
    `\x20${terminal.dim(utils.timestamp())}\x20\x20` +
      `${dimColor("/")}${color(path_name)}${dimColor(path_ext)} ${dimColor(sep)} ${color(args.status)} ${dimColor(
        `(${dur})`,
      )}`,
  )
}

</details>

I recorded my output to provide an example. Also a screenshot since I think GH requires the video be downloaded first?

Screen Shot 2021-03-06 at 7 16 21 PM

https://user-images.githubusercontent.com/58870766/110203178-f170a500-7eaf-11eb-8d88-773b154adac9.mov

rogemus

comment created time in 8 hours

issue commentevanw/esbuild

Mix esbuild-loader and ts-loader

Just checking for an @ is to naive. Npm repos often use orgs -> require('@org/reponame') and people could use @ in comments like jsdoc or just copyright statements. Thus this would limit the speed increase too much.

thomaschaaf

comment created time in 8 hours

push eventevanw/esbuild

Evan Wallace

commit sha ce41544761b331e7c4d322b2caacde821a332b50

single-line to multi-line build calls

view details

Evan Wallace

commit sha 059f7c07689b9793b8227c6a0175a95b571af93d

remove "--summary", always print summary instead

view details

push time in 8 hours

issue commentevanw/esbuild

[Question] Extending esbuild-style logs in TypeScript

I noticed the polish. It really shows! I wish more CLIs felt like esbuild. It has this really nice balance of discoverability / understandability which is ideal for your end users.

Thanks! That's great to hear.

I don’t mind starting with a naive implementation for now. Eventually I’d like to respect all of your original considerations.

I wouldn't do the Unicode table thing since esbuild doesn't even do that yet (and may never do it). I think it's actually really hard (or impossible?) to do this right anyway because different terminals have different levels of Unicode support so there is no one code point width table algorithm that works for all terminals. It felt appropriate to bring it up to mention what esbuild doesn't currently handle.

Am I right to assume that when plugins crash, esbuild decorates those errors too?

Yes. And esbuild also indicates where the plugin was registered as well. That looks like this:

Not sure if that's relevant for what you're doing or not.

zaydek

comment created time in 8 hours

issue commentevanw/esbuild

Mix esbuild-loader and ts-loader

it's very hard for me to detect if a file contains a decorator

I was imagining you could do something like /@\w+/.test(contentsOfFile) to reject files that definitely don't contain a decorator. That might look something like this (a real-world plugin would likely read from tsconfig.json somehow):

let tscDecoratorPlugin = {
  name: 'tsc-decorator',
  setup(build) {
    let options = {
      // Note: This would be more complicated in practice
      experimentalDecorators: true,
      emitDecoratorMetadata: true,
    };

    // Note: May want to support "*.tsx" too
    build.onLoad({ filter: /\.ts$/ }, async (args) => {
      let ts = await require('fs').promises.readFile(args.path, 'utf8')
      if (/@\w+/.test(ts)) {
        let program = require('typescript').createProgram([args.path], options);
        let contents;
        program.emit(void 0, (path, js) => contents = js);
        return { contents };
      }
    })
  },
}

require('esbuild').build({
  entryPoints: ['entry.ts'],
  bundle: true,
  outfile: 'out.js',
  plugins: [tscDecoratorPlugin],
}).catch(() => process.exit(1))
thomaschaaf

comment created time in 8 hours

issue commentevanw/esbuild

Support emitting typescript decorator metadata

I'm currently trying to write an esbuild plugin which will try to build with esbuild and compile only the files containing docorators with tsc again. This should result in faster build times as usually not all files use decorators. See https://github.com/evanw/esbuild/issues/915#issuecomment-791758649

SirJosh3917

comment created time in 8 hours

issue commentevanw/esbuild

[Question] How to reuse esbuild-style logging in JavaScript?

Replicating this exactly will likely be kind of hard because there has been a lot of polish put into them.

I noticed the polish. It really shows! I wish more CLIs felt like esbuild. It has this really nice balance of discoverability / understandability which is ideal for your end users.

Thank you for elaborating on some of the more subtle implementations details. I can mostly understand logger.go now.

Another detail that esbuild doesn't yet handle is that some Unicode code points can technically take up zero or two columns instead of just one. This isn't common with the code I work with but may be more of an issue for people working in other languages. Handling this is really gross though and involves large tables of code point values.

I’ve dealt with a fair share of Unicode / regex / tables and yeah, I know what you mean. I don’t mind starting with a naive implementation for now. Eventually I’d like to respect all of your original considerations.

Here’s my WIP for what it’s worth:

<details> <summary>WIP implementation here:</summary>

export function formatMessage(message: esbuild.Message): string {
  const loc = message.location!

  let file = ""
  file += path.relative(process.cwd(), loc.file) + ":"
  file += loc.line + ":"
  file += loc.column + 1 // One-based

  const text = message.text

  if (text.endsWith("is not defined")) loc.length = text.slice(0, -" is not defined".length).length

  let code = ""
  code += loc.lineText.slice(0, loc.column)
  code += terminal.green(loc.lineText.slice(loc.column, loc.column + loc.length))
  code += loc.lineText.slice(loc.column + loc.length)

  let gap1 = ""
  gap1 += " ".repeat(3)
  gap1 += loc.line + " "
  gap1 += "│"

  let gap2 = ""
  gap2 += " ".repeat(3)
  gap2 += " ".repeat((loc.line + " ").length)
  gap2 += "│"

  return (
    terminal.bold(` > ${file}: ${terminal.red("error:")} ${text}`) +
    `
${gap1} ${code}
${gap2} ${" ".repeat(loc.column)}${loc.length === 0 ? terminal.green("^") : terminal.green("~".repeat(loc.length))}
`
  )
}

export function formatMessages(messages: esbuild.Message[]): string {
  let str = ""
  str += messages.map(message => formatMessage(message))
  str += "\n"
  str += `${messages.length} error${messages.length === 1 ? "" : "s"}`
  return str
}

</details>

I discovered your extractErrorMessageV8 code in lib/common.ts, and used that to help me bootstrap parsing V8 stack traces.

<details> <summary>Working implementation here:</summary>

import * as esbuild from "esbuild"
import * as fsp from "fs/promises"

// This implementation is heavily based on @evanw’s "extractErrorMessageV8"
// implementation in esbuild.
//
// https://github.com/evanw/esbuild/blob/master/lib/common.ts

export default async function parseV8ErrorStackTrace(error: any): Promise<esbuild.Message> {
  let text = "Internal error"
  let location: esbuild.Location | null = null

  try {
    text = ((error && error.message) || error) + ""
  } catch {}

  // Optionally attempt to extract the file from the stack trace, works in V8/node
  try {
    const stack = error.stack + ""
    const lines = stack.split("\n", 3)
    const at = "    at "

    // Check to see if this looks like a V8 stack trace
    if (!lines[0]!.startsWith(at) && lines[1]!.startsWith(at)) {
      let line = lines[1]!.slice(at.length)
      while (true) {
        // Unwrap a function name
        let match = /^\S+ \((.*)\)$/.exec(line)
        if (match) {
          line = match[1]!
          continue
        }

        // Unwrap an eval wrapper
        match = /^eval at \S+ \((.*)\)(?:, \S+:\d+:\d+)?$/.exec(line)
        if (match) {
          line = match[1]!
          continue
        }

        // Match on the file location
        match = /^(\S+):(\d+):(\d+)$/.exec(line)
        if (match) {
          const contents = await fsp.readFile(match[1]!, "utf8")
          const lineText = contents.split(/\r\n|\r|\n|\u2028|\u2029/)[+match[2]! - 1] || ""
          location = {
            file: match[1]!,
            namespace: "file",
            line: +match[2]!,
            column: +match[3]! - 1,
            length: 0,
            lineText: lineText, // + "\n" + lines.slice(1).join("\n"),
          }
        }
        break
      }
    }
  } catch {}

  const message: esbuild.Message = {
    detail: undefined, // Must be defined
    location,
    notes: [], // Must be defined
    text,
  }
  return message
}

</details>

Am I right to assume that when plugins crash, esbuild decorates those errors too?

zaydek

comment created time in 8 hours

issue commentevanw/esbuild

Mix esbuild-loader and ts-loader

@evanw it's very hard for me to detect if a file contains a decorator. Thus I'm thinking of compiling the code with esbuild and then check for __decorate in the esbuild output and compile it again with tsc. Is there a hook available for this? Or could you create a new one where I could do this?

thomaschaaf

comment created time in 8 hours

pull request commentevanw/esbuild

WIP for version 0.9.0

This plan looks amazing! Thank you! Please, let me know if I can contribute with something, could work on some tasks for the JS APIs for example.

evanw

comment created time in 9 hours

push eventevanw/esbuild

Evan Wallace

commit sha 745d9c0121a412745fe9d8feabda255c9f6a9345

remove unused "SpinnerBusy" and "SpinnerIdle"

view details

push time in 9 hours

PR opened evanw/esbuild

WIP for version 0.9.0

I'm using this PR to put together the next breaking change release as I build it. Not sure if a PR is the best format for this but I'm giving it a shot. So this PR exists to publicly document what's going into the upcoming version 0.9.0 release. I will be updating it as I go.

For context: I have been trying to batch breaking changes together so that they are less disruptive. But they have been piling up because one of the big changes (the linker rewrite) is unfortunately taking longer than I had hoped. I'd like to do a breaking change release now to get some of these breaking changes out even though it won't contain the linker rewrite just to keep other things moving along. This might mean there are at least two breaking change releases coming up: this one and another one with the linker rewrite. Note that the next version after version 0.9.0 will be version 0.10.0, not version 1.0.0. It's definitely getting close to 1.0.0 but it's not quite that close.

The rewrite of the linker is being done to address many shortcomings of the current linker all at once. However, that has involved some significant R&D effort (e.g. 1, 2, 3, 4) and it is unfortunately still a work in progress. I did a brain dump of some information about the linker rewrite at the end of this post.

Definitely in version 0.9.0

  • [x] Remove the now-unused --avoid-tdz flag

    This flag no longer does anything because this transformation is now always applied during bundling. It hasn't been removed yet to avoid a breaking change.

  • [ ] Remove SpinnerBusy and SpinnerIdle from the Go API

    These options were part of an experiment with the CLI that didn't work out. Watch mode no longer uses a spinner because it turns out people want to be able to interleave esbuild's stderr pipe with other tools and were getting tripped up by the spinner animation. These options are currently ignored but haven't been removed yet to avoid a breaking change.

  • [ ] Remove the --summary flag and instead just always print a summary

    The summary can be disabled if you don't want it by passing --log-level=warning instead. I'm going to try this because I believe it will improve the UX. People have this problem with esbuild when they first try it where it runs so quickly that they think it must be broken, only to later discover that it actually worked fine. While this is funny, it seems like a good indication that the UX could be improved. So I'm going to try automatically printing a summary to see how that goes.

  • [ ] Remove the startService() API, leaving only build() and transform()

    The context is that the startService() API was added quickly in response to a user request and at the time I was unaware of unref() which makes it possible to use a child process without requiring people to call service.stop(). Many thanks to @SalvatorePreviti who contributed this change in #656. Calling service.stop() no longer does anything, so there is no longer a strong reason for keeping the startService() API around. The primary thing it currently does is just make the API more complicated and harder to use. I plan to add an initialize({ wasmURL }) API to replace the configuration of wasmURL in the browser that used to be done by startService(). And the buildSync() and transformSync() APIs will still exist on node.

  • [ ] Split the banner and footer features into separate values for JS and CSS (#712)

    I have been trying to make sure all of the features added are language-agnostic where it makes sense. However, the banner and footer features were an oversight and were added as a JavaScript-specific feature. They don't currently work with CSS. I plan to change the API to make it a mapping of language to value to make it more language-agnostic, which is a breaking change.

Probably in version 0.9.0

  • [ ] Remove the implicit .mjs and .cjs extensions

    I added these because Webpack has implicit .mjs extensions and it seemed like a reasonable idea to support these at the time. However, doing this can actually break packages that have some-file.mjs and some-file.js sitting side-by-side. This also isn't how node works so doing this breaks compatibility with node. So it feels like this should be an opt-in change instead of an opt-out change. I'm going to try removing them from the default configuration. You can still configure them yourself with the "resolve extensions" setting if you'd like, although it might break stuff. Otherwise you should use the .mjs and .cjs extensions explicitly instead of omitting them.

  • [ ] Remove the ability to use require(...) on a file in ESM (ECMAScript module) format

    I had originally enabled this because it seemed reasonable to support. You can just convert the ESM file to a CommonJS file when you hit this case and then load it as CommonJS instead. However, this has a few problems:

    • This isn't how node works. Doing this in node is impossible and will cause a syntax error. So it's kind of a weird thing to support and there isn't really a precedent that says how it should behave. I want esbuild to follow existing conventions instead of inventing new ones, and it turns out this was kind of inventing a new convention. It feels best for esbuild and for the ecosystem if people don't come to rely on this weird thing actually working, so I'm going to try disabling this.

    • Doing this makes import order really confusing to reason about. Part of what I am doing with the linker rewrite is to be much more strict about having esbuild follow the correct import order, but this edge case doesn't have a well-defined import ordering. The problem is that ESM import order is determined by a depth-first traversal of the import graph and is entirely statically determined at link time, but require(...) is dynamically-determined at run time. Where do ESM imports in the required file end up in the static module initialization order? Do they even end up in the static module initialization order at all? Should they also be converted from ESM to CommonJS recursively, potentially contaminating a large part of the bundle? There isn't really any existing ESM-native platform that behaves this way.

    • It also causes extra issues now that TLA (top-level await) is a part of JavaScript. TLA is only available in ESM files and means that loading them can now be asynchronous. But require(...) is synchronous so loading an ESM file with a TLA file anywhere in its dependency chain must be forbidden. If using require(...) with ESM were to be supported then you may be seemingly arbitrarily prevented from adding an await to a file because some other file in a seemingly unrelated place happened to use a require(...) call. It seems better for code health to just avoid this possibility in the first place. This constraint also makes the bundler simpler to implement.

    Getting this change out should hopefully make the linker rewrite go more smoothly.

Maybe in version 0.9.0

  • [ ] Remove the metafile from outputFiles (#633)

    Right now using metafile with the API is unnecessarily cumbersome because you have to extract the JSON metadata from the output file yourself instead of it just being provided to you as a return value. This is especially a bummer if you are using write: false because then you need to use a for loop over the output files and do string comparisons with the file paths to try to find the one corresponding to the metafile. Returning the metadata directly is an important UX improvement for the API. This hasn't been done yet because doing this is a breaking change.

  • [ ] Add support for node's exports field in package.json (#187)

    This feature was recently added to node. It allows you to rewrite what import paths inside your package map to as well as to prevent people from importing certain files in your package. Adding support for this to esbuild is a breaking change (i.e. code that was working fine before can easily stop working) so it must be done in a breaking change release.

  • [ ] Support content hashes in entry point names (#518)

    Support for this is already planned. The plan is to add the --entry-names= flag and let you put the [hash] placeholder somewhere inside it to cause esbuild to include the content hash in entry point names. You can then use the metafile to figure out what the output path was for each entry point. However, this would introduce cycles into the content hash calculation as the content hash includes not just the current file but also all of the content hashes of all transitive dependencies and entry points can reference each other using dynamic import(...) expressions.

    Addressing this requires a different approach to chunk generation that is part of the linker rewrite. This technically isn't a breaking change but it has a strong possibility of accidentally introducing bugs (especially with source maps) since it's a lot of new code, so it may also be appropriate for a breaking change release. Although it could potentially make sense to hold this back for the next breaking change release to avoid delays.

Hopefully in version 0.10.0

  • Code splitting for all output formats (#16)

    Right now code splitting only works for the esm output format, and only works for JavaScript. It should also work for the iife and cjs output formats, and also for CSS. I have a plan to address this and it is a part of the linker redesign.

  • Manual chunk labels (#207)

    This feature isn't implemented yet because it creates import cycles and the content hashing algorithm can't deal with cycles yet. But this is being kept in mind during the linker rewrite so this should be relatively easy to add when the new linker is ready.

  • Collapse duplicate external imports (#475)

    Right now each individual import statement for an external module ends up in the bundle even though they all reference the same external path and the external file is only ever imported once. I plan to collapse this into as few import statements as possible (usually one but may be up to three) as part of the rewrite of the linker.

  • Bundling with top-level await (#253)

    It's important to have esbuild support top-level await now that it's officially a part of JavaScript and usable in both node and in the browser. Unfortunately the semantics are extremely complicated (and not even implemented correctly in V8 yet!) so esbuild's initial implementation may not be totally accurate. It will at least contain the important parts (await works and sibling modules go in parallel). Currently all implementations behave slightly differently so there isn't yet common consensus about exact behavior among implementations. My current plan is to bundle files into groups by what asynchronous files they transitively depend on to reduce the overall number of files, then evaluate files within those groups in the order they would have been evalauted in if there was no top-level await. Cross-group ordering will depend on the order of promise resolutions.

  • Fix import ordering issues with internal code (#399, #465)

    Right now import order is not accurate when code splitting is active. The fundamental issue is that code in chunks is eagerly evaluated when that chunk is imported but different parts of the chunk need to be evaluated at different times for correctness. Either the chunk needs to be split into smaller chunks or code in chunks must be lazily evaluated to fix import order. I'm currently planning to lazily evaluate code to avoid additional chunk splits.

  • Potentially break the ordering of external imports

    External imports cannot be lazily evaluated in esm and respecting the correct import order would hurt bundle optimization and make the bundler more complicated. Either chunks would have to be split into further chunks or code would potentially need to be awkwardly stuffed into import statements containing data URLs, both of which would bloat code and hurt bundle download performance. I'm currently considering having external import order still be incorrect (i.e. hoisted to the top) since that's much simpler and seems to be what some other bundlers do anyway.

  • Potentially roll back the file splitting optimization

    Currently esbuild contains an optimization that no other bundler supports: individual unrelated statements within a file can end up in separate chunks when code splitting is active if they are used by a disjoint set of entry points. This means you can have a big library file of functions and potentially not have any shared chunks if no two entry points use any of the same functions. However, the blocking for top-level await works at the file boundary and this optimization made thinking through top-level await evaluation order too complicated. I'm seriously considering removing this optimization to restore my sanity and get top-level await out the door. I don't feel too bad about this because no other bundler does this yet anyway.

+6 -14

0 comment

6 changed files

pr created time in 9 hours

issue commentevanw/esbuild

TypeScript support

Yes, that's correct. It doesn't use Microsoft's implementation at all although I did reference it heavily when I made it. There are other similar efforts that do this too (reimplement TypeScript-to-JavaScript conversion from scratch) such as Babel, Sucrase, and SWC. I know at least Babel and Sucrase have similar limitations to esbuild around isolatedModules.

sergey-morenets

comment created time in 10 hours

issue commentevanw/esbuild

TypeScript support

Can you please mention list of supported fields (from tsconfig.json) in the corresponding section?

👍

What is the ES version used by default during TS compilation to JS? And can adjust this version somehow?

The --target= flag defaults to esnext but you can set it to values such as --target=es2018, --target=chrome80, --target=node15, or even a comma-separated list of targets such as --target=es2020,chrome58,firefox57,safari11,edge16. The full documentation is here: https://esbuild.github.io/api/#target.

Thank you. Do I understand correctly that esbuild contains its own (custom) compiler/transpiler from Typescript to ES?

sergey-morenets

comment created time in 10 hours

create barnchevanw/esbuild

branch : 0.9.0-wip

created branch time in 11 hours

issue commentevanw/esbuild

vite events.js:292 throw er; // Unhandled 'error' event

No, npm did not report an error, the installation is normal, but I found no esbuild.exe exists in node_modules/esbuild

TuiMao233

comment created time in 12 hours

issue commentevanw/esbuild

[Question] How to reuse esbuild-style logging in JavaScript?

Replicating this exactly will likely be kind of hard because there has been a lot of polish put into them. Some details to think about:

  • When computing column counts, keep in mind that JavaScript strings are UTF-16 while the actual files are UTF-8. You may need to operate on UTF-8 buffers instead for sanity. Not sure.
  • Lines that are longer than the terminal width are truncated with ... ellipses, potentially on one or both sides (this gets pretty complicated). This helps a lot with errors in minified code but is otherwise probably not necessary.
  • Tab stops should be expanded for the underline to line up correctly. This is also relevant when truncating lines since the post-expanded content should be the truncated part.

Another detail that esbuild doesn't yet handle is that some Unicode code points can technically take up zero or two columns instead of just one. This isn't common with the code I work with but may be more of an issue for people working in other languages. Handling this is really gross though and involves large tables of code point values.

zaydek

comment created time in 14 hours

push eventevanw/esbuild

Evan Wallace

commit sha 8ca9f4626fa1dffb9f99eec06838f93878c0efb9

fix class fields with "keep names"

view details

push time in 15 hours