Skip to content

Commit

Permalink
apply encapsulating attributes to correct elements and selector parts (
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Aug 14, 2017
1 parent 276b799 commit c135d0c
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 52 deletions.
37 changes: 25 additions & 12 deletions src/css/Selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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);
});
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -169,34 +170,43 @@ 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;
}

else {
// bail. TODO figure out what these could be
toEncapsulate.push({ node, block });
return true;
}
}

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

Expand Down Expand Up @@ -247,6 +257,7 @@ class Block {
selectors: Node[]
start: number;
end: number;
shouldEncapsulate: boolean;

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

this.start = null;
this.end = null;

this.shouldEncapsulate = false;
}

add(selector: Node) {
Expand Down
99 changes: 59 additions & 40 deletions test/css/index.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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)
);
}
Expand All @@ -104,7 +123,7 @@ describe("css", () => {
function read(file) {
try {
return fs.readFileSync(file, 'utf-8');
} catch(err) {
} catch (err) {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
cascade: false
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
p[svelte-xyz] span[svelte-xyz]{color:red}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div><p svelte-xyz=''><span svelte-xyz=''>styled</span></p></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div>
<p>
<span>styled</span>
</p>
</div>

<style>
p span {
color: red;
}
</style>

0 comments on commit c135d0c

Please sign in to comment.