clojurific 0.12.6 → 0.12.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,8 +16,8 @@ counts Clojurific's releases, starting from 1.
16
16
 
17
17
  ## Usage
18
18
 
19
- `npm create clojurific@latest` scaffolds a project (Vanilla, Reagent or UIx)
20
- like the one below, installs it and starts the dev server.
19
+ `npm create clojurific@latest` scaffolds a project (Vanilla, Reagent, UIx, or
20
+ a Hono backend) like the one below, installs it and starts the dev server.
21
21
 
22
22
  Dependencies and source paths come from the project's `deps.edn`:
23
23
 
@@ -28,10 +28,10 @@ Dependencies and source paths come from the project's `deps.edn`:
28
28
 
29
29
  ```js
30
30
  // vite.config.mjs
31
- import cljs from 'clojurific/vite';
31
+ import cljf from 'clojurific/vite';
32
32
 
33
33
  export default {
34
- plugins: [cljs({ outputDir: 'target/cljs' })],
34
+ plugins: [cljf({ outputDir: 'target/cljs' })],
35
35
  };
36
36
  ```
37
37
 
@@ -57,7 +57,7 @@ npx cljf repl
57
57
  ```
58
58
 
59
59
  `cljf repl` is a ClojureScript REPL evaluating in the pages the dev server
60
- runs, through its nREPL server: `cljs({ outputDir: 'target/cljs',
60
+ runs, through its nREPL server: `cljf({ outputDir: 'target/cljs',
61
61
  compilerOptions: { repl: { 'nrepl-port': 0 } } })` with `nrepl/nrepl` in
62
62
  `deps.edn`.
63
63
 
@@ -87,7 +87,7 @@ npx cljf setup-java
87
87
  | `CLJF_MAVEN_REPO` | a Maven Central mirror to download the dependency resolver from; other dependencies use `:mvn/repos` |
88
88
  | `CLJF_CACHE_DIR` | where `setup-java` installs Java |
89
89
 
90
- See [ESM.md](https://github.com/WhimsicalCode/clojurific/blob/main/ESM.md) for
90
+ See [README.md](https://github.com/WhimsicalCode/clojurific/blob/main/README.md) for
91
91
  compiler options, the Vite plugin's options, the REPL and how the launcher
92
92
  works.
93
93
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clojurific",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "description": "ClojureScript compiled to ES modules, bundled by Vite",
5
5
  "license": "EPL-1.0",
6
6
  "repository": {
@@ -247,12 +247,16 @@ export default module.exports;
247
247
  * true
248
248
  * @param {string[]} [options.entries] the :js-entries this build bundles,
249
249
  * defaults to all; each is self-contained when it's the only one
250
+ * @param {string[]} [options.ssrEntries] :js-entries the dev server runs in
251
+ * its ssr environment once they're compiled, i.e. a server's (which a
252
+ * plugin like @hono/vite-dev-server imports from the same runner): the REPL
253
+ * evaluates in them before their first request
250
254
  * @param {boolean} [options.replRemoteConsole] accept cljs_eval (forms the
251
255
  * compiler compiles, running its macros) from pages on other hosts too,
252
256
  * defaults to false: only from this machine. A proxy in front of the dev
253
257
  * server must set X-Real-IP to the client's address.
254
258
  */
255
- export default function cljs(options) {
259
+ export default function cljf(options) {
256
260
  const manifestName = options.manifest ?? 'manifest.json';
257
261
  let config, proc, server, minify, root, cwd, outputDir, compiler;
258
262
  // the namespaces of the pages' scripts, compiled as main namespaces
@@ -324,8 +328,8 @@ export default function cljs(options) {
324
328
  } else if (event.type === 'compiled' || event.type === 'error') {
325
329
  compiled();
326
330
  const logger = config?.logger ?? console;
327
- if (event.type === 'error') logger.error(`[cljs] ${event.message}`);
328
- else logger.info(`[cljs] compiled ${event.namespaces} namespace(s) in ${event.ms}ms`);
331
+ if (event.type === 'error') logger.error(`[cljf] ${event.message}`);
332
+ else logger.info(`[cljf] compiled ${event.namespaces} namespace(s) in ${event.ms}ms`);
329
333
  if (first && event.type === 'compiled') {
330
334
  first = false;
331
335
  resolve();
@@ -398,7 +402,7 @@ export default function cljs(options) {
398
402
  }
399
403
 
400
404
  return {
401
- name: 'cljs',
405
+ name: 'cljf',
402
406
  // before Vite's resolver, which would take cljs-npm-as: specifiers
403
407
  enforce: 'pre',
404
408
 
@@ -619,7 +623,7 @@ export default function cljs(options) {
619
623
  this.warn('nothing was pruned: package.json has "type": "commonjs", so the bundler treats the project\'s ' +
620
624
  '.js files as CommonJS and wraps the ClojureScript they import in initializers. Use "type": "module".');
621
625
  }
622
- config.logger.info(`[cljs] removed ${pruned.removed} unused statements (${pruneMs}ms), ` +
626
+ config.logger.info(`[cljf] removed ${pruned.removed} unused statements (${pruneMs}ms), ` +
623
627
  `renamed ${Object.keys(renames).length} properties, ` +
624
628
  `minified (${Date.now() - start - pruneMs}ms)`);
625
629
  }
@@ -682,56 +686,106 @@ export default function cljs(options) {
682
686
  // since: a reloaded page runs the output with warnings
683
687
  let problem = null;
684
688
  const showProblem = err => {
685
- problem = { type: 'error', err: { stack: '', plugin: 'cljs', ...err } };
689
+ problem = { type: 'error', err: { stack: '', plugin: 'cljf', ...err } };
686
690
  server.ws.send(problem);
687
691
  };
688
692
  server.ws.on('connection', socket => problem && socket.send(JSON.stringify(problem)));
693
+ // options.ssrEntries run in the ssr environment's runner once compiled,
694
+ // until they ran
695
+ let ssrEntries = options.ssrEntries ?? [];
696
+ const runSsrEntries = async () => {
697
+ if (!ssrEntries.length) return;
698
+ // created when it's first read
699
+ const runner = server.environments.ssr?.runner;
700
+ if (!runner) return;
701
+ const info = await buildInfo();
702
+ for (const name of ssrEntries) {
703
+ const file = info?.entries?.[name];
704
+ if (!file) {
705
+ config.logger.error(`[cljf] ssrEntries: no :js-entries ${name}`, { timestamp: true });
706
+ continue;
707
+ }
708
+ try {
709
+ await runner.import(path.join(outputDir, file));
710
+ ssrEntries = ssrEntries.filter(n => n !== name);
711
+ } catch (e) {
712
+ config.logger.error(`[cljf] running ${name} (${file}): ${e.stack ?? e}`, { timestamp: true });
713
+ }
714
+ }
715
+ };
689
716
  // The REPL (cljf.esm.repl): pages running the build (cljf.esm.repl-runtime)
690
- // say hello over Vite's websocket, each connection gets a runtime id. Their
691
- // cljs:repl messages go to the compiler as `repl <json>` lines, the
692
- // compiler's repl-send events to the page of their runtime id.
717
+ // say hello over Vite's websocket, and modules the ssr environment's
718
+ // runner runs (a server's :js-entries) over its hot channel; each
719
+ // connection gets a runtime id. Their cljs:repl messages go to the
720
+ // compiler as `repl <json>` lines, the compiler's repl-send events to the
721
+ // runtime of their runtime id, runtimes[id](data).
693
722
  const runtimes = new Map();
694
723
  let lastRuntimeId = 0;
695
724
  const toRepl = msg => proc.stdin.writable && proc.stdin.write(`repl ${JSON.stringify(msg)}\n`);
696
- const send = (socket, data) => socket.send(JSON.stringify({ type: 'custom', event: 'cljs:repl', data }));
697
- server.ws.on('connection', (socket, req) => {
698
- // cljs_eval compiles forms in the compiler's JVM, which runs their macros:
699
- // only for pages on this machine, unless replRemoteConsole
700
- const consoleAllowed = options.replRemoteConsole || isLocalConnection(req);
725
+ // A runtime's connection: receive takes the data of its cljs:repl events
726
+ // for the REPL, close says bye. send replies to it, consoleAllowed whether
727
+ // it may cljs_eval.
728
+ const connectRuntime = (send, consoleAllowed) => {
701
729
  let id;
702
- socket.on('message', raw => {
703
- let msg;
704
- try {
705
- msg = JSON.parse(String(raw));
706
- } catch {
707
- return;
708
- }
709
- if (msg?.type !== 'custom' || msg.event !== 'cljs:repl' || !msg.data) return;
710
- const data = msg.data;
730
+ const receive = data => {
711
731
  if (data.op === 'hello' && id === undefined) {
712
732
  id = ++lastRuntimeId;
713
- runtimes.set(id, socket);
733
+ runtimes.set(id, send);
714
734
  }
715
735
  if (id === undefined) return;
716
- if (data.op === 'hello') send(socket, { op: 'welcome', runtime: id });
736
+ if (data.op === 'hello') send({ op: 'welcome', runtime: id });
717
737
  if (data.op === 'console-eval' && !consoleAllowed) {
718
- send(socket, {
738
+ send({
719
739
  op: 'console-result',
720
740
  rid: data.rid,
721
- result: { error: 'cljs_eval only runs in pages on the dev server\'s machine, see the cljs plugin\'s replRemoteConsole' },
741
+ result: { error: 'cljs_eval only runs in pages on the dev server\'s machine, see the cljf plugin\'s replRemoteConsole' },
722
742
  });
723
743
  return;
724
744
  }
725
745
  toRepl({ ...data, runtime: id });
726
- });
727
- socket.on('close', () => {
746
+ };
747
+ const close = () => {
728
748
  if (id !== undefined && runtimes.delete(id)) toRepl({ op: 'bye', runtime: id });
749
+ };
750
+ return { receive, close };
751
+ };
752
+ server.ws.on('connection', (socket, req) => {
753
+ // cljs_eval compiles forms in the compiler's JVM, which runs their macros:
754
+ // only for pages on this machine, unless replRemoteConsole
755
+ const runtime = connectRuntime(
756
+ data => socket.send(JSON.stringify({ type: 'custom', event: 'cljs:repl', data })),
757
+ options.replRemoteConsole || isLocalConnection(req));
758
+ socket.on('message', raw => {
759
+ let msg;
760
+ try {
761
+ msg = JSON.parse(String(raw));
762
+ } catch {
763
+ return;
764
+ }
765
+ if (msg?.type !== 'custom' || msg.event !== 'cljs:repl' || !msg.data) return;
766
+ runtime.receive(msg.data);
729
767
  });
768
+ socket.on('close', runtime.close);
730
769
  });
770
+ // the ssr runner's modules run in this process, each runner is a client
771
+ // of the environment's hot channel
772
+ const ssrHot = server.environments.ssr?.hot;
773
+ if (ssrHot) {
774
+ const ssrRuntimes = new Map();
775
+ const ssrRuntime = client => {
776
+ if (!ssrRuntimes.has(client)) ssrRuntimes.set(client, connectRuntime(data => client.send('cljs:repl', data), true));
777
+ return ssrRuntimes.get(client);
778
+ };
779
+ ssrHot.on('cljs:repl', (data, client) => data && ssrRuntime(client).receive(data));
780
+ ssrHot.on('vite:client:disconnect', (data, client) => {
781
+ ssrRuntimes.get(client)?.close();
782
+ ssrRuntimes.delete(client);
783
+ });
784
+ }
731
785
  proc = run(compilerArgs('watch', { ':esm-hmr': true, ':exit-with-parent': true, ':watch-events': ':stdin' }), async event => {
732
786
  if (event.type === 'repl-send') {
733
- const socket = runtimes.get(event.runtime);
734
- if (socket) send(socket, event.msg);
787
+ const send = runtimes.get(event.runtime);
788
+ if (send) send(event.msg);
735
789
  else toRepl({ op: 'bye', runtime: event.runtime });
736
790
  return;
737
791
  }
@@ -775,7 +829,7 @@ export default function cljs(options) {
775
829
  const outstanding = [...warnings.values()].flat();
776
830
  if (event.type === 'compiled' && outstanding.length) {
777
831
  const [first] = outstanding;
778
- config.logger.warn(`[cljs] compiled ${event.namespaces} namespace(s) in ${event.ms}ms, ` +
832
+ config.logger.warn(`[cljf] compiled ${event.namespaces} namespace(s) in ${event.ms}ms, ` +
779
833
  `hot reload paused by ${outstanding.length} warning(s)`, { timestamp: true });
780
834
  showProblem({
781
835
  message: outstanding
@@ -786,16 +840,17 @@ export default function cljs(options) {
786
840
  });
787
841
  ready();
788
842
  } else if (event.type === 'compiled') {
789
- config.logger.info(`[cljs] compiled ${event.namespaces} namespace(s) in ${event.ms}ms`, { timestamp: true });
843
+ config.logger.info(`[cljf] compiled ${event.namespaces} namespace(s) in ${event.ms}ms`, { timestamp: true });
790
844
  problem = null;
791
845
  // the namespaces held back that this compile didn't update
792
846
  const client = server.environments.client;
793
847
  for (const file of released) {
794
848
  for (const mod of client.moduleGraph.getModulesByFile(file) ?? []) client.reloadModule(mod);
795
849
  }
850
+ runSsrEntries();
796
851
  ready();
797
852
  } else if (event.type === 'error') {
798
- config.logger.error(`[cljs] ${event.message}`, { timestamp: true });
853
+ config.logger.error(`[cljf] ${event.message}`, { timestamp: true });
799
854
  showProblem({
800
855
  message: event.message,
801
856
  id: event.file,