New Guide: ClojureScript with Webpack


#1

I’ve put together a new guide on using ClojureScript with Webpack https://github.com/clojure/clojurescript-site/blob/a197371d210df8ff6eca2c1e3703860fa11370c4/content/guides/webpack.adoc. I’m looking for feedback as I cleaned up externs inference quite a bit which should make this approach considerably more reliable. While :npm-deps continues to be a long term goal, I think it’s important to highlight that foreign libs and externs inference are designed to make integrating with the JavaScript ecosystem relatively straightforward today.


#2

Feeling strange to me that there’s a step is a step attaching variable to js/window:

import React from 'react';
import ReactDOM from 'react-dom';
window.React = React;
window.ReactDOM = React;

#3

Is this guide intended for JavaScript or Clojure users? There are a couple of things I’d probably suggest as JS user.


#4

Very happy to see this. Webpack plus window.React style interop has been my preferred method for more than a year; it’s pretty much guaranteed to work and gives you access to any NPM lib (including React components).

Is there a reason to add babel as a dependency? It works without them for all libraries I’ve tried; from what I understand deployed NPM packages contain a compiled ES5 bundle. See for example this configuration from the Double Bundle example repo (plus accompanying blog post).


#5

NPM packages don’t necessarily have a compiled ES5 bundle — and when they do, it might not be in the format you want (IIFE, AMD, UMD etc). This is a huge source of pain in the browser world (Node.js at least started with a single standard for modules).

Ideally NPM packages would use ES6 modules which can be statically analysed and converted to all other formats by multiple tools.


#6

@orestis Maybe I shouldn’t say “bundle”, Let me rephrase. Many JS libs require Babel or other transformers for parts written in TypeScript, future EcmaScript versions, JSX etc. But these transformers are required at build time only. Though their git repository doesn’t contain compiled artefacts, well-behaved packages as distributed on NPM include the compiled versions, conventionally in the dist/ folder or in the package root.

For example see the “main” key in the package.json for react-notification-system, react-select or victory. In each case the entry point refers to a compiled artefact.

Can you name one NPM package for which this is not true?


#7

You are right — the pertinent point being here “well behaved”. I can’t actually give any examples because webpack does a lot of things behind the scenes so that you don’t have to suffer badly-behaved modules. I do remember though that when I trying to using rollup.js + TypeScript I had a number of issues, that I had to work around — doing the equivalent of writing externs for rollup to be able to work.

The specific examples you mention are indeed well behaved – but still only two of them define the ‘jsnext:main’ key in their package.json that points to the ES6 modules that are already preprocessed. I wonder @dnolen if there is support for that attribute in CLJS compiler?


#8

@dnolen Here’s some edits/improvements

  • Perhaps it worth mentioning in the very beginning that this guide is about packing NMP deps as a foreign lib and provide a link to read more about what foreign libs are. Also it might be useful to say that this approach is safer than :npm-deps stuff, but if :npm-deps works for you prefer it because it feed NPM deps into Closure Compiler.

  • I don’t think JSX extension is needed here since it’s not common that someone is using it these days

{
  test: /\.jsx?$/,
  exclude: /node_modules/,
  use: "babel-loader"
}
  • Because of exclude: /node_modules/ in module loader config babel-loader won’t apply on NPM dependencies, which means that Babel stuff is not needed here. On the other hand every Webpack config excludes node_modules because code published to NPM is already transpiled usually. So you might not need babel-preset-react at all here, only babel-preset-es2015 to transform ES6 modules in your index.js

  • webpack --mode=production can be used to create optimized bundle, not sure if this is needed in the guide though

  "scripts": {
    "build": "webpack --mode=production"
  }
  • About exporting everything into window. It would be probably safer to create UMD bundle and export everything under a single namespace, but since many ClojureScript libraries depend on globally defined dependencies this will break cljs libraries. One thing to take into account is that JS devs might try to do UMD bundle instead of exporting to window, just because they used to bundle everything into “modules”. I’d add a quick note about importance of exporting NPM deps into window if they are going to be used in cljs deps in your project. On the other hand someone who is replacing JS dependency in CLJS library is probably more experienced and already know about it :woman_shrugging:

Anyway here’s how to configure UMD bundle

import React from 'react';
import ReactDOM from 'react-dom';
module.exports = { React, ReactDOM }
  output: {
    library: 'myLibName', // becomes window.myLibName
    libraryTarget: 'umd',
    filename: 'bundle.js',
  }

#9

Good point, exactly the kind of feedback I was hoping for! Removed for now :slight_smile:


#10

I don’t really understand what “safer” can really mean in this context? Exporting to UMD should also work but I don’t think it’s necessary to cover.

Thanks for the other feedback!


#11

Personally I enjoy being able to quickly verify the existence of window.React etc. in the devtools console.


#12
  • First snippet of code : init yarn should be yarn init ?
  • In webpack.config.js the module > rules objects overlap, the second regexp covers the first

#13

I’ve updated the post to cover an important use case - overriding the version of React used by a ClojureScript library (Reagent etc.) https://github.com/clojure/clojurescript-site/blob/c1233554495a965efc9dfafa3aaa7e63d7d9a294/content/guides/webpack.adoc

It also demonstrates that externs inference works under advanced compilation.


#14

@dnolen small change: yarn init -y will skip asking questions about package names etc.

Another small one, but not that necessary, is to change Serving HTTP on localhost port 9000 message to Serving HTTP at http://localhost:9000 when executing clj -m cljs.main -s, this will make it possible to open browser from the terminal with a single click.


#15

Fixed, thanks! Will consider the other change for another time :slight_smile:


#16

Just wondering: is it necessary to alias yarn webpack to yarn build? If not I feel like the guide would be more to the point without this step.


#17

Good point will fix that up shortly.


Guide on how to use/import npm modules/packages in ClojureScript?
#18

This barely even warrants a comment, but personally I like syntax highlighting in code snippets wherever available.


#19

There is syntax highlighting on the published guide https://clojurescript.org/guides/webpack, but I guess it doesn’t show up in the link above.


#20

Happy to do a PR if you’d like!

*edit, it seems like github preview doesn’t support syntax highlighting for .adoc; :slightly_frowning_face: