[HN Gopher] JavaScript macros in Bun
       ___________________________________________________________________
        
       JavaScript macros in Bun
        
       Author : fagnerbrack
       Score  : 82 points
       Date   : 2023-06-29 11:02 UTC (1 days ago)
        
 (HTM) web link (bun.sh)
 (TXT) w3m dump (bun.sh)
        
       | creatable wrote:
       | Every time I see a new Bun update it's always because Bun has
       | implemented a feature that violates the spec in some way. While
       | this does rely on syntax from a proposal, said proposal has not
       | made it into the language yet. Another failure I see is the fact
       | that the "type" property is not meant to be used for this type of
       | language augmentation, it's meant to describe the type of the
       | file without inferring from the file extension. I think Bun has a
       | habit of moving too fast and breaking things and if it catches on
       | I worry it will have just as many legacy anti-spec things as Node
       | does.
        
         | paulddraper wrote:
         | How do you think features make it into the spec?
         | 
         | Specification and implementation are chickend-and-egg.
        
           | badrequest wrote:
           | Is bun actively participating in these discussions, or are
           | they just implementing whatever they wish and not
           | participating in the chicken-and-egg process?
        
           | spankalee wrote:
           | The main JS implementations do not run ahead of the spec.
           | They implement at Stage 3.
        
             | paulddraper wrote:
             | A. Import attributes are stage 3.
             | 
             | B. If you blur the line between "runtime" and "compiler",
             | you'll realize that a lot of JS ecosystem (e.g. Babel) run
             | ahead of that.
        
             | jakelazaroff wrote:
             | Import attributes are at Stage 3: https://tc39.es/proposal-
             | import-attributes/
        
               | spankalee wrote:
               | I'm well aware, but this is not just import attributes.
               | It's using import attributes syntax to trigger a
               | transform of the importing module, in addition to
               | changing the behavior of the exporting module.
        
         | pictur wrote:
         | I guess today's software development culture requires it.
         | constantly discover new things and continue by breaking the
         | old.
        
         | the__alchemist wrote:
         | [flagged]
        
         | bluepnume wrote:
         | Eh. Just treat Bun as a testing ground for (very) experimental
         | new features. It's no secret that the project moves very
         | quickly, and anything that gains enough traction can be folded
         | back into the spec.
        
         | Jarred wrote:
         | Import Attributes are a stage3 proposal which means it is very
         | likely to become part of the language. It's the last stage
         | before becoming part of the language.
         | 
         | > Another failure I see is the fact that the "type" property is
         | not meant to be used for this type of language augmentation,
         | it's meant to describe the type of the file without inferring
         | from the file extension
         | 
         | It sounds like you're confusing Import Attributes with Import
         | Assertions, which was the previous iteration of Import
         | Attributes.
         | 
         | Interpretation of import attributes is host defined. For Import
         | Assertions, that wasn't true - they were intended never to
         | impact runtime evaluation. That's the difference with Import
         | Attributes. Import Attributes do impact runtime evaluation.
        
           | creatable wrote:
           | What I specifically meant is that the "type" property can
           | impact runtime evaluation as per the spec, however it's
           | implied to be used specifically for impacting runtime
           | evaluation based on a file's type. Say I have a module in a
           | language that transpiles to JavaScript based off of the
           | "type" property. In Bun, I would not be able to use any
           | functions exported by it as a macro because the "type"
           | property is being used for defining the module as a macro.
           | That's the specific issue I have.
        
             | spankalee wrote:
             | Exactly. Import attributes are supposed to effect the
             | imported module, not the _importing_ module.
             | 
             | This turns what looks like a function call in the importer
             | into something like macro expansion (it doesn't look like
             | actual macros though).
        
           | unlog wrote:
           | What about adding signals?
        
             | [deleted]
        
           | DanHulton wrote:
           | I mean, it looks like they support Import Assertions, too:
           | 
           | https://bun.sh/blog/bun-macros#how-it-works
           | 
           | So the worry about having legacy anti-spec things still seems
           | pretty valid.
        
       | jgalt212 wrote:
       | Is Bun the current killer app in Zig?
        
         | pyrolistical wrote:
         | Written in zig? But that doesn't matter?
        
           | revskill wrote:
           | It matters for Ziglang and Zig users.
        
             | pyrolistical wrote:
             | I agree. I just think "killer app" means "killer end user
             | application", whereas you seem to be using it as "killer
             | application of zig"
        
             | paulddraper wrote:
             | How so?
             | 
             | The "killer app" is the app that necessitates adoption of
             | the platform.
             | 
             | E.g. Lotus 1-2-3 is the killer app of the IBM PC. Lots of
             | users want Lotus 1-2-3, therefore they need to adopt the
             | IBC PC platform.
        
               | revskill wrote:
               | Real world testing requires real world workload.
        
               | detaro wrote:
               | Sure, but that's not what "killer app" usually means.
        
       | jitl wrote:
       | This blog post is missing a few things from the official docs:
       | https://bun.sh/docs/bundler/macros
       | 
       | ---
       | 
       | The way we do this kind of thing at Notion is very simple. We
       | have normal CLI commands that generate code and write it to disk,
       | and we check in those outputs. Then in CI, we run all the
       | generation commands and verify the codegen is up-to-date.
       | Checking in generated code means it's really easy to understand
       | the behavior of codegen and how it changes, and everyone gets
       | excellent typechecking and auto-completion of codegen'd
       | artifacts.
       | 
       | The downside to static codegen is that the "templates" we use for
       | codegen need to be valid Typescript files, or we risk breakage if
       | imports or types change.
       | 
       | Bun macros have some advantages like execution at build time for
       | stuff like Git SHA interpolation, but because these macros can't
       | actually emit code, they feel _less_ powerful that straight-up
       | codegen. I could replace a lot of the Notion CLI commands with
       | Bun macros if:
       | 
       | 1. There's a return type for macros that emits code, like:
       | // src/macros/prism.ts         export function emitPrismImports()
       | {           const importOrder =
       | toposortLangagues(PRISM_LANGUAGES)           const imports =
       | importOrder             .map(lang => `await import(/*
       | webpackChunkName: "prism" */ "${importPath}"`)
       | .join('\n')           return Bun.code`${imports}`         }
       | // src/client/syntaxHighlighting.ts         import {
       | emitPrismImports } from '@notionhq/macros/prism' with { type:
       | 'macro' }         async function highlight(text, lang) {
       | emitPrismImports()           return Prism.highlight(text, lang)
       | }
       | 
       | 2. There's a way to run `bun build` and _just_ do macro
       | evaluation. I need to continue using Typescript, Webpack, Jest,
       | etc for now, so we need eval 'd macros on disk so they can be
       | typechecked, tab-completed, and bundled by other tools.
       | 
       | The uncompiled versions still wouldn't typecheck nicely though
       | :thinking_face:
        
       | explaininjs wrote:
       | Very nice. This should get rid of a lot of clunky
       | webpack/esbuild/etc junk I have lying around to inline various
       | constants or otherwise one-time configure the runtime.
        
       | jakelazaroff wrote:
       | Pretty cool! Sounds like it was inspired by comptime in Zig
       | (which shouldn't be too surprising since Bun is written in Zig).
        
         | Jarred wrote:
         | It was mostly inspired by comptime in Zig. Babel macros came to
         | mind too, but those rely on an AST and Babel-specific api.
         | What's really cool about comptime in Zig is it feels like
         | ordinary code. Macros in Bun aim for that too.
        
           | zigobserver wrote:
           | If Zig drops LLVM, where does that leave Bun development?
           | There is no alternative Zig implementation.
        
         | erichocean wrote:
         | Woah, nice. Zig has a nice sweet spot of providing useful
         | functionality at compile time vs. having "macros can make the
         | text you read mean practically anything" like in, e.g., Lisp.
        
       | Zambyte wrote:
       | It's cool to be able to compute things at compile time, but one
       | thing that doesn't seem clear from this post is: can macros
       | return code? If so, how? It seems like they can only return
       | values that are computed at compile time, which severly limits
       | what you can do with macros.
        
         | colinmcd wrote:
         | This is mentioned under "Limitations"
         | 
         | > The result of the macro must be serializable!
         | 
         | > Functions and instances of most classes (except those
         | mentioned above) are not serializable.
        
           | Zambyte wrote:
           | Well those are both slightly different than "code". I mean
           | something more like returning an S-expression, or a JS AST
           | object, and having that be inlined. That should have no
           | problem with serialization, since it could just be dropped
           | into the AST (unlike a function or class).
        
       | skybrian wrote:
       | Neat! I like that it's not allowed in npm modules. Module authors
       | can do whatever compile-time codegen they want as part of their
       | own builds.
        
       | bitblender wrote:
       | Is there a way to disable arbitrary I/O in macros? I would like
       | to use this feature to compute lookup tables and inline the
       | results of pure functions, but I really really do not want my
       | bundler making arbitrary http calls or other sorts of
       | nondeterminism. I need to be able to reproduce my bundles
       | confidently.
        
       | iddan wrote:
       | I love macros and feel like they would be a great addition for
       | the JavaScript language and ecosystem. That being said I would
       | much rather them to be standardised. Now with the past few years
       | of TC39 it feels like introducing new big things worked only when
       | one of the big vendors made them available de-facto and then they
       | got real chance to progress in the review stages (take for
       | example decorators that were boosted by TypeScript).
        
       | goranmoomin wrote:
       | I was ready to be excited from the title, but was utterly
       | disappointed :(
       | 
       | IMO these aren't macros in the Lisp-sense of the word (or Rust,
       | or even C); yeah they run code at compile time, but that's where
       | the common ends.
       | 
       | Macros should be able to apply syntactic transformation on the
       | code. Lisp is famous for allowing that by representing code as
       | lists. Rust has a compiler-level API to give tokens and run
       | arbitrary code, then spit new tokens out. C macros operate on the
       | tokens level, so with enough magic you can transform code to the
       | shape you want.
       | 
       | This... isn't any of that.
       | 
       | A pretty good example (and something I'm still sad that it didn't
       | take off) of macros in JS is Sweet.js[0]. Babel macros[1] are a
       | bit higher level, where macros require the input to already be a
       | valid AST, but that's also something I'd call macros.
       | 
       | This... I'd say it's more of a compile time code execution
       | feature, not a macro feature.
       | 
       | [0]: https://www.sweetjs.org/ [1]:
       | https://babeljs.io/blog/2017/09/11/zero-config-with-babel-ma...
        
         | Jarred wrote:
         | An earlier version of macros in Bun looked more like what you
         | describe. We nerfed it because the API was too complicated. But
         | we probably will revisit in the future.
         | 
         | https://gist.github.com/Jarred-Sumner/454da846d614f7bb4bcceb...
        
       | paxys wrote:
       | Hmm, seems to me that this is for a _really_ niche use case
       | (adding metadata like timestamps or git commit tags to your JS
       | bundle). Is it really something that needs to have native
       | language and syntax support over just adding a custom build step?
        
       | Zekio wrote:
       | gotta say I'm puzzled why time is spent on features like these,
       | rather than focusing on getting the remaining node apis finished
        
         | monroewalker wrote:
         | Second this. I've tried Bun a couple times and have been
         | surprised by how fast it actually is but also run into issues
         | getting it to work with existing libraries. Issues that make it
         | seem like it's _almost_ there but just not quite able to
         | replace node yet in most cases.
         | 
         | It's surprising to see effort spent coming up with and
         | developing niche new features rather than on bridging the gaps
         | (within reason) with node.
         | 
         | I've never worked on a language project like this before though
         | so I'm not in a place to cast any judgement, just echoing the
         | sentiment that this seems strange from the outside. Maybe this
         | is just what it takes to keep the project interesting for
         | Jarred? I can relate to getting bored with a project once I've
         | proved out the difficult parts and most of what remains just
         | feels like chores
        
         | Jarred wrote:
         | I wrote nearly all of macros in Bun over a year ago, when Bun
         | was in private beta. We just never documented it or talked
         | about it much
         | 
         | Our focus is very much on Node.js compatibility
        
       ___________________________________________________________________
       (page generated 2023-06-30 23:00 UTC)