Skip to content

Commit 9ef7dbc

Browse files
authored
Merge pull request #770 from sveltejs/gh-767
apply encapsulating attributes to correct elements and selector parts
2 parents 276b799 + b2a81fb commit 9ef7dbc

File tree

6 files changed

+100
-52
lines changed

6 files changed

+100
-52
lines changed

src/css/Selector.ts

+25-12
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,16 @@ export default class Selector {
2626
}
2727

2828
apply(node: Node, stack: Node[]) {
29-
const applies = selectorAppliesTo(this.localBlocks.slice(), node, stack.slice());
29+
const toEncapsulate: Node[] = [];
30+
applySelector(this.localBlocks.slice(), node, stack.slice(), toEncapsulate);
3031

31-
if (applies) {
32-
this.used = true;
32+
if (toEncapsulate.length > 0) {
33+
toEncapsulate.filter((_, i) => i === 0 || i === toEncapsulate.length - 1).forEach(({ node, block }) => {
34+
node._needsCssAttribute = true;
35+
block.shouldEncapsulate = true;
36+
});
3337

34-
// add svelte-123xyz attribute to outermost and innermost
35-
// elements — no need to add it to intermediate elements
36-
node._needsCssAttribute = true;
37-
if (stack[0] && this.node.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true;
38+
this.used = true;
3839
}
3940
}
4041

@@ -86,9 +87,9 @@ export default class Selector {
8687
const first = selector.children[0];
8788
const last = selector.children[selector.children.length - 1];
8889
code.remove(selector.start, first.start).remove(last.end, selector.end);
89-
} else if (i === 0 || i === this.blocks.length - 1) {
90-
encapsulateBlock(block);
9190
}
91+
92+
if (block.shouldEncapsulate) encapsulateBlock(block);
9293
});
9394
}
9495

@@ -126,7 +127,7 @@ function isDescendantSelector(selector: Node) {
126127
return selector.type === 'WhiteSpace' || selector.type === 'Combinator';
127128
}
128129

129-
function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean {
130+
function applySelector(blocks: Block[], node: Node, stack: Node[], toEncapsulate: any[]): boolean {
130131
const block = blocks.pop();
131132
if (!block) return false;
132133

@@ -169,34 +170,43 @@ function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean
169170
else if (selector.type === 'RefSelector') {
170171
if (node.attributes.some((attr: Node) => attr.type === 'Ref' && attr.name === selector.name)) {
171172
node._cssRefAttribute = selector.name;
173+
toEncapsulate.push({ node, block });
172174
return true;
173175
}
174176
return;
175177
}
176178

177179
else {
178180
// bail. TODO figure out what these could be
181+
toEncapsulate.push({ node, block });
179182
return true;
180183
}
181184
}
182185

183186
if (block.combinator) {
184187
if (block.combinator.type === 'WhiteSpace') {
185188
while (stack.length) {
186-
if (selectorAppliesTo(blocks.slice(), stack.pop(), stack)) {
189+
if (applySelector(blocks.slice(), stack.pop(), stack, toEncapsulate)) {
190+
toEncapsulate.push({ node, block });
187191
return true;
188192
}
189193
}
190194

191195
return false;
192196
} else if (block.combinator.name === '>') {
193-
return selectorAppliesTo(blocks, stack.pop(), stack);
197+
if (applySelector(blocks, stack.pop(), stack, toEncapsulate)) {
198+
toEncapsulate.push({ node, block });
199+
return true;
200+
}
201+
return false;
194202
}
195203

196204
// TODO other combinators
205+
toEncapsulate.push({ node, block });
197206
return true;
198207
}
199208

209+
toEncapsulate.push({ node, block });
200210
return true;
201211
}
202212

@@ -247,6 +257,7 @@ class Block {
247257
selectors: Node[]
248258
start: number;
249259
end: number;
260+
shouldEncapsulate: boolean;
250261

251262
constructor(combinator: Node) {
252263
this.combinator = combinator;
@@ -255,6 +266,8 @@ class Block {
255266

256267
this.start = null;
257268
this.end = null;
269+
270+
this.shouldEncapsulate = false;
258271
}
259272

260273
add(selector: Node) {

test/css/index.js

+59-40
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,78 @@
1-
import assert from "assert";
2-
import * as fs from "fs";
3-
import { env, normalizeHtml, svelte } from "../helpers.js";
1+
import assert from 'assert';
2+
import * as fs from 'fs';
3+
import { env, normalizeHtml, svelte } from '../helpers.js';
44

55
function tryRequire(file) {
66
try {
77
const mod = require(file);
88
return mod.default || mod;
99
} catch (err) {
10-
if (err.code !== "MODULE_NOT_FOUND") throw err;
10+
if (err.code !== 'MODULE_NOT_FOUND') throw err;
1111
return null;
1212
}
1313
}
1414

1515
function normalizeWarning(warning) {
16-
warning.frame = warning.frame.replace(/^\n/, '').replace(/^\t+/gm, '').replace(/\s+$/gm, '');
16+
warning.frame = warning.frame
17+
.replace(/^\n/, '')
18+
.replace(/^\t+/gm, '')
19+
.replace(/\s+$/gm, '');
1720
delete warning.filename;
1821
delete warning.toString;
1922
return warning;
2023
}
2124

22-
describe("css", () => {
23-
fs.readdirSync("test/css/samples").forEach(dir => {
24-
if (dir[0] === ".") return;
25+
describe('css', () => {
26+
fs.readdirSync('test/css/samples').forEach(dir => {
27+
if (dir[0] === '.') return;
2528

2629
// add .solo to a sample directory name to only run that test
2730
const solo = /\.solo/.test(dir);
2831
const skip = /\.skip/.test(dir);
2932

3033
if (solo && process.env.CI) {
31-
throw new Error("Forgot to remove `solo: true` from test");
34+
throw new Error('Forgot to remove `solo: true` from test');
3235
}
3336

3437
(solo ? it.only : skip ? it.skip : it)(dir, () => {
3538
const config = tryRequire(`./samples/${dir}/_config.js`) || {};
3639
const input = fs
37-
.readFileSync(`test/css/samples/${dir}/input.html`, "utf-8")
38-
.replace(/\s+$/, "");
40+
.readFileSync(`test/css/samples/${dir}/input.html`, 'utf-8')
41+
.replace(/\s+$/, '');
3942

4043
const expectedWarnings = (config.warnings || []).map(normalizeWarning);
4144
const domWarnings = [];
4245
const ssrWarnings = [];
4346

44-
const dom = svelte.compile(input, Object.assign(config, {
45-
format: 'iife',
46-
name: 'SvelteComponent',
47-
onwarn: warning => {
48-
domWarnings.push(warning);
49-
}
50-
}));
51-
52-
const ssr = svelte.compile(input, Object.assign(config, {
53-
format: 'iife',
54-
generate: 'ssr',
55-
name: 'SvelteComponent',
56-
onwarn: warning => {
57-
ssrWarnings.push(warning);
58-
}
59-
}));
47+
const dom = svelte.compile(
48+
input,
49+
Object.assign(config, {
50+
format: 'iife',
51+
name: 'SvelteComponent',
52+
onwarn: warning => {
53+
domWarnings.push(warning);
54+
}
55+
})
56+
);
57+
58+
const ssr = svelte.compile(
59+
input,
60+
Object.assign(config, {
61+
format: 'iife',
62+
generate: 'ssr',
63+
name: 'SvelteComponent',
64+
onwarn: warning => {
65+
ssrWarnings.push(warning);
66+
}
67+
})
68+
);
6069

6170
assert.equal(dom.css, ssr.css);
6271

63-
assert.deepEqual(domWarnings.map(normalizeWarning), ssrWarnings.map(normalizeWarning));
72+
assert.deepEqual(
73+
domWarnings.map(normalizeWarning),
74+
ssrWarnings.map(normalizeWarning)
75+
);
6476
assert.deepEqual(domWarnings.map(normalizeWarning), expectedWarnings);
6577

6678
fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css);
@@ -75,25 +87,32 @@ describe("css", () => {
7587
if (expected.html !== null) {
7688
const window = env();
7789

78-
const Component = eval(`(function () { ${dom.code}; return SvelteComponent; }())`);
79-
const target = window.document.querySelector("main");
90+
const Component = eval(
91+
`(function () { ${dom.code}; return SvelteComponent; }())`
92+
);
93+
const target = window.document.querySelector('main');
8094

8195
new Component({ target, data: config.data });
8296
const html = target.innerHTML;
8397

84-
// dom
85-
assert.equal(
86-
normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')),
87-
normalizeHtml(window, expected.html)
88-
);
89-
9098
fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html);
9199

100+
// dom
101+
assert.equal(
102+
normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')),
103+
normalizeHtml(window, expected.html)
104+
);
105+
92106
// ssr
93-
const component = eval(`(function () { ${ssr.code}; return SvelteComponent; }())`);
107+
const component = eval(
108+
`(function () { ${ssr.code}; return SvelteComponent; }())`
109+
);
94110

95111
assert.equal(
96-
normalizeHtml(window, component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz')),
112+
normalizeHtml(
113+
window,
114+
component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz')
115+
),
97116
normalizeHtml(window, expected.html)
98117
);
99118
}
@@ -104,7 +123,7 @@ describe("css", () => {
104123
function read(file) {
105124
try {
106125
return fs.readFileSync(file, 'utf-8');
107-
} catch(err) {
126+
} catch (err) {
108127
return null;
109128
}
110-
}
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
cascade: false
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
p[svelte-xyz] span[svelte-xyz]{color:red}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div><p svelte-xyz=''><span svelte-xyz=''>styled</span></p></div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div>
2+
<p>
3+
<span>styled</span>
4+
</p>
5+
</div>
6+
7+
<style>
8+
p span {
9+
color: red;
10+
}
11+
</style>

0 commit comments

Comments
 (0)