Repository: sveltejs/svelte-preprocess
Pull request #645 (2024-07-04 22:50:20)

chore: remove all dependencies
these are such tiny utils it doesn't feel worth adding three dependencies (one has a transitive dependency of its own)


Patch:
diff --git a/docs/migration-guide.md b/docs/migration-guide.md
index e4b83bc..84c0bd3 100644
--- a/docs/migration-guide.md
+++ b/docs/migration-guide.md
@@ -9,6 +9,7 @@
   - [Executing some function before preprocessing](#executing-some-function-before-preprocessing)
   - [Defining preprocessor properties](#defining-preprocessor-properties)
   - [Type-checking components](#type-checking-components)
+- [From `v5` to `v6`](#from-v5-to-v6)
 
 <!-- /code_chunk_output -->
 
@@ -95,7 +96,8 @@ In `v4`, your TypeScript code will only be transpiled into JavaScript, with no t
 
 - Svelte 4 or higher is required now
 - Node 18 or higher is required now
-- When using TypeScript, the minimum required version is now 5.0, `"verbatimModuleSyntax": true` is now required in your `tsconfig.json`, and the mixed imports transpiler (`handleMixedImports`) was removed
+- When using TypeScript, the minimum required version is now 5.0
+- When using TypeScript, `"verbatimModuleSyntax": true` is now required in your `tsconfig.json` (instead of the deprecated `preserveValueImports` and `importsNotUsedAsValues` options). As a consequence, the mixed imports transpiler (`handleMixedImports`) was removed from the TypeScript preprocessor. This means that you now need to specify for each import if it's a type or value import. For example instead of `import { value, Type } from 'somewhere'` you now need to write `import { value, type Type } from 'somewhere'`
 - The `preserve` option was removed as it's obsolete
 - The default export is deprecated in favor of its new named export:
 
diff --git a/package.json b/package.json
index fdd234c..4e67d3a 100644
--- a/package.json
+++ b/package.json
@@ -84,10 +84,6 @@
     "typescript": "^5.0.2",
     "vitest": "^1.6.0"
   },
-  "dependencies": {
-    "detect-indent": "^6.1.0",
-    "strip-indent": "^3.0.0"
-  },
   "peerDependencies": {
     "@babel/core": "^7.10.2",
     "coffeescript": "^2.5.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f5c1446..aab9c16 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,13 +7,6 @@ settings:
 importers:
 
   .:
-    dependencies:
-      detect-indent:
-        specifier: ^6.1.0
-        version: 6.1.0
-      strip-indent:
-        specifier: ^3.0.0
-        version: 3.0.0
     devDependencies:
       '@babel/core':
         specifier: ^7.23.6
@@ -1974,10 +1967,6 @@ packages:
     resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
     engines: {node: '>=6'}
 
-  detect-indent@6.1.0:
-    resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
-    engines: {node: '>=8'}
-
   detect-newline@3.1.0:
     resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
     engines: {node: '>=8'}
@@ -6564,8 +6553,6 @@ snapshots:
 
   dequal@2.0.3: {}
 
-  detect-indent@6.1.0: {}
-
   detect-newline@3.1.0:
     optional: true
 
diff --git a/src/modules/prepareContent.ts b/src/modules/prepareContent.ts
index b296edb..5dadf5d 100644
--- a/src/modules/prepareContent.ts
+++ b/src/modules/prepareContent.ts
@@ -1,6 +1,4 @@
-import stripIndent from 'strip-indent';
-
-// todo: could use magig-string and generate some sourcemaps 🗺
+// todo: could use magic-string and generate some sourcemaps 🗺
 export function prepareContent({
   options,
   content,
@@ -22,3 +20,27 @@ export function prepareContent({
 
   return content;
 }
+
+/** Get the shortest leading whitespace from lines in a string */
+function minIndent(s: string) {
+  const match = s.match(/^[ \t]*(?=\S)/gm);
+
+  if (!match) {
+    return 0;
+  }
+
+  return match.reduce((r, a) => Math.min(r, a.length), Infinity);
+}
+
+/** Strip leading whitespace from each line in a string */
+function stripIndent(s: string) {
+  const indent = minIndent(s);
+
+  if (indent === 0) {
+    return s;
+  }
+
+  const regex = new RegExp(`^[ \\t]{${indent}}`, 'gm');
+
+  return s.replace(regex, '');
+}
diff --git a/src/transformers/pug.ts b/src/transformers/pug.ts
index b02192b..dfad060 100644
--- a/src/transformers/pug.ts
+++ b/src/transformers/pug.ts
@@ -1,4 +1,3 @@
-import detectIndent from 'detect-indent';
 import pug from 'pug';
 
 import type { Transformer, Options } from '../types';
@@ -67,8 +66,8 @@ const transformer: Transformer<Options.Pug> = async ({
     ...options,
   };
 
-  const { type: indentationType } = detectIndent(content);
-  const input = `${GET_MIXINS(indentationType ?? 'space')}\n${content}`;
+  const spaces = guessIndentString(content);
+  const input = `${GET_MIXINS(spaces ? 'space' : 'tab')}\n${content}`;
   const compiled = pug.compile(
     input,
     pugOptions,
@@ -94,4 +93,41 @@ const transformer: Transformer<Options.Pug> = async ({
   };
 };
 
+// Sourced from `golden-fleece`
+// https://github.com/Rich-Harris/golden-fleece/blob/f2446f331640f325e13609ed99b74b6a45e755c2/src/patch.ts#L302
+function guessIndentString(str: string): number | undefined {
+  const lines = str.split('\n');
+
+  let tabs = 0;
+  let spaces = 0;
+  let minSpaces = 8;
+
+  lines.forEach((line) => {
+    const match = /^(?: +|\t+)/.exec(line);
+
+    if (!match) return;
+
+    const [whitespace] = match;
+
+    if (whitespace.length === line.length) return;
+
+    if (whitespace[0] === '\t') {
+      tabs += 1;
+    } else {
+      spaces += 1;
+      if (whitespace.length > 1 && whitespace.length < minSpaces) {
+        minSpaces = whitespace.length;
+      }
+    }
+  });
+
+  if (spaces > tabs) {
+    let result = '';
+
+    while (minSpaces--) result += ' ';
+
+    return result.length;
+  }
+}
+
 export { transformer };
diff --git a/src/transformers/typescript.ts b/src/transformers/typescript.ts
index d652a56..27fd6a4 100644
--- a/src/transformers/typescript.ts
+++ b/src/transformers/typescript.ts
@@ -47,12 +47,15 @@ function getCompilerOptions({
   options: Options.Typescript;
   basePath: string;
 }): CompilerOptions {
-  const inputOptions = options.compilerOptions ?? {};
+  const inputOptions = ts.convertCompilerOptionsFromJson(
+    options.compilerOptions ?? {},
+    basePath,
+  );
 
   const { errors, options: convertedCompilerOptions } =
     options.tsconfigFile !== false || options.tsconfigDirectory
       ? loadTsconfig(inputOptions, filename, options)
-      : ts.convertCompilerOptionsFromJson(inputOptions, basePath);
+      : inputOptions;
 
   if (errors.length) {
     throw new Error(formatDiagnostics(errors, basePath));
@@ -60,12 +63,17 @@ function getCompilerOptions({
 
   const compilerOptions: CompilerOptions = {
     target: ts.ScriptTarget.ES2015,
-    ...(convertedCompilerOptions as CompilerOptions),
+    ...convertedCompilerOptions,
     // force module(resolution) to esnext and a compatible moduleResolution. Reason:
     // transpileModule treats NodeNext as CommonJS because it doesn't read the package.json.
     // Also see https://github.com/microsoft/TypeScript/issues/53022 (the filename workaround doesn't work).
     module: ts.ModuleKind.ESNext,
-    moduleResolution: ts.ModuleResolutionKind.Node10,
+    moduleResolution:
+      convertedCompilerOptions.moduleResolution ===
+      ts.ModuleResolutionKind.Bundler
+        ? ts.ModuleResolutionKind.Bundler
+        : ts.ModuleResolutionKind.Node10,
+    customConditions: undefined, // fails when using an invalid moduleResolution combination which could happen when we force moduleResolution to Node10
     allowNonTsExtensions: true,
     // Clear outDir since it causes source map issues when the files aren't actually written to disk.
     outDir: undefined,
@@ -74,8 +82,16 @@ function getCompilerOptions({
   if (!warned_verbatim && !compilerOptions.verbatimModuleSyntax) {
     warned_verbatim = true;
     console.warn(
+      '\x1b[1m%s\x1b[0m',
       'The TypeScript option verbatimModuleSyntax is now required when using Svelte files with lang="ts". Please add it to your tsconfig.json.',
     );
+    // best effort to still add it, if possible, in case no config was found whatsoever
+    if (
+      Object.keys(inputOptions.options).length === 0 &&
+      convertedCompilerOptions === inputOptions.options
+    ) {
+      compilerOptions.verbatimModuleSyntax = true;
+    }
   }
 
   if (
@@ -138,12 +154,18 @@ function transpileTs({
 }
 
 export function loadTsconfig(
-  compilerOptionsJSON: any,
+  fallback: {
+    options: ts.CompilerOptions;
+    errors: ts.Diagnostic[];
+  },
   filename: string,
   tsOptions: Options.Typescript,
-) {
+): {
+  options: ts.CompilerOptions;
+  errors: ts.Diagnostic[];
+} {
   if (typeof tsOptions.tsconfigFile === 'boolean') {
-    return { errors: [], options: compilerOptionsJSON };
+    return fallback;
   }
 
   let basePath = process.cwd();
@@ -156,7 +178,7 @@ export function loadTsconfig(
     ts.findConfigFile(fileDirectory, ts.sys.fileExists);
 
   if (!tsconfigFile) {
-    return { errors: [], options: compilerOptionsJSON };
+    return fallback;
   }
 
   tsconfigFile = isAbsolute(tsconfigFile)
@@ -185,7 +207,7 @@ export function loadTsconfig(
     config,
     ts.sys,
     basePath,
-    compilerOptionsJSON,
+    fallback.options,
     tsconfigFile,
   );
 
diff --git a/src/types/modules.d.ts b/src/types/modules.d.ts
index e762681..6b524a7 100644
--- a/src/types/modules.d.ts
+++ b/src/types/modules.d.ts
@@ -1,6 +1,5 @@
 declare module 'svelte/package.json';
 declare module 'coffeescript';
-declare module 'strip-indent';
 declare module 'postcss-load-config';
 declare module 'less';
 declare module 'sorcery';


Test patch:
diff --git a/test/transformers/typescript.test.ts b/test/transformers/typescript.test.ts
index 53c67e9..756affc 100644
--- a/test/transformers/typescript.test.ts
+++ b/test/transformers/typescript.test.ts
@@ -12,7 +12,7 @@ import {
 import type { Processed } from '../../src/types';
 import type { Diagnostic } from 'typescript';
 
-spyConsole({ silent: true });
+spyConsole({ silent: false });
 
 const EXPECTED_SCRIPT = getFixtureContent('script.js');
 
@@ -203,5 +203,22 @@ describe('transformer - typescript', () => {
       expect(code).not.toContain('&&=');
       expect(code).not.toContain('||=');
     });
+
+    it('should remove customConditions option if necessary to prevent config error', async () => {
+      const opts = sveltePreprocess({
+        typescript: {
+          tsconfigFile: false,
+          compilerOptions: {
+            // we force a different module resolution in our transformer which
+            // would fail if we wouldn't also remove the customConditions
+            moduleResolution: 'NodeNext',
+            customConditions: ['development'],
+          },
+        },
+      });
+      const preprocessed = await preprocess(template, opts);
+
+      expect(preprocessed.toString?.()).toContain('export var hello');
+    });
   });
 });