@sveltejs/vite-plugin-svelte 2.4.6 → 3.0.0-next.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/vite-plugin-svelte",
3
- "version": "2.4.6",
3
+ "version": "3.0.0-next.1",
4
4
  "license": "MIT",
5
5
  "author": "dominikg",
6
6
  "files": [
@@ -16,7 +16,7 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "engines": {
19
- "node": "^14.18.0 || >= 16"
19
+ "node": "^18.0.0 || >=20"
20
20
  },
21
21
  "repository": {
22
22
  "type": "git",
@@ -34,23 +34,23 @@
34
34
  },
35
35
  "homepage": "https://github.com/sveltejs/vite-plugin-svelte#readme",
36
36
  "dependencies": {
37
- "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4",
37
+ "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0-next.0 || ^2.0.0",
38
38
  "debug": "^4.3.4",
39
39
  "deepmerge": "^4.3.1",
40
40
  "kleur": "^4.1.5",
41
- "magic-string": "^0.30.3",
41
+ "magic-string": "^0.30.5",
42
42
  "svelte-hmr": "^0.15.3",
43
- "vitefu": "^0.2.4"
43
+ "vitefu": "^0.2.5"
44
44
  },
45
45
  "peerDependencies": {
46
- "svelte": "^3.54.0 || ^4.0.0",
47
- "vite": "^4.0.0"
46
+ "svelte": "^4.0.0",
47
+ "vite": "^5.0.0-beta.1 || ^5.0.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@types/debug": "^4.1.8",
51
- "esbuild": "^0.19.3",
52
- "svelte": "^4.2.0",
53
- "vite": "^4.4.9"
50
+ "@types/debug": "^4.1.9",
51
+ "esbuild": "^0.19.4",
52
+ "svelte": "^4.2.1",
53
+ "vite": "^5.0.0-beta.7"
54
54
  },
55
55
  "scripts": {
56
56
  "check:publint": "publint --strict",
@@ -14,7 +14,11 @@ import { toRollupError } from './utils/error.js';
14
14
  export async function handleHotUpdate(compileSvelte, ctx, svelteRequest, cache, options) {
15
15
  if (!cache.has(svelteRequest)) {
16
16
  // file hasn't been requested yet (e.g. async component)
17
- log.debug(`handleHotUpdate called before initial transform for ${svelteRequest.id}`);
17
+ log.debug(
18
+ `handleHotUpdate called before initial transform for ${svelteRequest.id}`,
19
+ undefined,
20
+ 'hmr'
21
+ );
18
22
  return;
19
23
  }
20
24
  const { read, server, modules } = ctx;
@@ -39,7 +43,7 @@ export async function handleHotUpdate(compileSvelte, ctx, svelteRequest, cache,
39
43
  if (cssIdx > -1) {
40
44
  const cssUpdated = cssChanged(cachedCss, compileData.compiled.css);
41
45
  if (!cssUpdated) {
42
- log.debug(`skipping unchanged css for ${svelteRequest.cssId}`);
46
+ log.debug(`skipping unchanged css for ${svelteRequest.cssId}`, undefined, 'hmr');
43
47
  affectedModules.splice(cssIdx, 1);
44
48
  }
45
49
  }
@@ -47,7 +51,7 @@ export async function handleHotUpdate(compileSvelte, ctx, svelteRequest, cache,
47
51
  if (jsIdx > -1) {
48
52
  const jsUpdated = jsChanged(cachedJS, compileData.compiled.js, svelteRequest.filename);
49
53
  if (!jsUpdated) {
50
- log.debug(`skipping unchanged js for ${svelteRequest.id}`);
54
+ log.debug(`skipping unchanged js for ${svelteRequest.id}`, undefined, 'hmr');
51
55
  affectedModules.splice(jsIdx, 1);
52
56
  // transform won't be called, log warnings here
53
57
  logCompilerWarnings(svelteRequest, compileData.compiled.warnings, options);
@@ -57,14 +61,20 @@ export async function handleHotUpdate(compileSvelte, ctx, svelteRequest, cache,
57
61
  // TODO is this enough? see also: https://github.com/vitejs/vite/issues/2274
58
62
  const ssrModulesToInvalidate = affectedModules.filter((m) => !!m.ssrTransformResult);
59
63
  if (ssrModulesToInvalidate.length > 0) {
60
- log.debug(`invalidating modules ${ssrModulesToInvalidate.map((m) => m.id).join(', ')}`);
64
+ log.debug(
65
+ `invalidating modules ${ssrModulesToInvalidate.map((m) => m.id).join(', ')}`,
66
+ undefined,
67
+ 'hmr'
68
+ );
61
69
  ssrModulesToInvalidate.forEach((moduleNode) => server.moduleGraph.invalidateModule(moduleNode));
62
70
  }
63
71
  if (affectedModules.length > 0) {
64
72
  log.debug(
65
73
  `handleHotUpdate for ${svelteRequest.id} result: ${affectedModules
66
74
  .map((m) => m.id)
67
- .join(', ')}`
75
+ .join(', ')}`,
76
+ undefined,
77
+ 'hmr'
68
78
  );
69
79
  }
70
80
  return affectedModules;
package/src/index.d.ts CHANGED
@@ -121,20 +121,7 @@ interface SvelteOptions {
121
121
  * @see https://svelte.dev/docs#svelte_compile
122
122
  */
123
123
  compilerOptions?: Omit<CompileOptions, 'filename' | 'format' | 'generate'>;
124
- /**
125
- * Handles warning emitted from the Svelte compiler
126
- */
127
- onwarn?: (warning: Warning, defaultHandler?: (warning: Warning) => void) => void;
128
- /**
129
- * Options for vite-plugin-svelte
130
- */
131
- vitePlugin?: PluginOptions;
132
- }
133
124
 
134
- /**
135
- * These options are considered experimental and breaking changes to them can occur in any release
136
- */
137
- interface ExperimentalOptions {
138
125
  /**
139
126
  * A function to update `compilerOptions` before compilation
140
127
  *
@@ -159,6 +146,21 @@ interface ExperimentalOptions {
159
146
  code: string;
160
147
  compileOptions: Partial<CompileOptions>;
161
148
  }) => Promise<Partial<CompileOptions> | void> | Partial<CompileOptions> | void;
149
+
150
+ /**
151
+ * Handles warning emitted from the Svelte compiler
152
+ */
153
+ onwarn?: (warning: Warning, defaultHandler?: (warning: Warning) => void) => void;
154
+ /**
155
+ * Options for vite-plugin-svelte
156
+ */
157
+ vitePlugin?: PluginOptions;
158
+ }
159
+
160
+ /**
161
+ * These options are considered experimental and breaking changes to them can occur in any release
162
+ */
163
+ interface ExperimentalOptions {
162
164
  /**
163
165
  * send a websocket message with svelte compiler warnings during dev
164
166
  *
package/src/index.js CHANGED
@@ -1,9 +1,7 @@
1
1
  import fs from 'node:fs';
2
- import { version as viteVersion } from 'vite';
3
2
 
4
3
  import { svelteInspector } from '@sveltejs/vite-plugin-svelte-inspector';
5
4
 
6
- import { isDepExcluded } from 'vitefu';
7
5
  import { handleHotUpdate } from './handle-hot-update.js';
8
6
  import { log, logCompilerWarnings } from './utils/log.js';
9
7
  import { createCompileSvelte } from './utils/compile.js';
@@ -17,16 +15,10 @@ import {
17
15
  } from './utils/options.js';
18
16
 
19
17
  import { ensureWatchedFile, setupWatchers } from './utils/watch.js';
20
- import { resolveViaPackageJsonSvelte } from './utils/resolve.js';
21
-
22
18
  import { toRollupError } from './utils/error.js';
23
19
  import { saveSvelteMetadata } from './utils/optimizer.js';
24
20
  import { VitePluginSvelteCache } from './utils/vite-plugin-svelte-cache.js';
25
21
  import { loadRaw } from './utils/load-raw.js';
26
- import { FAQ_LINK_CONFLICTS_IN_SVELTE_RESOLVE } from './utils/constants.js';
27
- import { isSvelte3 } from './utils/svelte-version.js';
28
-
29
- const isVite4_0 = viteVersion.startsWith('4.0');
30
22
 
31
23
  /** @type {import('./index.d.ts').svelte} */
32
24
  export function svelte(inlineOptions) {
@@ -42,15 +34,9 @@ export function svelte(inlineOptions) {
42
34
  let options;
43
35
  /** @type {import('vite').ResolvedConfig} */
44
36
  let viteConfig;
45
-
46
37
  /** @type {import('./types/compile.d.ts').CompileSvelte} */
47
38
  let compileSvelte;
48
39
  /* eslint-enable no-unused-vars */
49
-
50
- /** @type {Promise<import('vite').Rollup.PartialResolvedId | null>} */
51
- let resolvedSvelteSSR;
52
- /** @type {Set<string>} */
53
- let packagesWithResolveWarnings;
54
40
  /** @type {import('./types/plugin-api.d.ts').PluginAPI} */
55
41
  const api = {};
56
42
  /** @type {import('vite').Plugin[]} */
@@ -71,7 +57,7 @@ export function svelte(inlineOptions) {
71
57
  options = await preResolveOptions(inlineOptions, config, configEnv);
72
58
  // extra vite config
73
59
  const extraViteConfig = await buildExtraViteConfig(options, config);
74
- log.debug('additional vite config', extraViteConfig);
60
+ log.debug('additional vite config', extraViteConfig, 'config');
75
61
  return extraViteConfig;
76
62
  },
77
63
 
@@ -83,11 +69,10 @@ export function svelte(inlineOptions) {
83
69
  viteConfig = config;
84
70
  // TODO deep clone to avoid mutability from outside?
85
71
  api.options = options;
86
- log.debug('resolved options', options);
72
+ log.debug('resolved options', options, 'config');
87
73
  },
88
74
 
89
75
  async buildStart() {
90
- packagesWithResolveWarnings = new Set();
91
76
  if (!options.prebundleSvelteLibraries) return;
92
77
  const isSvelteMetadataChanged = await saveSvelteMetadata(viteConfig.cacheDir, options);
93
78
  if (isSvelteMetadataChanged) {
@@ -108,18 +93,24 @@ export function svelte(inlineOptions) {
108
93
  if (svelteRequest) {
109
94
  const { filename, query, raw } = svelteRequest;
110
95
  if (raw) {
111
- return loadRaw(svelteRequest, compileSvelte, options);
96
+ const code = await loadRaw(svelteRequest, compileSvelte, options);
97
+ // prevent vite from injecting sourcemaps in the results.
98
+ return {
99
+ code,
100
+ map: {
101
+ mappings: ''
102
+ }
103
+ };
112
104
  } else {
113
105
  if (query.svelte && query.type === 'style') {
114
106
  const css = cache.getCSS(svelteRequest);
115
107
  if (css) {
116
- log.debug(`load returns css for ${filename}`);
117
108
  return css;
118
109
  }
119
110
  }
120
111
  // prevent vite asset plugin from loading files as url that should be compiled in transform
121
112
  if (viteConfig.assetsInclude(filename)) {
122
- log.debug(`load returns raw content for ${filename}`);
113
+ log.debug(`load returns raw content for ${filename}`, undefined, 'load');
123
114
  return fs.readFileSync(filename, 'utf-8');
124
115
  }
125
116
  }
@@ -133,78 +124,12 @@ export function svelte(inlineOptions) {
133
124
  if (svelteRequest.query.type === 'style' && !svelteRequest.raw) {
134
125
  // return cssId with root prefix so postcss pipeline of vite finds the directory correctly
135
126
  // see https://github.com/sveltejs/vite-plugin-svelte/issues/14
136
- log.debug(`resolveId resolved virtual css module ${svelteRequest.cssId}`);
137
- return svelteRequest.cssId;
138
- }
139
- }
140
-
141
- // TODO: remove this after bumping peerDep on Vite to 4.1+ or Svelte to 4.0+
142
- if (isVite4_0 && isSvelte3 && ssr && importee === 'svelte') {
143
- if (!resolvedSvelteSSR) {
144
- resolvedSvelteSSR = this.resolve('svelte/ssr', undefined, { skipSelf: true }).then(
145
- (svelteSSR) => {
146
- log.debug('resolved svelte to svelte/ssr');
147
- return svelteSSR;
148
- },
149
- (err) => {
150
- log.debug(
151
- 'failed to resolve svelte to svelte/ssr. Update svelte to a version that exports it',
152
- err
153
- );
154
- return null; // returning null here leads to svelte getting resolved regularly
155
- }
127
+ log.debug(
128
+ `resolveId resolved virtual css module ${svelteRequest.cssId}`,
129
+ undefined,
130
+ 'resolve'
156
131
  );
157
- }
158
- return resolvedSvelteSSR;
159
- }
160
- //@ts-expect-error scan
161
- const scan = !!opts?.scan; // scanner phase of optimizeDeps
162
- const isPrebundled =
163
- options.prebundleSvelteLibraries &&
164
- viteConfig.optimizeDeps?.disabled !== true &&
165
- viteConfig.optimizeDeps?.disabled !== (options.isBuild ? 'build' : 'dev') &&
166
- !isDepExcluded(importee, viteConfig.optimizeDeps?.exclude ?? []);
167
- // for prebundled libraries we let vite resolve the prebundling result
168
- // for ssr, during scanning and non-prebundled, we do it
169
- if (ssr || scan || !isPrebundled) {
170
- try {
171
- const isFirstResolve = !cache.hasResolvedSvelteField(importee, importer);
172
- const resolved = await resolveViaPackageJsonSvelte(importee, importer, cache);
173
- if (isFirstResolve && resolved) {
174
- const packageInfo = await cache.getPackageInfo(resolved);
175
- const packageVersion = `${packageInfo.name}@${packageInfo.version}`;
176
- log.debug.once(
177
- `resolveId resolved ${importee} to ${resolved} via package.json svelte field of ${packageVersion}`
178
- );
179
-
180
- try {
181
- const viteResolved = (
182
- await this.resolve(importee, importer, { ...opts, skipSelf: true })
183
- )?.id;
184
- if (resolved !== viteResolved) {
185
- packagesWithResolveWarnings.add(packageVersion);
186
- log.debug.enabled &&
187
- log.debug.once(
188
- `resolve difference for ${packageVersion} ${importee} - svelte: "${resolved}", vite: "${viteResolved}"`
189
- );
190
- }
191
- } catch (e) {
192
- packagesWithResolveWarnings.add(packageVersion);
193
- log.debug.enabled &&
194
- log.debug.once(
195
- `resolve error for ${packageVersion} ${importee} - svelte: "${resolved}", vite: ERROR`,
196
- e
197
- );
198
- }
199
- }
200
- return resolved;
201
- } catch (e) {
202
- log.debug.once(
203
- `error trying to resolve ${importee} from ${importer} via package.json svelte field `,
204
- e
205
- );
206
- // this error most likely happens due to non-svelte related importee/importers so swallow it here
207
- // in case it really way a svelte library, users will notice anyway. (lib not working due to failed resolve)
132
+ return svelteRequest.cssId;
208
133
  }
209
134
  }
210
135
  },
@@ -235,7 +160,6 @@ export function svelte(inlineOptions) {
235
160
  }
236
161
  }
237
162
  }
238
- log.debug(`transform returns compiled js for ${svelteRequest.filename}`);
239
163
  return {
240
164
  ...compileData.compiled.js,
241
165
  meta: {
@@ -257,16 +181,6 @@ export function svelte(inlineOptions) {
257
181
  },
258
182
  async buildEnd() {
259
183
  await options.stats?.finishAll();
260
- if (
261
- !options.experimental?.disableSvelteResolveWarnings &&
262
- packagesWithResolveWarnings?.size > 0
263
- ) {
264
- log.warn(
265
- `WARNING: The following packages use a svelte resolve configuration in package.json that has conflicting results and is going to cause problems future.\n\n${[
266
- ...packagesWithResolveWarnings
267
- ].join('\n')}\n\nPlease see ${FAQ_LINK_CONFLICTS_IN_SVELTE_RESOLVE} for details.`
268
- );
269
- }
270
184
  }
271
185
  },
272
186
  svelteInspector()
package/src/preprocess.js CHANGED
@@ -1,19 +1,18 @@
1
- import { preprocessCSS, resolveConfig, transformWithEsbuild } from 'vite';
1
+ import { isCSSRequest, preprocessCSS, resolveConfig, transformWithEsbuild } from 'vite';
2
2
  import { mapToRelative, removeLangSuffix } from './utils/sourcemaps.js';
3
3
 
4
4
  /**
5
5
  * @typedef {(code: string, filename: string) => Promise<{ code: string; map?: any; deps?: Set<string> }>} CssTransform
6
6
  */
7
7
 
8
- const supportedStyleLangs = ['css', 'less', 'sass', 'scss', 'styl', 'stylus', 'postcss', 'sss'];
9
8
  const supportedScriptLangs = ['ts'];
10
9
 
11
- export const lang_sep = '.vite-preprocess.';
10
+ export const lang_sep = '.vite-preprocess';
12
11
 
13
12
  /** @type {import('./index.d.ts').vitePreprocess} */
14
13
  export function vitePreprocess(opts) {
15
14
  /** @type {import('svelte/types/compiler/preprocess').PreprocessorGroup} */
16
- const preprocessor = {};
15
+ const preprocessor = { name: 'vite-preprocess' };
17
16
  if (opts?.script !== false) {
18
17
  preprocessor.script = viteScript().script;
19
18
  }
@@ -63,8 +62,8 @@ function viteStyle(config = {}) {
63
62
  let transform;
64
63
  /** @type {import('svelte/types/compiler/preprocess').Preprocessor} */
65
64
  const style = async ({ attributes, content, filename = '' }) => {
66
- const lang = /** @type {string} */ (attributes.lang);
67
- if (!supportedStyleLangs.includes(lang)) return;
65
+ const ext = attributes.lang ? `.${attributes.lang}` : '.css';
66
+ if (attributes.lang && !isCSSRequest(ext)) return;
68
67
  if (!transform) {
69
68
  /** @type {import('vite').ResolvedConfig} */
70
69
  let resolvedConfig;
@@ -82,7 +81,7 @@ function viteStyle(config = {}) {
82
81
  }
83
82
  transform = getCssTransformFn(resolvedConfig);
84
83
  }
85
- const suffix = `${lang_sep}${lang}`;
84
+ const suffix = `${lang_sep}${ext}`;
86
85
  const moduleId = `${filename}${suffix}`;
87
86
  const { code, map, deps } = await transform(content, moduleId);
88
87
  removeLangSuffix(map, suffix);
@@ -4,13 +4,15 @@ import { createMakeHot } from 'svelte-hmr';
4
4
  import { safeBase64Hash } from './hash.js';
5
5
  import { log } from './log.js';
6
6
 
7
- import { createInjectScopeEverythingRulePreprocessorGroup } from './preprocess.js';
7
+ import {
8
+ checkPreprocessDependencies,
9
+ createInjectScopeEverythingRulePreprocessorGroup
10
+ } from './preprocess.js';
8
11
  import { mapToRelative } from './sourcemaps.js';
12
+ import { enhanceCompileError } from './error.js';
9
13
 
10
14
  const scriptLangRE = /<script [^>]*lang=["']?([^"' >]+)["']?[^>]*>/;
11
15
 
12
- import { isSvelte3 } from './svelte-version.js';
13
-
14
16
  /**
15
17
  * @param {Function} [makeHot]
16
18
  * @returns {import('../types/compile.d.ts').CompileSvelte}
@@ -23,7 +25,10 @@ export const _createCompileSvelte = (makeHot) => {
23
25
  return async function compileSvelte(svelteRequest, code, options) {
24
26
  const { filename, normalizedFilename, cssId, ssr, raw } = svelteRequest;
25
27
  const { emitCss = true } = options;
28
+ /** @type {string[]} */
26
29
  const dependencies = [];
30
+ /** @type {import('svelte/types/compiler/interfaces').Warning[]} */
31
+ const warnings = [];
27
32
 
28
33
  if (options.stats) {
29
34
  if (options.isBuild) {
@@ -55,13 +60,9 @@ export const _createCompileSvelte = (makeHot) => {
55
60
  filename,
56
61
  generate: ssr ? 'ssr' : 'dom'
57
62
  };
58
- if (isSvelte3) {
59
- // @ts-ignore
60
- compileOptions.format = 'esm';
61
- }
63
+
62
64
  if (options.hot && options.emitCss) {
63
65
  const hash = `s-${safeBase64Hash(normalizedFilename)}`;
64
- log.debug(`setting cssHash ${hash} for ${normalizedFilename}`);
65
66
  compileOptions.cssHash = () => hash;
66
67
  }
67
68
  if (ssr && compileOptions.enableSourcemap !== false) {
@@ -92,7 +93,16 @@ export const _createCompileSvelte = (makeHot) => {
92
93
  throw e;
93
94
  }
94
95
 
95
- if (preprocessed.dependencies) dependencies.push(...preprocessed.dependencies);
96
+ if (preprocessed.dependencies?.length) {
97
+ const checked = checkPreprocessDependencies(filename, preprocessed.dependencies);
98
+ if (checked.warnings.length) {
99
+ warnings.push(...checked.warnings);
100
+ }
101
+ if (checked.dependencies.length) {
102
+ dependencies.push(...checked.dependencies);
103
+ }
104
+ }
105
+
96
106
  if (preprocessed.map) compileOptions.sourcemap = preprocessed.map;
97
107
  }
98
108
  if (typeof preprocessed?.map === 'object') {
@@ -105,14 +115,16 @@ export const _createCompileSvelte = (makeHot) => {
105
115
  };
106
116
  }
107
117
  const finalCode = preprocessed ? preprocessed.code : code;
108
- const dynamicCompileOptions = await options.experimental?.dynamicCompileOptions?.({
118
+ const dynamicCompileOptions = await options?.dynamicCompileOptions?.({
109
119
  filename,
110
120
  code: finalCode,
111
121
  compileOptions
112
122
  });
113
123
  if (dynamicCompileOptions && log.debug.enabled) {
114
124
  log.debug(
115
- `dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`
125
+ `dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`,
126
+ undefined,
127
+ 'compile'
116
128
  );
117
129
  }
118
130
  const finalCompileOptions = dynamicCompileOptions
@@ -123,22 +135,26 @@ export const _createCompileSvelte = (makeHot) => {
123
135
  : compileOptions;
124
136
 
125
137
  const endStat = stats?.start(filename);
126
- const compiled = compile(finalCode, finalCompileOptions);
127
-
128
- if (isSvelte3) {
129
- // prevent dangling pure comments
130
- // see https://github.com/sveltejs/kit/issues/9492#issuecomment-1487704985
131
- // uses regex replace with whitespace to keep sourcemap/character count unmodified
132
- compiled.js.code = compiled.js.code.replace(
133
- /\/\* [@#]__PURE__ \*\/(\s*)$/gm,
134
- ' $1'
135
- );
138
+ /** @type {import('svelte/types/compiler/interfaces').CompileResult} */
139
+ let compiled;
140
+ try {
141
+ compiled = compile(finalCode, finalCompileOptions);
142
+ } catch (e) {
143
+ enhanceCompileError(e, code, preprocessors);
144
+ throw e;
136
145
  }
146
+
137
147
  if (endStat) {
138
148
  endStat();
139
149
  }
140
150
  mapToRelative(compiled.js?.map, filename);
141
151
  mapToRelative(compiled.css?.map, filename);
152
+ if (warnings.length) {
153
+ if (!compiled.warnings) {
154
+ compiled.warnings = [];
155
+ }
156
+ compiled.warnings.push(...warnings);
157
+ }
142
158
  if (!raw) {
143
159
  // wire css import and code for hmr
144
160
  const hasCss = compiled.css?.code?.trim().length > 0;
@@ -1,5 +1,3 @@
1
- import { isSvelte3 } from './svelte-version.js';
2
-
3
1
  export const VITE_RESOLVE_MAIN_FIELDS = ['module', 'jsnext:main', 'jsnext'];
4
2
 
5
3
  export const SVELTE_RESOLVE_MAIN_FIELDS = ['svelte'];
@@ -8,16 +6,13 @@ export const SVELTE_IMPORTS = [
8
6
  'svelte/animate',
9
7
  'svelte/easing',
10
8
  'svelte/internal',
9
+ 'svelte/internal/disclose-version',
11
10
  'svelte/motion',
12
11
  'svelte/ssr',
13
12
  'svelte/store',
14
13
  'svelte/transition',
15
14
  'svelte'
16
15
  ];
17
- // TODO add to global list after dropping svelte 3
18
- if (!isSvelte3) {
19
- SVELTE_IMPORTS.push('svelte/internal/disclose-version');
20
- }
21
16
 
22
17
  export const SVELTE_HMR_IMPORTS = [
23
18
  'svelte-hmr/runtime/hot-api-esm.js',
@@ -27,5 +22,5 @@ export const SVELTE_HMR_IMPORTS = [
27
22
 
28
23
  export const SVELTE_EXPORT_CONDITIONS = ['svelte'];
29
24
 
30
- export const FAQ_LINK_CONFLICTS_IN_SVELTE_RESOLVE =
31
- 'https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#conflicts-in-svelte-resolve';
25
+ export const FAQ_LINK_MISSING_EXPORTS_CONDITION =
26
+ 'https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#missing-exports-condition';
@@ -100,3 +100,79 @@ function formatFrameForVite(frame) {
100
100
  .map((line) => (line.match(/^\s+\^/) ? ' ' + line : ' ' + line.replace(':', ' | ')))
101
101
  .join('\n');
102
102
  }
103
+
104
+ /**
105
+ * @param {import('svelte/types/compiler/interfaces').Warning & Error} err a svelte compiler error, which is a mix of Warning and an error
106
+ * @param {string} originalCode
107
+ * @param {import('../index.js').Arrayable<import('svelte/types/compiler/preprocess').PreprocessorGroup>} [preprocessors]
108
+ */
109
+ export function enhanceCompileError(err, originalCode, preprocessors) {
110
+ preprocessors = arraify(preprocessors ?? []);
111
+
112
+ /** @type {string[]} */
113
+ const additionalMessages = [];
114
+
115
+ // Handle incorrect TypeScript usage
116
+ if (err.code === 'parse-error') {
117
+ // Reference from Svelte: https://github.com/sveltejs/svelte/blob/800f6c076be5dd87dd4d2e9d66c59b973d54d84b/packages/svelte/src/compiler/preprocess/index.js#L262
118
+ const scriptRe = /<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi;
119
+ const errIndex = err.pos ?? -1;
120
+
121
+ let m;
122
+ while ((m = scriptRe.exec(originalCode))) {
123
+ const matchStart = m.index;
124
+ const matchEnd = matchStart + m[0].length;
125
+ const isErrorInScript = matchStart <= errIndex && errIndex <= matchEnd;
126
+ if (isErrorInScript) {
127
+ // Warn missing lang="ts"
128
+ const hasLangTs = m[1]?.includes('lang="ts"');
129
+ if (!hasLangTs) {
130
+ additionalMessages.push('Did you forget to add lang="ts" to your script tag?');
131
+ }
132
+ // Warn missing script preprocessor
133
+ if (preprocessors.every((p) => p.script == null)) {
134
+ const preprocessorType = hasLangTs ? 'TypeScript' : 'script';
135
+ additionalMessages.push(
136
+ `Did you forget to add a ${preprocessorType} preprocessor? See https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/preprocess.md for more information.`
137
+ );
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ // Handle incorrect CSS preprocessor usage
144
+ if (err.code === 'css-syntax-error') {
145
+ const styleRe = /<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi;
146
+
147
+ let m;
148
+ while ((m = styleRe.exec(originalCode))) {
149
+ // Warn missing lang attribute
150
+ if (!m[1]?.includes('lang=')) {
151
+ additionalMessages.push('Did you forget to add a lang attribute to your style tag?');
152
+ }
153
+ // Warn missing style preprocessor
154
+ if (
155
+ preprocessors.every((p) => p.style == null || p.name === 'inject-scope-everything-rule')
156
+ ) {
157
+ const preprocessorType = m[1]?.match(/lang="(.+?)"/)?.[1] ?? 'style';
158
+ additionalMessages.push(
159
+ `Did you forget to add a ${preprocessorType} preprocessor? See https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/preprocess.md for more information.`
160
+ );
161
+ }
162
+ }
163
+ }
164
+
165
+ if (additionalMessages.length) {
166
+ err.message += '\n\n- ' + additionalMessages.join('\n- ');
167
+ }
168
+
169
+ return err;
170
+ }
171
+
172
+ /**
173
+ * @param {T | T[]} value
174
+ * @template T
175
+ */
176
+ function arraify(value) {
177
+ return Array.isArray(value) ? value : [value];
178
+ }
@@ -2,7 +2,6 @@ import { readFileSync } from 'node:fs';
2
2
  import { compile, preprocess } from 'svelte/compiler';
3
3
  import { log } from './log.js';
4
4
  import { toESBuildError } from './error.js';
5
- import { isSvelte3 } from './svelte-version.js';
6
5
 
7
6
  /**
8
7
  * @typedef {NonNullable<import('vite').DepOptimizationOptions['esbuildOptions']>} EsbuildOptions
@@ -67,10 +66,7 @@ async function compileSvelte(options, { filename, code }, statsCollection) {
67
66
  filename,
68
67
  generate: 'dom'
69
68
  };
70
- if (isSvelte3) {
71
- // @ts-ignore
72
- compileOptions.format = 'esm';
73
- }
69
+
74
70
  let preprocessed;
75
71
 
76
72
  if (options.preprocess) {
@@ -85,14 +81,18 @@ async function compileSvelte(options, { filename, code }, statsCollection) {
85
81
 
86
82
  const finalCode = preprocessed ? preprocessed.code : code;
87
83
 
88
- const dynamicCompileOptions = await options.experimental?.dynamicCompileOptions?.({
84
+ const dynamicCompileOptions = await options?.dynamicCompileOptions?.({
89
85
  filename,
90
86
  code: finalCode,
91
87
  compileOptions
92
88
  });
93
89
 
94
90
  if (dynamicCompileOptions && log.debug.enabled) {
95
- log.debug(`dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`);
91
+ log.debug(
92
+ `dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`,
93
+ undefined,
94
+ 'compile'
95
+ );
96
96
  }
97
97
 
98
98
  const finalCompileOptions = dynamicCompileOptions
@@ -64,7 +64,7 @@ export async function loadRaw(svelteRequest, compileSvelte, options) {
64
64
  }" combined with direct in ${id}. supported are: ${supportedDirectTypes.join(', ')}`
65
65
  );
66
66
  }
67
- log.debug(`load returns direct result for ${id}`);
67
+ log.debug(`load returns direct result for ${id}`, undefined, 'load');
68
68
  let directOutput = result.code;
69
69
  if (query.sourcemap && result.map?.toUrl) {
70
70
  const map = `sourceMappingURL=${result.map.toUrl()}`;
@@ -76,7 +76,7 @@ export async function loadRaw(svelteRequest, compileSvelte, options) {
76
76
  }
77
77
  return directOutput;
78
78
  } else if (query.raw) {
79
- log.debug(`load returns raw result for ${id}`);
79
+ log.debug(`load returns raw result for ${id}`, undefined, 'load');
80
80
  return toRawExports(result);
81
81
  } else {
82
82
  throw new Error(`invalid raw mode in ${id}, supported are raw, direct`);
@@ -59,7 +59,8 @@ export async function loadSvelteConfig(viteConfig, inlineOptions) {
59
59
  // identify which require function to use (esm and cjs mode)
60
60
  const _require = import.meta.url
61
61
  ? esmRequire ?? (esmRequire = createRequire(import.meta.url))
62
- : require;
62
+ : // eslint-disable-next-line no-undef
63
+ require;
63
64
 
64
65
  // avoid loading cached version on reload
65
66
  delete _require.cache[_require.resolve(configFile)];
@@ -104,7 +105,7 @@ function findConfigToLoad(viteConfig, inlineOptions) {
104
105
  .map((candidate) => path.resolve(root, candidate))
105
106
  .filter((file) => fs.existsSync(file));
106
107
  if (existingKnownConfigFiles.length === 0) {
107
- log.debug(`no svelte config found at ${root}`);
108
+ log.debug(`no svelte config found at ${root}`, undefined, 'config');
108
109
  return;
109
110
  } else if (existingKnownConfigFiles.length > 1) {
110
111
  log.warn(
package/src/utils/log.js CHANGED
@@ -8,7 +8,7 @@ const prefix = 'vite-plugin-svelte';
8
8
  /** @type {Record<import('../types/log.d.ts').LogLevel, any>} */
9
9
  const loggers = {
10
10
  debug: {
11
- log: debug(`vite:${prefix}`),
11
+ log: debug(`${prefix}`),
12
12
  enabled: false,
13
13
  isDebug: true
14
14
  },
@@ -65,7 +65,13 @@ function _log(logger, message, payload, namespace) {
65
65
  return;
66
66
  }
67
67
  if (logger.isDebug) {
68
- const log = namespace ? logger.log.extend(namespace) : logger.log;
68
+ let log = logger.log;
69
+ if (namespace) {
70
+ if (!isDebugNamespaceEnabled(namespace)) {
71
+ return;
72
+ }
73
+ log = logger.log.extend(namespace);
74
+ }
69
75
  payload !== undefined ? log(message, payload) : log(message);
70
76
  } else {
71
77
  logger.log(
@@ -251,5 +257,5 @@ export function buildExtendedLogMessage(w) {
251
257
  * @returns {boolean}
252
258
  */
253
259
  export function isDebugNamespaceEnabled(namespace) {
254
- return debug.enabled(`vite:${prefix}:${namespace}`);
260
+ return debug.enabled(`${prefix}:${namespace}`);
255
261
  }
@@ -3,6 +3,7 @@ import { normalizePath } from 'vite';
3
3
  import { isDebugNamespaceEnabled, log } from './log.js';
4
4
  import { loadSvelteConfig } from './load-svelte-config.js';
5
5
  import {
6
+ FAQ_LINK_MISSING_EXPORTS_CONDITION,
6
7
  SVELTE_EXPORT_CONDITIONS,
7
8
  SVELTE_HMR_IMPORTS,
8
9
  SVELTE_IMPORTS,
@@ -35,6 +36,7 @@ const allowedPluginOptions = new Set([
35
36
  'disableDependencyReinclusion',
36
37
  'prebundleSvelteLibraries',
37
38
  'inspector',
39
+ 'dynamicCompileOptions',
38
40
  'experimental'
39
41
  ]);
40
42
 
@@ -315,13 +317,13 @@ function removeIgnoredOptions(options) {
315
317
  function handleDeprecatedOptions(options) {
316
318
  const experimental = /** @type {Record<string, any>} */ (options.experimental);
317
319
  if (experimental) {
318
- for (const promoted of ['prebundleSvelteLibraries', 'inspector']) {
320
+ for (const promoted of ['prebundleSvelteLibraries', 'inspector', 'dynamicCompileOptions']) {
319
321
  if (experimental[promoted]) {
320
322
  //@ts-expect-error untyped assign
321
323
  options[promoted] = experimental[promoted];
322
324
  delete experimental[promoted];
323
325
  log.warn(
324
- `Option "vitePlugin.experimental.${promoted}" is no longer experimental and has moved to "vitePlugin.${promoted}". Please update your svelte config.`
326
+ `Option "experimental.${promoted}" is no longer experimental and has moved to "${promoted}". Please update your Svelte or Vite config.`
325
327
  );
326
328
  }
327
329
  }
@@ -428,7 +430,7 @@ export async function buildExtraViteConfig(options, config) {
428
430
  (options.hot && options.hot.partialAccept !== false)) && // deviate from svelte-hmr, default to true
429
431
  config.experimental?.hmrPartialAccept !== false
430
432
  ) {
431
- log.debug('enabling "experimental.hmrPartialAccept" in vite config');
433
+ log.debug('enabling "experimental.hmrPartialAccept" in vite config', undefined, 'config');
432
434
  extraViteConfig.experimental = { hmrPartialAccept: true };
433
435
  }
434
436
  validateViteConfig(extraViteConfig, config, options);
@@ -481,6 +483,7 @@ function validateViteConfig(extraViteConfig, config, options) {
481
483
  */
482
484
  async function buildExtraConfigForDependencies(options, config) {
483
485
  // extra handling for svelte dependencies in the project
486
+ const packagesWithoutSvelteExportsCondition = new Set();
484
487
  const depsConfig = await crawlFrameworkPkgs({
485
488
  root: options.root,
486
489
  isBuild: options.isBuild,
@@ -496,7 +499,11 @@ async function buildExtraConfigForDependencies(options, config) {
496
499
  return value;
497
500
  });
498
501
  }
499
- return hasSvelteCondition || !!pkgJson.svelte;
502
+ const hasSvelteField = !!pkgJson.svelte;
503
+ if (hasSvelteField && !hasSvelteCondition) {
504
+ packagesWithoutSvelteExportsCondition.add(`${pkgJson.name}@${pkgJson.version}`);
505
+ }
506
+ return hasSvelteCondition || hasSvelteField;
500
507
  },
501
508
  isSemiFrameworkPkgByJson(pkgJson) {
502
509
  return !!pkgJson.dependencies?.svelte || !!pkgJson.peerDependencies?.svelte;
@@ -510,8 +517,17 @@ async function buildExtraConfigForDependencies(options, config) {
510
517
  }
511
518
  }
512
519
  });
513
-
514
- log.debug('extra config for dependencies generated by vitefu', depsConfig);
520
+ if (
521
+ !options.experimental?.disableSvelteResolveWarnings &&
522
+ packagesWithoutSvelteExportsCondition?.size > 0
523
+ ) {
524
+ log.warn(
525
+ `WARNING: The following packages have a svelte field in their package.json but no exports condition for svelte.\n\n${[
526
+ ...packagesWithoutSvelteExportsCondition
527
+ ].join('\n')}\n\nPlease see ${FAQ_LINK_MISSING_EXPORTS_CONDITION} for details.`
528
+ );
529
+ }
530
+ log.debug('extra config for dependencies generated by vitefu', depsConfig, 'config');
515
531
 
516
532
  if (options.prebundleSvelteLibraries) {
517
533
  // prebundling enabled, so we don't need extra dependency excludes
@@ -545,7 +561,7 @@ async function buildExtraConfigForDependencies(options, config) {
545
561
  });
546
562
  }
547
563
 
548
- log.debug('post-processed extra config for dependencies', depsConfig);
564
+ log.debug('post-processed extra config for dependencies', depsConfig, 'config');
549
565
 
550
566
  return depsConfig;
551
567
  }
@@ -562,11 +578,17 @@ function buildExtraConfigForSvelte(config) {
562
578
  if (!isDepExcluded('svelte', config.optimizeDeps?.exclude ?? [])) {
563
579
  const svelteImportsToInclude = SVELTE_IMPORTS.filter((x) => x !== 'svelte/ssr'); // not used on clientside
564
580
  log.debug(
565
- `adding bare svelte packages to optimizeDeps.include: ${svelteImportsToInclude.join(', ')} `
581
+ `adding bare svelte packages to optimizeDeps.include: ${svelteImportsToInclude.join(', ')} `,
582
+ undefined,
583
+ 'config'
566
584
  );
567
585
  include.push(...svelteImportsToInclude);
568
586
  } else {
569
- log.debug('"svelte" is excluded in optimizeDeps.exclude, skipped adding it to include.');
587
+ log.debug(
588
+ '"svelte" is excluded in optimizeDeps.exclude, skipped adding it to include.',
589
+ undefined,
590
+ 'config'
591
+ );
570
592
  }
571
593
  /** @type {(string | RegExp)[]} */
572
594
  const noExternal = [];
@@ -1,6 +1,7 @@
1
1
  import MagicString from 'magic-string';
2
2
  import { log } from './log.js';
3
3
  import path from 'node:path';
4
+ import { normalizePath } from 'vite';
4
5
 
5
6
  /**
6
7
  * this appends a *{} rule to component styles to force the svelte compiler to add style classes to all nodes
@@ -12,6 +13,7 @@ import path from 'node:path';
12
13
  */
13
14
  export function createInjectScopeEverythingRulePreprocessorGroup() {
14
15
  return {
16
+ name: 'inject-scope-everything-rule',
15
17
  style({ content, filename }) {
16
18
  const s = new MagicString(content);
17
19
  s.append(' *{}');
@@ -84,14 +86,18 @@ function buildExtraPreprocessors(options, config) {
84
86
  log.debug(
85
87
  `Ignoring svelte preprocessors defined by these vite plugins: ${ignored
86
88
  .map((p) => p.name)
87
- .join(', ')}`
89
+ .join(', ')}`,
90
+ undefined,
91
+ 'preprocess'
88
92
  );
89
93
  }
90
94
  if (included.length > 0) {
91
95
  log.debug(
92
96
  `Adding svelte preprocessors defined by these vite plugins: ${included
93
97
  .map((p) => p.name)
94
- .join(', ')}`
98
+ .join(', ')}`,
99
+ undefined,
100
+ 'preprocess'
95
101
  );
96
102
  appendPreprocessors.push(...pluginsWithPreprocessors.map((p) => p.api.sveltePreprocess));
97
103
  }
@@ -116,3 +122,52 @@ export function addExtraPreprocessors(options, config) {
116
122
  }
117
123
  }
118
124
  }
125
+
126
+ /**
127
+ *
128
+ * @param filename {string}
129
+ * @param dependencies {string[]}
130
+ * @returns {({dependencies: string[], warnings:import('svelte/types/compiler/interfaces').Warning[] })}
131
+ */
132
+ export function checkPreprocessDependencies(filename, dependencies) {
133
+ /** @type {import('svelte/types/compiler/interfaces').Warning[]} */
134
+ const warnings = [];
135
+
136
+ // to find self, we have to compare normalized filenames, but must keep the original values in `dependencies`
137
+ // because otherwise file watching on windows doesn't work
138
+ // so we track idx and filter by that in the end
139
+ /** @type {number[]} */
140
+ const selfIdx = [];
141
+ const normalizedFullFilename = normalizePath(filename);
142
+ const normalizedDeps = dependencies.map(normalizePath);
143
+ for (let i = 0; i < normalizedDeps.length; i++) {
144
+ if (normalizedDeps[i] === normalizedFullFilename) {
145
+ selfIdx.push(i);
146
+ }
147
+ }
148
+ const hasSelfDependency = selfIdx.length > 0;
149
+ if (hasSelfDependency) {
150
+ warnings.push({
151
+ code: 'vite-plugin-svelte-preprocess-depends-on-self',
152
+ message:
153
+ 'svelte.preprocess returned this file as a dependency of itself. This can be caused by an invalid configuration or importing generated code that depends on .svelte files (eg. tailwind base css)',
154
+ filename
155
+ });
156
+ }
157
+
158
+ if (dependencies.length > 10) {
159
+ warnings.push({
160
+ code: 'vite-plugin-svelte-preprocess-many-dependencies',
161
+ message: `svelte.preprocess depends on more than 10 external files which can cause slow builds and poor DX, try to reduce them. Found: ${dependencies.join(
162
+ ', '
163
+ )}`,
164
+ filename
165
+ });
166
+ }
167
+ return {
168
+ dependencies: hasSelfDependency
169
+ ? dependencies.filter((_, i) => !selfIdx.includes(i)) // remove self dependency
170
+ : dependencies,
171
+ warnings
172
+ };
173
+ }
@@ -24,8 +24,6 @@ export class VitePluginSvelteCache {
24
24
  #dependencies = new Map();
25
25
  /** @type {Map<string, Set<string>>} */
26
26
  #dependants = new Map();
27
- /** @type {Map<string, string>} */
28
- #resolvedSvelteFields = new Map();
29
27
  /** @type {Map<string, any>} */
30
28
  #errors = new Map();
31
29
  /** @type {PackageInfo[]} */
@@ -168,45 +166,6 @@ export class VitePluginSvelteCache {
168
166
  return dependants ? [...dependants] : [];
169
167
  }
170
168
 
171
- /**
172
- * @param {string} name
173
- * @param {string} [importer]
174
- * @returns {string|void}
175
- */
176
- getResolvedSvelteField(name, importer) {
177
- return this.#resolvedSvelteFields.get(this.#getResolvedSvelteFieldKey(name, importer));
178
- }
179
-
180
- /**
181
- * @param {string} name
182
- * @param {string} [importer]
183
- * @returns {boolean}
184
- */
185
- hasResolvedSvelteField(name, importer) {
186
- return this.#resolvedSvelteFields.has(this.#getResolvedSvelteFieldKey(name, importer));
187
- }
188
- /**
189
- *
190
- * @param {string} importee
191
- * @param {string | undefined} importer
192
- * @param {string} resolvedSvelte
193
- */
194
- setResolvedSvelteField(importee, importer, resolvedSvelte) {
195
- this.#resolvedSvelteFields.set(
196
- this.#getResolvedSvelteFieldKey(importee, importer),
197
- resolvedSvelte
198
- );
199
- }
200
-
201
- /**
202
- * @param {string} importee
203
- * @param {string | undefined} importer
204
- * @returns {string}
205
- */
206
- #getResolvedSvelteFieldKey(importee, importer) {
207
- return importer ? `${importer} > ${importee}` : importee;
208
- }
209
-
210
169
  /**
211
170
  * @param {string} file
212
171
  * @returns {Promise<PackageInfo>}
@@ -22,7 +22,9 @@ export function setupWatchers(options, cache, requestParser) {
22
22
  dependants.forEach((dependant) => {
23
23
  if (fs.existsSync(dependant)) {
24
24
  log.debug(
25
- `emitting virtual change event for "${dependant}" because depdendency "${filename}" changed`
25
+ `emitting virtual change event for "${dependant}" because depdendency "${filename}" changed`,
26
+ undefined,
27
+ 'hmr'
26
28
  );
27
29
  watcher.emit('change', dependant);
28
30
  }
@@ -34,7 +36,7 @@ export function setupWatchers(options, cache, requestParser) {
34
36
  if (svelteRequest) {
35
37
  const removedFromCache = cache.remove(svelteRequest);
36
38
  if (removedFromCache) {
37
- log.debug(`cleared VitePluginSvelteCache for deleted file ${filename}`);
39
+ log.debug(`cleared VitePluginSvelteCache for deleted file ${filename}`, undefined, 'hmr');
38
40
  }
39
41
  }
40
42
  };
@@ -1,66 +0,0 @@
1
- import path from 'node:path';
2
- import { builtinModules } from 'node:module';
3
- import { resolveDependencyData, isCommonDepWithoutSvelteField } from './dependencies.js';
4
- import { normalizePath } from 'vite';
5
-
6
- /**
7
- * @param {string} importee
8
- * @param {string | undefined} importer
9
- * @param {import('./vite-plugin-svelte-cache').VitePluginSvelteCache} cache
10
- * @returns {Promise<string | void>}
11
- */
12
- export async function resolveViaPackageJsonSvelte(importee, importer, cache) {
13
- if (
14
- importer &&
15
- isBareImport(importee) &&
16
- !isNodeInternal(importee) &&
17
- !isCommonDepWithoutSvelteField(importee)
18
- ) {
19
- const cached = cache.getResolvedSvelteField(importee, importer);
20
- if (cached) {
21
- return cached;
22
- }
23
- const pkgData = await resolveDependencyData(importee, importer);
24
- if (pkgData) {
25
- const { pkg, dir } = pkgData;
26
- if (pkg.svelte) {
27
- const result = normalizePath(path.resolve(dir, pkg.svelte));
28
- cache.setResolvedSvelteField(importee, importer, result);
29
- return result;
30
- }
31
- }
32
- }
33
- }
34
-
35
- /**
36
- * @param {string} importee
37
- * @returns {boolean}
38
- */
39
- function isNodeInternal(importee) {
40
- return importee.startsWith('node:') || builtinModules.includes(importee);
41
- }
42
-
43
- /**
44
- * @param {string} importee
45
- * @returns {boolean}
46
- */
47
- function isBareImport(importee) {
48
- if (
49
- !importee ||
50
- importee[0] === '.' ||
51
- importee[0] === '\0' ||
52
- importee.includes(':') ||
53
- path.isAbsolute(importee)
54
- ) {
55
- return false;
56
- }
57
- const parts = importee.split('/');
58
- switch (parts.length) {
59
- case 1:
60
- return true;
61
- case 2:
62
- return parts[0].startsWith('@');
63
- default:
64
- return false;
65
- }
66
- }