[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)