@sveltejs/vite-plugin-svelte 1.0.0-next.9 → 1.0.2

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,135 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { createFilter } from '@rollup/pluginutils';
3
+ import { Arrayable, ResolvedOptions } from './options';
4
+ import { normalizePath } from 'vite';
5
+ import * as fs from 'fs';
6
+
7
+ const VITE_FS_PREFIX = '/@fs/';
8
+ const IS_WINDOWS = process.platform === 'win32';
9
+
10
+ export type SvelteQueryTypes = 'style' | 'script';
11
+
12
+ export interface RequestQuery {
13
+ // our own
14
+ svelte?: boolean;
15
+ type?: SvelteQueryTypes;
16
+ // vite specific
17
+ url?: boolean;
18
+ raw?: boolean;
19
+ }
20
+
21
+ export interface SvelteRequest {
22
+ id: string;
23
+ cssId: string;
24
+ filename: string;
25
+ normalizedFilename: string;
26
+ query: RequestQuery;
27
+ timestamp: number;
28
+ ssr: boolean;
29
+ }
30
+
31
+ function splitId(id: string) {
32
+ const parts = id.split(`?`, 2);
33
+ const filename = parts[0];
34
+ const rawQuery = parts[1];
35
+ return { filename, rawQuery };
36
+ }
37
+
38
+ function parseToSvelteRequest(
39
+ id: string,
40
+ filename: string,
41
+ rawQuery: string,
42
+ root: string,
43
+ timestamp: number,
44
+ ssr: boolean
45
+ ): SvelteRequest | undefined {
46
+ const query = parseRequestQuery(rawQuery);
47
+ if (query.url || query.raw) {
48
+ // skip requests with special vite tags
49
+ return;
50
+ }
51
+ const normalizedFilename = normalize(filename, root);
52
+ const cssId = createVirtualImportId(filename, root, 'style');
53
+
54
+ return {
55
+ id,
56
+ filename,
57
+ normalizedFilename,
58
+ cssId,
59
+ query,
60
+ timestamp,
61
+ ssr
62
+ };
63
+ }
64
+
65
+ function createVirtualImportId(filename: string, root: string, type: SvelteQueryTypes) {
66
+ const parts = ['svelte', `type=${type}`];
67
+ if (type === 'style') {
68
+ parts.push('lang.css');
69
+ }
70
+ if (existsInRoot(filename, root)) {
71
+ filename = root + filename;
72
+ } else if (filename.startsWith(VITE_FS_PREFIX)) {
73
+ filename = IS_WINDOWS
74
+ ? filename.slice(VITE_FS_PREFIX.length) // remove /@fs/ from /@fs/C:/...
75
+ : filename.slice(VITE_FS_PREFIX.length - 1); // remove /@fs from /@fs/home/user
76
+ }
77
+ // return same virtual id format as vite-plugin-vue eg ...App.svelte?svelte&type=style&lang.css
78
+ return `${filename}?${parts.join('&')}`;
79
+ }
80
+
81
+ function parseRequestQuery(rawQuery: string): RequestQuery {
82
+ const query = Object.fromEntries(new URLSearchParams(rawQuery));
83
+ for (const key in query) {
84
+ if (query[key] === '') {
85
+ // @ts-ignore
86
+ query[key] = true;
87
+ }
88
+ }
89
+ return query as RequestQuery;
90
+ }
91
+
92
+ /**
93
+ * posixify and remove root at start
94
+ *
95
+ * @param filename
96
+ * @param normalizedRoot
97
+ */
98
+ function normalize(filename: string, normalizedRoot: string) {
99
+ return stripRoot(normalizePath(filename), normalizedRoot);
100
+ }
101
+
102
+ function existsInRoot(filename: string, root: string) {
103
+ if (filename.startsWith(VITE_FS_PREFIX)) {
104
+ return false; // vite already tagged it as out of root
105
+ }
106
+ return fs.existsSync(root + filename);
107
+ }
108
+
109
+ function stripRoot(normalizedFilename: string, normalizedRoot: string) {
110
+ return normalizedFilename.startsWith(normalizedRoot + '/')
111
+ ? normalizedFilename.slice(normalizedRoot.length)
112
+ : normalizedFilename;
113
+ }
114
+
115
+ function buildFilter(
116
+ include: Arrayable<string> | undefined,
117
+ exclude: Arrayable<string> | undefined,
118
+ extensions: string[]
119
+ ): (filename: string) => boolean {
120
+ const rollupFilter = createFilter(include, exclude);
121
+ return (filename) => rollupFilter(filename) && extensions.some((ext) => filename.endsWith(ext));
122
+ }
123
+
124
+ export type IdParser = (id: string, ssr: boolean, timestamp?: number) => SvelteRequest | undefined;
125
+ export function buildIdParser(options: ResolvedOptions): IdParser {
126
+ const { include, exclude, extensions, root } = options;
127
+ const normalizedRoot = normalizePath(root);
128
+ const filter = buildFilter(include, exclude, extensions!);
129
+ return (id, ssr, timestamp = Date.now()) => {
130
+ const { filename, rawQuery } = splitId(id);
131
+ if (filter(filename)) {
132
+ return parseToSvelteRequest(id, filename, rawQuery, normalizedRoot, timestamp, ssr);
133
+ }
134
+ };
135
+ }
@@ -0,0 +1,115 @@
1
+ import { createRequire } from 'module';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { pathToFileURL } from 'url';
5
+ import { log } from './log';
6
+ import { Options, SvelteOptions } from './options';
7
+ import { UserConfig } from 'vite';
8
+
9
+ // used to require cjs config in esm.
10
+ // NOTE dynamic import() cjs technically works, but timestamp query cache bust
11
+ // have no effect, likely because it has another internal cache?
12
+ let esmRequire: NodeRequire;
13
+
14
+ export const knownSvelteConfigNames = [
15
+ 'svelte.config.js',
16
+ 'svelte.config.cjs',
17
+ 'svelte.config.mjs'
18
+ ];
19
+
20
+ // hide dynamic import from ts transform to prevent it turning into a require
21
+ // see https://github.com/microsoft/TypeScript/issues/43329#issuecomment-811606238
22
+ // also use timestamp query to avoid caching on reload
23
+ const dynamicImportDefault = new Function(
24
+ 'path',
25
+ 'timestamp',
26
+ 'return import(path + "?t=" + timestamp).then(m => m.default)'
27
+ );
28
+
29
+ export async function loadSvelteConfig(
30
+ viteConfig?: UserConfig,
31
+ inlineOptions?: Partial<Options>
32
+ ): Promise<Partial<SvelteOptions> | undefined> {
33
+ if (inlineOptions?.configFile === false) {
34
+ return;
35
+ }
36
+ const configFile = findConfigToLoad(viteConfig, inlineOptions);
37
+ if (configFile) {
38
+ let err;
39
+ // try to use dynamic import for svelte.config.js first
40
+ if (configFile.endsWith('.js') || configFile.endsWith('.mjs')) {
41
+ try {
42
+ const result = await dynamicImportDefault(
43
+ pathToFileURL(configFile).href,
44
+ fs.statSync(configFile).mtimeMs
45
+ );
46
+ if (result != null) {
47
+ return {
48
+ ...result,
49
+ configFile
50
+ };
51
+ } else {
52
+ throw new Error(`invalid export in ${configFile}`);
53
+ }
54
+ } catch (e) {
55
+ log.error(`failed to import config ${configFile}`, e);
56
+ err = e;
57
+ }
58
+ }
59
+ // cjs or error with dynamic import
60
+ if (!configFile.endsWith('.mjs')) {
61
+ try {
62
+ // identify which require function to use (esm and cjs mode)
63
+ const _require = import.meta.url
64
+ ? (esmRequire ??= createRequire(import.meta.url))
65
+ : require;
66
+
67
+ // avoid loading cached version on reload
68
+ delete _require.cache[_require.resolve(configFile)];
69
+ const result = _require(configFile);
70
+ if (result != null) {
71
+ return {
72
+ ...result,
73
+ configFile
74
+ };
75
+ } else {
76
+ throw new Error(`invalid export in ${configFile}`);
77
+ }
78
+ } catch (e) {
79
+ log.error(`failed to require config ${configFile}`, e);
80
+ if (!err) {
81
+ err = e;
82
+ }
83
+ }
84
+ }
85
+ // failed to load existing config file
86
+ throw err;
87
+ }
88
+ }
89
+
90
+ function findConfigToLoad(viteConfig?: UserConfig, inlineOptions?: Partial<Options>) {
91
+ const root = viteConfig?.root || process.cwd();
92
+ if (inlineOptions?.configFile) {
93
+ const abolutePath = path.isAbsolute(inlineOptions.configFile)
94
+ ? inlineOptions.configFile
95
+ : path.resolve(root, inlineOptions.configFile);
96
+ if (!fs.existsSync(abolutePath)) {
97
+ throw new Error(`failed to find svelte config file ${abolutePath}.`);
98
+ }
99
+ return abolutePath;
100
+ } else {
101
+ const existingKnownConfigFiles = knownSvelteConfigNames
102
+ .map((candidate) => path.resolve(root, candidate))
103
+ .filter((file) => fs.existsSync(file));
104
+ if (existingKnownConfigFiles.length === 0) {
105
+ log.debug(`no svelte config found at ${root}`);
106
+ return;
107
+ } else if (existingKnownConfigFiles.length > 1) {
108
+ log.warn(
109
+ `found more than one svelte config file, using ${existingKnownConfigFiles[0]}. you should only have one!`,
110
+ existingKnownConfigFiles
111
+ );
112
+ }
113
+ return existingKnownConfigFiles[0];
114
+ }
115
+ }
@@ -0,0 +1,211 @@
1
+ /* eslint-disable no-unused-vars,no-console */
2
+ import { cyan, yellow, red } from 'kleur/colors';
3
+ import debug from 'debug';
4
+ import { ResolvedOptions, Warning } from './options';
5
+ import { SvelteRequest } from './id';
6
+
7
+ const levels: string[] = ['debug', 'info', 'warn', 'error', 'silent'];
8
+ const prefix = 'vite-plugin-svelte';
9
+ const loggers: { [key: string]: any } = {
10
+ debug: {
11
+ log: debug(`vite:${prefix}`),
12
+ enabled: false,
13
+ isDebug: true
14
+ },
15
+ info: {
16
+ color: cyan,
17
+ log: console.log,
18
+ enabled: true
19
+ },
20
+ warn: {
21
+ color: yellow,
22
+ log: console.warn,
23
+ enabled: true
24
+ },
25
+ error: {
26
+ color: red,
27
+ log: console.error,
28
+ enabled: true
29
+ },
30
+ silent: {
31
+ enabled: false
32
+ }
33
+ };
34
+
35
+ let _level: string = 'info';
36
+ function setLevel(level: string) {
37
+ if (level === _level) {
38
+ return;
39
+ }
40
+ const levelIndex = levels.indexOf(level);
41
+ if (levelIndex > -1) {
42
+ _level = level;
43
+ for (let i = 0; i < levels.length; i++) {
44
+ loggers[levels[i]].enabled = i >= levelIndex;
45
+ }
46
+ } else {
47
+ _log(loggers.error, `invalid log level: ${level} `);
48
+ }
49
+ }
50
+
51
+ function _log(logger: any, message: string, payload?: any) {
52
+ if (!logger.enabled) {
53
+ return;
54
+ }
55
+ if (logger.isDebug) {
56
+ payload !== undefined ? logger.log(message, payload) : logger.log(message);
57
+ } else {
58
+ logger.log(logger.color(`${new Date().toLocaleTimeString()} [${prefix}] ${message}`));
59
+ if (payload) {
60
+ logger.log(payload);
61
+ }
62
+ }
63
+ }
64
+
65
+ export interface LogFn {
66
+ (message: string, payload?: any): void;
67
+ enabled: boolean;
68
+ once: (message: string, payload?: any) => void;
69
+ }
70
+
71
+ function createLogger(level: string): LogFn {
72
+ const logger = loggers[level];
73
+ const logFn: LogFn = _log.bind(null, logger) as LogFn;
74
+ const logged = new Set<String>();
75
+ const once = function (message: string, payload?: any) {
76
+ if (logged.has(message)) {
77
+ return;
78
+ }
79
+ logged.add(message);
80
+ logFn.apply(null, [message, payload]);
81
+ };
82
+ Object.defineProperty(logFn, 'enabled', {
83
+ get() {
84
+ return logger.enabled;
85
+ }
86
+ });
87
+ Object.defineProperty(logFn, 'once', {
88
+ get() {
89
+ return once;
90
+ }
91
+ });
92
+ return logFn;
93
+ }
94
+
95
+ export const log = {
96
+ debug: createLogger('debug'),
97
+ info: createLogger('info'),
98
+ warn: createLogger('warn'),
99
+ error: createLogger('error'),
100
+ setLevel
101
+ };
102
+
103
+ export type SvelteWarningsMessage = {
104
+ id: string;
105
+ filename: string;
106
+ normalizedFilename: string;
107
+ timestamp: number;
108
+ warnings: Warning[]; // allWarnings filtered by warnings where onwarn did not call the default handler
109
+ allWarnings: Warning[]; // includes warnings filtered by onwarn and our extra vite plugin svelte warnings
110
+ rawWarnings: Warning[]; // raw compiler output
111
+ };
112
+
113
+ export function logCompilerWarnings(
114
+ svelteRequest: SvelteRequest,
115
+ warnings: Warning[],
116
+ options: ResolvedOptions
117
+ ) {
118
+ const { emitCss, onwarn, isBuild } = options;
119
+ const sendViaWS = !isBuild && options.experimental?.sendWarningsToBrowser;
120
+ let warn = isBuild ? warnBuild : warnDev;
121
+ const handledByDefaultWarn: Warning[] = [];
122
+ const notIgnored = warnings?.filter((w) => !ignoreCompilerWarning(w, isBuild, emitCss));
123
+ const extra = buildExtraWarnings(warnings, isBuild);
124
+ const allWarnings = [...notIgnored, ...extra];
125
+ if (sendViaWS) {
126
+ const _warn = warn;
127
+ warn = (w: Warning) => {
128
+ handledByDefaultWarn.push(w);
129
+ _warn(w);
130
+ };
131
+ }
132
+ allWarnings.forEach((warning) => {
133
+ if (onwarn) {
134
+ onwarn(warning, warn);
135
+ } else {
136
+ warn(warning);
137
+ }
138
+ });
139
+ if (sendViaWS) {
140
+ const message: SvelteWarningsMessage = {
141
+ id: svelteRequest.id,
142
+ filename: svelteRequest.filename,
143
+ normalizedFilename: svelteRequest.normalizedFilename,
144
+ timestamp: svelteRequest.timestamp,
145
+ warnings: handledByDefaultWarn, // allWarnings filtered by warnings where onwarn did not call the default handler
146
+ allWarnings, // includes warnings filtered by onwarn and our extra vite plugin svelte warnings
147
+ rawWarnings: warnings // raw compiler output
148
+ };
149
+ log.debug(`sending svelte:warnings message for ${svelteRequest.normalizedFilename}`);
150
+ options.server?.ws?.send('svelte:warnings', message);
151
+ }
152
+ }
153
+
154
+ function ignoreCompilerWarning(
155
+ warning: Warning,
156
+ isBuild: boolean,
157
+ emitCss: boolean | undefined
158
+ ): boolean {
159
+ return (
160
+ (!emitCss && warning.code === 'css-unused-selector') || // same as rollup-plugin-svelte
161
+ (!isBuild && isNoScopableElementWarning(warning))
162
+ );
163
+ }
164
+
165
+ function isNoScopableElementWarning(warning: Warning) {
166
+ // see https://github.com/sveltejs/vite-plugin-svelte/issues/153
167
+ return warning.code === 'css-unused-selector' && warning.message.includes('"*"');
168
+ }
169
+
170
+ function buildExtraWarnings(warnings: Warning[], isBuild: boolean): Warning[] {
171
+ const extraWarnings = [];
172
+ if (!isBuild) {
173
+ const noScopableElementWarnings = warnings.filter((w) => isNoScopableElementWarning(w));
174
+ if (noScopableElementWarnings.length > 0) {
175
+ // in case there are multiple, use last one as that is the one caused by our *{} rule
176
+ const noScopableElementWarning =
177
+ noScopableElementWarnings[noScopableElementWarnings.length - 1];
178
+ extraWarnings.push({
179
+ ...noScopableElementWarning,
180
+ code: 'vite-plugin-svelte-css-no-scopable-elements',
181
+ message: `No scopable elements found in template. If you're using global styles in the style tag, you should move it into an external stylesheet file and import it in JS. See https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#where-should-i-put-my-global-styles.`
182
+ });
183
+ }
184
+ }
185
+ return extraWarnings;
186
+ }
187
+
188
+ function warnDev(w: Warning) {
189
+ log.info.enabled && log.info(buildExtendedLogMessage(w));
190
+ }
191
+
192
+ function warnBuild(w: Warning) {
193
+ log.warn.enabled && log.warn(buildExtendedLogMessage(w), w.frame);
194
+ }
195
+
196
+ export function buildExtendedLogMessage(w: Warning) {
197
+ const parts = [];
198
+ if (w.filename) {
199
+ parts.push(w.filename);
200
+ }
201
+ if (w.start) {
202
+ parts.push(':', w.start.line, ':', w.start.column);
203
+ }
204
+ if (w.message) {
205
+ if (parts.length > 0) {
206
+ parts.push(' ');
207
+ }
208
+ parts.push(w.message);
209
+ }
210
+ return parts.join('');
211
+ }
@@ -0,0 +1,45 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import { ResolvedOptions } from './options';
4
+
5
+ // List of options that changes the prebundling result
6
+ const PREBUNDLE_SENSITIVE_OPTIONS: (keyof ResolvedOptions)[] = [
7
+ 'compilerOptions',
8
+ 'configFile',
9
+ 'experimental',
10
+ 'extensions',
11
+ 'ignorePluginPreprocessors',
12
+ 'preprocess'
13
+ ];
14
+
15
+ /**
16
+ * @returns Whether the Svelte metadata has changed
17
+ */
18
+ export async function saveSvelteMetadata(cacheDir: string, options: ResolvedOptions) {
19
+ const svelteMetadata = generateSvelteMetadata(options);
20
+ const svelteMetadataPath = path.resolve(cacheDir, '_svelte_metadata.json');
21
+
22
+ const currentSvelteMetadata = JSON.stringify(svelteMetadata, (_, value) => {
23
+ // Handle preprocessors
24
+ return typeof value === 'function' ? value.toString() : value;
25
+ });
26
+
27
+ let existingSvelteMetadata: string | undefined;
28
+ try {
29
+ existingSvelteMetadata = await fs.readFile(svelteMetadataPath, 'utf8');
30
+ } catch {
31
+ // ignore
32
+ }
33
+
34
+ await fs.mkdir(cacheDir, { recursive: true });
35
+ await fs.writeFile(svelteMetadataPath, currentSvelteMetadata);
36
+ return currentSvelteMetadata !== existingSvelteMetadata;
37
+ }
38
+
39
+ function generateSvelteMetadata(options: ResolvedOptions) {
40
+ const metadata: Record<string, any> = {};
41
+ for (const key of PREBUNDLE_SENSITIVE_OPTIONS) {
42
+ metadata[key] = options[key];
43
+ }
44
+ return metadata;
45
+ }