@rsvelte/vite-plugin-svelte 0.1.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.
- package/LICENSE +21 -0
- package/README.md +28 -0
- package/package.json +59 -0
- package/src/index.js +42 -0
- package/src/plugins/compile-module.js +130 -0
- package/src/plugins/compile.js +65 -0
- package/src/plugins/configure.js +97 -0
- package/src/plugins/hot-update.js +196 -0
- package/src/plugins/load-compiled-css.js +66 -0
- package/src/plugins/load-custom.js +42 -0
- package/src/plugins/preprocess.js +208 -0
- package/src/plugins/setup-optimizer.js +340 -0
- package/src/preprocess.js +166 -0
- package/src/public.d.ts +217 -0
- package/src/types/compile.d.ts +37 -0
- package/src/types/id.d.ts +46 -0
- package/src/types/log.d.ts +24 -0
- package/src/types/options.d.ts +21 -0
- package/src/types/plugin-api.d.ts +15 -0
- package/src/types/vite-plugin-svelte-stats.d.ts +30 -0
- package/src/utils/compile.js +152 -0
- package/src/utils/constants.js +36 -0
- package/src/utils/dependencies.js +60 -0
- package/src/utils/error.js +167 -0
- package/src/utils/hash.js +43 -0
- package/src/utils/id.js +274 -0
- package/src/utils/load-svelte-config.js +80 -0
- package/src/utils/log.js +256 -0
- package/src/utils/options.js +618 -0
- package/src/utils/preprocess.js +89 -0
- package/src/utils/sourcemaps.js +82 -0
- package/src/utils/svelte-version.js +46 -0
- package/src/utils/vite-plugin-svelte-stats.js +248 -0
- package/src/utils/watch.js +95 -0
- package/types/index.d.ts +228 -0
- package/types/index.d.ts.map +31 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import * as svelte from '@rsvelte/compiler';
|
|
2
|
+
import { safeBase64Hash } from './hash.js';
|
|
3
|
+
import { log } from './log.js';
|
|
4
|
+
|
|
5
|
+
import { mapToRelative } from './sourcemaps.js';
|
|
6
|
+
import { enhanceCompileError } from './error.js';
|
|
7
|
+
|
|
8
|
+
// TODO this is a patched version of https://github.com/sveltejs/vite-plugin-svelte/pull/796/files#diff-3bce0b33034aad4b35ca094893671f7e7ddf4d27254ae7b9b0f912027a001b15R10
|
|
9
|
+
// which is closer to the other regexes in at least not falling into commented script
|
|
10
|
+
// but ideally would be shared exactly with svelte and other tools that use it
|
|
11
|
+
const scriptLangRE =
|
|
12
|
+
/<!--[^]*?-->|<script\s+(?:[^>]*|(?:[^=>'"/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)\s+)*)lang=(["'])?([^"' >]+)\1[^>]*>/g;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @returns {import('../types/compile.d.ts').CompileSvelte}
|
|
16
|
+
*/
|
|
17
|
+
export function createCompileSvelte() {
|
|
18
|
+
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
|
|
19
|
+
let stats;
|
|
20
|
+
/** @type {import('../types/compile.d.ts').CompileSvelte} */
|
|
21
|
+
return async function compileSvelte(svelteRequest, code, options, sourcemap) {
|
|
22
|
+
const { filename, normalizedFilename, cssId, ssr, raw } = svelteRequest;
|
|
23
|
+
const { emitCss = true } = options;
|
|
24
|
+
/** @type {import('@rsvelte/compiler').Warning[]} */
|
|
25
|
+
const warnings = [];
|
|
26
|
+
|
|
27
|
+
if (options.stats) {
|
|
28
|
+
if (options.isBuild) {
|
|
29
|
+
if (!stats) {
|
|
30
|
+
// build is either completely ssr or csr, create stats collector on first compile
|
|
31
|
+
// it is then finished in the buildEnd hook.
|
|
32
|
+
stats = options.stats.startCollection(`${ssr ? 'ssr' : 'dom'} compile`, {
|
|
33
|
+
logInProgress: () => false
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
// dev time ssr, it's a ssr request and there are no stats, assume new page load and start collecting
|
|
38
|
+
if (ssr && !stats) {
|
|
39
|
+
stats = options.stats.startCollection('ssr compile');
|
|
40
|
+
}
|
|
41
|
+
// stats are being collected but this isn't an ssr request, assume page loaded and stop collecting
|
|
42
|
+
if (!ssr && stats) {
|
|
43
|
+
stats.finish();
|
|
44
|
+
stats = undefined;
|
|
45
|
+
}
|
|
46
|
+
// TODO find a way to trace dom compile during dev
|
|
47
|
+
// problem: we need to call finish at some point but have no way to tell if page load finished
|
|
48
|
+
// also they for hmr updates too
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @type {import('@rsvelte/compiler').CompileOptions} */
|
|
53
|
+
const compileOptions = {
|
|
54
|
+
...options.compilerOptions,
|
|
55
|
+
filename,
|
|
56
|
+
generate: ssr ? 'server' : 'client'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let finalCode = code;
|
|
60
|
+
if (compileOptions.hmr && options.emitCss) {
|
|
61
|
+
const hash = `s-${safeBase64Hash(normalizedFilename)}`;
|
|
62
|
+
compileOptions.cssHash = () => hash;
|
|
63
|
+
const closeStylePos = code.lastIndexOf('</style>');
|
|
64
|
+
if (closeStylePos > -1) {
|
|
65
|
+
// inject rule that forces compile to attach scope class to every node in the template
|
|
66
|
+
// this reduces the amount of js hot updates when editing css in .svelte files
|
|
67
|
+
finalCode = finalCode.slice(0, closeStylePos) + ' *{}' + finalCode.slice(closeStylePos);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const dynamicCompileOptions = await options?.dynamicCompileOptions?.({
|
|
72
|
+
filename,
|
|
73
|
+
code: finalCode,
|
|
74
|
+
compileOptions
|
|
75
|
+
});
|
|
76
|
+
if (dynamicCompileOptions && log.debug.enabled) {
|
|
77
|
+
log.debug(
|
|
78
|
+
`dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`,
|
|
79
|
+
undefined,
|
|
80
|
+
'compile'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const finalCompileOptions = dynamicCompileOptions
|
|
84
|
+
? {
|
|
85
|
+
...compileOptions,
|
|
86
|
+
...dynamicCompileOptions
|
|
87
|
+
}
|
|
88
|
+
: compileOptions;
|
|
89
|
+
if (sourcemap) {
|
|
90
|
+
finalCompileOptions.sourcemap = sourcemap;
|
|
91
|
+
}
|
|
92
|
+
const endStat = stats?.start(filename);
|
|
93
|
+
/** @type {import('@rsvelte/compiler').CompileResult} */
|
|
94
|
+
let compiled;
|
|
95
|
+
try {
|
|
96
|
+
compiled = svelte.compile(finalCode, { ...finalCompileOptions, filename });
|
|
97
|
+
|
|
98
|
+
// patch output with partial accept until svelte does it
|
|
99
|
+
// TODO remove later
|
|
100
|
+
if (
|
|
101
|
+
options.server?.config.experimental.hmrPartialAccept &&
|
|
102
|
+
compiled.js.code.includes('import.meta.hot.accept(')
|
|
103
|
+
) {
|
|
104
|
+
compiled.js.code = compiled.js.code.replaceAll(
|
|
105
|
+
'import.meta.hot.accept(',
|
|
106
|
+
'import.meta.hot.acceptExports(["default"],'
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
enhanceCompileError(e, code, options.preprocess);
|
|
111
|
+
throw e;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (endStat) {
|
|
115
|
+
endStat();
|
|
116
|
+
}
|
|
117
|
+
mapToRelative(compiled.js?.map, filename);
|
|
118
|
+
mapToRelative(compiled.css?.map, filename);
|
|
119
|
+
if (warnings.length) {
|
|
120
|
+
if (!compiled.warnings) {
|
|
121
|
+
compiled.warnings = [];
|
|
122
|
+
}
|
|
123
|
+
compiled.warnings.push(...warnings);
|
|
124
|
+
}
|
|
125
|
+
if (!raw) {
|
|
126
|
+
// wire css import and code for hmr
|
|
127
|
+
const hasCss = compiled.css?.code?.trim()?.length ?? 0 > 0;
|
|
128
|
+
// compiler might not emit css with mode none or it may be empty
|
|
129
|
+
if (emitCss && hasCss) {
|
|
130
|
+
// TODO properly update sourcemap?
|
|
131
|
+
compiled.js.code += `\nimport ${JSON.stringify(cssId)};\n`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let lang = 'js';
|
|
136
|
+
for (const match of code.matchAll(scriptLangRE)) {
|
|
137
|
+
if (match[2]) {
|
|
138
|
+
lang = match[2];
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
filename,
|
|
145
|
+
normalizedFilename,
|
|
146
|
+
cssId,
|
|
147
|
+
lang,
|
|
148
|
+
compiled,
|
|
149
|
+
ssr
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
|
|
3
|
+
const sveltePkg = createRequire(import.meta.url)('svelte/package.json');
|
|
4
|
+
|
|
5
|
+
// list of svelte runtime dependencies to optimize together with svelte itself
|
|
6
|
+
export const SVELTE_RUNTIME_DEPENDENCIES = [
|
|
7
|
+
'clsx' // avoids dev server restart after page load with npm + vite6 (see #1067)
|
|
8
|
+
].filter((dep) => !!sveltePkg.dependencies?.[dep]);
|
|
9
|
+
|
|
10
|
+
export const SVELTE_IMPORTS = Object.entries(sveltePkg.exports)
|
|
11
|
+
.map(([name, config]) => {
|
|
12
|
+
// ignore type only
|
|
13
|
+
if (typeof config === 'object' && Object.keys(config).length === 1 && config.types) {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
// ignore names
|
|
17
|
+
if (name === './package.json' || name === './compiler') {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
return name.replace(/^\./, 'svelte');
|
|
21
|
+
})
|
|
22
|
+
.filter((s) => s.length > 0);
|
|
23
|
+
|
|
24
|
+
export const SVELTE_EXPORT_CONDITIONS = ['svelte'];
|
|
25
|
+
|
|
26
|
+
export const FAQ_LINK_MISSING_EXPORTS_CONDITION =
|
|
27
|
+
'https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#missing-exports-condition';
|
|
28
|
+
|
|
29
|
+
export const LINK_TRANSFORM_WITH_PLUGIN =
|
|
30
|
+
'https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/advanced-usage.md#transform-svelte-files-with-vite-plugins';
|
|
31
|
+
|
|
32
|
+
export const DEFAULT_SVELTE_EXT = ['.svelte'];
|
|
33
|
+
export const DEFAULT_SVELTE_MODULE_INFIX = ['.svelte.'];
|
|
34
|
+
export const DEFAULT_SVELTE_MODULE_EXT = ['.js', '.ts'];
|
|
35
|
+
|
|
36
|
+
export const SVELTE_VIRTUAL_STYLE_ID_REGEX = /[?&]svelte&type=style&lang.css$/;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const COMMON_DEPENDENCIES_WITHOUT_SVELTE_FIELD = [
|
|
2
|
+
'@lukeed/uuid',
|
|
3
|
+
'@playwright/test',
|
|
4
|
+
'@sveltejs/kit',
|
|
5
|
+
'@sveltejs/package',
|
|
6
|
+
'@sveltejs/vite-plugin-svelte',
|
|
7
|
+
'autoprefixer',
|
|
8
|
+
'cookie',
|
|
9
|
+
'dotenv',
|
|
10
|
+
'esbuild',
|
|
11
|
+
'eslint',
|
|
12
|
+
'jest',
|
|
13
|
+
'mdsvex',
|
|
14
|
+
'playwright',
|
|
15
|
+
'postcss',
|
|
16
|
+
'prettier',
|
|
17
|
+
'svelte',
|
|
18
|
+
'svelte2tsx',
|
|
19
|
+
'svelte-check',
|
|
20
|
+
'svelte-preprocess',
|
|
21
|
+
'tslib',
|
|
22
|
+
'typescript',
|
|
23
|
+
'vite',
|
|
24
|
+
'vitest',
|
|
25
|
+
'__vite-browser-external' // see https://github.com/sveltejs/vite-plugin-svelte/issues/362
|
|
26
|
+
];
|
|
27
|
+
const COMMON_PREFIXES_WITHOUT_SVELTE_FIELD = [
|
|
28
|
+
'@fontsource/',
|
|
29
|
+
'@postcss-plugins/',
|
|
30
|
+
'@rollup/',
|
|
31
|
+
'@sveltejs/adapter-',
|
|
32
|
+
'@types/',
|
|
33
|
+
'@typescript-eslint/',
|
|
34
|
+
'eslint-',
|
|
35
|
+
'jest-',
|
|
36
|
+
'postcss-plugin-',
|
|
37
|
+
'prettier-plugin-',
|
|
38
|
+
'rollup-plugin-',
|
|
39
|
+
'vite-plugin-'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Test for common dependency names that tell us it is not a package including a svelte field, eg. eslint + plugins.
|
|
44
|
+
*
|
|
45
|
+
* This speeds up the find process as we don't have to try and require the package.json for all of them
|
|
46
|
+
*
|
|
47
|
+
* @param {string} dependency
|
|
48
|
+
* @returns {boolean} true if it is a dependency without a svelte field
|
|
49
|
+
*/
|
|
50
|
+
export function isCommonDepWithoutSvelteField(dependency) {
|
|
51
|
+
return (
|
|
52
|
+
COMMON_DEPENDENCIES_WITHOUT_SVELTE_FIELD.includes(dependency) ||
|
|
53
|
+
COMMON_PREFIXES_WITHOUT_SVELTE_FIELD.some(
|
|
54
|
+
(prefix) =>
|
|
55
|
+
prefix.startsWith('@')
|
|
56
|
+
? dependency.startsWith(prefix)
|
|
57
|
+
: dependency.substring(dependency.lastIndexOf('/') + 1).startsWith(prefix) // check prefix omitting @scope/
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { buildExtendedLogMessage } from './log.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* convert an error thrown by svelte.compile to a RollupError so that vite displays it in a user friendly way
|
|
5
|
+
* @param {import('@rsvelte/compiler').Warning & Error & {frame?: string}} error a svelte compiler error, which is a mix of Warning and an error
|
|
6
|
+
* @param {import('../types/options.d.ts').ResolvedOptions} options
|
|
7
|
+
* @returns {import('vite').Rollup.RollupError} the converted error
|
|
8
|
+
*/
|
|
9
|
+
export function toRollupError(error, options) {
|
|
10
|
+
const { filename, frame, start, code, name, stack } = error;
|
|
11
|
+
/** @type {import('vite').Rollup.RollupError} */
|
|
12
|
+
const rollupError = {
|
|
13
|
+
name, // needed otherwise sveltekit coalesce_to_error turns it into a string
|
|
14
|
+
id: filename,
|
|
15
|
+
message: buildExtendedLogMessage(error), // include filename:line:column so that it's clickable
|
|
16
|
+
frame: formatFrameForVite(frame),
|
|
17
|
+
code,
|
|
18
|
+
stack: options.isBuild || options.isDebug || !frame ? stack : ''
|
|
19
|
+
};
|
|
20
|
+
if (start) {
|
|
21
|
+
rollupError.loc = {
|
|
22
|
+
line: start.line,
|
|
23
|
+
column: start.column,
|
|
24
|
+
file: filename
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return rollupError;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* convert an error thrown by svelte.compile to an esbuild PartialMessage
|
|
32
|
+
* @param {import('@rsvelte/compiler').Warning & Error & {frame?: string}} error a svelte compiler error, which is a mix of Warning and an error
|
|
33
|
+
* @param {import('../types/options.d.ts').ResolvedOptions} options
|
|
34
|
+
* @returns {any} the converted error as esbuild PartialMessage
|
|
35
|
+
*
|
|
36
|
+
* note: typed any to avoid esbuild devDependency for a single internal type import
|
|
37
|
+
*/
|
|
38
|
+
export function toESBuildError(error, options) {
|
|
39
|
+
const { filename, frame, start, stack } = error;
|
|
40
|
+
/** @type any */
|
|
41
|
+
const partialMessage = {
|
|
42
|
+
text: buildExtendedLogMessage(error)
|
|
43
|
+
};
|
|
44
|
+
if (start) {
|
|
45
|
+
partialMessage.location = {
|
|
46
|
+
line: start.line,
|
|
47
|
+
column: start.column,
|
|
48
|
+
file: filename,
|
|
49
|
+
lineText: lineFromFrame(start.line, frame) // needed to get a meaningful error message on cli
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (options.isBuild || options.isDebug || !frame) {
|
|
53
|
+
partialMessage.detail = stack;
|
|
54
|
+
}
|
|
55
|
+
return partialMessage;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* extract line with number from codeframe
|
|
60
|
+
*
|
|
61
|
+
* @param {number} lineNo
|
|
62
|
+
* @param {string} [frame]
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
function lineFromFrame(lineNo, frame) {
|
|
66
|
+
if (!frame) {
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
const lines = frame.split('\n');
|
|
70
|
+
const errorLine = lines.find((line) => line.trimStart().startsWith(`${lineNo}: `));
|
|
71
|
+
return errorLine ? errorLine.substring(errorLine.indexOf(': ') + 3) : '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* vite error overlay expects a specific format to show frames
|
|
76
|
+
* this reformats svelte frame (colon separated, less whitespace)
|
|
77
|
+
* to one that vite displays on overlay ( pipe separated, more whitespace)
|
|
78
|
+
* e.g.
|
|
79
|
+
* ```
|
|
80
|
+
* 1: foo
|
|
81
|
+
* 2: bar;
|
|
82
|
+
* ^
|
|
83
|
+
* 3: baz
|
|
84
|
+
* ```
|
|
85
|
+
* to
|
|
86
|
+
* ```
|
|
87
|
+
* 1 | foo
|
|
88
|
+
* 2 | bar;
|
|
89
|
+
* ^
|
|
90
|
+
* 3 | baz
|
|
91
|
+
* ```
|
|
92
|
+
* @see https://github.com/vitejs/vite/blob/96591bf9989529de839ba89958755eafe4c445ae/packages/vite/src/client/overlay.ts#L116
|
|
93
|
+
* @param {string} [frame]
|
|
94
|
+
* @returns {string}
|
|
95
|
+
*/
|
|
96
|
+
function formatFrameForVite(frame) {
|
|
97
|
+
if (!frame) {
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
return frame
|
|
101
|
+
.split('\n')
|
|
102
|
+
.map((line) => (line.match(/^\s+\^/) ? ' ' + line : ' ' + line.replace(':', ' | ')))
|
|
103
|
+
.join('\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
*
|
|
108
|
+
* @param {string} code the svelte error code
|
|
109
|
+
* @see https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/compiler/errors.js
|
|
110
|
+
* @returns {boolean}
|
|
111
|
+
*/
|
|
112
|
+
function couldBeFixedByCssPreprocessor(code) {
|
|
113
|
+
return code === 'expected_token' || code === 'unexpected_eof' || code?.startsWith('css_');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {import('@rsvelte/compiler').Warning & Error} err a svelte compiler error, which is a mix of Warning and an error
|
|
118
|
+
* @param {string} originalCode
|
|
119
|
+
* @param {import('../public.d.ts').Options['preprocess']} [preprocessors]
|
|
120
|
+
*/
|
|
121
|
+
export function enhanceCompileError(err, originalCode, preprocessors) {
|
|
122
|
+
preprocessors = arraify(preprocessors ?? []);
|
|
123
|
+
|
|
124
|
+
/** @type {string[]} */
|
|
125
|
+
const additionalMessages = [];
|
|
126
|
+
|
|
127
|
+
// Handle incorrect CSS preprocessor usage
|
|
128
|
+
if (couldBeFixedByCssPreprocessor(err.code)) {
|
|
129
|
+
// Reference from Svelte: https://github.com/sveltejs/svelte/blob/9926347ad9dbdd0f3324d5538e25dcb7f5e442f8/packages/svelte/src/compiler/preprocess/index.js#L257
|
|
130
|
+
const styleRe =
|
|
131
|
+
/<!--[^]*?-->|<style((?:\s+[^=>'"/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"/]+)*\s*)(?:\/>|>([\S\s]*?)<\/style>)/g;
|
|
132
|
+
|
|
133
|
+
let m;
|
|
134
|
+
while ((m = styleRe.exec(originalCode))) {
|
|
135
|
+
if (m[0]?.startsWith('<!--')) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Warn missing lang attribute
|
|
139
|
+
if (!m[1]?.includes('lang=')) {
|
|
140
|
+
additionalMessages.push('Did you forget to add a lang attribute to your style tag?');
|
|
141
|
+
}
|
|
142
|
+
// Warn missing style preprocessor
|
|
143
|
+
if (
|
|
144
|
+
preprocessors.every((p) => p.style == null || p.name === 'inject-scope-everything-rule')
|
|
145
|
+
) {
|
|
146
|
+
const preprocessorType = m[1]?.match(/lang="(.+?)"/)?.[1] ?? 'style';
|
|
147
|
+
additionalMessages.push(
|
|
148
|
+
`Did you forget to add a ${preprocessorType} preprocessor? See https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/preprocess.md for more information.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (additionalMessages.length) {
|
|
155
|
+
err.message += '\n\n- ' + additionalMessages.join('\n- ');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return err;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @param {T | T[]} value
|
|
163
|
+
* @template T
|
|
164
|
+
*/
|
|
165
|
+
function arraify(value) {
|
|
166
|
+
return Array.isArray(value) ? value : [value];
|
|
167
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const hashes = Object.create(null);
|
|
4
|
+
|
|
5
|
+
//TODO shorter?
|
|
6
|
+
const hash_length = 12;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* replaces +/= in base64 output so they don't interfere
|
|
10
|
+
*
|
|
11
|
+
* @param {string} input
|
|
12
|
+
* @returns {string} base64 hash safe to use in any context
|
|
13
|
+
*/
|
|
14
|
+
export function safeBase64Hash(input) {
|
|
15
|
+
if (hashes[input]) {
|
|
16
|
+
return hashes[input];
|
|
17
|
+
}
|
|
18
|
+
//TODO if performance really matters, use a faster one like xx-hash etc.
|
|
19
|
+
// should be evenly distributed because short input length and similarities in paths could cause collisions otherwise
|
|
20
|
+
// OR DON'T USE A HASH AT ALL, what about a simple counter?
|
|
21
|
+
const md5 = crypto.createHash('md5');
|
|
22
|
+
md5.update(input);
|
|
23
|
+
const hash = toSafe(md5.digest('base64')).slice(0, hash_length);
|
|
24
|
+
hashes[input] = hash;
|
|
25
|
+
return hash;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** @type {Record<string, string>} */
|
|
29
|
+
const replacements = {
|
|
30
|
+
'+': '-',
|
|
31
|
+
'/': '_',
|
|
32
|
+
'=': ''
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const replaceRE = new RegExp(`[${Object.keys(replacements).join('')}]`, 'g');
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} base64
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
function toSafe(base64) {
|
|
42
|
+
return base64.replace(replaceRE, (x) => replacements[x]);
|
|
43
|
+
}
|