apply encapsulating attributes to correct elements and selector parts (#767)

pull/770/head
Rich Harris 7 years ago
parent 276b7998f9
commit c135d0cfab

@ -26,15 +26,16 @@ export default class Selector {
}
apply(node: Node, stack: Node[]) {
const applies = selectorAppliesTo(this.localBlocks.slice(), node, stack.slice());
const toEncapsulate: Node[] = [];
selectorAppliesTo(this.localBlocks.slice(), node, stack.slice(), toEncapsulate);
if (applies) {
this.used = true;
if (toEncapsulate.length > 0) {
toEncapsulate.filter((_, i) => i === 0 || i === toEncapsulate.length - 1).forEach(({ node, block }) => {
node._needsCssAttribute = true;
block.shouldEncapsulate = true;
});
// add svelte-123xyz attribute to outermost and innermost
// elements — no need to add it to intermediate elements
node._needsCssAttribute = true;
if (stack[0] && this.node.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true;
this.used = true;
}
}
@ -86,9 +87,9 @@ export default class Selector {
const first = selector.children[0];
const last = selector.children[selector.children.length - 1];
code.remove(selector.start, first.start).remove(last.end, selector.end);
} else if (i === 0 || i === this.blocks.length - 1) {
encapsulateBlock(block);
}
if (block.shouldEncapsulate) encapsulateBlock(block);
});
}
@ -126,7 +127,7 @@ function isDescendantSelector(selector: Node) {
return selector.type === 'WhiteSpace' || selector.type === 'Combinator';
}
function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean {
function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[], toEncapsulate: any[]): boolean {
const block = blocks.pop();
if (!block) return false;
@ -169,6 +170,7 @@ function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean
else if (selector.type === 'RefSelector') {
if (node.attributes.some((attr: Node) => attr.type === 'Ref' && attr.name === selector.name)) {
node._cssRefAttribute = selector.name;
toEncapsulate.push({ node, block });
return true;
}
return;
@ -176,6 +178,7 @@ function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean
else {
// bail. TODO figure out what these could be
toEncapsulate.push({ node, block });
return true;
}
}
@ -183,20 +186,27 @@ function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean
if (block.combinator) {
if (block.combinator.type === 'WhiteSpace') {
while (stack.length) {
if (selectorAppliesTo(blocks.slice(), stack.pop(), stack)) {
if (selectorAppliesTo(blocks.slice(), stack.pop(), stack, toEncapsulate)) {
toEncapsulate.push({ node, block });
return true;
}
}
return false;
} else if (block.combinator.name === '>') {
return selectorAppliesTo(blocks, stack.pop(), stack);
if (selectorAppliesTo(blocks, stack.pop(), stack, toEncapsulate)) {
toEncapsulate.push({ node, block });
return true;
}
return false;
}
// TODO other combinators
toEncapsulate.push({ node, block });
return true;
}
toEncapsulate.push({ node, block });
return true;
}
@ -247,6 +257,7 @@ class Block {
selectors: Node[]
start: number;
end: number;
shouldEncapsulate: boolean;
constructor(combinator: Node) {
this.combinator = combinator;
@ -255,6 +266,8 @@ class Block {
this.start = null;
this.end = null;
this.shouldEncapsulate = false;
}
add(selector: Node) {

@ -1,66 +1,78 @@
import assert from "assert";
import * as fs from "fs";
import { env, normalizeHtml, svelte } from "../helpers.js";
import assert from 'assert';
import * as fs from 'fs';
import { env, normalizeHtml, svelte } from '../helpers.js';
function tryRequire(file) {
try {
const mod = require(file);
return mod.default || mod;
} catch (err) {
if (err.code !== "MODULE_NOT_FOUND") throw err;
if (err.code !== 'MODULE_NOT_FOUND') throw err;
return null;
}
}
function normalizeWarning(warning) {
warning.frame = warning.frame.replace(/^\n/, '').replace(/^\t+/gm, '').replace(/\s+$/gm, '');
warning.frame = warning.frame
.replace(/^\n/, '')
.replace(/^\t+/gm, '')
.replace(/\s+$/gm, '');
delete warning.filename;
delete warning.toString;
return warning;
}
describe("css", () => {
fs.readdirSync("test/css/samples").forEach(dir => {
if (dir[0] === ".") return;
describe('css', () => {
fs.readdirSync('test/css/samples').forEach(dir => {
if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test
const solo = /\.solo/.test(dir);
const skip = /\.skip/.test(dir);
if (solo && process.env.CI) {
throw new Error("Forgot to remove `solo: true` from test");
throw new Error('Forgot to remove `solo: true` from test');
}
(solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = tryRequire(`./samples/${dir}/_config.js`) || {};
const input = fs
.readFileSync(`test/css/samples/${dir}/input.html`, "utf-8")
.replace(/\s+$/, "");
.readFileSync(`test/css/samples/${dir}/input.html`, 'utf-8')
.replace(/\s+$/, '');
const expectedWarnings = (config.warnings || []).map(normalizeWarning);
const domWarnings = [];
const ssrWarnings = [];
const dom = svelte.compile(input, Object.assign(config, {
format: 'iife',
name: 'SvelteComponent',
onwarn: warning => {
domWarnings.push(warning);
}
}));
const ssr = svelte.compile(input, Object.assign(config, {
format: 'iife',
generate: 'ssr',
name: 'SvelteComponent',
onwarn: warning => {
ssrWarnings.push(warning);
}
}));
const dom = svelte.compile(
input,
Object.assign(config, {
format: 'iife',
name: 'SvelteComponent',
onwarn: warning => {
domWarnings.push(warning);
}
})
);
const ssr = svelte.compile(
input,
Object.assign(config, {
format: 'iife',
generate: 'ssr',
name: 'SvelteComponent',
onwarn: warning => {
ssrWarnings.push(warning);
}
})
);
assert.equal(dom.css, ssr.css);
assert.deepEqual(domWarnings.map(normalizeWarning), ssrWarnings.map(normalizeWarning));
assert.deepEqual(
domWarnings.map(normalizeWarning),
ssrWarnings.map(normalizeWarning)
);
assert.deepEqual(domWarnings.map(normalizeWarning), expectedWarnings);
fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css);
@ -75,25 +87,32 @@ describe("css", () => {
if (expected.html !== null) {
const window = env();
const Component = eval(`(function () { ${dom.code}; return SvelteComponent; }())`);
const target = window.document.querySelector("main");
const Component = eval(
`(function () { ${dom.code}; return SvelteComponent; }())`
);
const target = window.document.querySelector('main');
new Component({ target, data: config.data });
const html = target.innerHTML;
// dom
assert.equal(
normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')),
normalizeHtml(window, expected.html)
);
fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html);
// dom
assert.equal(
normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')),
normalizeHtml(window, expected.html)
);
// ssr
const component = eval(`(function () { ${ssr.code}; return SvelteComponent; }())`);
const component = eval(
`(function () { ${ssr.code}; return SvelteComponent; }())`
);
assert.equal(
normalizeHtml(window, component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz')),
normalizeHtml(
window,
component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz')
),
normalizeHtml(window, expected.html)
);
}
@ -104,7 +123,7 @@ describe("css", () => {
function read(file) {
try {
return fs.readFileSync(file, 'utf-8');
} catch(err) {
} catch (err) {
return null;
}
}
}

@ -0,0 +1 @@
<div><p svelte-xyz=''><span svelte-xyz=''>styled</span></p></div>

@ -0,0 +1,11 @@
<div>
<p>
<span>styled</span>
</p>
</div>
<style>
p span {
color: red;
}
</style>
Loading…
Cancel
Save