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 6 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 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
/** @type {ComponentAnalysis} */
const analysis = {
name: module.scope.generate(options.name ?? component_name),
name,
root: scope_root,
module,
instance,
@ -520,7 +529,7 @@ export function analyze_component(root, source, options) {
hash: root.css
? options.cssHash({
css: root.css.content.styles,
filename: options.filename,
filename: state.filename,
name: component_name,
hash
})
@ -534,13 +543,6 @@ export function analyze_component(root, source, options) {
async_deriveds: new Set()
};
state.adjust({
component_name: analysis.name,
dev: options.dev,
rootDir: options.rootDir,
runes
});
if (!runes) {
// every exported `let` or `var` declaration becomes a prop, everything else becomes an export
for (const node of instance.ast.body) {

@ -105,7 +105,7 @@ export interface CompileOptions extends ModuleCompileOptions {
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.
* It defaults to returning `svelte-${hash(css)}`.
* It defaults to returning `svelte-${hash(filename ?? css)}`.
*
* @default undefined
*/

@ -70,8 +70,8 @@ const component_options = {
return input;
}),
cssHash: fun(({ css, hash }) => {
return `svelte-${hash(css)}`;
cssHash: fun(({ css, filename, hash }) => {
return `svelte-${hash(filename === '(unknown)' ? css : filename ?? css)}`;
}),
// 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;
}
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]) {

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

@ -1,43 +1,43 @@
import { ok, test } from '../../test';
export default test({
html: '<div class="svelte-x1o6ra"></div>',
html: '<div class="svelte-70s021"></div>',
test({ assert, component, target }) {
const div = target.querySelector('div');
ok(div);
component.testName = null;
assert.equal(div.className, 'svelte-x1o6ra');
assert.equal(div.className, 'svelte-70s021');
component.testName = undefined;
assert.equal(div.className, 'svelte-x1o6ra');
assert.equal(div.className, 'svelte-70s021');
component.testName = undefined + '';
assert.equal(div.className, 'undefined svelte-x1o6ra');
assert.equal(div.className, 'undefined svelte-70s021');
component.testName = null + '';
assert.equal(div.className, 'null svelte-x1o6ra');
assert.equal(div.className, 'null svelte-70s021');
component.testName = 1;
assert.equal(div.className, '1 svelte-x1o6ra');
assert.equal(div.className, '1 svelte-70s021');
component.testName = 0;
assert.equal(div.className, '0 svelte-x1o6ra');
assert.equal(div.className, '0 svelte-70s021');
component.testName = false;
assert.equal(div.className, 'false svelte-x1o6ra');
assert.equal(div.className, 'false svelte-70s021');
component.testName = true;
assert.equal(div.className, 'true svelte-x1o6ra');
assert.equal(div.className, 'true svelte-70s021');
component.testName = {};
assert.equal(div.className, 'svelte-x1o6ra');
assert.equal(div.className, 'svelte-70s021');
component.testName = '';
assert.equal(div.className, 'svelte-x1o6ra');
assert.equal(div.className, 'svelte-70s021');
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 }) {
const div = target.querySelector('div');
ok(div);
assert.equal(div.className, 'test1test2 svelte-x1o6ra');
assert.equal(div.className, 'test1test2 svelte-70s021');
component.testName1 = null;
component.testName2 = null;
assert.equal(div.className, '0 svelte-x1o6ra');
assert.equal(div.className, '0 svelte-70s021');
component.testName1 = null;
component.testName2 = 'test';
assert.equal(div.className, 'nulltest svelte-x1o6ra');
assert.equal(div.className, 'nulltest svelte-70s021');
component.testName1 = undefined;
component.testName2 = 'test';
assert.equal(div.className, 'undefinedtest svelte-x1o6ra');
assert.equal(div.className, 'undefinedtest svelte-70s021');
component.testName1 = undefined;
component.testName2 = undefined;
assert.equal(div.className, 'NaN svelte-x1o6ra');
assert.equal(div.className, 'NaN svelte-70s021');
component.testName1 = null;
component.testName2 = 1;
assert.equal(div.className, '1 svelte-x1o6ra');
assert.equal(div.className, '1 svelte-70s021');
component.testName1 = undefined;
component.testName2 = 1;
assert.equal(div.className, 'NaN svelte-x1o6ra');
assert.equal(div.className, 'NaN svelte-70s021');
component.testName1 = null;
component.testName2 = 0;
assert.equal(div.className, '0 svelte-x1o6ra');
assert.equal(div.className, '0 svelte-70s021');
component.testName1 = undefined;
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 }) {
const div = target.querySelector('div');
ok(div);
assert.equal(div.className, 'testClassName svelte-x1o6ra');
assert.equal(div.className, 'testClassName svelte-70s021');
component.testName = null;
assert.equal(div.className, 'svelte-x1o6ra');
assert.equal(div.className, 'svelte-70s021');
component.testName = undefined;
assert.equal(div.className, 'svelte-x1o6ra');
assert.equal(div.className, 'svelte-70s021');
component.testName = undefined + '';
assert.equal(div.className, 'undefined svelte-x1o6ra');
assert.equal(div.className, 'undefined svelte-70s021');
component.testName = null + '';
assert.equal(div.className, 'null svelte-x1o6ra');
assert.equal(div.className, 'null svelte-70s021');
component.testName = 1;
assert.equal(div.className, '1 svelte-x1o6ra');
assert.equal(div.className, '1 svelte-70s021');
component.testName = 0;
assert.equal(div.className, '0 svelte-x1o6ra');
assert.equal(div.className, '0 svelte-70s021');
component.testName = false;
assert.equal(div.className, 'false svelte-x1o6ra');
assert.equal(div.className, 'false svelte-70s021');
component.testName = true;
assert.equal(div.className, 'true svelte-x1o6ra');
assert.equal(div.className, 'true svelte-70s021');
component.testName = {};
assert.equal(div.className, 'svelte-x1o6ra');
assert.equal(div.className, 'svelte-70s021');
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 }) {
const div = target.querySelector('div');
ok(div);
assert.equal(div.className, 'test1test2 svelte-x1o6ra');
assert.equal(div.className, 'test1test2 svelte-70s021');
component.testName1 = null;
component.testName2 = null;
assert.equal(div.className, '0 svelte-x1o6ra');
assert.equal(div.className, '0 svelte-70s021');
component.testName1 = null;
component.testName2 = 'test';
assert.equal(div.className, 'nulltest svelte-x1o6ra');
assert.equal(div.className, 'nulltest svelte-70s021');
component.testName1 = undefined;
component.testName2 = 'test';
assert.equal(div.className, 'undefinedtest svelte-x1o6ra');
assert.equal(div.className, 'undefinedtest svelte-70s021');
component.testName1 = undefined;
component.testName2 = undefined;
assert.equal(div.className, 'NaN svelte-x1o6ra');
assert.equal(div.className, 'NaN svelte-70s021');
component.testName1 = null;
component.testName2 = 1;
assert.equal(div.className, '1 svelte-x1o6ra');
assert.equal(div.className, '1 svelte-70s021');
component.testName1 = undefined;
component.testName2 = 1;
assert.equal(div.className, 'NaN svelte-x1o6ra');
assert.equal(div.className, 'NaN svelte-70s021');
component.testName1 = null;
component.testName2 = 0;
assert.equal(div.className, '0 svelte-x1o6ra');
assert.equal(div.className, '0 svelte-70s021');
component.testName1 = undefined;
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({
async test({ assert, target, logs }) {
const [my_element, my_element_1] = target.querySelectorAll('my-element');
assert.equal(my_element.classList.contains('svelte-1koh33s'), true);
assert.equal(my_element_1.classList.contains('svelte-1koh33s'), true);
assert.equal(my_element.classList.contains('svelte-70s021'), true);
assert.equal(my_element_1.classList.contains('svelte-70s021'), true);
}
});

@ -1,14 +1,14 @@
import { ok, test } from '../../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 }) {
const [el, el2] = target.querySelectorAll('custom-element');
ok(el);
ok(el2);
assert.deepEqual(el.className, 'red svelte-p153w3');
assert.deepEqual(el2.className, 'red svelte-p153w3');
assert.deepEqual(el.className, 'red svelte-70s021');
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;
}

@ -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;
}

@ -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;
}

@ -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: '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 }) {
assert.equal(
code_preprocessed.includes('\n/*# sourceMappingURL=data:application/json;base64,'),

@ -1,5 +1,5 @@
import { test } from '../../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';
/**
* 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
*/
@ -2933,7 +2933,7 @@ declare module 'svelte/types/compiler/interfaces' {
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.
* It defaults to returning `svelte-${hash(css)}`.
* It defaults to returning `svelte-${hash(filename ?? css)}`.
*
* @default undefined
*/

Loading…
Cancel
Save