chore: generate CSS hash using the filename (#16740)

* chore: generate CSS hash using the filename

* fix all tests but one

* slightly kludgy fix

* try this

* fix

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
warn-on-state-proxy-unmount
ComputerGuy 5 days ago committed by GitHub
parent f343b00cc6
commit 68d27f1c4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: generate CSS hash using the filename

@ -456,10 +456,19 @@ export function analyze_component(root, source, options) {
const is_custom_element = !!options.customElementOptions || options.customElement; const is_custom_element = !!options.customElementOptions || options.customElement;
const name = module.scope.generate(options.name ?? component_name);
state.adjust({
component_name: name,
dev: options.dev,
rootDir: options.rootDir,
runes
});
// TODO remove all the ?? stuff, we don't need it now that we're validating the config // TODO remove all the ?? stuff, we don't need it now that we're validating the config
/** @type {ComponentAnalysis} */ /** @type {ComponentAnalysis} */
const analysis = { const analysis = {
name: module.scope.generate(options.name ?? component_name), name,
root: scope_root, root: scope_root,
module, module,
instance, instance,
@ -520,7 +529,7 @@ export function analyze_component(root, source, options) {
hash: root.css hash: root.css
? options.cssHash({ ? options.cssHash({
css: root.css.content.styles, css: root.css.content.styles,
filename: options.filename, filename: state.filename,
name: component_name, name: component_name,
hash hash
}) })
@ -534,13 +543,6 @@ export function analyze_component(root, source, options) {
async_deriveds: new Set() async_deriveds: new Set()
}; };
state.adjust({
component_name: analysis.name,
dev: options.dev,
rootDir: options.rootDir,
runes
});
if (!runes) { if (!runes) {
// every exported `let` or `var` declaration becomes a prop, everything else becomes an export // every exported `let` or `var` declaration becomes a prop, everything else becomes an export
for (const node of instance.ast.body) { for (const node of instance.ast.body) {

@ -105,7 +105,7 @@ export interface CompileOptions extends ModuleCompileOptions {
css?: 'injected' | 'external'; css?: 'injected' | 'external';
/** /**
* A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. * A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS.
* It defaults to returning `svelte-${hash(css)}`. * It defaults to returning `svelte-${hash(filename ?? css)}`.
* *
* @default undefined * @default undefined
*/ */

@ -70,8 +70,8 @@ const component_options = {
return input; return input;
}), }),
cssHash: fun(({ css, hash }) => { cssHash: fun(({ css, filename, hash }) => {
return `svelte-${hash(css)}`; return `svelte-${hash(filename === '(unknown)' ? css : filename ?? css)}`;
}), }),
// TODO this is a sourcemap option, would be good to put under a sourcemap namespace // TODO this is a sourcemap option, would be good to put under a sourcemap namespace

@ -32,7 +32,13 @@ function clean_children(node, opts) {
return; return;
} }
node.setAttribute(attr.name, attr.value); let value = attr.value;
if (attr.name === 'class') {
value = value.replace(/svelte-\w+/, 'svelte-xyz123');
}
node.setAttribute(attr.name, value);
}); });
for (let child of [...node.childNodes]) { for (let child of [...node.childNodes]) {

@ -74,6 +74,7 @@ function normalize_html(window, html) {
node.innerHTML = html node.innerHTML = html
.replace(/<!--.*?-->/g, '') .replace(/<!--.*?-->/g, '')
.replace(/>[\s\r\n]+</g, '><') .replace(/>[\s\r\n]+</g, '><')
.replace(/svelte-\w+/g, 'svelte-xyz123')
.trim(); .trim();
normalize_children(node); normalize_children(node);

@ -1,43 +1,43 @@
import { ok, test } from '../../test'; import { ok, test } from '../../test';
export default test({ export default test({
html: '<div class="svelte-x1o6ra"></div>', html: '<div class="svelte-70s021"></div>',
test({ assert, component, target }) { test({ assert, component, target }) {
const div = target.querySelector('div'); const div = target.querySelector('div');
ok(div); ok(div);
component.testName = null; component.testName = null;
assert.equal(div.className, 'svelte-x1o6ra'); assert.equal(div.className, 'svelte-70s021');
component.testName = undefined; component.testName = undefined;
assert.equal(div.className, 'svelte-x1o6ra'); assert.equal(div.className, 'svelte-70s021');
component.testName = undefined + ''; component.testName = undefined + '';
assert.equal(div.className, 'undefined svelte-x1o6ra'); assert.equal(div.className, 'undefined svelte-70s021');
component.testName = null + ''; component.testName = null + '';
assert.equal(div.className, 'null svelte-x1o6ra'); assert.equal(div.className, 'null svelte-70s021');
component.testName = 1; component.testName = 1;
assert.equal(div.className, '1 svelte-x1o6ra'); assert.equal(div.className, '1 svelte-70s021');
component.testName = 0; component.testName = 0;
assert.equal(div.className, '0 svelte-x1o6ra'); assert.equal(div.className, '0 svelte-70s021');
component.testName = false; component.testName = false;
assert.equal(div.className, 'false svelte-x1o6ra'); assert.equal(div.className, 'false svelte-70s021');
component.testName = true; component.testName = true;
assert.equal(div.className, 'true svelte-x1o6ra'); assert.equal(div.className, 'true svelte-70s021');
component.testName = {}; component.testName = {};
assert.equal(div.className, 'svelte-x1o6ra'); assert.equal(div.className, 'svelte-70s021');
component.testName = ''; component.testName = '';
assert.equal(div.className, 'svelte-x1o6ra'); assert.equal(div.className, 'svelte-70s021');
component.testName = 'testClassName'; component.testName = 'testClassName';
assert.equal(div.className, 'testClassName svelte-x1o6ra'); assert.equal(div.className, 'testClassName svelte-70s021');
} }
}); });

@ -10,43 +10,43 @@ export default test({
}; };
}, },
html: '<div class="test1test2 svelte-x1o6ra"></div>', html: '<div class="test1test2 svelte-70s021"></div>',
test({ assert, component, target }) { test({ assert, component, target }) {
const div = target.querySelector('div'); const div = target.querySelector('div');
ok(div); ok(div);
assert.equal(div.className, 'test1test2 svelte-x1o6ra'); assert.equal(div.className, 'test1test2 svelte-70s021');
component.testName1 = null; component.testName1 = null;
component.testName2 = null; component.testName2 = null;
assert.equal(div.className, '0 svelte-x1o6ra'); assert.equal(div.className, '0 svelte-70s021');
component.testName1 = null; component.testName1 = null;
component.testName2 = 'test'; component.testName2 = 'test';
assert.equal(div.className, 'nulltest svelte-x1o6ra'); assert.equal(div.className, 'nulltest svelte-70s021');
component.testName1 = undefined; component.testName1 = undefined;
component.testName2 = 'test'; component.testName2 = 'test';
assert.equal(div.className, 'undefinedtest svelte-x1o6ra'); assert.equal(div.className, 'undefinedtest svelte-70s021');
component.testName1 = undefined; component.testName1 = undefined;
component.testName2 = undefined; component.testName2 = undefined;
assert.equal(div.className, 'NaN svelte-x1o6ra'); assert.equal(div.className, 'NaN svelte-70s021');
component.testName1 = null; component.testName1 = null;
component.testName2 = 1; component.testName2 = 1;
assert.equal(div.className, '1 svelte-x1o6ra'); assert.equal(div.className, '1 svelte-70s021');
component.testName1 = undefined; component.testName1 = undefined;
component.testName2 = 1; component.testName2 = 1;
assert.equal(div.className, 'NaN svelte-x1o6ra'); assert.equal(div.className, 'NaN svelte-70s021');
component.testName1 = null; component.testName1 = null;
component.testName2 = 0; component.testName2 = 0;
assert.equal(div.className, '0 svelte-x1o6ra'); assert.equal(div.className, '0 svelte-70s021');
component.testName1 = undefined; component.testName1 = undefined;
component.testName2 = 0; component.testName2 = 0;
assert.equal(div.className, 'NaN svelte-x1o6ra'); assert.equal(div.className, 'NaN svelte-70s021');
} }
}); });

@ -8,41 +8,41 @@ export default test({
}; };
}, },
html: '<div class="testClassName svelte-x1o6ra"></div>', html: '<div class="testClassName svelte-70s021"></div>',
test({ assert, component, target }) { test({ assert, component, target }) {
const div = target.querySelector('div'); const div = target.querySelector('div');
ok(div); ok(div);
assert.equal(div.className, 'testClassName svelte-x1o6ra'); assert.equal(div.className, 'testClassName svelte-70s021');
component.testName = null; component.testName = null;
assert.equal(div.className, 'svelte-x1o6ra'); assert.equal(div.className, 'svelte-70s021');
component.testName = undefined; component.testName = undefined;
assert.equal(div.className, 'svelte-x1o6ra'); assert.equal(div.className, 'svelte-70s021');
component.testName = undefined + ''; component.testName = undefined + '';
assert.equal(div.className, 'undefined svelte-x1o6ra'); assert.equal(div.className, 'undefined svelte-70s021');
component.testName = null + ''; component.testName = null + '';
assert.equal(div.className, 'null svelte-x1o6ra'); assert.equal(div.className, 'null svelte-70s021');
component.testName = 1; component.testName = 1;
assert.equal(div.className, '1 svelte-x1o6ra'); assert.equal(div.className, '1 svelte-70s021');
component.testName = 0; component.testName = 0;
assert.equal(div.className, '0 svelte-x1o6ra'); assert.equal(div.className, '0 svelte-70s021');
component.testName = false; component.testName = false;
assert.equal(div.className, 'false svelte-x1o6ra'); assert.equal(div.className, 'false svelte-70s021');
component.testName = true; component.testName = true;
assert.equal(div.className, 'true svelte-x1o6ra'); assert.equal(div.className, 'true svelte-70s021');
component.testName = {}; component.testName = {};
assert.equal(div.className, 'svelte-x1o6ra'); assert.equal(div.className, 'svelte-70s021');
component.testName = ''; component.testName = '';
assert.equal(div.className, 'svelte-x1o6ra'); assert.equal(div.className, 'svelte-70s021');
} }
}); });

@ -10,43 +10,43 @@ export default test({
}; };
}, },
html: '<div class="test1test2 svelte-x1o6ra"></div>', html: '<div class="test1test2 svelte-70s021"></div>',
async test({ assert, component, target }) { async test({ assert, component, target }) {
const div = target.querySelector('div'); const div = target.querySelector('div');
ok(div); ok(div);
assert.equal(div.className, 'test1test2 svelte-x1o6ra'); assert.equal(div.className, 'test1test2 svelte-70s021');
component.testName1 = null; component.testName1 = null;
component.testName2 = null; component.testName2 = null;
assert.equal(div.className, '0 svelte-x1o6ra'); assert.equal(div.className, '0 svelte-70s021');
component.testName1 = null; component.testName1 = null;
component.testName2 = 'test'; component.testName2 = 'test';
assert.equal(div.className, 'nulltest svelte-x1o6ra'); assert.equal(div.className, 'nulltest svelte-70s021');
component.testName1 = undefined; component.testName1 = undefined;
component.testName2 = 'test'; component.testName2 = 'test';
assert.equal(div.className, 'undefinedtest svelte-x1o6ra'); assert.equal(div.className, 'undefinedtest svelte-70s021');
component.testName1 = undefined; component.testName1 = undefined;
component.testName2 = undefined; component.testName2 = undefined;
assert.equal(div.className, 'NaN svelte-x1o6ra'); assert.equal(div.className, 'NaN svelte-70s021');
component.testName1 = null; component.testName1 = null;
component.testName2 = 1; component.testName2 = 1;
assert.equal(div.className, '1 svelte-x1o6ra'); assert.equal(div.className, '1 svelte-70s021');
component.testName1 = undefined; component.testName1 = undefined;
component.testName2 = 1; component.testName2 = 1;
assert.equal(div.className, 'NaN svelte-x1o6ra'); assert.equal(div.className, 'NaN svelte-70s021');
component.testName1 = null; component.testName1 = null;
component.testName2 = 0; component.testName2 = 0;
assert.equal(div.className, '0 svelte-x1o6ra'); assert.equal(div.className, '0 svelte-70s021');
component.testName1 = undefined; component.testName1 = undefined;
component.testName2 = 0; component.testName2 = 0;
assert.equal(div.className, 'NaN svelte-x1o6ra'); assert.equal(div.className, 'NaN svelte-70s021');
} }
}); });

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({ export default test({
async test({ assert, target, logs }) { async test({ assert, target, logs }) {
const [my_element, my_element_1] = target.querySelectorAll('my-element'); const [my_element, my_element_1] = target.querySelectorAll('my-element');
assert.equal(my_element.classList.contains('svelte-1koh33s'), true); assert.equal(my_element.classList.contains('svelte-70s021'), true);
assert.equal(my_element_1.classList.contains('svelte-1koh33s'), true); assert.equal(my_element_1.classList.contains('svelte-70s021'), true);
} }
}); });

@ -1,14 +1,14 @@
import { ok, test } from '../../test'; import { ok, test } from '../../test';
export default test({ export default test({
html: `<custom-element class="red svelte-p153w3"></custom-element><custom-element class="red svelte-p153w3"></custom-element>`, html: `<custom-element class="red svelte-70s021"></custom-element><custom-element class="red svelte-70s021"></custom-element>`,
async test({ assert, target }) { async test({ assert, target }) {
const [el, el2] = target.querySelectorAll('custom-element'); const [el, el2] = target.querySelectorAll('custom-element');
ok(el); ok(el);
ok(el2); ok(el2);
assert.deepEqual(el.className, 'red svelte-p153w3'); assert.deepEqual(el.className, 'red svelte-70s021');
assert.deepEqual(el2.className, 'red svelte-p153w3'); assert.deepEqual(el2.className, 'red svelte-70s021');
} }
}); });

@ -1 +1 @@
<!--[--><div class="foo svelte-gfnjhw">foo</div><!--]--> <!--[--><div class="foo svelte-1h3glmj">foo</div><!--]-->

@ -1 +1 @@
<style id="svelte-gfnjhw">.foo.svelte-gfnjhw {color:green;}.foo.svelte-gfnjhw {color:green;} .foo.svelte-gfnjhw {color:green;}.foo.svelte-gfnjhw, .foo.svelte-gfnjhw {color:green;}</style> <style id="svelte-1h3glmj">.foo.svelte-1h3glmj {color:green;}.foo.svelte-1h3glmj {color:green;} .foo.svelte-1h3glmj {color:green;}.foo.svelte-1h3glmj, .foo.svelte-1h3glmj {color:green;}</style>

@ -1,4 +1,4 @@
.foo.svelte-sg04hs { .foo.svelte-1nvcr6w {
color: red; color: red;
} }

@ -1 +1 @@
<!--[--><div class="bar svelte-ievf05">bar</div><!----> <div class="foo svelte-sg04hs">foo</div><!--]--> <!--[--><div class="bar svelte-1nvcr6w">bar</div><!----> <div class="foo svelte-sg04hs">foo</div><!--]-->

@ -1 +1 @@
<style id="svelte-ievf05">.bar.svelte-ievf05 {color:red;}</style> <style id="svelte-lr8eda">.bar.svelte-lr8eda {color:red;}</style>

@ -1,4 +1,4 @@
.foo.svelte-sg04hs { .foo.svelte-okauro {
color: red; color: red;
} }

@ -1 +1 @@
<div class="foo svelte-sg04hs">foo</div> <div class="foo svelte-okauro">foo</div>

@ -1 +1 @@
<style id="svelte-sg04hs">.foo.svelte-sg04hs {color:red;}</style> <style id="svelte-okauro">.foo.svelte-okauro {color:red;}</style>

@ -1,4 +1,4 @@
.foo.svelte-sg04hs { .foo.svelte-e9omc {
color: red; color: red;
} }

@ -1 +1 @@
<div class="foo svelte-sg04hs">foo</div> <div class="foo svelte-e9omc">foo</div>

@ -1 +1 @@
<style id="svelte-sg04hs">.foo.svelte-sg04hs {color:red;}</style> <style id="svelte-e9omc">.foo.svelte-e9omc {color:red;}</style>

@ -57,7 +57,7 @@ export default test({
{ str: 'replace_me_script', strGenerated: 'done_replace_script_2' }, { str: 'replace_me_script', strGenerated: 'done_replace_script_2' },
{ str: 'done_replace_script_2', idxGenerated: 1 } { str: 'done_replace_script_2', idxGenerated: 1 }
], ],
css: [{ str: '.replace_me_style', strGenerated: '.done_replace_style_2.svelte-o6vre' }], css: [{ str: '.replace_me_style', strGenerated: '.done_replace_style_2.svelte-1vsrjd4' }],
test({ assert, code_preprocessed, code_css }) { test({ assert, code_preprocessed, code_css }) {
assert.equal( assert.equal(
code_preprocessed.includes('\n/*# sourceMappingURL=data:application/json;base64,'), code_preprocessed.includes('\n/*# sourceMappingURL=data:application/json;base64,'),

@ -1,5 +1,5 @@
import { test } from '../../test'; import { test } from '../../test';
export default test({ export default test({
css: [{ str: '.foo', strGenerated: '.foo.svelte-sg04hs' }] css: [{ str: '.foo', strGenerated: '.foo.svelte-1eyw86p' }]
}); });

@ -998,7 +998,7 @@ declare module 'svelte/compiler' {
css?: 'injected' | 'external'; css?: 'injected' | 'external';
/** /**
* A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. * A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS.
* It defaults to returning `svelte-${hash(css)}`. * It defaults to returning `svelte-${hash(filename ?? css)}`.
* *
* @default undefined * @default undefined
*/ */
@ -2933,7 +2933,7 @@ declare module 'svelte/types/compiler/interfaces' {
css?: 'injected' | 'external'; css?: 'injected' | 'external';
/** /**
* A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. * A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS.
* It defaults to returning `svelte-${hash(css)}`. * It defaults to returning `svelte-${hash(filename ?? css)}`.
* *
* @default undefined * @default undefined
*/ */

Loading…
Cancel
Save