       :cljs
       (maybe-compile-cljs state src))))
 
-
 (defn compile-sources
   "compile a list of sources by name,
    requires that the names are in dependency order
    requires that ALL of the dependencies NOT in source names are already compiled
    eg. you cannot just compile [\"clojure/string.cljs\"] as it requires other files to be compiled first"
   [state source-names]
-  (with-compiler-env state
-    (ana/load-core)
-    (reduce
-      (fn [state source-name]
-        (let [src (get-in state [:sources source-name])
-              compiled-src (generate-output-for-source state src)]
-          (-> state
-              (assoc-in [:sources source-name] compiled-src)
-              (cond->
-                (not= (:compiled-at src)
-                      (:compiled-at compiled-src))
-                (update :compiled conj source-name)
-                ))))
-      (assoc state :compiled [])
-      source-names)))
-
-(defn compile-modules [state]
+  (with-redefs [ana/parse shadow-parse]
+    (with-compiler-env state
+      (ana/load-core)
+      (reduce
+        (fn [state source-name]
+          (let [src (get-in state [:sources source-name])
+                compiled-src (generate-output-for-source state src)]
+            (-> state
+                (assoc-in [:sources source-name] compiled-src)
+                (cond->
+                  (not= (:compiled-at src)
+                        (:compiled-at compiled-src))
+                  (update :compiled conj source-name)
+                  ))))
+        (assoc state :compiled [])
+        source-names))))
+
+(defn par-compile-one [state ready compiled errors source-name]
+  (let [{:keys [requires] :as src} (get-in state [:sources source-name])]
+    (loop []
+      (cond
+        ;; skip work if errors occured
+        (seq @errors)
+        src
+
+        ;; only compile once all dependencies are compiled
+        ;; FIXME: sleep is not great, cljs.core takes a couple of sec to compile
+        ;; this will spin a couple hundred times, doing additional work
+        ;; don't increase the sleep time since many files compile in the 5-10 range
+        (not (set/superset? @ready requires))
+        (do (Thread/sleep 5)
+            (recur))
+
+        :ready-to-compile
+        (try
+          (let [{:keys [provides] :as compiled-src}
+                (generate-output-for-source state src)]
+
+            (when (not= (:compiled-at src)
+                        (:compiled-at compiled-src))
+              (swap! compiled conj source-name))
+
+            (swap! ready set/union provides)
+            compiled-src)
+          (catch Exception e
+            (swap! errors assoc source-name e)
+            src
+            ))))))
+
+(defn par-compile-sources
+  "compile files in parallel, files MUST be in dependency order and ALL dependencies must be present
+   this cannot do a partial incremental compile"
+  [{:keys [n-compile-threads logger n-compile-threads] :as state} source-names]
+  (log-progress logger (format "Compiling with %d threads" n-compile-threads))
+  (with-redefs [ana/parse shadow-parse]
+    (with-compiler-env state
+      (ana/load-core)
+      (let [ready (atom #{}) ;; namespaces that are ready to be used
+            compiled (atom []) ;; files that were actually compiled (not recycled)
+            errors (atom {}) ;; source-name -> exception
+
+            exec (Executors/newFixedThreadPool n-compile-threads)
+            tasks
+            (->> (for [source-name source-names]
+                   ;; bound-fn for with-compiler-state
+                   (let [task-fn (bound-fn [] (par-compile-one state ready compiled errors source-name))]
+                     (.submit exec ^Callable task-fn)))
+                 (doall) ;; force submit all, then deref
+                 (into [] (map deref)))]
+
+        ;; FIXME: might deadlock here if any of the derefs fail
+        (.shutdown exec)
+
+        ;; unlikely to encounter 2 concurrent errors
+        ;; so unpack for a single error for better stacktrace
+        (let [errs @errors]
+          (case (count errs)
+            0 nil
+            1 (let [[name err] (first errs)]
+                (throw (ex-info (format "compilation of \"%s\" failed" name) {} err)))
+            (throw (ex-info "compilation failed" errs))))
+
+        (-> state
+            (update :sources (fn [sources]
+                               (reduce
+                                 (fn [sources {:keys [name] :as src}]
+                                   (assoc sources name src))
+                                 sources
+                                 tasks)))
+            (assoc :compiled @compiled))))))
+
+(defn compile-modules
+  [{:keys [n-compile-threads] :as state}]
   (with-logged-time
     [(:logger state) "Compiling Modules ..."]
     (let [state (finalize-config state)

   :public-path "js"

   :optimizations :none

   :source-paths {}
   :closure-defines {"goog.DEBUG" false
                     "goog.LOCALE" "en"}

   :logger (reify BuildLog
             (log-warning [_ msg]
               (println (str "WARN: " msg)))
             (log-time-start [_ msg]
               (println (format "-> %s" msg)))
             (log-time-end [_ msg ms]
               (println (format "<- %s (%dms)" msg ms)))
             (log-progress [_ msg]
               (println msg)))
   })

(defn watch-and-repeat! [state callback]
  (loop [state (callback state [])]

          state (reduce do-analyze-module state (-> state :modules (vals)))
          modules (sort-and-compact-modules state)
          source-names (mapcat :sources modules)
          state (if (> n-compile-threads 1)
                  (par-compile-sources state source-names)
                  (compile-sources state source-names))]

      (-> state
          (assoc :build-modules modules)
