@salty-css/core 0.1.0-alpha.9 → 0.1.0-feat-define-import.0

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.
Files changed (41) hide show
  1. package/README.md +44 -0
  2. package/bin/confirm-install.d.ts +34 -0
  3. package/bin/context.d.ts +3 -0
  4. package/bin/integrations/index.d.ts +11 -3
  5. package/bin/integrations/types.d.ts +14 -2
  6. package/bin/main.cjs +149 -48
  7. package/bin/main.js +149 -48
  8. package/{class-name-generator-YeSQe_Ik.js → class-name-generator-CMWY5KTJ.js} +1 -1
  9. package/{class-name-generator-B2Pb2obX.cjs → class-name-generator-DB5aQwC_.cjs} +1 -1
  10. package/compiler/resolve-import.d.ts +17 -0
  11. package/compiler/salty-compiler.cjs +85 -26
  12. package/compiler/salty-compiler.d.ts +7 -1
  13. package/compiler/salty-compiler.js +86 -26
  14. package/config/index.cjs +2 -0
  15. package/config/index.js +3 -1
  16. package/css/dynamic-styles.cjs +1 -1
  17. package/css/dynamic-styles.js +1 -1
  18. package/css/keyframes.cjs +1 -1
  19. package/css/keyframes.js +1 -1
  20. package/factories/define-import.d.ts +14 -0
  21. package/factories/index.cjs +19 -0
  22. package/factories/index.d.ts +1 -0
  23. package/factories/index.js +19 -0
  24. package/generators/index.cjs +1 -1
  25. package/generators/index.js +2 -2
  26. package/instances/classname-instance.cjs +1 -1
  27. package/instances/classname-instance.js +1 -1
  28. package/package.json +1 -1
  29. package/{parse-styles-CA3TP5n1.cjs → parse-styles-C54MOrPg.cjs} +106 -7
  30. package/{parse-styles-BTIoYnBr.js → parse-styles-CLMTHo2H.js} +107 -8
  31. package/parsers/index.cjs +2 -1
  32. package/parsers/index.d.ts +1 -0
  33. package/parsers/index.js +4 -3
  34. package/parsers/parser-regexes.d.ts +3 -0
  35. package/parsers/strict.d.ts +2 -0
  36. package/runtime/index.cjs +1 -1
  37. package/runtime/index.js +1 -1
  38. package/{salty.config-cqavVm2t.cjs → salty.config-DogY_sSQ.cjs} +1 -1
  39. package/salty.config-GV37Q-D2.js +4 -0
  40. package/types/config-types.d.ts +9 -0
  41. package/salty.config-DjosWdPw.js +0 -4
package/bin/main.js CHANGED
@@ -9,6 +9,7 @@ import { exec } from "child_process";
9
9
  import ora from "ora";
10
10
  import { p as pascalCase } from "../pascal-case-F3Usi5Wf.js";
11
11
  import ejs from "ejs";
12
+ import { createInterface } from "readline/promises";
12
13
  const defaultPackageJsonPath = join(process.cwd(), "package.json");
13
14
  const readPackageJson = async (filePath = defaultPackageJsonPath) => {
14
15
  const content = await readFile(filePath, "utf-8").then(JSON.parse).catch(() => void 0);
@@ -138,17 +139,21 @@ const buildContext = async (opts) => {
138
139
  packageJson,
139
140
  rcFile,
140
141
  cliVersion: cliPackageJson.version || "0.0.0",
141
- skipInstall: !!opts.skipInstall
142
+ skipInstall: !!opts.skipInstall,
143
+ yes: !!opts.yes
142
144
  };
143
145
  };
144
146
  const registerBuildCommand = (program, defaultProject) => {
145
- program.command("build [directory]").alias("b").description("Build the Salty-CSS project.").option("-d, --dir <dir>", "Project directory to build the project in.").option("--watch", "Watch for changes and rebuild the project.").action(async function(_dir = defaultProject) {
147
+ program.command("build [directory]").alias("b").description("Build the Salty-CSS project.").option("-d, --dir <dir>", "Project directory to build the project in.").option("--watch", "Watch for changes and rebuild the project.").option("--mode <mode>", 'Build mode: "production" or "development". Defaults to NODE_ENV-based detection.').action(async function(_dir = defaultProject) {
146
148
  logger.info("Building the Salty-CSS project...");
147
- const { dir = _dir, watch: watch$1 } = this.opts();
149
+ const { dir = _dir, watch: watch$1, mode } = this.opts();
150
+ if (mode !== void 0 && mode !== "production" && mode !== "development") {
151
+ return logError(`Invalid --mode "${mode}". Expected "production" or "development".`);
152
+ }
148
153
  const resolved = dir ?? await getDefaultProject();
149
154
  if (!resolved) return logError("Project directory must be provided. Add it as the first argument after build command or use the --dir option.");
150
155
  const projectDir = resolveProjectDir(resolved);
151
- const compiler = new SaltyCompiler(projectDir);
156
+ const compiler = new SaltyCompiler(projectDir, { mode });
152
157
  await compiler.generateCss();
153
158
  if (watch$1) {
154
159
  logger.info("Watching for changes in the project directory...");
@@ -222,7 +227,7 @@ const getFramework = (name) => {
222
227
  return frameworksByName[name];
223
228
  };
224
229
  const templateLoaders = {
225
- "salty.config.ts": () => import("../salty.config-DjosWdPw.js"),
230
+ "salty.config.ts": () => import("../salty.config-GV37Q-D2.js"),
226
231
  "saltygen/index.css": () => import("../index-DKz1QXqs.js"),
227
232
  "react/styled-file.ts": () => import("../styled-file-Cda3EeR6.js"),
228
233
  "react/vanilla-file.ts": () => import("../vanilla-file-1kOqbCIM.js"),
@@ -290,6 +295,52 @@ const registerGenerateCommand = (program, defaultProject) => {
290
295
  await formatWithPrettier(formattedStyledFilePath);
291
296
  });
292
297
  };
298
+ const formatPackageForDisplay = (spec) => {
299
+ const trimmed = spec.trim();
300
+ if (trimmed.startsWith("-D ")) return `${trimmed.slice(3).trim()} (dev)`;
301
+ return trimmed;
302
+ };
303
+ const renderPackageList = (packages) => {
304
+ return packages.map((p) => ` + ${formatPackageForDisplay(p)}`).join("\n");
305
+ };
306
+ const confirmInstall = async (packages, yes, options = {}) => {
307
+ if (yes) return;
308
+ if (packages.length === 0) return;
309
+ const input = options.input ?? process.stdin;
310
+ const output = options.output ?? process.stdout;
311
+ const isTTY = options.isTTY ?? process.stdin.isTTY ?? false;
312
+ if (!isTTY) {
313
+ throw new Error("Cannot prompt for install confirmation: stdin is not a TTY. Re-run with --yes to install the listed packages without prompting.");
314
+ }
315
+ output.write(`The following packages will be installed:
316
+ ${renderPackageList(packages)}
317
+ `);
318
+ const rl = createInterface({ input, output, terminal: false });
319
+ try {
320
+ const answer = (await rl.question("Proceed? (y/N) ")).trim().toLowerCase();
321
+ if (answer !== "y" && answer !== "yes") {
322
+ throw new Error("Install cancelled by user.");
323
+ }
324
+ } finally {
325
+ rl.close();
326
+ }
327
+ };
328
+ const confirmYesNo = async (question, options = {}) => {
329
+ if (options.yes) return true;
330
+ const input = options.input ?? process.stdin;
331
+ const output = options.output ?? process.stdout;
332
+ const isTTY = options.isTTY ?? process.stdin.isTTY ?? false;
333
+ if (!isTTY) return false;
334
+ const suffix = options.defaultYes ? "(Y/n)" : "(y/N)";
335
+ const rl = createInterface({ input, output, terminal: false });
336
+ try {
337
+ const answer = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
338
+ if (answer === "") return !!options.defaultYes;
339
+ return answer === "y" || answer === "yes";
340
+ } finally {
341
+ rl.close();
342
+ }
343
+ };
293
344
  const CSS_FILE_FOLDERS = ["src", "public", "assets", "styles", "css", "app"];
294
345
  const CSS_SECOND_LEVEL_FOLDERS = ["styles", "css", "app", "pages"];
295
346
  const CSS_FILE_NAMES = ["index", "styles", "main", "app", "global", "globals"];
@@ -336,17 +387,22 @@ const editAstroConfig = (existing) => {
336
387
  const astroIntegration = {
337
388
  name: "astro",
338
389
  detect: (ctx) => findAstroConfig(ctx.projectDir),
339
- apply: async (ctx, configPath) => {
390
+ plan: async (ctx, configPath) => {
340
391
  const existing = await readFile(configPath, "utf-8").catch(() => void 0);
341
- if (existing === void 0) return { changed: false };
392
+ if (existing === void 0) return null;
342
393
  const result = editAstroConfig(existing);
343
394
  if (result.warning) logger.warn(result.warning);
344
- if (result.content === null) return { changed: false };
345
- if (!ctx.skipInstall) await npmInstall(`-D ${astroPackage(ctx.cliVersion)}`);
346
- logger.info("Adding Salty-CSS integration to Astro config: " + configPath);
347
- await writeFile(configPath, result.content);
348
- await formatWithPrettier(configPath);
349
- return { changed: true };
395
+ if (result.content === null) return null;
396
+ const newContent = result.content;
397
+ return {
398
+ packages: [`-D ${astroPackage(ctx.cliVersion)}`],
399
+ execute: async () => {
400
+ logger.info("Adding Salty-CSS integration to Astro config: " + configPath);
401
+ await writeFile(configPath, newContent);
402
+ await formatWithPrettier(configPath);
403
+ return { changed: true };
404
+ }
405
+ };
350
406
  }
351
407
  };
352
408
  const ESLINT_CONFIG_CANDIDATES = [
@@ -405,20 +461,25 @@ const eslintIntegration = {
405
461
  const candidates = eslintConfigCandidates(ctx.projectDir, ctx.cwd);
406
462
  return candidates.find((p) => existsSync(p)) ?? null;
407
463
  },
408
- apply: async (ctx, configPath) => {
464
+ plan: async (ctx, configPath) => {
409
465
  const existing = await readFile(configPath, "utf-8").catch(() => void 0);
410
466
  if (existing === void 0) {
411
467
  logger.error("Could not read ESLint config file.");
412
- return { changed: false };
468
+ return null;
413
469
  }
414
- if (!ctx.skipInstall) await npmInstall(corePackages.eslintConfigCore(ctx.cliVersion));
415
470
  const result = editEslintConfig(existing, configPath.endsWith("js"));
416
471
  if (result.warning) logger.warn(result.warning);
417
- if (result.content === null) return { changed: false };
418
- logger.info("Edit file: " + configPath);
419
- await writeFile(configPath, result.content);
420
- await formatWithPrettier(configPath);
421
- return { changed: true };
472
+ if (result.content === null) return null;
473
+ const newContent = result.content;
474
+ return {
475
+ packages: [corePackages.eslintConfigCore(ctx.cliVersion)],
476
+ execute: async () => {
477
+ logger.info("Edit file: " + configPath);
478
+ await writeFile(configPath, newContent);
479
+ await formatWithPrettier(configPath);
480
+ return { changed: true };
481
+ }
482
+ };
422
483
  }
423
484
  };
424
485
  const nextConfigFiles = ["next.config.js", "next.config.cjs", "next.config.ts", "next.config.mjs"];
@@ -448,16 +509,20 @@ const nextIntegration = {
448
509
  const found = nextConfigFiles.map((file) => join(ctx.projectDir, file)).find((p) => existsSync(p));
449
510
  return found ?? null;
450
511
  },
451
- apply: async (ctx, configPath) => {
512
+ plan: async (ctx, configPath) => {
452
513
  const existing = await readFile(configPath, "utf-8").catch(() => void 0);
453
- if (existing === void 0) return { changed: false };
514
+ if (existing === void 0) return null;
454
515
  const { content } = editNextConfig(existing);
455
- if (content === null) return { changed: false };
456
- if (!ctx.skipInstall) await npmInstall(`-D ${nextPackage(ctx.cliVersion)}`);
457
- logger.info("Adding Salty-CSS plugin to Next.js config...");
458
- await writeFile(configPath, content);
459
- await formatWithPrettier(configPath);
460
- return { changed: true };
516
+ if (content === null) return null;
517
+ return {
518
+ packages: [`-D ${nextPackage(ctx.cliVersion)}`],
519
+ execute: async () => {
520
+ logger.info("Adding Salty-CSS plugin to Next.js config...");
521
+ await writeFile(configPath, content);
522
+ await formatWithPrettier(configPath);
523
+ return { changed: true };
524
+ }
525
+ };
461
526
  }
462
527
  };
463
528
  const vitePackage = (version) => `@salty-css/vite@${version}`;
@@ -475,27 +540,40 @@ const viteIntegration = {
475
540
  const path = join(ctx.projectDir, "vite.config.ts");
476
541
  return existsSync(path) ? path : null;
477
542
  },
478
- apply: async (ctx, configPath) => {
543
+ plan: async (ctx, configPath) => {
479
544
  const existing = await readFile(configPath, "utf-8").catch(() => void 0);
480
- if (existing === void 0) return { changed: false };
545
+ if (existing === void 0) return null;
481
546
  const { content } = editViteConfig(existing);
482
- if (content === null) return { changed: false };
483
- logger.info("Edit file: " + configPath);
484
- if (!ctx.skipInstall) await npmInstall(`-D ${vitePackage(ctx.cliVersion)}`);
485
- logger.info("Adding Salty-CSS plugin to Vite config...");
486
- await writeFile(configPath, content);
487
- await formatWithPrettier(configPath);
488
- return { changed: true };
547
+ if (content === null) return null;
548
+ return {
549
+ packages: [`-D ${vitePackage(ctx.cliVersion)}`],
550
+ execute: async () => {
551
+ logger.info("Edit file: " + configPath);
552
+ logger.info("Adding Salty-CSS plugin to Vite config...");
553
+ await writeFile(configPath, content);
554
+ await formatWithPrettier(configPath);
555
+ return { changed: true };
556
+ }
557
+ };
489
558
  }
490
559
  };
491
560
  const buildIntegrationRegistry = [eslintIntegration, viteIntegration, nextIntegration, astroIntegration];
492
- const detectAndApplyIntegrations = async (ctx) => {
493
- const results = [];
561
+ const planIntegrations = async (ctx) => {
562
+ const planned = [];
494
563
  for (const integration of buildIntegrationRegistry) {
495
564
  const configPath = await integration.detect(ctx);
496
565
  if (!configPath) continue;
497
- const result = await integration.apply(ctx, configPath);
498
- results.push({ name: integration.name, configPath, changed: result.changed });
566
+ const plan = await integration.plan(ctx, configPath);
567
+ if (!plan) continue;
568
+ planned.push({ name: integration.name, configPath, plan });
569
+ }
570
+ return planned;
571
+ };
572
+ const applyIntegrationPlans = async (planned) => {
573
+ const results = [];
574
+ for (const { name, configPath, plan } of planned) {
575
+ const result = await plan.execute();
576
+ results.push({ name, configPath, changed: result.changed });
499
577
  }
500
578
  return results;
501
579
  };
@@ -546,17 +624,24 @@ const wirePrepareScript = async () => {
546
624
  await updatePackageJson(next);
547
625
  };
548
626
  const registerInitCommand = (program) => {
549
- program.command("init [directory]").description("Initialize a new Salty-CSS project.").option("-d, --dir <dir>", "Project directory to initialize the project in.").option("--css-file <css-file>", "Existing CSS file where to import the generated CSS. Path must be relative to the given project directory.").option("--skip-install", "Skip installing dependencies.").action(async function(_dir = ".") {
627
+ program.command("init [directory]").description("Initialize a new Salty-CSS project.").option("-d, --dir <dir>", "Project directory to initialize the project in.").option("--css-file <css-file>", "Existing CSS file where to import the generated CSS. Path must be relative to the given project directory.").option("--skip-install", "Skip installing dependencies.").option("-y, --yes", "Skip the install confirmation prompt.").action(async function(_dir = ".") {
550
628
  try {
551
629
  const opts = this.opts();
552
630
  const dir = opts.dir ?? _dir;
553
631
  if (!dir) return logError("Project directory must be provided. Add it as the first argument after init command or use the --dir option.");
554
- const ctx = await buildContext({ dir, skipInstall: opts.skipInstall });
632
+ const ctx = await buildContext({ dir, skipInstall: opts.skipInstall, yes: opts.yes });
555
633
  logger.info("Initializing a new Salty-CSS project!");
556
634
  const framework = await detectFramework(ctx);
557
635
  logger.info(`Detected framework: ${framework.name}`);
636
+ const plannedIntegrations = await planIntegrations(ctx);
558
637
  if (!ctx.skipInstall) {
559
- await npmInstall(corePackages.core(ctx.cliVersion), framework.runtimePackage(ctx.cliVersion));
638
+ const packages = [
639
+ corePackages.core(ctx.cliVersion),
640
+ framework.runtimePackage(ctx.cliVersion),
641
+ ...plannedIntegrations.flatMap((p) => p.plan.packages)
642
+ ];
643
+ await confirmInstall(packages, ctx.yes);
644
+ await npmInstall(...packages);
560
645
  }
561
646
  const projectFiles = await Promise.all([readTemplate("salty.config.ts"), readTemplate("saltygen/index.css")]);
562
647
  await mkdir(ctx.projectDir, { recursive: true });
@@ -564,7 +649,7 @@ const registerInitCommand = (program) => {
564
649
  await writeProjectToRc(ctx.cwd, ctx.relativeProjectPath, framework);
565
650
  await ensureGitignoreSaltygen(ctx.cwd);
566
651
  await importSaltygenIntoCss(ctx.projectDir, opts.cssFile);
567
- await detectAndApplyIntegrations(ctx);
652
+ await applyIntegrationPlans(plannedIntegrations);
568
653
  await wirePrepareScript();
569
654
  logger.info("Running the build to generate initial CSS...");
570
655
  const compiler = new SaltyCompiler(ctx.projectDir);
@@ -595,8 +680,8 @@ const getSaltyCssPackages = async () => {
595
680
  return saltyCssPackages;
596
681
  };
597
682
  const registerUpdateCommand = (program) => {
598
- program.command("update [version]").alias("up").description("Update Salty-CSS packages to the latest or specified version.").option("-v, --version <version>", "Version to update to.").option("--legacy-peer-deps <legacyPeerDeps>", "Use legacy peer dependencies (not recommended).", false).action(async function(_version = "latest") {
599
- const { legacyPeerDeps, version = _version } = this.opts();
683
+ program.command("update [version]").alias("up").description("Update Salty-CSS packages to the latest or specified version.").option("-v, --version <version>", "Version to update to.").option("--legacy-peer-deps <legacyPeerDeps>", "Use legacy peer dependencies (not recommended).", false).option("-y, --yes", "Skip confirmation prompts (install and rebuild).").option("-d, --dir <dir>", "Project directory to rebuild after updating.").action(async function(_version = "latest") {
684
+ const { legacyPeerDeps, version = _version, yes = false, dir } = this.opts();
600
685
  const saltyCssPackages = await getSaltyCssPackages();
601
686
  if (!saltyCssPackages) return logError("Could not update Salty-CSS packages as any were found in package.json.");
602
687
  const cli = await readThisPackageJson();
@@ -604,6 +689,11 @@ const registerUpdateCommand = (program) => {
604
689
  if (version === "@") return `${name}@${cli.version}`;
605
690
  return `${name}@${version.replace(/^@/, "")}`;
606
691
  });
692
+ try {
693
+ await confirmInstall(packagesToUpdate, yes);
694
+ } catch (err) {
695
+ return logError(err instanceof Error ? err.message : String(err));
696
+ }
607
697
  if (legacyPeerDeps) {
608
698
  logger.warn("Using legacy peer dependencies to update packages.");
609
699
  await npmInstall(...packagesToUpdate, "--legacy-peer-deps");
@@ -626,6 +716,17 @@ const registerUpdateCommand = (program) => {
626
716
  logger.info(`Updated to ${v.replace(/^\^/, "")}: ${names.join(", ")}`);
627
717
  }
628
718
  }
719
+ const project = dir ?? await getDefaultProject();
720
+ if (!project) {
721
+ logger.warn("Skipping rebuild: no project directory configured. Run `salty-css build [dir]` manually.");
722
+ return;
723
+ }
724
+ const shouldRebuild = await confirmYesNo("Rebuild Salty CSS now?", { yes });
725
+ if (!shouldRebuild) return;
726
+ const projectDir = resolveProjectDir(project);
727
+ logger.info("Rebuilding Salty-CSS project...");
728
+ await new SaltyCompiler(projectDir).generateCss();
729
+ logger.info("Rebuild complete.");
629
730
  });
630
731
  };
631
732
  const registerVersionOption = (program) => {
@@ -1,7 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { p as parseAndJoinStyles } from "./parse-styles-BTIoYnBr.js";
4
+ import { p as parseAndJoinStyles } from "./parse-styles-CLMTHo2H.js";
5
5
  import { d as dashCase } from "./dash-case-DblXvymC.js";
6
6
  import { t as toHash } from "./to-hash-DAN2LcHK.js";
7
7
  class StylesGenerator {
@@ -2,7 +2,7 @@
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
- const parseStyles = require("./parse-styles-CA3TP5n1.cjs");
5
+ const parseStyles = require("./parse-styles-C54MOrPg.cjs");
6
6
  const dashCase = require("./dash-case-DIwKaYgE.cjs");
7
7
  const toHash = require("./to-hash-C05Y906F.cjs");
8
8
  class StylesGenerator {
@@ -0,0 +1,17 @@
1
+ import { ImportSpec } from '../factories/define-import';
2
+ export interface ResolveImportOptions {
3
+ /**
4
+ * Override for node_modules resolution. Receives the bare specifier (with any leading `~` already
5
+ * stripped) and the source file path of the salty file that called `defineImport`. Must return the
6
+ * absolute filesystem path of the resolved CSS file. Defaults to `createRequire(sourceFile).resolve`.
7
+ */
8
+ resolveModule?: (specifier: string, sourceFile: string) => string;
9
+ /**
10
+ * Override for copying resolved node_modules CSS into `<destDir>/imports/`. Receives the absolute
11
+ * source and destination paths. Defaults to `copyFileSync`.
12
+ */
13
+ copyAsset?: (from: string, to: string) => void;
14
+ }
15
+ export declare const resolveImport: (spec: ImportSpec, sourceFile: string, destDir: string, options?: ResolveImportOptions) => {
16
+ rule: string;
17
+ };
@@ -11,14 +11,14 @@ const fs = require("fs");
11
11
  const child_process = require("child_process");
12
12
  const compiler_helpers = require("./helpers.cjs");
13
13
  const defineTemplates = require("../define-templates-Deq1aCbN.cjs");
14
- const dashCase = require("../dash-case-DIwKaYgE.cjs");
14
+ const module$1 = require("module");
15
15
  const toHash = require("../to-hash-C05Y906F.cjs");
16
- const parseStyles = require("../parse-styles-CA3TP5n1.cjs");
16
+ const dashCase = require("../dash-case-DIwKaYgE.cjs");
17
+ const parseStyles = require("../parse-styles-C54MOrPg.cjs");
17
18
  const css_merge = require("../css/merge.cjs");
18
19
  const parsers_index = require("../parsers/index.cjs");
19
- const compiler_getFiles = require("./get-files.cjs");
20
+ const moduleType = require("../util/module-type");
20
21
  const console = require("console");
21
- var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
22
22
  function _interopNamespaceDefault(e) {
23
23
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
24
24
  if (e) {
@@ -44,19 +44,53 @@ const logger = winston.createLogger({
44
44
  const logError = (message) => {
45
45
  logger.error(message);
46
46
  };
47
- const readPackageJsonModule = async (dirname) => {
48
- const packageJsonContent = await compiler_getFiles.getPackageJson(dirname);
49
- if (!packageJsonContent) return void 0;
50
- return packageJsonContent.type;
47
+ const EXTERNAL_URL = /^(?:[a-z][a-z0-9+.-]*:)?\/\//i;
48
+ const normaliseSpec = (spec) => {
49
+ if (typeof spec === "string") return { url: spec };
50
+ return spec;
51
+ };
52
+ const ensureRelativePrefix = (path2) => {
53
+ if (path2.startsWith(".") || path2.startsWith("/")) return path2;
54
+ return `./${path2}`;
55
+ };
56
+ const toPosix = (path2) => path2.split("\\").join("/");
57
+ const defaultResolveModule = (specifier, sourceFile) => {
58
+ return module$1.createRequire(sourceFile).resolve(specifier);
51
59
  };
52
- let cachedModuleType;
53
- const detectCurrentModuleType = async (dirname) => {
54
- if (cachedModuleType) return cachedModuleType;
55
- const packageJsonModule = await readPackageJsonModule(dirname);
56
- if (packageJsonModule === "module") cachedModuleType = "esm";
57
- else if (packageJsonModule === "commonjs") cachedModuleType = "cjs";
58
- else if ((typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("compiler/salty-compiler.cjs", document.baseURI).href).endsWith(".cjs")) cachedModuleType = "cjs";
59
- return cachedModuleType || "esm";
60
+ const defaultCopyAsset = (from, to) => {
61
+ const dir = path.dirname(to);
62
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
63
+ fs.copyFileSync(from, to);
64
+ };
65
+ const buildRule = (url, { media, supports }) => {
66
+ let rule = `@import url('${url}')`;
67
+ if (supports) rule += ` supports(${supports})`;
68
+ if (media) rule += ` ${media}`;
69
+ return `${rule};`;
70
+ };
71
+ const resolveImport = (spec, sourceFile, destDir, options = {}) => {
72
+ const opts = normaliseSpec(spec);
73
+ const { url } = opts;
74
+ const resolveModule = options.resolveModule ?? defaultResolveModule;
75
+ const copyAsset = options.copyAsset ?? defaultCopyAsset;
76
+ if (EXTERNAL_URL.test(url)) {
77
+ return { rule: buildRule(url, opts) };
78
+ }
79
+ if (url.startsWith("/")) {
80
+ return { rule: buildRule(url, opts) };
81
+ }
82
+ if (url.startsWith("./") || url.startsWith("../")) {
83
+ const absolute2 = path.resolve(path.dirname(sourceFile), url);
84
+ const fromImportsFile = path.relative(path.join(destDir, "css"), absolute2);
85
+ return { rule: buildRule(ensureRelativePrefix(toPosix(fromImportsFile)), opts) };
86
+ }
87
+ const specifier = url.startsWith("~") ? url.slice(1) : url;
88
+ const absolute = resolveModule(specifier, sourceFile);
89
+ const hash = toHash.toHash(absolute, 6);
90
+ const fileName = `${hash}-${path.basename(absolute)}`;
91
+ const destPath = path.join(destDir, "imports", fileName);
92
+ copyAsset(absolute, destPath);
93
+ return { rule: buildRule(`../imports/${fileName}`, opts) };
60
94
  };
61
95
  function dotCase(str) {
62
96
  if (!str) return "";
@@ -111,7 +145,7 @@ const saltyReset = {
111
145
  }
112
146
  };
113
147
  class SaltyCompiler {
114
- constructor(projectRootDir) {
148
+ constructor(projectRootDir, options = {}) {
115
149
  __publicField(this, "importFile", (path2) => {
116
150
  const now = Date.now();
117
151
  return import(
@@ -173,7 +207,7 @@ class SaltyCompiler {
173
207
  const destDir = await this.getDestDir();
174
208
  const coreConfigPath = path.join(this.projectRootDir, (rcProject == null ? void 0 : rcProject.configDir) || "", "salty.config.ts");
175
209
  const coreConfigDest = path.join(destDir, "salty.config.js");
176
- const moduleType = await detectCurrentModuleType(this.projectRootDir);
210
+ const moduleType$1 = await moduleType.detectCurrentModuleType(this.projectRootDir);
177
211
  const externalModules = this.getExternalModules(coreConfigPath);
178
212
  await esbuild__namespace.build({
179
213
  entryPoints: [coreConfigPath],
@@ -181,7 +215,7 @@ class SaltyCompiler {
181
215
  treeShaking: true,
182
216
  bundle: true,
183
217
  outfile: coreConfigDest,
184
- format: moduleType,
218
+ format: moduleType$1,
185
219
  external: externalModules
186
220
  });
187
221
  const { config } = await this.importFile(coreConfigDest);
@@ -233,6 +267,7 @@ ${currentFile}`;
233
267
  fs.mkdirSync(path.join(destDir, "types"));
234
268
  fs.mkdirSync(path.join(destDir, "js"));
235
269
  fs.mkdirSync(path.join(destDir, "cache"));
270
+ fs.mkdirSync(path.join(destDir, "imports"));
236
271
  };
237
272
  if (clean) clearDistDir();
238
273
  const files = /* @__PURE__ */ new Set();
@@ -348,7 +383,7 @@ ${currentFile}`;
348
383
  });
349
384
  }
350
385
  const otherGlobalCssFiles = globalCssFiles.map((file) => `@import url('./css/${file}');`).join("\n");
351
- const globalCssFilenames = ["_variables.css", "_reset.css", "_global.css", "_templates.css"];
386
+ const globalCssFilenames = ["_imports.css", "_variables.css", "_reset.css", "_global.css", "_templates.css"];
352
387
  const importsWithData = globalCssFilenames.filter((file) => {
353
388
  try {
354
389
  const data = fs.readFileSync(path.join(destDir, "css", file), "utf8");
@@ -357,9 +392,12 @@ ${currentFile}`;
357
392
  return false;
358
393
  }
359
394
  });
360
- const globalImports = importsWithData.map((file) => `@import url('./css/${file}');`);
395
+ const globalImports = importsWithData.map((file) => {
396
+ const layerSuffix = file === "_imports.css" ? " layer(imports)" : "";
397
+ return `@import url('./css/${file}')${layerSuffix};`;
398
+ });
361
399
  const generatorText = "/*!\n * Generated with Salty CSS (https://salty-css.dev)\n * Do not edit this file directly\n */\n";
362
- let cssContent = `${generatorText}@layer reset, global, templates, l0, l1, l2, l3, l4, l5, l6, l7, l8;
400
+ let cssContent = `${generatorText}@layer imports, reset, global, templates, l0, l1, l2, l3, l4, l5, l6, l7, l8;
363
401
 
364
402
  ${globalImports.join(
365
403
  "\n"
@@ -405,7 +443,8 @@ ${css}
405
443
  mediaQueries: [],
406
444
  globalStyles: [],
407
445
  variables: [],
408
- templates: []
446
+ templates: [],
447
+ imports: []
409
448
  };
410
449
  await Promise.all(
411
450
  [...configFiles].map(async (src) => {
@@ -415,6 +454,7 @@ ${css}
415
454
  else if (value.isGlobalDefine) generationResults.globalStyles.push(value);
416
455
  else if (value.isDefineVariables) generationResults.variables.push(value);
417
456
  else if (value.isDefineTemplates) generationResults.templates.push(value._setPath(`${name};;${outputFilePath}`));
457
+ else if (value.isDefineImport) generationResults.imports.push(value._setPath(src));
418
458
  });
419
459
  })
420
460
  );
@@ -513,6 +553,22 @@ ${css}
513
553
  const templateTokens = parsers_index.getTemplateTypes(templates);
514
554
  fs.writeFileSync(templateStylesPath, `@layer templates { ${templateStylesString} }`);
515
555
  configCacheContent.templates = templates;
556
+ const importsPath = path.join(destDir, "css/_imports.css");
557
+ const importRules = [];
558
+ for (const factory of generationResults.imports) {
559
+ const sourceFile = factory._path;
560
+ if (!sourceFile) continue;
561
+ for (const spec of factory._current) {
562
+ try {
563
+ const { rule } = resolveImport(spec, sourceFile, destDir);
564
+ importRules.push(rule);
565
+ } catch (e) {
566
+ const url = typeof spec === "string" ? spec : spec.url;
567
+ logger.error(`Failed to resolve defineImport(${JSON.stringify(url)}) from ${sourceFile}: ${e.message}`);
568
+ }
569
+ }
570
+ }
571
+ fs.writeFileSync(importsPath, importRules.join("\n"));
516
572
  const configTemplateFactories = config.templates ? [defineTemplates.defineTemplates(config.templates)._setPath(`config;;${configPath}`)] : [];
517
573
  const templateFactories = css_merge.mergeFactories(generationResults.templates, configTemplateFactories);
518
574
  configCacheContent.templatePaths = Object.fromEntries(Object.entries(templateFactories).map(([key, faktory]) => [key, faktory._path]));
@@ -546,11 +602,12 @@ ${css}
546
602
  let currentFile = fs.readFileSync(sourceFilePath, "utf8");
547
603
  currentFile = this.replaceStyledTag(currentFile);
548
604
  currentFile = this.addConfigCache(currentFile);
549
- const outputFilePath = path.join(outputDirectory, "js", hashedName + ".js");
605
+ const currentFileHash = toHash.toHash(currentFile);
606
+ const outputFilePath = path.join(outputDirectory, "js", `${hashedName}-${currentFileHash}.js`);
550
607
  const rcProject = await this.getRCProjectConfig(this.projectRootDir);
551
608
  const coreConfigPath = path.join(this.projectRootDir, (rcProject == null ? void 0 : rcProject.configDir) || "", "salty.config.ts");
552
609
  const externalModules = this.getExternalModules(coreConfigPath);
553
- const moduleType = await detectCurrentModuleType(this.projectRootDir);
610
+ const moduleType$1 = await moduleType.detectCurrentModuleType(this.projectRootDir);
554
611
  await esbuild__namespace.build({
555
612
  stdin: {
556
613
  contents: currentFile,
@@ -562,7 +619,7 @@ ${css}
562
619
  treeShaking: true,
563
620
  bundle: true,
564
621
  outfile: outputFilePath,
565
- format: moduleType,
622
+ format: moduleType$1,
566
623
  target: ["node20"],
567
624
  keepNames: true,
568
625
  external: externalModules,
@@ -690,11 +747,13 @@ ${newContent}
690
747
  });
691
748
  });
692
749
  this.projectRootDir = projectRootDir;
750
+ this.options = options;
693
751
  if (typeof process === "undefined") {
694
752
  throw new Error("SaltyServer can only be used in a Node.js environment.");
695
753
  }
696
754
  }
697
755
  get isProduction() {
756
+ if (this.options.mode) return this.options.mode === "production";
698
757
  try {
699
758
  return process.env["NODE_ENV"] !== "development";
700
759
  } catch {
@@ -1,9 +1,14 @@
1
1
  import { CachedConfig, SaltyConfig } from '../config';
2
+ export type SaltyCompilerMode = 'production' | 'development';
3
+ export interface SaltyCompilerOptions {
4
+ mode?: SaltyCompilerMode;
5
+ }
2
6
  export declare class SaltyCompiler {
3
7
  projectRootDir: string;
8
+ private options;
4
9
  importFile: (path: string) => Promise<any>;
5
10
  private cache;
6
- constructor(projectRootDir: string);
11
+ constructor(projectRootDir: string, options?: SaltyCompilerOptions);
7
12
  get isProduction(): boolean;
8
13
  /**
9
14
  * Locate and read the .saltyrc.json file starting from the current directory and moving up the directory tree.
@@ -39,6 +44,7 @@ export declare class SaltyCompiler {
39
44
  isGlobalDefine?: boolean;
40
45
  isDefineVariables?: boolean;
41
46
  isDefineTemplates?: boolean;
47
+ isDefineImport?: boolean;
42
48
  isKeyframes?: boolean;
43
49
  animationName?: string;
44
50
  css?: Promise<string>;