clojurific 0.12.2 → 0.12.3
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/cljf/cli.js +3 -2
- package/cljf/java.js +2 -1
- package/cljf/launcher.js +5 -3
- package/package.json +1 -1
- package/vite-plugin-cljs/index.js +77 -22
package/cljf/cli.js
CHANGED
|
@@ -42,9 +42,10 @@ function parse(argv) {
|
|
|
42
42
|
|
|
43
43
|
function run(command, args) {
|
|
44
44
|
const child = spawn(command, args, { stdio: 'inherit' });
|
|
45
|
+
// forwarded: only a terminal signals the JVM too (SIGINT), not kill, an IDE
|
|
46
|
+
// or a process supervisor; a second SIGINT is harmless
|
|
45
47
|
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
46
|
-
|
|
47
|
-
process.on(signal, () => signal !== 'SIGINT' && child.kill(signal));
|
|
48
|
+
process.on(signal, () => child.kill(signal));
|
|
48
49
|
}
|
|
49
50
|
child.on('error', e => {
|
|
50
51
|
console.error(`cljf: ${command}: ${e.message}`);
|
package/cljf/java.js
CHANGED
|
@@ -203,7 +203,8 @@ export async function setupJava({
|
|
|
203
203
|
await fs.mkdir(dir, { recursive: true });
|
|
204
204
|
const tmp = await fs.mkdtemp(path.join(dir, '.setup-'));
|
|
205
205
|
try {
|
|
206
|
-
|
|
206
|
+
// a name from the API, not a path
|
|
207
|
+
const archive = path.join(tmp, path.basename(pkg.name));
|
|
207
208
|
try {
|
|
208
209
|
await download(pkg.link, archive, { sha256: pkg.checksum, fetch });
|
|
209
210
|
} catch (e) {
|
package/cljf/launcher.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Classpaths are cached in the project's .cljf/cpcache, like the Clojure
|
|
5
5
|
// CLI's .cpcache, until a deps.edn they were resolved from changes.
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
|
-
import { createHash } from 'node:crypto';
|
|
7
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
8
8
|
import { existsSync, readFileSync } from 'node:fs';
|
|
9
9
|
import fs from 'node:fs/promises';
|
|
10
10
|
import path from 'node:path';
|
|
@@ -78,8 +78,10 @@ async function resolve({ project, aliases, java, cache, env, log }) {
|
|
|
78
78
|
repository: env.CLJF_MAVEN_REPO || undefined,
|
|
79
79
|
log,
|
|
80
80
|
});
|
|
81
|
-
|
|
82
|
-
const
|
|
81
|
+
// per process: another one may resolve the same classpath at the same time
|
|
82
|
+
const tmp = `${cache}.${process.pid}.${randomBytes(4).toString('hex')}`;
|
|
83
|
+
const input = `${tmp}.resolve.edn`;
|
|
84
|
+
const output = `${tmp}.resolve.json`;
|
|
83
85
|
await fs.writeFile(input, edn({
|
|
84
86
|
':aliases': aliases,
|
|
85
87
|
':compiler': compiler,
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// TypeScript files they import.
|
|
8
8
|
import { spawn } from 'node:child_process';
|
|
9
9
|
import fs from 'node:fs/promises';
|
|
10
|
-
import { existsSync } from 'node:fs';
|
|
10
|
+
import { existsSync, statSync } from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
import { pathToFileURL } from 'node:url';
|
|
@@ -31,16 +31,51 @@ async function sourceNamespace(file) {
|
|
|
31
31
|
return ns;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
const SCRIPT_SRC = /<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["']/gi;
|
|
35
|
+
// import "x", import x from "x", export … from "x", import("x")
|
|
36
|
+
const IMPORT = /(?:\bimport\s*(?:[\w$*{}\s,]+\bfrom\s*)?|\bexport\s*[\w$*{}\s,]+\bfrom\s*|\bimport\s*\(\s*)["']([^"']+)["']/g;
|
|
37
|
+
const SCRIPT_FILE = /\.(?:[cm]?[jt]sx?)$/;
|
|
38
|
+
// extensionless imports, as TypeScript projects write them
|
|
39
|
+
const SCRIPT_SUFFIXES = ['', '.ts', '.tsx', '.js', '.jsx', '.mjs', '/index.ts', '/index.js'];
|
|
40
|
+
|
|
41
|
+
// The file a page's script or a module's import names: root-absolute or
|
|
42
|
+
// relative paths, not packages or aliases, which the bundler resolves
|
|
43
|
+
function importedFile(spec, from, root) {
|
|
44
|
+
const file = spec.replace(/[?#].*$/, '');
|
|
45
|
+
if (!file.startsWith('/') && !file.startsWith('.')) return null;
|
|
46
|
+
return file.startsWith('/') ? path.join(root, file) : path.resolve(path.dirname(from), file);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The ClojureScript sources the entries (pages and JavaScript or TypeScript
|
|
50
|
+
// modules) load: the pages' scripts and the modules' imports, followed through
|
|
51
|
+
// the project's modules
|
|
52
|
+
async function entrySources(entries, root) {
|
|
53
|
+
const sources = new Set();
|
|
54
|
+
const seen = new Set();
|
|
55
|
+
const queue = [...entries];
|
|
56
|
+
while (queue.length) {
|
|
57
|
+
const file = queue.shift();
|
|
58
|
+
if (seen.has(file) || file.includes(`${path.sep}node_modules${path.sep}`)) continue;
|
|
59
|
+
seen.add(file);
|
|
60
|
+
let code;
|
|
61
|
+
try {
|
|
62
|
+
code = await fs.readFile(file, 'utf8');
|
|
63
|
+
} catch (e) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
for (const [, spec] of code.matchAll(file.endsWith('.html') ? SCRIPT_SRC : IMPORT)) {
|
|
67
|
+
const target = importedFile(spec, file, root);
|
|
68
|
+
if (!target) continue;
|
|
69
|
+
if (SOURCE_FILE.test(target)) {
|
|
70
|
+
if (existsSync(target)) sources.add(target);
|
|
71
|
+
} else {
|
|
72
|
+
const module = SCRIPT_SUFFIXES.map(suffix => target + suffix)
|
|
73
|
+
.find(f => SCRIPT_FILE.test(f) && existsSync(f) && !statSync(f).isDirectory());
|
|
74
|
+
if (module) queue.push(module);
|
|
75
|
+
}
|
|
41
76
|
}
|
|
42
77
|
}
|
|
43
|
-
return [...
|
|
78
|
+
return [...sources];
|
|
44
79
|
}
|
|
45
80
|
|
|
46
81
|
// Whether address is this machine's
|
|
@@ -73,14 +108,20 @@ async function commonJSPackage(root) {
|
|
|
73
108
|
}
|
|
74
109
|
}
|
|
75
110
|
|
|
76
|
-
// A Rollup input (a file, files or {name: file}) as {name: absolute file}
|
|
111
|
+
// A Rollup input (a file, files or {name: file}) as {name: absolute file},
|
|
112
|
+
// files named by their path in the root without the extension, so pages with
|
|
113
|
+
// the same file name in different directories (about/index.html) keep apart
|
|
77
114
|
function inputObject(input, root) {
|
|
78
115
|
if (!input) return {};
|
|
79
116
|
if (typeof input === 'object' && !Array.isArray(input)) {
|
|
80
117
|
return Object.fromEntries(Object.entries(input).map(([name, file]) => [name, path.resolve(root, file)]));
|
|
81
118
|
}
|
|
82
|
-
return Object.fromEntries([].concat(input).map(file =>
|
|
83
|
-
|
|
119
|
+
return Object.fromEntries([].concat(input).map(file => {
|
|
120
|
+
const absolute = path.resolve(root, file);
|
|
121
|
+
const relative = path.relative(root, absolute);
|
|
122
|
+
const name = relative.startsWith('..') || path.isAbsolute(relative) ? path.basename(absolute) : relative;
|
|
123
|
+
return [name.slice(0, name.length - path.extname(name).length).split(path.sep).join('/'), absolute];
|
|
124
|
+
}));
|
|
84
125
|
}
|
|
85
126
|
|
|
86
127
|
function ednString(s) {
|
|
@@ -315,6 +356,13 @@ export default function cljs(options) {
|
|
|
315
356
|
return (compiled ? path.relative(outputDir, file) : path.relative(cwd, file)).split(path.sep).join('/');
|
|
316
357
|
}
|
|
317
358
|
|
|
359
|
+
// Whether a ClojureScript source may be served: in Vite's root or
|
|
360
|
+
// server.fs.allow
|
|
361
|
+
function servedSource(file) {
|
|
362
|
+
const dirs = [config.root, ...(config.server?.fs?.allow ?? [])].map(dir => path.resolve(dir));
|
|
363
|
+
return SOURCE_FILE.test(file) && dirs.some(dir => file.startsWith(dir + path.sep));
|
|
364
|
+
}
|
|
365
|
+
|
|
318
366
|
// The compiled module of a ClojureScript source file: its namespace's in the
|
|
319
367
|
// build info, or its munged name
|
|
320
368
|
async function compiledModule(file) {
|
|
@@ -325,7 +373,8 @@ export default function cljs(options) {
|
|
|
325
373
|
`${ns.split('.').map(part => part.replace(/-/g, '_')).join('/')}.js`;
|
|
326
374
|
const module = path.join(outputDir, relative);
|
|
327
375
|
if (!existsSync(module)) {
|
|
328
|
-
throw new Error(`${ns} (${file}) isn't compiled: load it
|
|
376
|
+
throw new Error(`${ns} (${file}) isn't compiled: load it from a page's script or a module the pages ` +
|
|
377
|
+
'import (through relative or root-absolute imports, restarting Vite after adding it), or add it to :main');
|
|
329
378
|
}
|
|
330
379
|
return module;
|
|
331
380
|
}
|
|
@@ -372,8 +421,8 @@ export default function cljs(options) {
|
|
|
372
421
|
// the project's page, an input of builds and the dev server's /
|
|
373
422
|
const indexHtml = existsSync(path.join(root, 'index.html')) ? path.join(root, 'index.html') : null;
|
|
374
423
|
const userInput = userConfig.build?.rolldownOptions?.input ?? userConfig.build?.rollupOptions?.input;
|
|
375
|
-
const
|
|
376
|
-
pageNamespaces = await Promise.all((await
|
|
424
|
+
const entries = Object.values(inputObject(userInput ?? indexHtml, root));
|
|
425
|
+
pageNamespaces = await Promise.all((await entrySources(entries, root)).map(sourceNamespace));
|
|
377
426
|
if (env.command === 'build' && !env.isPreview) {
|
|
378
427
|
const start = Date.now();
|
|
379
428
|
if (userConfig.build?.watch) {
|
|
@@ -452,14 +501,17 @@ export default function cljs(options) {
|
|
|
452
501
|
// A ClojureScript source: its compiled module, as a module importing it
|
|
453
502
|
// when serving, which a page loading it by its source's URL and the
|
|
454
503
|
// modules importing it by its own share
|
|
455
|
-
const file = source.split(
|
|
504
|
+
const file = source.split(/[?#]/)[0];
|
|
456
505
|
if (SOURCE_FILE.test(file) && !source.startsWith('\0')) {
|
|
506
|
+
// URLs are root-relative, files are only served from the project
|
|
457
507
|
const candidates = [
|
|
458
|
-
...(path.isAbsolute(file) ? [file] : []),
|
|
459
508
|
...(file.startsWith('/') ? [path.join(root, file)] : []),
|
|
460
|
-
...(importer && !importer.startsWith('\0')
|
|
509
|
+
...(importer && !importer.startsWith('\0') && !file.startsWith('/')
|
|
510
|
+
? [path.resolve(path.dirname(importer.split('?')[0]), file)]
|
|
511
|
+
: []),
|
|
512
|
+
...(path.isAbsolute(file) ? [file] : []),
|
|
461
513
|
];
|
|
462
|
-
const found = candidates.find(f => existsSync(f));
|
|
514
|
+
const found = candidates.find(f => servedSource(f) && existsSync(f));
|
|
463
515
|
if (!found) return null;
|
|
464
516
|
return config.command === 'build' ? compiledModule(found) : '\0' + SOURCE + found;
|
|
465
517
|
}
|
|
@@ -479,7 +531,9 @@ export default function cljs(options) {
|
|
|
479
531
|
|
|
480
532
|
async load(id) {
|
|
481
533
|
if (id.startsWith('\0' + SOURCE)) {
|
|
482
|
-
const
|
|
534
|
+
const file = id.slice(SOURCE.length + 1);
|
|
535
|
+
if (!servedSource(file)) this.error(`${file} is outside the project`);
|
|
536
|
+
const module = await compiledModule(file);
|
|
483
537
|
return `import ${JSON.stringify(module)};\nexport * from ${JSON.stringify(module)};\n`;
|
|
484
538
|
}
|
|
485
539
|
// compiled namespaces with their source maps, to the ClojureScript sources
|
|
@@ -604,9 +658,10 @@ export default function cljs(options) {
|
|
|
604
658
|
async handler(html, { filename }) {
|
|
605
659
|
if (config.command !== 'serve') return html;
|
|
606
660
|
const replacements = await Promise.all(
|
|
607
|
-
|
|
661
|
+
// with a query or fragment, which the compiled module's URL keeps
|
|
662
|
+
[...html.matchAll(/(<script\b[^>]*\bsrc\s*=\s*["'])([^"'?#]+\.clj[sc])([?#][^"']*)?(["'])/gi)].map(async match => {
|
|
608
663
|
const file = match[2].startsWith('/') ? path.join(config.root, match[2]) : path.resolve(path.dirname(filename), match[2]);
|
|
609
|
-
return [match[0], match[1] + devUrl(await compiledModule(file)) + match[3]];
|
|
664
|
+
return [match[0], match[1] + devUrl(await compiledModule(file)) + (match[3] ?? '') + match[4]];
|
|
610
665
|
}));
|
|
611
666
|
return replacements.reduce((result, [from, to]) => result.replace(from, to), html);
|
|
612
667
|
},
|