aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2023-08-11 10:30:59 -0400
committerXe Iaso <me@xeiaso.net>2023-08-11 10:31:07 -0400
commit91618787effe6ed66b6a2bc8bf8ac91a5e3ec4ee (patch)
treeb8b6673b3a5e656d6fca48c5d340e601b7ab95d7
parent5c1120f06d436ccb8850acbb61d02246e2a597a4 (diff)
downloadxesite-91618787effe6ed66b6a2bc8bf8ac91a5e3ec4ee.tar.xz
xesite-91618787effe6ed66b6a2bc8bf8ac91a5e3ec4ee.zip
blog: nixexpr post
Signed-off-by: Xe Iaso <me@xeiaso.net>
-rw-r--r--blog/nixexpr.markdown175
-rw-r--r--flake.lock18
-rw-r--r--lib/xesite_templates/src/lib.rs1
3 files changed, 184 insertions, 10 deletions
diff --git a/blog/nixexpr.markdown b/blog/nixexpr.markdown
new file mode 100644
index 0000000..6ec7e85
--- /dev/null
+++ b/blog/nixexpr.markdown
@@ -0,0 +1,175 @@
+---
+title: "Introducing nixexpr: Nix expressions for JavaScript"
+date: 2023-08-11
+tags: [nix, javascript, nodejs, npm, cursed]
+---
+
+<xeblog-hero photo ai="Nikon D3300, photo by Xe Iaso" file="eifel-tower2" prompt="A picture of the tip of the Eifel Tower facsimilie in Las Vegas with a partially cloudy sky"></xeblog-hero>
+
+As a regular reminder, it is a bad idea to give me ideas. Today's bad idea is
+brought to you by managerial nerd sniping, insomnia, and the letter "Q".
+
+At a high level: writing complicated data structures in JavaScript kinda sucks.
+Here's an example of the kinds of things that I've been writing as I go down the
+[ElasticSearch tour-de-insanite](/blog/elasticsearch):
+
+```javascript
+{
+ highlight: {
+ pre_tags: ['<em>'],
+ post_tags: ['</em>'],
+ require_field_match: false,
+ fields: {
+ body_content: {
+ fragment_size: 200,
+ number_of_fragments: 1,
+ },
+ },
+ },
+}
+```
+
+This works, this is perfectly valid code. It creates an object that has a few
+nested layers of stuff in it, but overall I just don't like how it _looks_. I
+think it looks superfluous. What if we could make it look a little bit nicer?
+How about something like this?
+
+```nix
+{
+ highlight = {
+ pre_tags = [ "em" ];
+ post_tags = [ "</em>" ];
+ require_fields_match = false;
+ fields.body_content.fragment_size = 200;
+ fields.body_content.number_of_fragments = 1;
+ };
+}
+```
+
+This is a Nix expression. It's a data structure that looks like JSON, but you
+have the power of a programming language at your fingertips. Note the difference
+between these two parts:
+
+```javascript
+{
+ fields: {
+ body_content: {
+ fragment_size: 200,
+ number_of_fragments: 1,
+ },
+ },
+}
+```
+
+```
+{
+ fields.body_content.fragment_size = 200;
+ fields.body_content.number_of_fragments = 1;
+}
+```
+
+These are semantically equal, but you don't have to use so much indentation and
+layering. These settings are all related, so it makes sense that the way that
+you use them is on the same level as the way that you define them.
+
+If you want to try out this awesome power for yourself,
+[Install Nix](https://github.com/DeterminateSystems/nix-installer) and then add
+`@xeserv/nixexpr` to your JavaScript dependencies.
+
+```bash
+npm install --save @xeserv/nixexpr
+```
+
+Then you can use it like this:
+
+```javascript
+import { nix } from "@xeserv/nixexpr";
+
+const someValue = "this is a string";
+
+const myData = nix`{
+ hello = "world";
+ someValue = ${someValue};
+}`;
+
+console.log(myData);
+```
+
+I originally wrote this
+[in Go](https://github.com/Xe/x/blob/c822591c5a46ad9e8f13d14ac96d2e9d26e7c828/cmd/yeet/main.go#L115-L151)
+for my scripting automation tool named
+[yeet](https://www.urbandictionary.com/define.php?term=Yeet), but I think it's
+generically useful enough to exist in its own right in JavaScript. I think that
+there's a lot of things that the JavaScript ecosystem can gain from Nix, and I'm
+excited to see what people do with this.
+
+This was made so I could write scripts like this:
+
+```javascript
+// snipped for brevity
+const url = slug.push("within.website");
+const hash = nix.hashURL(url);
+
+const expr = nix.expr`{ stdenv }:
+
+stdenv.mkDerivation {
+ name = "within.website";
+ src = builtins.fetchurl {
+ url = ${url};
+ sha256 = ${hash};
+ };
+
+ phases = "installPhase";
+
+ installPhase = ''
+ tar xf $src
+ mkdir -p $out/bin
+ cp web $out/bin/withinwebsite
+ cp config.ts $out/config.ts
+ '';
+}
+`;
+```
+
+And then I'd be able to put that Nix expression into a file. I'll get into more
+details about this in a future post.
+
+<xeblog-conv name="Mara" mood="hacker">Something something this isn't best
+practice something something this is a hack to make dealing with a legacy
+deployment easier something something</xeblog-conv>
+
+## How it works
+
+This is a very cheeky library, and it's all powered by one of the most fun to
+abuse Nix functions ever:
+[builtins.fromJSON](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-fromJSON).
+This function takes a string and turns it into a Nix value at the interpreter
+level and it's part of the callpath for turning a string into an integer in Nix.
+It's an amazingly powerful function in its own right, but it gets even more fun
+when we bring JavaScript into the mix.
+
+Any JavaScript data value (simple objects, strings, numbers, etc) can be
+formatted as JSON with the `JSON.stringify` function:
+
+```
+> JSON.stringify({"hi": "there"})
+'{"hi":"there"}'
+```
+
+This includes strings. So if we use `JSON.stringify` to convert it to a JSON
+string, then string encode it again, we can inject arbitrary JavaScript code
+into Nix expressions:
+
+```js
+let formattedValue = `(builtins.fromJSON ${
+ JSON.stringify(JSON.stringify(value))
+})`;
+```
+
+The most horrifying part about this hack is that it works.
+
+## What's next?
+
+If this ends up getting used, I may try and make "fast paths" for strings and
+numbers so that they don't have to go through the JSON encoding/decoding
+process. But so far this works well enough for my purposes.
diff --git a/flake.lock b/flake.lock
index d1a9835..dabe6d4 100644
--- a/flake.lock
+++ b/flake.lock
@@ -148,11 +148,11 @@
]
},
"locked": {
- "lastModified": 1688534083,
- "narHash": "sha256-/bI5vsioXscQTsx+Hk9X5HfweeNZz/6kVKsbdqfwW7g=",
+ "lastModified": 1690373729,
+ "narHash": "sha256-e136hTT7LqQ2QjOTZQMW+jnsevWwBpMj78u6FRUsH9I=",
"owner": "nix-community",
"repo": "naersk",
- "rev": "abca1fb7a6cfdd355231fc220c3d0302dbb4369a",
+ "rev": "d9a33d69a9c421d64c8d925428864e93be895dcc",
"type": "github"
},
"original": {
@@ -178,11 +178,11 @@
},
"nixpkgs_2": {
"locked": {
- "lastModified": 1689679375,
- "narHash": "sha256-LHUC52WvyVDi9PwyL1QCpaxYWBqp4ir4iL6zgOkmcb8=",
+ "lastModified": 1690881714,
+ "narHash": "sha256-h/nXluEqdiQHs1oSgkOOWF+j8gcJMWhwnZ9PFabN6q0=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "684c17c429c42515bafb3ad775d2a710947f3d67",
+ "rev": "9e1960bc196baf6881340d53dccb203a951745a2",
"type": "github"
},
"original": {
@@ -242,11 +242,11 @@
]
},
"locked": {
- "lastModified": 1689850762,
- "narHash": "sha256-zz62sRw+Bh8zd24GQpQ3Jp0fXgMuRLmhKIpVarkl1DI=",
+ "lastModified": 1690926877,
+ "narHash": "sha256-933VGrQlTxBPpbpAtfAOTU83RbJusdL1x7gYdqJN9v4=",
"owner": "typst",
"repo": "typst",
- "rev": "51a21403ba2e8ced2aeb6a80996d9247f0b7ffd0",
+ "rev": "77cc05b121bef5a7feb62345d19d9c693415d7cd",
"type": "github"
},
"original": {
diff --git a/lib/xesite_templates/src/lib.rs b/lib/xesite_templates/src/lib.rs
index f5219d9..26dcaec 100644
--- a/lib/xesite_templates/src/lib.rs
+++ b/lib/xesite_templates/src/lib.rs
@@ -47,7 +47,6 @@ pub fn hero(file: String, prompt: Option<String>, ai: Option<String>) -> Markup
img style="padding:0" loading="lazy" alt={"hero image " (file)} src={"https://cdn.xeiaso.net/file/christine-static/hero/" (file) "-smol.png"};
}
figcaption {
- "Image generated by "
(ai)
@if let Some(prompt) = prompt { " -- " (prompt) }
}