   [file-str]
   (clojure.string/replace file-str #"\.cljs$" ".go"))
 
+(defn mkdirs
+  "Create all parent directories for the passed file."
+  [^File f]
+  (.mkdirs (.getParentFile (.getCanonicalFile f))))
+
+(defmacro with-core-cljs
+  "Ensure that core.cljs has been loaded."
+  [& body]
+  `(do (when-not (get-in @env/*compiler* [::ana/namespaces 'cljs.core :defs])
+         (ana/analyze-file "cljs/core.cljs"))
+       ~@body))
+
+(defn url-path [^File f]
+  (.getPath (.toURL (.toURI f))))
+
+(defn compile-file*
+  ([src dest] (compile-file* src dest nil))
+  ([src dest opts]
+    (env/ensure
+      (with-core-cljs
+        (with-open [out ^java.io.Writer (io/make-writer dest {})]
+          (binding [*out* out
+                    ana/*cljs-ns* 'cljs.user
+                    ana/*cljs-file* (.getPath ^File src)
+                    reader/*alias-map* (or reader/*alias-map* {})
+                    *source-map-data* (when (:source-map opts)
+                                        (atom
+                                          {:source-map (sorted-map)
+                                           :gen-col 0
+                                           :gen-line 0}))]
+            (emitln "// Compiled by ClojureScript " (clojurescript-version))
+            (loop [forms (ana/forms-seq src)
+                   ns-name nil
+                   deps nil]
+              (if (seq forms)
+                (let [env (ana/empty-env)
+                      ast (ana/analyze env (first forms))]
+                  (do (emit ast)
+                    (if (= (:op ast) :ns)
+                      (recur (rest forms) (:name ast) (merge (:uses ast) (:requires ast)))
+                      (recur (rest forms) ns-name deps))))
+                (let [sm-data (when *source-map-data* @*source-map-data*)
+                      ret (merge
+                            {:ns (or ns-name 'cljs.user)
+                             :provides [ns-name]
+                             :requires (if (= ns-name 'cljs.core)
+                                         (set (vals deps))
+                                         (cond-> (conj (set (vals deps)) 'cljs.core)
+                                           (get-in @env/*compiler* [:opts :emit-constants])
+                                           (conj 'constants-table)))
+                             :file dest
+                             :source-file src}
+                            (when sm-data
+                              {:source-map (:source-map sm-data)}))]
+                  (when (and sm-data (= (:optimizations opts) :none))
+                    (let [sm-file (io/file (str (.getPath ^File dest) ".map"))]
+                      (emits "\n//# sourceMappingURL=" (.getName sm-file))
+                      (spit sm-file
+                        (sm/encode {(url-path src) (:source-map sm-data)}
+                          {:lines (+ (:gen-line sm-data) 2)
+                           :file (url-path dest)}))))
+                  (let [path (.getPath (.toURL ^File dest))]
+                    (swap! env/*compiler* assoc-in [::compiled-cljs path] ret)
+                    (swap! env/*compiler* assoc-in [::ana/analyzed-cljs path] true))
+                  ret)))))))))
+
+(defn compiled-by-version [^File f]
+  (with-open [reader (io/reader f)]
+    (let [match (->> reader line-seq first
+                     (re-matches #".*ClojureScript (.*)$"))]
+      (and match (second match)))))
+
+(defn requires-compilation?
+  "Return true if the src file requires compilation."
+  ([src dest] (requires-compilation? src dest nil))
+  ([^File src ^File dest opts]
+    (env/ensure
+      (or (not (.exists dest))
+          (> (.lastModified src) (.lastModified dest))
+          (let [version' (compiled-by-version dest)
+                version  (clojurescript-version)]
+            (and version (not= version version')))
+          (and opts
+            (:source-map opts)
+            (if (= (:optimizations opts) :none)
+              (not (.exists (io/file (str (.getPath dest) ".map"))))
+              (not (get-in @env/*compiler* [::compiled-cljs (.getAbsolutePath dest)]))))))))
+
+(defn parse-ns
+  ([src] (parse-ns src nil nil))
+  ([src dest opts]
+    (env/ensure
+      (let [namespaces' (::ana/namespaces @env/*compiler*)
+            ret
+            (binding [ana/*cljs-ns* 'cljs.user
+                      ana/*analyze-deps* false]
+              (loop [forms (ana/forms-seq src)]
+                (if (seq forms)
+                  (let [env (ana/empty-env)
+                        ast (ana/no-warn (ana/analyze env (first forms)))]
+                    (if (= (:op ast) :ns)
+                      (let [ns-name (:name ast)
+                            deps    (merge (:uses ast) (:requires ast))]
+                        (merge
+                          {:ns (or ns-name 'cljs.user)
+                           :provides [ns-name]
+                           :requires (if (= ns-name 'cljs.core)
+                                       (set (vals deps))
+                                       (cond-> (conj (set (vals deps)) 'cljs.core)
+                                         (get-in @env/*compiler* [:opts :emit-constants])
+                                         (conj 'constants-table)))
+                           :file dest
+                           :source-file src}
+                          (when (and dest (.exists ^File dest))
+                            {:lines (with-open [reader (io/reader dest)]
+                                      (-> reader line-seq count))})))
+                      (recur (rest forms)))))))]
+        ;; TODO this _was_ a reset! of the old ana/namespaces atom; should we capture and
+        ;; then restore the entirety of env/*compiler* here instead?
+        (swap! env/*compiler* assoc ::ana/namespaces namespaces')
+        ret))))
+
+(defn compile-file
+  "Compiles src to a file of the same name, but with a .js extension,
+   in the src file's directory.
+
+   With dest argument, write file to provided location. If the dest
+   argument is a file outside the source tree, missing parent
+   directories will be created. The src file will only be compiled if
+   the dest file has an older modification time.
+
+   Both src and dest may be either a String or a File.
+
+   Returns a map containing {:ns .. :provides .. :requires .. :file ..}.
+   If the file was not compiled returns only {:file ...}"
+  ([src]
+    (let [dest (rename-to-js src)]
+      (compile-file src dest nil)))
+  ([src dest]
+    (compile-file src dest nil))
+  ([src dest opts]
+    (let [src-file (io/file src)
+          dest-file (io/file dest)]
+      (if (.exists src-file)
+        (try
+          (let [{ns :ns :as ns-info} (parse-ns src-file dest-file opts)]
+            (if (requires-compilation? src-file dest-file opts)
+              (do (mkdirs dest-file)
+                (when (contains? (::ana/namespaces @env/*compiler*) ns)
+                  (swap! env/*compiler* update-in [::ana/namespaces] dissoc ns))
+                (compile-file* src-file dest-file opts))
+              (do
+                (when-not (contains? (::ana/namespaces @env/*compiler*) ns)
+                  (with-core-cljs
+                    (ana/analyze-file src-file)))
+                ns-info)))
+          (catch Exception e
+            (throw (ex-info (str "failed compiling file:" src) {:file src} e))))
+        (throw (java.io.FileNotFoundException. (str "The file " src " does not exist.")))))))
+
+(defn path-seq
+  [file-str]
+  (->> File/separator
+       java.util.regex.Pattern/quote
+       re-pattern
+       (string/split file-str)))
+
+(defn to-path
+  ([parts]
+     (to-path parts File/separator))
+  ([parts sep]
+    (apply str (interpose sep parts))))
+
 (defn ^File to-target-file
   [target cljs-file]
   (let [relative-path (string/split

    (emits value " = ")
    (emits-keyword keyword)
    (emitln)))

  (if (= :expr (:context env))
    (emits "func() interface{} {panic(" throw ")}()")
    (emitln "panic(" throw ")")))

(defn emit-comment
  "Emit a nicely formatted comment string."
  [doc jsdoc]
  (let [docs (when doc [doc])
        docs (if jsdoc (concat docs jsdoc) docs)
        docs (remove nil? docs)]
    (letfn [(print-comment-lines [e] (doseq [next-line (string/split-lines e)]
                                       (emitln "* " (string/trim next-line))))]
      (when (seq docs)
        (emitln "/**")
        (doseq [e docs]
          (when e
            (print-comment-lines e)))
        (emitln "*/")))))

(defmethod emit* :def
  [{:keys [name var init env doc export]}]
