Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve symbolic namespaces via API #494

Open
martinklepsch opened this issue Mar 28, 2024 · 7 comments
Open

Resolve symbolic namespaces via API #494

martinklepsch opened this issue Mar 28, 2024 · 7 comments

Comments

@martinklepsch
Copy link
Contributor

martinklepsch commented Mar 28, 2024

To upvote this issue, give it a thumbs up. See this list for the most upvoted issues.

Is your feature request related to a problem? Please describe.

I'm using squint with vite as a build tool and I'd like to explore how I could integrate library dependencies like promesa into squint. Currently when requiring a namespace it just shows up as a verbatim string which makes it difficult to implement custom resolver logic.

Describe the solution you'd like

Something like this would be great, the prefix could be configurable but also a static prefix would be enough.

(require '[foo.bar :as foo])
import foo from "cljs-ns:foo.bar";

Additional context

Here's a basic Rollup plugin using resolveId

Rollup is the bundler behind vite and supports emitting code for the browser, node etc.

The below can be used as an entry in the plugin array in vite's config.

    {
      name: 'resolve-cljs-ns',
      resolveId(source) {
        if (source.startsWith("cljs-ns:")) {
          console.log("id", source)
          // find ns in node_modules or other places
          return {
            id: "src/foo/bar.cljs"
          }
        }
      }
    }
@borkdude
Copy link
Member

borkdude commented Apr 2, 2024

Although we've discussed this, I think this also falls into the category of proposing a solution rather than stating a problem.
I think the issue is: when squint compiles a file, should compileString already resolve symbolic namespaces to files (e.g. foo.bar becomes ../foo/bar.cljs or ../foo/bar.cljc etc such that the plugin won't have to implement extra logic to resolve files or should this logic be in the plugin (to which the above proposed solution is but one detail).

I think I prefer to have this logic as much as possible in one place such that efforts shouldn't have to be duplicated. This logic already exists for the squint CLI + squint.edn approach and maybe it would be interesting to see if we could lift this logic into compileString somehow such that the plugin can also benefit from this.

@martinklepsch
Copy link
Contributor Author

Fair point, let me try to get more at the underlying problem:

A bit of context

These days many tools operate on ESM module graphs, they use them to create bundles, transpile files as they are imported etc. Tools like Vite and Rollup allow you to tell them how to resolve the right side of an import statement. For example vite-plugin-svgr can turn SVGs into React components:

import Logo from "./logo.svg?react";

In this case the plugin can tell Vite that it knows how to resolve modules ending with .svg?react and automatically compiles them to JS modules containing a React component on the fly.

Why does this matter for Squint?

Currently to resolve namespaces we have squint.edn which tells the compiler where to look for files. But in the case of tools like Vite it's not actually squint that does the lookup and instead it is Vite. It would be great to be able to teach Vite how to look up Squint namespaces.

Right now a Vite plugin could intercept module resolution but with namespaces just being compiled to plain strings we'd need to employ heuristics to determine if a given string is in fact a namespace.

(require [foo.bar :as foo])
import foo from "foo.bar";

The JS doesn't give a clear indication that foo.bar is a namespace making it difficult for Vite to actually find the respective .cljs file and compile it.

Other notes

  1. The Vite plugin could internally still use :paths from squint.edn to know about directories it should scan for namespaces.
  2. The prefix wouldn't actually need to be configurable, anything that just clearly says "this is a namespace" would be fine.
  3. It would be fine if this is an undocumented API for now as regular users don't need to care about this.

it would be interesting to see if we could lift this logic into compileString somehow such that the plugin can also benefit from this.

I guess compileString could also transform symbolic namespaces to filepaths so the above example would become something like

import foo from "./src/foo/bar.cljs";

but then that isn't really runnable JS since the cljs file still needs to be compiled / the import needs to point at JS. But for the purpose of the Vite plugin this could work.

@martinklepsch
Copy link
Contributor Author

Another thought: Maybe the API could also be to pass the contents of squint.edn to compileString. That way the APIs would be reachable from both the CLI (with squint.edn implicitly read) and JS, where the data could either be read from squint.edn or provided raw.

@borkdude
Copy link
Member

borkdude commented Apr 5, 2024

But in the case of tools like Vite it's not actually squint that does the lookup and instead it is Vite. It would be great to be able to teach Vite how to look up Squint namespaces.

squint could still resolve those symbols to e.g. "./foo.cljs" using the same mechanism that is now used in the squint CLI and then vite would work exactly the same as when you would manually write that.

@borkdude
Copy link
Member

borkdude commented Apr 5, 2024

This is more in line with your second post I think, but the API could also just read squint.edn itself. Basically we need programmatic access to compileString not from the lower level API but what is used by the CLI and hook that up to the plugin, then we're all set I think.

@borkdude
Copy link
Member

borkdude commented Apr 5, 2024

That would also then enable using macros

@borkdude borkdude changed the title Emit configurable prefix for symbolic ns requires Resolve symbolic namespaces via API Apr 5, 2024
@borkdude
Copy link
Member

borkdude commented Apr 5, 2024

Similar discussion as #500

I think if we would expose this function:

(defn compile-files
[opts files]
(let [cfg @utils/!cfg
opts (merge cfg opts)
paths (:paths cfg)
copy-resources (:copy-resources cfg)
output-dir (:output-dir cfg ".")
files (if (empty? files)
(files-from-paths paths)
files)]
;; shouldn't need this if :coerce worked in babashka.cli
(when-let [out-dir (:output-dir opts)]
(when-not (string? out-dir)
(throw (js/Error. "output-dir must be a string"))))
(if (:help opts)
(do (println "Usage: squint compile <files> <opts>")
(println)
(println "Options:
--elide-imports: do not include imports
--elide-exports: do not include exports
--extension: default extension for JS files
--output-dir: output directory for JS files"))
(reduce (fn [prev f]
(-> (js/Promise.resolve prev)
(.then
#(do
(if (contains? #{".cljc" ".cljs"} (path/extname f ))
(do (println "[squint] Compiling CLJS file:" f)
(compiler/compile-file (assoc opts
:in-file f
:resolve-ns (fn [x]
(resolve-ns opts f x)))))
(copy-file copy-resources f output-dir paths))))
(.then (fn [{:keys [out-file]}]
(when out-file (println "[squint] Wrote file:" out-file))
out-file))))
nil
files))))

or this one:

(defn compile-file [{:keys [in-file in-str out-file extension output-dir]

or perhaps a slight variation on both

then we would have both macro and symbolic namespace support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants