6 ms·
JavaScript macros in Bun
- jakelazaroff 3y agoPretty cool! Sounds like it was inspired by comptime in Zig (which shouldn't be too surprising since Bun is written in Zig).
- Jarred 3y agoIt 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 3y agoIf Zig drops LLVM, where does that leave Bun development? There is no alternative Zig implementation.
- erichocean 3y agoWoah, 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.
- jgalt212 3y agoIs Bun the current killer app in Zig?
- pyrolistical 3y agoWritten in zig? But that doesn’t matter?
- revskill 3y agoIt matters for Ziglang and Zig users.
- pyrolistical 3y agoI agree. I just think “killer app” means “killer end user application”, whereas you seem to be using it as “killer application of zig”
- paulddraper 3y agoHow 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.
- explaininjs 3y agoVery 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.
- creatable 3y agoEvery 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.
- bluepnume 3y agoEh. 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 3y agoImport 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 3y agoWhat 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.
- Zambyte 3y agoIt'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 3y agoThis 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 3y agoWell 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).
- bitblender 3y agoIs 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.
- jitl 3y agoThis blog post is missing a few things from the official docs: https://bun.sh/docs/bundler/macros 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:
- wolfgang42 3y ago> the "templates" we use for codegen need to be valid Typescript files, or we risk breakage if imports or types change. Any tips on how to do this? I’ve been running into similar problems with code generation in TS, and I haven’t been able to come up with a good technique for how to solve this problem yet. (Anything I’ve been able to find is either an AST that’s awkward to use, or string templating that can’t be valid code since it has extra syntax in it.)
- love2read 3y ago
- iddan 3y agoI 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 3y agoI 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/ https://www.sweetjs.org/ [1]: https://babeljs.io/blog/2017/09/11/zero-config-with-babel-macros https://babeljs.io/blog/2017/09/11/zero-config-with-babel-ma...
- Jarred 3y agoAn 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/454da846d614f7bb4bcceb6ddb0e8515 https://gist.github.com/Jarred-Sumner/454da846d614f7bb4bcceb...
- namuol 3y agoCame here to suggest renaming this feature for the reasons described here. Ideas are cheap, and naming is hard, but I like Zig’s “comptime” term, which is closer to this than “macro”. “comptime” distinguishes it from C preprocessor stuff, but also describes what it actually does more clearly.
- mhio 3y agoI've been using ts-morph for this recently, the accompanying AST viewer makes getting started easy. https://github.com/dsherret/ts-morph https://github.com/dsherret/ts-morph https://ts-ast-viewer.com https://ts-ast-viewer.com
- 3y ago
- Zekio 3y agogotta say I'm puzzled why time is spent on features like these, rather than focusing on getting the remaining node apis finished
- monroewalker 3y agoSecond 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 3y agoI 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
- skybrian 3y agoNeat! 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.
- paxys 3y agoHmm, 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?
- activitypea 3y agoI feel like macros might be a poor name for the feature. Most people in Javascriptland expect macros to enable some sort of AST manipulation, whereas these are "just" compile-time statements. Still pretty cool, but putting "Javascript macros" right there in the title might've been the wrong choice
- AbhyudayaSharma 3y agoBabel has macros too! My super small npm package gets git commit hashes using the babel-plugin-macros. https://www.npmjs.com/package/babel-plugin-macros https://www.npmjs.com/package/babel-plugin-macros https://github.com/AbhyudayaSharma/react-git-info https://github.com/AbhyudayaSharma/react-git-info
- Solvency 3y agoAre people really turning to Bun over Vite these days? I struggle to see any advantage it offers.