I wrote a few lines of code that should hopefully make it easier to get started with CLJS for people coming from the JS world.
I created a demo here:
https://github.com/thheller/npm-module-example
The gist is that you gain: var foo = require("shadow-npm/demo.foo");
Where "shadow-npm" is a pseudo node-module generated by the tool and "demo.foo" is just the CLJS namespace you want. The require will return the NS so foo.bar() just works. The CLJS sources can use any npm module and local sources as well.
See: https://github.com/thheller/npm-module-example/blob/master/src/main/demo/foo.cljs
Given that the generated code is NPM compliant it should work with any npm JS tools. The demo has a basic setup for webpack, no special configuration for CLJS is needed.
I barely know anything about any NPM tools so I might be doing something completely wrong, please create issues on the shadow-devtools [1] project if you run into issues. I only tested node itself and webpack.
The sacrifice is that you can't use the Closure Compiler but you gain everything the npm world has to offer with hopefully seemless interop. Do not underestimate the impact of this sacrifice though, I cannot recommend using this for production builds.
The example still uses leiningen by I may drop that and create something standalone.
Need to work on a lot of things still but the proof of concept works, maybe it is useful to someone.
Cheers,
/thomas
[1] https://github.com/thheller/shadow-devtools
PS: This is not related to :npm-deps at all.
https://github.com/thheller/npm-module-example/tree/master/examples/create-react-app
Is that project compiling with something like lein-cljs(in shadow-devtools) inside and then emit JavaScript that can be processed by Webpack?
I want to package it all up so you can "npm install shadow-npm".
Need to figure out how to best do that first though. Should probably also chose a better name.
I'd like to show you around my "cljs/tool" some time for your standalone thing. I'm a year into building it to wrap a good UX around cljs on npm things as they become available, and I think this experimental feature could be delivered pretty easily through it.
Hey Shaun,
I've been following your work on github.com/cljs pretty much since the beginning. It would safe me a ton of work if you want to cover the npm package side of things.
My plan so far was:
- create an uberjar for shadow-devtools (which would contain clojure, clojurescript, core.async and could add more)
- create a npm package with a shadow-cljs bin that uses java or node-jre
- shadow-cljs cli opts --watch --repl etc
- zero conf CLJS compilation mode that compiles to the shadow-npm module to quickstart things
When the user is ready to go into full-blown CLJS first mode it should be as simple as creating a build config and then running the full featured mode which includes Google Closure.
The hard part for that transition would be that the require("shadow-npm/cljs.core") would no longer work since it is pretty much impossible to make that work with :advanced.
The results of "webpack -p" on the shadow-npm thing are ok but nowhere close to what Closure would give you. I guess it is good enough if you are JS first with a bit of CLJS sprinkled on top.
I have no attachments to the shadow-cljs or shadow-npm names, I just used them because there already are so many cljs+npm, clojurescript+npm variations out there that I just didn't want to conflict with.
If you want to use this for your cljs/tool I'd be happy to make any changes you'd need. Although your current mix of lumo and figwheel really doesn't translate too well to the "create a pseudo node module" (ie. "./node_modules/shadow-npm/cljs.core.js" to enable require("shadow-npm/cljs.core"). Also the configs for shadow-devtools are substantially different from lumo/figwheel/cljsbuild, hopefully simpler though.
Currently it is all driven by this little CLI ns:
https://github.com/thheller/shadow-devtools/blob/master/src/main/shadow/npm/cli.clj
I just use it through leiningen since I didn't want to work through the dependency management, but you already have that covered. lein is not needed at all beyond that.
Since I'm not a JS dev and have been purely CLJS for 3+ years I really don't feel qualified to make decisions on what would be ideal for a JS dev. Current solutions all felt unnecessary complex though.
Happy to consider all feedback.
https://github.com/thheller/npm-module-example/tree/master/examples/react-native-example
Since it is the first time I started react-native I really have no idea what is going. It works but is extremely slow to start. I don't know why, just wanted to see if it can work at all.
If someone has suggestions on how to make it start faster let me know, also need to look into their HMR which doesn't seem to work.
Certainly not the final solution I have in mind but getting closer.
I think it should be possible to use closure-compiler-js [1] with this project to do DCE if needed.
In a way you can and you can't. The same way :npm-deps has issues.
The Closure Compiler (JS or JVM version, doesn't matter) is not yet capable of compiling everything in the npm world. Some things just don't work.
So instead of making something that "maybe" works I opted to drop the Closure Compiler entirely.
The intent here is to enable JS devs to start using CLJS seamlessly without giving up their tools. They don't have the Closure compiler to begin with so they won't miss it.
Enabling the Closure Compiler is my top priority though. I'm definitely keeping my eye on this and will provide a proper solution if it ever becomes solid.
https://www.npmjs.com/package/shadow-cljs
npm install -g shadow-cljs
shadow-cljs --once
node
var x = require("shadow-cljs/cljs.core");
x.enable_console_print_BANG_(); // oops
x.prn(x.assoc(null, "foo", "bar"));
More tomorrow ...
Looking for any kind of feedback.
--
Note that posts from new members are moderated - please be patient with your first post.
---
You received this message because you are subscribed to a topic in the Google Groups "ClojureScript" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojurescript/AGXku7Ous0Y/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojurescrip...@googlegroups.com.
To post to this group, send email to clojur...@googlegroups.com.
Visit this group at https://groups.google.com/group/clojurescript.
I should add: You can use shadow-cljs in your coffeescript project if you just want to use cljs.core or some other CLJS package. You do not need to write CLJS yourself to make use of shadow-cljs.
var x = require("shadow-cljs/cljs.core");
x.assoc(null, "foo", "bar");
is basically the same as
var x = require("mori")
x.assoc(null, "foo", "bar");
But you would need to run shadow-cljs in your project.
https://github.com/thheller/npm-module-example/tree/master/examples/AwesomeProject
https://github.com/callstack-io/haul
The default react-native packager tries to run cljs.core through a large number of babel transforms which we don't need. The code works fine but the development experience is extremely terrible with about 4min of packager bundle time.
haul seems to be a drop-in replacement for this packager and works without modification since it just uses webpack.
I don't do any react-native development but the proof of concept works well.
I'll work on adding live-reloading and a REPL for everything tomorrow or so.
It does however know nothing about JS files that may be affected as well so it would not reload those. I don't know if the webpack HMR will reload dependent files?
Does it reload ./index.js if that does require("shadow.cljs/some.foo") and some.foo was recompiled?
I have never even seen a HMR config, can you share one so I can try a few things?
entry: [
'react-hot-loader/patch',
// activate HMR for React
'webpack-dev-server/client?http://localhost:8080', <---- add this
// bundle the client for webpack-dev-server
// and connect to the provided endpoint
'webpack/hot/only-dev-server', <---- add this
// bundle the client for hot reloading
// only- means to only hot reload for successful updates
'./index.js'
// the entry point of our app
],
plugins: [
new webpack.HotModuleReplacementPlugin(), <---- add this
// enable HMR globally
new webpack.NamedModulesPlugin(),
// prints more readable module names in the browser console on HMR updates
],
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./components/App', () => {
render(App)
});
}
3) replace `webpack` to `webpack-dev-server` to start bunding:webpack-dev-server
Closure is also doing dead code elimination which is only possible if you code in a restricted dialect of javascript, which cljs compiler does emit. Dead code elimination is important because, for example, the standard library and data structures are not implemented naively and bundled with the js vm, the standard lib has to be shipped on the wire and parsed.
--
Hey,I understand what you want to achieve and I definitely want to make the whole thing easier but some issues don't have an easy solution.TL;DR: It's complicated.
First we need to split compilation and bundling.
CLJS->JS compilation just takes one .cljs file and compiles it to .js. To do that it needs analyzer data of all CLJS dependencies. If you were to publish compiled CLJS to npm you'd need to also ship the analyzer data somehow. The other problem is that certain compiler options affect the generated JS which means that for every .cljs file there are many possible .js results. Another thing is that you also need to account for the compiler version since that may also affect the output or the analyzer data which then may affect other files.
The second part is bundling things together, which means reducing the number of individual files down to a more reasonable number. shadow-cljs enables JS tools to do that for us (eg. webpack, create-react-app or any other), since those tools use "require" to discover dependencies.The best option for this however is to use the Closure Compiler but to get the whole benefit of it you need to optimize your whole program, which includes EVERY .js file. There is promising progress in Closure to support optimizing common js and ES6 code but it is not quite there yet and can't compile everything. It will be coming to shadow-cljs soon though, it is already in CLJS but I didn't hook it up yet since I wanted to solve some issues first.
:npm-module tries to bridge the gap a little but as soon as you don't optimize your whole program you need externs to teach Closure about the things it can't see. Which means you can still use :advanced compilation for the CLJS/Closure parts but the rest of the code remains untouched. It also means that you need a second bundler (eg. webpack) to then combine the Closure output with the remaining unoptimized parts.
Given all these constraints I don't think it is feasible to publish compiled (but unbundled) CLJS code as a library to NPM. It would be relatively easy to consume uncompiled .cljs code from NPM packages but unless everybody agrees to do that it would just make things a whole lot more complicated for everyone. Maven is also a whole lot better than NPM IMHO, npm or yarn just have better UX (until something breaks).
:npm-deps is a feature in CLJS that lets you declare which npm packages your CLJS code requires to work. Technically this would need a complement in package.json to allow declaring which CLJS packages the JS code needs. I don't have a solution for that and I was and still am opposed to whole idea of :npm-deps. It does however solve the issue from the CLJS perspective, just not from the "Play as a Team" JS perspective.I do want integration to be painless and I do think shadow-cljs already makes life easier for some. I am very interested to hear about any other pain points people may have with CLJS<->JS interop. I tested webpack, create-react-app and create-react-native-app. They are all work reasonably well but I haven't gone beyond basic examples.I really don't have a good solution for the problem since part of the solution must come from the JS world and I have basically no idea what people are doing over there.The whole problem basically boils down to which bundler you want to use.
I'm personally only considering the Closure Compiler for that and shadow-cljs is trying to make that as easy as possible with support for common JS / ES6 coming soon.If you want to use webpack or so that also works reasonably well already but CLJS really is built with the Closure Compiler in mind so the end result of this won't be optimal.
If you want both that is always going to require more manual work for :externs and come with certain caveats.I hope that made some sort of sense, I have been thinking about this a lot but haven't gotten very far.
Yes. Currently optimization is something we are doing when compiling clojurescript, but perhaps *ideally* optimization could be done after bundling when all the javascript is available. However, I realize this may not actually work very well in practice since most modern js applications include dozens or hundreds of npm libraries that are not closure ready and would break in unexpected ways. Perhaps this is why UglyifyJS is more common for webpack users? UglyifyJS does dead code elimination as well I believe, is it as good? probably not? is it good enough? not sure.
Do we still need analyzer data after its compiled? Can't we just use the javascript as a lib? I assume that google closure isn't written in cljs but cljs has no problem using or optimizing it... perhaps the packages are distributed as unoptimized google closure compatible js and the webpack plugin turns google closure modules into commonjs modules? not sure. just ideas.
Best optimized perhaps, but possibly not the best js dev ux. Many js developers don't want to run java as part of their build process and might prefer to use uglifyjs even if the results are not as good.
But many js devs do optimize and the best results would probably be to put the unoptimized closure together with the unoptimized javascript and then optimize everything together. But my guess is that wont work in practice. UglifyJS I think is safer than closure, as good? probably not, good enough? not sure. Your solution probably produces best optimization, but its worth asking if thats what devs care about the most? Slower build process with more complexity vs a faster website. There will be devs on both sides I think.