weavejester / hiccup

Fast library for rendering HTML in Clojure
http://weavejester.github.io/hiccup
Eclipse Public License 1.0
2.68k stars 175 forks source link

Exponential increase in generated code when there are multiple nested hiccup2.core/html calls #210

Open luontola opened 6 months ago

luontola commented 6 months ago

This is similar to https://github.com/weavejester/hiccup/issues/205 but less common. Consider the following code:

(ns example
  (:require [clojure.walk :as walk]
            [hiccup2.core :as h]))

(walk/macroexpand-all
 `(h/html
   [:p (identity "") "foo"]
   (identity (h/html
              [:p (identity "") "bar"]
              (identity (h/html
                         [:p (identity "") "gazonk"]

In the generated code, foo will appear 8 times, bar 64 times, and gazonk 512 times.

If you try to evaluate this code normally (i.e. without macroexpand-all), it will throw "IndexOutOfBoundsException: Method code too large!"

Each level of nesting multiplies the code by 8, because every h/html checks hiccup.util/*html-mode* and hiccup.util/*escape-strings?* again and generates the 8 code paths.

Is there a way for a Clojure macro to recognize that it's nested within itself? Then those 8 code paths could be generated at only the outermost h/html, and the inner macros could use the same html and escape mode as the outermost macro.

Hiccup version 2.0.0-RC3

Workaround

Extract the inner html macro call to a function, so that you won't have many nested html macros.

weavejester commented 6 months ago

Macros are evaluated from the outside in, so we could certainly look through the syntax tree and do something to reduce the nesting. There may also be a better solution to the problem, but keeping the API backward compatible ties our hands somewhat.

luontola commented 6 months ago

If html receives an options map with :mode and :escape-strings?, will it then generate only one code variant? Could then the outermost html insert matching options maps to the nested html forms?

Alternatively, create a new internal function similar to compile-html-with-bindings, but for generating only one code variant, and replace the nested html forms with that.

habruening commented 2 months ago

Wouldn't it be helpful to be able to optionally disable all the preprocessing at compile time and do everything at runtime? Then the evaluation happens inside-out, and everything becomes easier. If someone has code like in this issue, he may not want an optimisation at compile time.

weavejester commented 2 months ago

I think ideally I want to separate out compilation entirely, but that will require some consideration and probably a hiccup3 namespace.