magnars / optimus

A Ring middleware for frontend performance optimization.
364 stars 23 forks source link

"Maximum call stack size exceeded" during Javascript minification #38

Open ocharles opened 9 years ago

ocharles commented 9 years ago

Not sure what info I can provide, but this is the file that uses Optimus:

(ns fynder.exporter
  (:require [clojure.java.io :as io]
            [clojure.string :refer [replace]]
            [net.cgrand.enlive-html :as enlive :refer [at do-> content html-resource attr= prepend append html emit* transform-content]]
            [fynder.env :refer [config]]
            [optimus.assets :as assets]
            [optimus-less.core]
            [optimus.export]
            [optimus.html :refer [link-to-css-bundles link-to-js-bundles]]
            [optimus.optimizations :as optimizations]
            [stasis.core :as stasis]))

(defn js-src
  "Generate a JavaScript src reference."
  [src]
  (html [:script {:type "text/javascript"
                  :src src}]))

(defn js-tag
  "Generate a JavaScript tag that runs the given (JavaScript) code."
  [string]
  (html [:script {:type "text/javascript"} string]))

(defn loader-tag
  [page-name]
  (js-tag (format "fynder.interop.call_on_load(fynder_%s.main.main);" page-name)))

(defn rewrite-index-page
  "Rewrites the main.html page for use in production.

  This is a single-page app, and that page should be targetted at
  development. It's this function's job to do any writing necessary to
  make it production-ready."
  [{:keys [uri page-config]
    :as context}]
  (->> (at (html-resource "public/main.html")

           ;; Remove anything tagged for development with a data-info='dev' attribute.
           [(attr= :data-info "dev")]
           nil

           ;; Remap the Google Analytics tracking code
           [(attr= :data-info "ga")]
           (transform-content #(replace % #"XXXX" (:tracking-code (page-config uri))))

           ;; Remap the Heap tracking code
           [(attr= :data-info "heap")]
           (transform-content #(replace % #"XXHEAPXX" (or (:admin-heap-tracking-code (page-config uri)) "")))

           ;; ;; Add in the compiled CSS.
           [:head]
           (prepend
            (link-to-css-bundles context (:css-files (page-config uri))))

           ;; Add in the compiled ClojureScript.
           [:span#scripts-above-ie8]
           (do->
            (append (link-to-js-bundles context (:js-files (page-config uri))))
            (append (loader-tag (:page-name (page-config uri))))))

       (emit*)
       (apply str)))

(defn rewrite-loader-page
  [{:keys [uri page-config]
    :as context}]
  (->> (at (html-resource "public/loader.html")
           ;; ;; Add in the compiled CSS.
           [:head]
           (append
            (html (link-to-css-bundles context (:css-files (page-config uri)))))

           ;; Add in the compiled ClojureScript.
           [:body]
           (append
            (html
              (link-to-js-bundles context (:js-files (page-config uri))))))

       (emit*)
       (apply str)))

(defn get-assets []
  ;; TODO If we were being super-classy, we could pick the JS targets up out of the project.clj file.
  (concat (assets/load-assets  "public" [#"\.png$"
                                         #"\.jpg$"])
          (assets/load-bundles "public" {"admin.css"       ["/style/admin.less"]
                                         "trainer.css"     ["/style/trainer.less"]
                                         "mobile.css"      ["/style/mobile.less"]
                                         "sweatybetty.css" ["/style/sweatybetty.less"]
                                         "loader.css"      ["/style/loader.less"]})

          (assets/load-bundles "public"   {"preamble.js" ["/datatransfer.js"
                                                          "/vendor/es5-shim.js"
                                                          "/vendor/es5-sham.js"
                                                          "/vendor/socket.io-1.2.1.js"
                                                          "/vendor/moment.min.js"
                                                          "/vendor/fastclick.js"
                                                          "/vendor/markdown.min.js"
                                                          "/vendor/xss.js"
                                                          "/vendor/jquery-1.10.2.min.js"
                                                          "/vendor/bootstrap.min.js"
                                                          "/vendor/bootstrap-select.min.js"
                                                          "/vendor/daterangepicker.js"
                                                          "/vendor/colorpicker/js/bootstrap-colorpicker.js"
                                                          "/vendor/countdown.js"
                                                          "/vendor/papaparse.min.js"]})

          (assets/load-bundles "public" {"sweatybetty-extras.js" ["/vendor/base32.js"]})

          (assets/load-bundles "assets/js/main"   {"cljs.js"         ["/cljs.js"]
                                                   "admin.js"        ["/admin.js"]
                                                   "trainer.js"      ["/trainer.js"]
                                                   "mobile.js"       ["/mobile.js"]
                                                   "sweatybetty.js"  ["/sweatybetty.js"]})
          (assets/load-bundles "assets/js/loader" {"fynder-loader.js" ["/fynder-loader.js"]})))

(def app-configs
  {"/admin.html" {:page-name "admin"
                  :css-files ["admin.css"]
                  :tracking-code (config :admin-tracking-code)
                  :admin-heap-tracking-code (config :admin-heap-tracking-code)
                  :js-files ["preamble.js" "cljs.js" "admin.js"]}

   "/trainer.html" {:page-name "trainer"
                    :css-files ["trainer.css"]
                    :tracking-code (config :trainer-tracking-code)
                    :js-files ["preamble.js" "cljs.js" "trainer.js"]}

   "/mobile.html" {:page-name "mobile"
                   :css-files ["mobile.css"]
                   :tracking-code (config :widget-tracking-code)
                   :js-files ["preamble.js" "cljs.js" "mobile.js"]}

   "/sweatybetty.html" {:page-name "sweatybetty"
                        :css-files ["sweatybetty.css"]
                        :tracking-code (config :widget-tracking-code)
                        :js-files ["preamble.js" "cljs.js" "sweatybetty-extras.js" "sweatybetty.js"]}})

(def loader-config
  {"/loader.html" {:page-name "loader"
                   :css-files ["loader.css"]
                   :tracking-code ""
                   :js-files ["fynder-loader.js"]}})

(defn my-optimize [assets options]
  (-> assets
      (optimizations/minify-css-assets options)
      (optimizations/inline-css-imports)
      (optimizations/concatenate-bundles)
      (optimizations/add-cache-busted-expires-headers)
      (optimizations/add-last-modified-headers)))

(defn -main
  []
  (let [app-pages (zipmap (map first app-configs)
                          (repeat rewrite-index-page))

        loader {"/loader.html" rewrite-loader-page}

        pages (merge loader app-pages)

        assets (optimizations/all (get-assets) {})

        target-dir "target/site"
        config {:page-config (merge loader-config app-configs)
                :optimus-assets assets}]
    (optimus.export/save-assets assets target-dir)
    (stasis/export-pages pages target-dir config)))

And this is the stack trace:

Exception in thread "main" java.lang.Exception: Exception in /fynder-loader.js: Maximum call stack size exceeded (line undefined, col undefined), compiling:(/tmp/form-init1279519951643400366.clj:1:73)
    at clojure.lang.Compiler.load(Compiler.java:7249)
    at clojure.lang.Compiler.loadFile(Compiler.java:7175)
    at clojure.main$load_script.invoke(main.clj:275)
    at clojure.main$init_opt.invoke(main.clj:280)
    at clojure.main$initialize.invoke(main.clj:308)
    at clojure.main$null_opt.invoke(main.clj:343)
    at clojure.main$main.doInvoke(main.clj:421)
    at clojure.lang.RestFn.invoke(RestFn.java:421)
    at clojure.lang.Var.invoke(Var.java:383)
    at clojure.lang.AFn.applyToHelper(AFn.java:156)
    at clojure.lang.Var.applyTo(Var.java:700)
    at clojure.main.main(main.java:37)
Caused by: java.lang.Exception: Exception in /fynder-loader.js: Maximum call stack size exceeded (line undefined, col undefined)
    at optimus.optimizations.minify$throw_v8_exception.invoke(minify.clj:15)
    at optimus.optimizations.minify$run_script_with_error_handling.invoke(minify.clj:50)
    at optimus.optimizations.minify$minify_js.invoke(minify.clj:61)
    at optimus.optimizations.minify$minify_js_asset$fn__1850.invoke(minify.clj:67)
    at clojure.lang.AFn.applyToHelper(AFn.java:154)
    at clojure.lang.AFn.applyTo(AFn.java:144)
    at clojure.core$apply.invoke(core.clj:630)
    at clojure.core$update_in.doInvoke(core.clj:5919)
    at clojure.lang.RestFn.invoke(RestFn.java:445)
    at optimus.optimizations.minify$minify_js_asset.invoke(minify.clj:67)
    at optimus.optimizations.minify$minify_js_assets$fn__1854.invoke(minify.clj:74)
    at clojure.core$map$fn__4550.invoke(core.clj:2622)
    at clojure.lang.LazySeq.sval(LazySeq.java:40)
    at clojure.lang.LazySeq.seq(LazySeq.java:49)
    at clojure.lang.RT.seq(RT.java:507)
    at clojure.core$seq__4125.invoke(core.clj:135)
    at clojure.core$map$fn__4550.invoke(core.clj:2614)
    at clojure.lang.LazySeq.sval(LazySeq.java:40)
    at clojure.lang.LazySeq.seq(LazySeq.java:49)
    at clojure.lang.RT.seq(RT.java:507)
    at clojure.core$seq__4125.invoke(core.clj:135)
    at clojure.core$map$fn__4550.invoke(core.clj:2614)
    at clojure.lang.LazySeq.sval(LazySeq.java:40)
    at clojure.lang.LazySeq.seq(LazySeq.java:49)
    at clojure.lang.Cons.next(Cons.java:39)
    at clojure.lang.RT.next(RT.java:674)
    at clojure.core$next__4109.invoke(core.clj:64)
    at clojure.core.protocols$fn__6517.invoke(protocols.clj:151)
    at clojure.core.protocols$fn__6474$G__6469__6483.invoke(protocols.clj:19)
    at clojure.core.protocols$seq_reduce.invoke(protocols.clj:31)
    at clojure.core.protocols$fn__6500.invoke(protocols.clj:81)
    at clojure.core.protocols$fn__6448$G__6443__6461.invoke(protocols.clj:13)
    at clojure.core$reduce.invoke(core.clj:6515)
    at clojure.core$group_by.invoke(core.clj:6857)
    at optimus.optimizations.concatenate_bundles$concatenate_bundles.invoke(concatenate_bundles.clj:27)
    at optimus.optimizations$all.invoke(optimizations.clj:20)
    at fynder.exporter$_main.invoke(exporter.clj:163)
    at clojure.lang.Var.invoke(Var.java:375)
    at user$eval5.invoke(form-init1279519951643400366.clj:1)
    at clojure.lang.Compiler.eval(Compiler.java:6792)
    at clojure.lang.Compiler.eval(Compiler.java:6782)
    at clojure.lang.Compiler.load(Compiler.java:7237)
    ... 11 more
magnars commented 9 years ago

This is an exception from V8, likely from uglify.js, looks like. Could you try minifying fynder-loader.js with uglify 2 on the command line and see what it says? https://github.com/mishoo/UglifyJS2

ocharles commented 9 years ago

I tried ./node_modules/.bin/uglifyjs resources/assets/js/loader/fynder-loader.js and that runs successfully. That's uglifyjs 2.4.0.

magnars commented 9 years ago

That is peculiar. How about

(require '[optimus.optimizations.minify :as m])
(m/minify-js (slurp "assets/js/loader/fynder-loader.js"))

in the repl?

sodiumjoe commented 9 years ago

I'm seeing this, too. ./node_modules/.bin/uglifyjs main.js works fine.

(-> (assets/load-bundle "." "main.js" ["/js/main.js"])
    (optimizations/minify-js-assets nil))

and

(m/minify-js (slurp "resources/js/main.js"))

both throw that error.

magnars commented 9 years ago

Both node and clj-v8 are using v8 to run the javascript, so I wonder what is different.

Are you able to cut down that main.js file to something that can be shared and still triggers this problem?

sodiumjoe commented 9 years ago

Yeah, i'll try that next, it's just a slow feedback loop and a lot of code to pare down, thanks for the quick reply, though!