From 7c0a7bab0fb80a9c167977d703c78513c8921e2d Mon Sep 17 00:00:00 2001 From: dominikg Date: Fri, 26 Jul 2024 18:08:17 +0200 Subject: [PATCH 1/4] fix(dev): avoid css hash mismatch by applying custom css hash even for prebundling --- .changeset/twenty-walls-watch.md | 5 +++++ .../vite-plugin-svelte/src/utils/esbuild.js | 20 ++++++++++++++++--- packages/vite-plugin-svelte/src/utils/id.js | 11 ++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 .changeset/twenty-walls-watch.md diff --git a/.changeset/twenty-walls-watch.md b/.changeset/twenty-walls-watch.md new file mode 100644 index 000000000..d583c1f11 --- /dev/null +++ b/.changeset/twenty-walls-watch.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/vite-plugin-svelte': patch +--- + +fix(dev): make sure custom cssHash is applied consistently even for prebundled components to avoid hash mismatches during hydration diff --git a/packages/vite-plugin-svelte/src/utils/esbuild.js b/packages/vite-plugin-svelte/src/utils/esbuild.js index cec88e545..4b04c22a2 100644 --- a/packages/vite-plugin-svelte/src/utils/esbuild.js +++ b/packages/vite-plugin-svelte/src/utils/esbuild.js @@ -2,6 +2,8 @@ import { readFileSync } from 'node:fs'; import * as svelte from 'svelte/compiler'; import { log } from './log.js'; import { toESBuildError } from './error.js'; +import { safeBase64Hash } from './hash.js'; +import { buildFilenameNormalizer } from './id.js'; /** * @typedef {NonNullable} EsbuildOptions @@ -26,6 +28,7 @@ export function esbuildSveltePlugin(options) { const filter = /\.svelte(?:\?.*)?$/; /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */ let statsCollection; + const normalizeFilename = buildFilenameNormalizer(options.root); build.onStart(() => { statsCollection = options.stats?.startCollection('prebundle library components', { logResult: (c) => c.stats.length > 1 @@ -34,7 +37,12 @@ export function esbuildSveltePlugin(options) { build.onLoad({ filter }, async ({ path: filename }) => { const code = readFileSync(filename, 'utf8'); try { - const contents = await compileSvelte(options, { filename, code }, statsCollection); + const contents = await compileSvelte( + options, + { filename, code }, + normalizeFilename, + statsCollection + ); return { contents }; } catch (e) { return { errors: [toESBuildError(e, options)] }; @@ -49,11 +57,12 @@ export function esbuildSveltePlugin(options) { /** * @param {import('../types/options.d.ts').ResolvedOptions} options - * @param {{ filename: string; code: string }} input + * @param {{ filename: string, code: string }} input + * @param {(filename: string) => string} normalizeFilename * @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection] * @returns {Promise} */ -async function compileSvelte(options, { filename, code }, statsCollection) { +async function compileSvelte(options, { filename, code }, normalizeFilename, statsCollection) { let css = options.compilerOptions.css; if (css !== 'injected') { // TODO ideally we'd be able to externalize prebundled styles too, but for now always put them in the js @@ -68,6 +77,11 @@ async function compileSvelte(options, { filename, code }, statsCollection) { generate: 'client' }; + if (compileOptions.hmr && options.emitCss) { + const hash = `s-${safeBase64Hash(normalizeFilename(filename))}`; + compileOptions.cssHash = () => hash; + } + let preprocessed; if (options.preprocess) { diff --git a/packages/vite-plugin-svelte/src/utils/id.js b/packages/vite-plugin-svelte/src/utils/id.js index a5fe602a1..6bc0c7334 100644 --- a/packages/vite-plugin-svelte/src/utils/id.js +++ b/packages/vite-plugin-svelte/src/utils/id.js @@ -137,6 +137,17 @@ function normalize(filename, normalizedRoot) { return stripRoot(normalizePath(filename), normalizedRoot); } +/** + * create a normalizer function for root + * + * @param {string} root + * @returns {function(filename: string): string} + */ +export function buildFilenameNormalizer(root) { + const normalizedRoot = normalizePath(root); + return (filename) => normalize(filename, normalizedRoot); +} + /** * @param {string} filename * @param {string} root From 2da7dbf18b04061109d1d4f5dd39e11fc03648b0 Mon Sep 17 00:00:00 2001 From: dominikg Date: Fri, 26 Jul 2024 18:16:27 +0200 Subject: [PATCH 2/4] fix(optimize): skip hmr compile for prebundled --- .changeset/healthy-years-battle.md | 5 +++++ packages/vite-plugin-svelte/src/utils/esbuild.js | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 .changeset/healthy-years-battle.md diff --git a/.changeset/healthy-years-battle.md b/.changeset/healthy-years-battle.md new file mode 100644 index 000000000..dcfaaeb2f --- /dev/null +++ b/.changeset/healthy-years-battle.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/vite-plugin-svelte': patch +--- + +fix(dev): compile with hmr: false for prebundled deps as hmr does not work with that diff --git a/packages/vite-plugin-svelte/src/utils/esbuild.js b/packages/vite-plugin-svelte/src/utils/esbuild.js index 4b04c22a2..72ed350c0 100644 --- a/packages/vite-plugin-svelte/src/utils/esbuild.js +++ b/packages/vite-plugin-svelte/src/utils/esbuild.js @@ -77,9 +77,12 @@ async function compileSvelte(options, { filename, code }, normalizeFilename, sta generate: 'client' }; - if (compileOptions.hmr && options.emitCss) { - const hash = `s-${safeBase64Hash(normalizeFilename(filename))}`; - compileOptions.cssHash = () => hash; + if (compileOptions.hmr) { + if (options.emitCss) { + const hash = `s-${safeBase64Hash(normalizeFilename(filename))}`; + compileOptions.cssHash = () => hash; + } + compileOptions.hmr = false; } let preprocessed; From 215d8fa4ee5730198edd9215b5ddda9652c5fb08 Mon Sep 17 00:00:00 2001 From: dominikg Date: Fri, 26 Jul 2024 18:26:00 +0200 Subject: [PATCH 3/4] fix: jsdoc --- packages/vite-plugin-svelte/src/utils/id.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite-plugin-svelte/src/utils/id.js b/packages/vite-plugin-svelte/src/utils/id.js index 6bc0c7334..8b082f383 100644 --- a/packages/vite-plugin-svelte/src/utils/id.js +++ b/packages/vite-plugin-svelte/src/utils/id.js @@ -141,7 +141,7 @@ function normalize(filename, normalizedRoot) { * create a normalizer function for root * * @param {string} root - * @returns {function(filename: string): string} + * @returns {(filename: string) => string} */ export function buildFilenameNormalizer(root) { const normalizedRoot = normalizePath(root); From 656b36a10802db85030bc5eccafe8123396ee99c Mon Sep 17 00:00:00 2001 From: dominikg Date: Sat, 27 Jul 2024 12:53:33 +0200 Subject: [PATCH 4/4] refactor: simplify normalize use in esbuild plugin --- packages/vite-plugin-svelte/src/utils/esbuild.js | 15 ++++----------- packages/vite-plugin-svelte/src/utils/id.js | 13 +------------ 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/packages/vite-plugin-svelte/src/utils/esbuild.js b/packages/vite-plugin-svelte/src/utils/esbuild.js index 72ed350c0..8779408c9 100644 --- a/packages/vite-plugin-svelte/src/utils/esbuild.js +++ b/packages/vite-plugin-svelte/src/utils/esbuild.js @@ -3,7 +3,7 @@ import * as svelte from 'svelte/compiler'; import { log } from './log.js'; import { toESBuildError } from './error.js'; import { safeBase64Hash } from './hash.js'; -import { buildFilenameNormalizer } from './id.js'; +import { normalize } from './id.js'; /** * @typedef {NonNullable} EsbuildOptions @@ -28,7 +28,6 @@ export function esbuildSveltePlugin(options) { const filter = /\.svelte(?:\?.*)?$/; /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */ let statsCollection; - const normalizeFilename = buildFilenameNormalizer(options.root); build.onStart(() => { statsCollection = options.stats?.startCollection('prebundle library components', { logResult: (c) => c.stats.length > 1 @@ -37,12 +36,7 @@ export function esbuildSveltePlugin(options) { build.onLoad({ filter }, async ({ path: filename }) => { const code = readFileSync(filename, 'utf8'); try { - const contents = await compileSvelte( - options, - { filename, code }, - normalizeFilename, - statsCollection - ); + const contents = await compileSvelte(options, { filename, code }, statsCollection); return { contents }; } catch (e) { return { errors: [toESBuildError(e, options)] }; @@ -58,11 +52,10 @@ export function esbuildSveltePlugin(options) { /** * @param {import('../types/options.d.ts').ResolvedOptions} options * @param {{ filename: string, code: string }} input - * @param {(filename: string) => string} normalizeFilename * @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection] * @returns {Promise} */ -async function compileSvelte(options, { filename, code }, normalizeFilename, statsCollection) { +async function compileSvelte(options, { filename, code }, statsCollection) { let css = options.compilerOptions.css; if (css !== 'injected') { // TODO ideally we'd be able to externalize prebundled styles too, but for now always put them in the js @@ -79,7 +72,7 @@ async function compileSvelte(options, { filename, code }, normalizeFilename, sta if (compileOptions.hmr) { if (options.emitCss) { - const hash = `s-${safeBase64Hash(normalizeFilename(filename))}`; + const hash = `s-${safeBase64Hash(normalize(filename, options.root))}`; compileOptions.cssHash = () => hash; } compileOptions.hmr = false; diff --git a/packages/vite-plugin-svelte/src/utils/id.js b/packages/vite-plugin-svelte/src/utils/id.js index 8b082f383..812a6c79b 100644 --- a/packages/vite-plugin-svelte/src/utils/id.js +++ b/packages/vite-plugin-svelte/src/utils/id.js @@ -133,21 +133,10 @@ function parseRequestQuery(rawQuery) { * @param {string} normalizedRoot * @returns {string} */ -function normalize(filename, normalizedRoot) { +export function normalize(filename, normalizedRoot) { return stripRoot(normalizePath(filename), normalizedRoot); } -/** - * create a normalizer function for root - * - * @param {string} root - * @returns {(filename: string) => string} - */ -export function buildFilenameNormalizer(root) { - const normalizedRoot = normalizePath(root); - return (filename) => normalize(filename, normalizedRoot); -} - /** * @param {string} filename * @param {string} root