[HN Gopher] Spinneret: A modern Common Lisp HTML generator
       ___________________________________________________________________
        
       Spinneret: A modern Common Lisp HTML generator
        
       Author : nanna
       Score  : 93 points
       Date   : 2023-09-26 14:36 UTC (8 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | neilv wrote:
       | If Scheme/Racket people are feeling inspired, you could look at
       | two that I made, and make something with different properties,
       | informed by Spinneret:
       | 
       | * Static syntax-based: https://docs.racket-lang.org/html-
       | template/
       | 
       | * Dynamic data-based: https://docs.racket-lang.org/html-writing/
       | 
       | I like that Spinneret focuses on HTML5. My work started over 20
       | years ago, with HTML always being quirky, and also sometimes used
       | in conjunction with my permissive HTML parser. So there's a need
       | to maintain backward-compatibility with how things worked before,
       | including weird non-open-source things. (With the parser, I've
       | also _recently_ gotten requests to support 20yo HTML, for dealing
       | with legacy data.) Doing something all new, tailored to HTML5 or
       | something higher-level, would be able to do some things better.
       | (But I strongly recommend _trying_ to stick with SXML
       | representation /syntax at some level, though, since it's a de-
       | facto standard in the Scheme world, and can be used in a way
       | compatible with representing HTML5.)
        
       | ducktective wrote:
       | So how does developing a modern website using CL looks like? If I
       | understood correctly, this produces the pages on server-side (so
       | SSR). But how does "styling" and "interactivity" comes into
       | place?
        
         | fiddlerwoaroof wrote:
         | I've been using spinneret + htmx + lass[1] + parenscript (as
         | needed) + hunchentoot for various interactive applications for
         | a while. I don't have a ton to show, but it's a nice quick way
         | to quickly produce one-off apps. And, with htmx's websocket
         | capabilities, you can use the browser as a sort of repl sidecar
         | for visualizing things (together with something that generates
         | svg)
         | 
         | [1]: https://github.com/Shinmera/LASS
        
           | aidenn0 wrote:
           | Have you written up anything about htmx with lisp? My last CL
           | webapp used MithrilJS and parenscript, but htmx looks like a
           | better fit.
        
             | fiddlerwoaroof wrote:
             | Not really, I've intended to come up with a sort of
             | framework for automatically serving partial pages when the
             | relevant headers are present, but it's pretty
             | straightforward to use.
             | 
             | The only slightly tricky part is spinneret's attribute
             | validation
        
         | medo-bear wrote:
         | It generates HTML from common lisp s-expressions. This is
         | useful because s-expressions are much easier to write and
         | manipulate, especially with a package like paredit.
         | 
         | https://paredit.org/
        
       | [deleted]
        
       | [deleted]
        
       | schemescape wrote:
       | Am I out of line for disliking the number/size of dependencies
       | Spinneret pulls in?
        
         | mtreis86 wrote:
         | ASDF depends-on ("parenscript" "alexandria" "cl-ppcre" "global-
         | vars" "serapeum" "trivia" "trivial-gray-streams")
         | 
         | That doesn't seem excessive. I didn't look to see how many
         | imports each gets.
         | 
         | Need the utility libraries? Probably not. Embrace the NIH.
         | 
         | Need both trivia and PPCRE tho? They both pattern match.
        
       | Capricorn2481 wrote:
       | Are there people here that prefer Common Lisp to Clojure for
       | webdev specifically and would like to tell us why?
        
         | aidenn0 wrote:
         | Sure:
         | 
         | 1. I've been using CL for longer than Clojure has existed
         | 
         | 2. SLIME is better than any Clojure dev setup I've seen
         | 
         | 3. Maybe I'm misunderstanding things, but if I get how
         | Leiningen works, I need to restart my REPL any time I want to
         | add dependencies?
         | 
         | 4. Kind of a minor thing, but working in a Lisp-1 hits the
         | "uncanny valley" of programming for me; I accidentally shadow
         | an important function at least once an hour when working in
         | Clojure or Scheme.
        
           | Capricorn2481 wrote:
           | > 3. Maybe I'm misunderstanding things, but if I get how
           | Leiningen works, I need to restart my REPL any time I want to
           | add dependencies?
           | 
           | That is correct, though a lot of people are moving to
           | `deps.edn` which recently lets you add dependencies on the
           | fly.
        
             | koito17 wrote:
             | This isn't a tools.deps specific functionality. Pomegranate
             | has always allowed one to dynamically add dependencies to a
             | running Clojure REPL. Now that add-lib was added in Clojure
             | 1.12 alpha, people do not need to use pomegranate to
             | dynamically add dependencies. In fact, Leiningen itself can
             | make use of add-lib.
        
           | capableweb wrote:
           | I'm no CL expert but I've written my fair share of Clojure
           | code, just as a preface.
           | 
           | > I accidentally shadow an important function at least once
           | an hour when working in Clojure
           | 
           | You mean that you accidentally "overwrite" (declare again) a
           | function with the same name as the one you're now declaring,
           | but you didn't mean to?
           | 
           | If so, I'm not sure how that'd be possible. In Clojure you're
           | always in a namespace, and you'd only have one function with
           | that one name in that namespace, and if you're declaring a
           | function with the same name in a different namespace, you now
           | correctly have two functions with the same name, but in
           | different namespaces.
           | 
           | Unless your editor<>repl connection somehow ignores the
           | current ns, I'm not sure how you'd end up in that situation.
           | 
           | > SLIME is better than any Clojure dev setup I've seen
           | 
           | This I'm also curious about, what exactly SLIME gives you
           | that for example Conjure for neovim wouldn't already? Maybe
           | something about continuations perhaps? That seems to be the
           | only feature I've seen from Common Lisp (besides actually
           | being able to compile to binaries) that I'd love to have in
           | Clojure.
        
             | aidenn0 wrote:
             | > You mean that you accidentally "overwrite" (declare
             | again) a function with the same name as the one you're now
             | declaring, but you didn't mean to?
             | 
             | I mean I use let to bind a variable with the same name as a
             | function. This is idiomatic in Common Lisp, and totally
             | breaks things in most other languages.
             | 
             | > This I'm also curious about, what exactly SLIME gives you
             | that for example Conjure for neovim wouldn't already? Maybe
             | something about continuations perhaps? That seems to be the
             | only feature I've seen from Common Lisp (besides actually
             | being able to compile to binaries) that I'd love to have in
             | Clojure.
             | 
             | I watched a video and it does seem rather complete, but [1]
             | indicates there is no debugger? That's a rather glaring
             | omission. I also don't see a profiler mentioned, and SLIME
             | with SBCL gives me a profiler (down to the assembly level
             | if needed). I'm sure Java in general has great profiling
             | tools, but how are the integrated into the Clojure system?
             | 
             | As an aside, by "continuations" did you mean "restarts"?
             | First-class continuations are a feature of scheme, not CL.
             | Indeed a _huge_ boost to CL productivity is simply allowing
             | you to handle an exception before the stack is unwound.
             | 
             | 1: https://github.com/Olical/conjure/wiki/Client-features
        
               | capableweb wrote:
               | > I mean I use let to bind a variable with the same name
               | as a function. This is idiomatic in Common Lisp, and
               | totally breaks things in most other languages.
               | 
               | Aha, that should be working fine?                   (defn
               | add [x y]             (let [add clojure.core/+]
               | (add x y)))
               | 
               | The `let` binding is local, so it's referring to
               | clojure.core/+, and outside the function `add` I can use
               | the name `add` to call it. Seems it's handling that case
               | correct?
               | 
               | > I watched a video and it does seem rather complete, but
               | [1] indicates there is no debugger?
               | 
               | I think debuggers tend to be used as a library across
               | many different editors, rather than the editor/plugin
               | providing that functionality. Personally, I don't use
               | debuggers much as the functions I write tend to be small
               | and evaluating small executions with the repl tends to
               | reveal the issue quickly. Sometimes when refactoring
               | others code I've used https://github.com/philoskim/debux
               | to various degrees of success.
               | 
               | I do think cider (https://github.com/clojure-emacs/cider)
               | has stuff regarding stepping debuggers, but I'm not sure
               | how common it is to use it. Maybe other Clojure users can
               | fill me in :)
               | 
               | > . I also don't see a profiler mentioned
               | 
               | Yeah, as you said, the Java ecosystem basically covers
               | that. For OSS stuff, I use VisualVM, and for professional
               | stuff I use YourKit, both of them work well with Clojure
               | and points out my user-space code with ease. And I've
               | never been paid anything for actually writing/maintaining
               | Java code, so even with that, seems I'm able to use those
               | tools just for Clojure :)
               | 
               | > As an aside, by "continuations" did you mean
               | "restarts"?
               | 
               | Ah yes, of course. The condition system and restarts :)
               | Thanks!
        
               | aidenn0 wrote:
               | > I do think cider (https://github.com/clojure-
               | emacs/cider) has stuff regarding stepping debuggers, but
               | I'm not sure how common it is to use it. Maybe other
               | Clojure users can fill me in :)
               | 
               | I don't really care about stepping; for me the debugger
               | is about inspecting the state of my program when an
               | exception (maybe because I interrupted it, or because I
               | inserted a breakpoint, or just because something went
               | wrong) happens. Backtrace, local variables, evaluating
               | forms at different stack frames and so-forth.
        
               | fiddlerwoaroof wrote:
               | I use Cider exclusively when I write Clojure, but it's
               | not really a match for slime, more of an echo.
        
               | aidenn0 wrote:
               | Here's a stupid example of what I mean by shadowing:
               | user=> (defn foo [seq]           #_=>  (every? seq seq))
               | #'user/foo         user=> (foo ["1" [1] '(1) {:1 1}
               | #{1}])         Execution error (IllegalArgumentException)
               | at user/foo (REPL:2).         Key must be integer
               | 
               | Note that in a CL with a "seq" function foo would look
               | like:                   (defun foo (seq)           (every
               | #'seq seq))
               | 
               | So there would be no ambiguity.
        
           | mksybr wrote:
           | > 3
           | 
           | https://github.com/clj-commons/pomegranate
           | https://stackoverflow.com/questions/16409182/any-way-to-
           | add-...
           | 
           | => (use '[cemerick.pomegranate :only (add-dependencies)]) nil
           | => (add-dependencies :coordinates '[[incanter "1.2.3"]])
        
             | fiddlerwoaroof wrote:
             | This works except when it doesn't: my experience is that it
             | seemed like the JVM is fundamentally static at its core in
             | a way that makes it hard to implement a CL-style
             | environment using Clojure's hosting strategy.
             | 
             | Boot build comes really close, here, but the community
             | never really adopted it.
        
         | [deleted]
        
       | kruhft wrote:
       | No examples. Bad name. Why is it better than cl-who and
       | parenscript together?
       | 
       | Nice job though. The code is clean.
        
         | junon wrote:
         | If you scroll just a tiny bit there is a large, complete
         | example.
        
         | svetlyak40wt wrote:
         | Spinneret is better than CL-WHO at least in it's string
         | escaping policy. All values are escaped by default whereas with
         | CL-WHO you need to wrap with CL-WHO:ESC all string values.
         | 
         | Thus CL-WHO is vulnerable to XSS attack by default.
        
         | medo-bear wrote:
         | I see examples
        
         | svetlyak40wt wrote:
         | Parenscript is other tool. Mainly it generates Javascript.
         | However it has support for CL-WHO like templating for
         | generating HTML on client side.
        
       | kazinator wrote:
       | I ported CL-WHO (another HTML generator for Common Lisp) to TXR
       | Lisp, under the name TL-WHO.
       | 
       | In TL-WHO, there is a _deftag_ inspired by Spinneret.
       | 
       | https://www.kylheku.com/cgit/tl-who/about/#the-deftag-macro
       | 
       | deftag lets you define macros targeting tags in the HTML
       | structure, which expand into combinations of other tags.
        
       | vindarel wrote:
       | More HTML generators for CL:
       | https://github.com/CodyReichert/awesome-cl#html-generators-a...
       | there are lispy ones (Spinneret), Django-like ones (Djula, I like
       | it, easy to use and extend), HTML-based allowing for inline Lisp
       | code (Ten), JSX-like ones (lsx, markup), and more.
        
       ___________________________________________________________________
       (page generated 2023-09-26 23:01 UTC)