@sveltejs/vite-plugin-svelte 1.0.0-next.42 → 1.0.0-next.45

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.
@@ -0,0 +1,106 @@
1
+ import { Plugin, normalizePath } from 'vite';
2
+ import { log } from '../../utils/log';
3
+ import { InspectorOptions } from '../../utils/options';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import fs from 'fs';
7
+
8
+ const defaultInspectorOptions: InspectorOptions = {
9
+ toggleKeyCombo: process.platform === 'win32' ? 'control-shift' : 'meta-shift',
10
+ holdMode: false,
11
+ showToggleButton: 'active',
12
+ toggleButtonPos: 'top-right',
13
+ customStyles: true
14
+ };
15
+
16
+ function getInspectorPath() {
17
+ const pluginPath = normalizePath(path.dirname(fileURLToPath(import.meta.url)));
18
+ return pluginPath.replace(/\/vite-plugin-svelte\/dist$/, '/vite-plugin-svelte/src/ui/inspector/');
19
+ }
20
+
21
+ export function svelteInspector(): Plugin {
22
+ const inspectorPath = getInspectorPath();
23
+ log.debug.enabled && log.debug(`svelte inspector path: ${inspectorPath}`);
24
+ let inspectorOptions: InspectorOptions;
25
+ let appendTo: string | undefined;
26
+ let disabled = false;
27
+
28
+ return {
29
+ name: 'vite-plugin-svelte:inspector',
30
+ apply: 'serve',
31
+ enforce: 'pre',
32
+
33
+ configResolved(config) {
34
+ const vps = config.plugins.find((p) => p.name === 'vite-plugin-svelte');
35
+ if (vps?.api?.options?.experimental?.inspector) {
36
+ inspectorOptions = {
37
+ ...defaultInspectorOptions,
38
+ ...vps.api.options.experimental.inspector
39
+ };
40
+ }
41
+ if (!vps || !inspectorOptions) {
42
+ log.debug('inspector disabled, could not find config');
43
+ disabled = true;
44
+ } else {
45
+ if (vps.api.options.kit && !inspectorOptions.appendTo) {
46
+ const out_dir = path.basename(vps.api.options.kit.outDir || '.svelte-kit');
47
+ inspectorOptions.appendTo = `${out_dir}/runtime/client/start.js`;
48
+ }
49
+ appendTo = inspectorOptions.appendTo;
50
+ }
51
+ },
52
+
53
+ async resolveId(importee: string, importer, options) {
54
+ if (options?.ssr || disabled) {
55
+ return;
56
+ }
57
+ if (importee.startsWith('virtual:svelte-inspector-options')) {
58
+ return importee;
59
+ } else if (importee.startsWith('virtual:svelte-inspector-path:')) {
60
+ const resolved = importee.replace('virtual:svelte-inspector-path:', inspectorPath);
61
+ log.debug.enabled && log.debug(`resolved ${importee} with ${resolved}`);
62
+ return resolved;
63
+ }
64
+ },
65
+
66
+ async load(id, options) {
67
+ if (options?.ssr || disabled) {
68
+ return;
69
+ }
70
+ if (id === 'virtual:svelte-inspector-options') {
71
+ return `export default ${JSON.stringify(inspectorOptions ?? {})}`;
72
+ } else if (id.startsWith(inspectorPath)) {
73
+ // read file ourselves to avoid getting shut out by vites fs.allow check
74
+ return await fs.promises.readFile(id, 'utf-8');
75
+ }
76
+ },
77
+
78
+ transform(code: string, id: string, options?: { ssr?: boolean }) {
79
+ if (options?.ssr || disabled || !appendTo) {
80
+ return;
81
+ }
82
+ if (id.endsWith(appendTo)) {
83
+ return { code: `${code}\nimport 'virtual:svelte-inspector-path:load-inspector.js'` };
84
+ }
85
+ },
86
+ transformIndexHtml(html) {
87
+ if (disabled || appendTo) {
88
+ return;
89
+ }
90
+ return {
91
+ html,
92
+ tags: [
93
+ {
94
+ tag: 'script',
95
+ injectTo: 'body',
96
+ attrs: {
97
+ type: 'module',
98
+ // /@id/ is needed, otherwise the virtual: is seen as protocol by browser and cors error happens
99
+ src: '/@id/virtual:svelte-inspector-path:load-inspector.js'
100
+ }
101
+ }
102
+ ]
103
+ };
104
+ }
105
+ };
106
+ }
@@ -1,6 +1,10 @@
1
+ import { describe, it, expect } from 'vitest';
1
2
  import { findRootSvelteDependencies, needsOptimization } from '../dependencies';
2
3
  import * as path from 'path';
3
4
  import { createRequire } from 'module';
5
+ import { fileURLToPath } from 'url';
6
+ const __dir = path.dirname(fileURLToPath(import.meta.url));
7
+ const e2eTestRoot = path.resolve(__dir, '../../../../../packages/e2e-tests');
4
8
 
5
9
  describe('dependencies', () => {
6
10
  describe('findRootSvelteDependencies', () => {
@@ -11,9 +15,7 @@ describe('dependencies', () => {
11
15
  expect(deps[0].path).toEqual([]);
12
16
  });
13
17
  it('should find nested svelte dependencies in packages/e2e-test/package-json-svelte-field', () => {
14
- const deps = findRootSvelteDependencies(
15
- path.resolve('packages/e2e-tests/package-json-svelte-field')
16
- );
18
+ const deps = findRootSvelteDependencies(path.join(e2eTestRoot, 'package-json-svelte-field'));
17
19
  expect(deps).toHaveLength(3);
18
20
  const hybrid = deps.find((dep) => dep.name === 'e2e-test-dep-svelte-hybrid');
19
21
  expect(hybrid).toBeTruthy();
@@ -29,7 +31,8 @@ describe('dependencies', () => {
29
31
  });
30
32
  describe('needsOptimization', () => {
31
33
  it('should optimize cjs deps only', () => {
32
- const localRequire = createRequire(path.resolve('packages/e2e-tests/dependencies'));
34
+ const testDepsPath = path.join(e2eTestRoot, 'dependencies/package.json');
35
+ const localRequire = createRequire(testDepsPath);
33
36
  expect(needsOptimization('e2e-test-dep-cjs-and-esm', localRequire)).toBe(false);
34
37
  expect(needsOptimization('e2e-test-dep-cjs-only', localRequire)).toBe(true);
35
38
  expect(needsOptimization('e2e-test-dep-esm-only', localRequire)).toBe(false);
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect } from 'vitest';
1
2
  import { buildMagicString, buildSourceMap } from '../sourcemap';
2
3
 
3
4
  describe('sourcemap', () => {
@@ -6,6 +6,8 @@ import { SvelteRequest } from './id';
6
6
  import { safeBase64Hash } from './hash';
7
7
  import { log } from './log';
8
8
 
9
+ const scriptLangRE = /<script [^>]*lang=["']?([^"' >]+)["']?[^>]*>/;
10
+
9
11
  const _createCompileSvelte = (makeHot: Function) =>
10
12
  async function compileSvelte(
11
13
  svelteRequest: SvelteRequest,
@@ -19,7 +21,8 @@ const _createCompileSvelte = (makeHot: Function) =>
19
21
  const compileOptions: CompileOptions = {
20
22
  ...options.compilerOptions,
21
23
  filename,
22
- generate: ssr ? 'ssr' : 'dom'
24
+ generate: ssr ? 'ssr' : 'dom',
25
+ format: 'esm'
23
26
  };
24
27
  if (options.hot && options.emitCss) {
25
28
  const hash = `s-${safeBase64Hash(normalizedFilename)}`;
@@ -88,6 +91,7 @@ const _createCompileSvelte = (makeHot: Function) =>
88
91
  return {
89
92
  filename,
90
93
  normalizedFilename,
94
+ lang: code.match(scriptLangRE)?.[1] || 'js',
91
95
  // @ts-ignore
92
96
  compiled,
93
97
  ssr,
@@ -148,6 +152,7 @@ export interface Compiled {
148
152
  export interface CompileData {
149
153
  filename: string;
150
154
  normalizedFilename: string;
155
+ lang: string;
151
156
  compiled: Compiled;
152
157
  ssr: boolean | undefined;
153
158
  dependencies: string[];
@@ -24,6 +24,7 @@ import { findRootSvelteDependencies, needsOptimization, SvelteDependency } from
24
24
  import { createRequire } from 'module';
25
25
  import { esbuildSveltePlugin, facadeEsbuildSveltePluginName } from './esbuild';
26
26
  import { addExtraPreprocessors } from './preprocess';
27
+ import deepmerge from 'deepmerge';
27
28
 
28
29
  const knownOptions = new Set([
29
30
  'configFile',
@@ -37,7 +38,8 @@ const knownOptions = new Set([
37
38
  'hot',
38
39
  'ignorePluginPreprocessors',
39
40
  'disableDependencyReinclusion',
40
- 'experimental'
41
+ 'experimental',
42
+ 'kit'
41
43
  ]);
42
44
 
43
45
  export function validateInlineOptions(inlineOptions?: Partial<Options>) {
@@ -59,32 +61,22 @@ export async function preResolveOptions(
59
61
  };
60
62
  const defaultOptions: Partial<Options> = {
61
63
  extensions: ['.svelte'],
62
- emitCss: true,
63
- compilerOptions: {
64
- format: 'esm'
65
- }
64
+ emitCss: true
66
65
  };
67
66
  const svelteConfig = await loadSvelteConfig(viteConfigWithResolvedRoot, inlineOptions);
68
- const merged = {
69
- ...defaultOptions,
70
- ...svelteConfig,
71
- ...inlineOptions,
72
- compilerOptions: {
73
- ...defaultOptions?.compilerOptions,
74
- ...svelteConfig?.compilerOptions,
75
- ...inlineOptions?.compilerOptions
76
- },
77
- experimental: {
78
- ...defaultOptions?.experimental,
79
- ...svelteConfig?.experimental,
80
- ...inlineOptions?.experimental
81
- },
82
- // extras
67
+ const extraOptions: Partial<PreResolvedOptions> = {
83
68
  root: viteConfigWithResolvedRoot.root!,
84
69
  isBuild: viteEnv.command === 'build',
85
70
  isServe: viteEnv.command === 'serve',
86
71
  isDebug: process.env.DEBUG != null
87
72
  };
73
+ const merged = mergeConfigs<Partial<PreResolvedOptions> | undefined>(
74
+ defaultOptions,
75
+ svelteConfig,
76
+ inlineOptions,
77
+ extraOptions
78
+ );
79
+
88
80
  // configFile of svelteConfig contains the absolute path it was loaded from,
89
81
  // prefer it over the possibly relative inline path
90
82
  if (svelteConfig?.configFile) {
@@ -93,6 +85,17 @@ export async function preResolveOptions(
93
85
  return merged;
94
86
  }
95
87
 
88
+ function mergeConfigs<T>(...configs: T[]): ResolvedOptions {
89
+ let result = {};
90
+ for (const config of configs.filter(Boolean)) {
91
+ result = deepmerge<T>(result, config, {
92
+ // replace arrays
93
+ arrayMerge: (target: any[], source: any[]) => source ?? target
94
+ });
95
+ }
96
+ return result as ResolvedOptions;
97
+ }
98
+
96
99
  // used in configResolved phase, merges a contextual default config, pre-resolved options, and some preprocessors.
97
100
  // also validates the final config.
98
101
  export function resolveOptions(
@@ -106,16 +109,13 @@ export function resolveOptions(
106
109
  dev: !viteConfig.isProduction
107
110
  }
108
111
  };
109
- const merged: ResolvedOptions = {
110
- ...defaultOptions,
111
- ...preResolveOptions,
112
- compilerOptions: {
113
- ...defaultOptions.compilerOptions,
114
- ...preResolveOptions.compilerOptions
115
- },
112
+ const extraOptions: Partial<ResolvedOptions> = {
116
113
  root: viteConfig.root,
117
114
  isProduction: viteConfig.isProduction
118
115
  };
116
+ const merged: ResolvedOptions = mergeConfigs(defaultOptions, preResolveOptions, extraOptions);
117
+
118
+ removeIgnoredOptions(merged);
119
119
  addExtraPreprocessors(merged, viteConfig);
120
120
  enforceOptionsForHmr(merged);
121
121
  enforceOptionsForProduction(merged);
@@ -175,6 +175,26 @@ function enforceOptionsForProduction(options: ResolvedOptions) {
175
175
  }
176
176
  }
177
177
 
178
+ function removeIgnoredOptions(options: ResolvedOptions) {
179
+ const ignoredCompilerOptions = ['generate', 'format', 'filename'];
180
+ if (options.hot && options.emitCss) {
181
+ ignoredCompilerOptions.push('cssHash');
182
+ }
183
+ const passedCompilerOptions = Object.keys(options.compilerOptions || {});
184
+ const passedIgnored = passedCompilerOptions.filter((o) => ignoredCompilerOptions.includes(o));
185
+ if (passedIgnored.length) {
186
+ log.warn(
187
+ `The following Svelte compilerOptions are controlled by vite-plugin-svelte and essential to its functionality. User-specified values are ignored. Please remove them from your configuration: ${passedIgnored.join(
188
+ ', '
189
+ )}`
190
+ );
191
+ passedIgnored.forEach((ignored) => {
192
+ // @ts-expect-error string access
193
+ delete options.compilerOptions[ignored];
194
+ });
195
+ }
196
+ }
197
+
178
198
  // vite passes unresolved `root`option to config hook but we need the resolved value, so do it here
179
199
  // https://github.com/sveltejs/vite-plugin-svelte/issues/113
180
200
  // https://github.com/vitejs/vite/blob/43c957de8a99bb326afd732c962f42127b0a4d1e/packages/vite/src/node/config.ts#L293
@@ -207,7 +227,7 @@ export function buildExtraViteConfig(
207
227
  );
208
228
  }
209
229
 
210
- if (options.experimental.prebundleSvelteLibraries) {
230
+ if (options.experimental?.prebundleSvelteLibraries) {
211
231
  extraViteConfig.optimizeDeps = {
212
232
  ...extraViteConfig.optimizeDeps,
213
233
  // Experimental Vite API to allow these extensions to be scanned and prebundled
@@ -256,7 +276,7 @@ function buildOptimizeDepsForSvelte(
256
276
  }
257
277
 
258
278
  // If we prebundle svelte libraries, we can skip the whole prebundling dance below
259
- if (options.experimental.prebundleSvelteLibraries) {
279
+ if (options.experimental?.prebundleSvelteLibraries) {
260
280
  return { include, exclude };
261
281
  }
262
282
 
@@ -355,7 +375,7 @@ export interface Options {
355
375
  /**
356
376
  * Path to a svelte config file, either absolute or relative to Vite root
357
377
  *
358
- * set to `false` to skip reading config from a file
378
+ * set to `false` to ignore the svelte config file
359
379
  *
360
380
  * @see https://vitejs.dev/config/#root
361
381
  */
@@ -399,11 +419,13 @@ export interface Options {
399
419
  emitCss?: boolean;
400
420
 
401
421
  /**
402
- * The options to be passed to the Svelte compiler
422
+ * The options to be passed to the Svelte compiler. A few options are set by default,
423
+ * including `dev` and `css`. However, some options are non-configurable, like
424
+ * `filename`, `format`, `generate`, and `cssHash` (in dev).
403
425
  *
404
426
  * @see https://svelte.dev/docs#svelte_compile
405
427
  */
406
- compilerOptions?: CompileOptions;
428
+ compilerOptions?: Omit<CompileOptions, 'filename' | 'format' | 'generate'>;
407
429
 
408
430
  /**
409
431
  * Handles warning emitted from the Svelte compiler
@@ -509,6 +531,55 @@ export interface ExperimentalOptions {
509
531
  code: string;
510
532
  compileOptions: Partial<CompileOptions>;
511
533
  }) => Promise<Partial<CompileOptions> | void> | Partial<CompileOptions> | void;
534
+
535
+ /**
536
+ * enable svelte inspector
537
+ */
538
+ inspector?: InspectorOptions | boolean;
539
+ }
540
+
541
+ export interface InspectorOptions {
542
+ /**
543
+ * define a key combo to toggle inspector,
544
+ * @default 'control-shift' on windows, 'meta-shift' on other os
545
+ *
546
+ * any number of modifiers `control` `shift` `alt` `meta` followed by zero or one regular key, separated by -
547
+ * examples: control-shift, control-o, control-alt-s meta-x control-meta
548
+ * Some keys have native behavior (e.g. alt-s opens history menu on firefox).
549
+ * To avoid conflicts or accidentally typing into inputs, modifier only combinations are recommended.
550
+ */
551
+ toggleKeyCombo?: string;
552
+
553
+ /**
554
+ * inspector is automatically disabled when releasing toggleKeyCombo after holding it for a longpress
555
+ * @default false
556
+ */
557
+ holdMode?: boolean;
558
+ /**
559
+ * when to show the toggle button
560
+ * @default 'active'
561
+ */
562
+ showToggleButton?: 'always' | 'active' | 'never';
563
+
564
+ /**
565
+ * where to display the toggle button
566
+ * @default top-right
567
+ */
568
+ toggleButtonPos?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
569
+
570
+ /**
571
+ * inject custom styles when inspector is active
572
+ */
573
+ customStyles?: boolean;
574
+
575
+ /**
576
+ * append an import to the module id ending with `appendTo` instead of adding a script into body
577
+ * useful for frameworks that do not support trannsformIndexHtml hook
578
+ *
579
+ * WARNING: only set this if you know exactly what it does.
580
+ * Regular users of vite-plugin-svelte or SvelteKit do not need it
581
+ */
582
+ appendTo?: string;
512
583
  }
513
584
 
514
585
  export interface PreResolvedOptions extends Options {
@@ -58,30 +58,34 @@ export function setupWatchers(
58
58
  }
59
59
  };
60
60
 
61
- const possibleSvelteConfigs = knownSvelteConfigNames.map((cfg) => path.join(root, cfg));
62
- const restartOnConfigAdd = (filename: string) => {
63
- if (possibleSvelteConfigs.includes(filename)) {
64
- triggerViteRestart(filename);
65
- }
66
- };
67
-
68
- const restartOnConfigChange = (filename: string) => {
69
- if (filename === svelteConfigFile) {
70
- triggerViteRestart(filename);
71
- }
72
- };
73
-
74
61
  // collection of watcher listeners by event
75
62
  const listenerCollection = {
76
63
  add: [] as Array<Function>,
77
64
  change: [emitChangeEventOnDependants],
78
65
  unlink: [removeUnlinkedFromCache, emitChangeEventOnDependants]
79
66
  };
80
- if (svelteConfigFile) {
81
- listenerCollection.change.push(restartOnConfigChange);
82
- listenerCollection.unlink.push(restartOnConfigChange);
83
- } else {
84
- listenerCollection.add.push(restartOnConfigAdd);
67
+
68
+ if (svelteConfigFile !== false) {
69
+ // configFile false means we ignore the file and external process is responsible
70
+ const possibleSvelteConfigs = knownSvelteConfigNames.map((cfg) => path.join(root, cfg));
71
+ const restartOnConfigAdd = (filename: string) => {
72
+ if (possibleSvelteConfigs.includes(filename)) {
73
+ triggerViteRestart(filename);
74
+ }
75
+ };
76
+
77
+ const restartOnConfigChange = (filename: string) => {
78
+ if (filename === svelteConfigFile) {
79
+ triggerViteRestart(filename);
80
+ }
81
+ };
82
+
83
+ if (svelteConfigFile) {
84
+ listenerCollection.change.push(restartOnConfigChange);
85
+ listenerCollection.unlink.push(restartOnConfigChange);
86
+ } else {
87
+ listenerCollection.add.push(restartOnConfigAdd);
88
+ }
85
89
  }
86
90
 
87
91
  Object.entries(listenerCollection).forEach(([evt, listeners]) => {