apply css optimisations to SSR

pull/689/head
Rich Harris 8 years ago
parent 7b289e95f1
commit 2ec0a850fc

@ -1,6 +1,7 @@
import deindent from '../../utils/deindent';
import Generator from '../Generator';
import Block from './Block';
import preprocess from './preprocess';
import visit from './visit';
import { removeNode, removeObjectKey } from '../../utils/removeNode';
import { Parsed, Node, CompileOptions } from '../../interfaces';
@ -24,6 +25,8 @@ export class SsrGenerator extends Generator {
// in an SSR context, we don't need to include events, methods, oncreate or ondestroy
const { templateProperties, defaultExport } = this;
preprocess(this, parsed.html);
if (templateProperties.oncreate)
removeNode(
this.code,

@ -0,0 +1,90 @@
import { SsrGenerator } from './index';
import { Node } from '../../interfaces';
function noop () {}
function isElseIf(node: Node) {
return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
);
}
const preprocessors = {
MustacheTag: noop,
RawMustacheTag: noop,
Text: noop,
IfBlock: (
generator: SsrGenerator,
node: Node,
elementStack: Node[]
) => {
function attachBlocks(node: Node) {
preprocessChildren(generator, node, elementStack);
if (isElseIf(node.else)) {
attachBlocks(node.else.children[0]);
} else if (node.else) {
preprocessChildren(
generator,
node.else,
elementStack
);
}
}
attachBlocks(node);
},
EachBlock: (
generator: SsrGenerator,
node: Node,
elementStack: Node[]
) => {
preprocessChildren(generator, node, elementStack);
if (node.else) {
preprocessChildren(
generator,
node.else,
elementStack
);
}
},
Element: (
generator: SsrGenerator,
node: Node,
elementStack: Node[]
) => {
const isComponent =
generator.components.has(node.name) || node.name === ':Self';
if (!isComponent) {
generator.applyCss(node, elementStack);
}
if (node.children.length) {
if (isComponent) {
preprocessChildren(generator, node, elementStack);
} else {
preprocessChildren(generator, node, elementStack.concat(node));
}
}
},
};
function preprocessChildren(
generator: SsrGenerator,
node: Node,
elementStack: Node[]
) {
node.children.forEach((child: Node, i: number) => {
const preprocessor = preprocessors[child.type];
if (preprocessor) preprocessor(generator, child, elementStack);
});
}
export default function preprocess(generator: SsrGenerator, html: Node) {
preprocessChildren(generator, html, []);
}

@ -56,7 +56,7 @@ export default function visitElement(
}
});
if (generator.cssId && (!generator.cascade || generator.elementDepth === 0)) {
if (node._needsCssAttribute) {
openingTag += ` ${generator.cssId}`;
}

@ -1,6 +1,6 @@
import assert from "assert";
import * as fs from "fs";
import { env, svelte } from "../helpers.js";
import { env, normalizeHtml, svelte } from "../helpers.js";
function tryRequire(file) {
try {
@ -23,27 +23,36 @@ describe("css", () => {
}
(solo ? it.only : it)(dir, () => {
const config = Object.assign(tryRequire(`./samples/${dir}/_config.js`) || {}, {
format: 'iife',
name: 'SvelteComponent'
});
const config = tryRequire(`./samples/${dir}/_config.js`) || {};
const input = fs
.readFileSync(`test/css/samples/${dir}/input.html`, "utf-8")
.replace(/\s+$/, "");
const actual = svelte.compile(input, config);
fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, actual.css);
const dom = svelte.compile(input, Object.assign(config, {
format: 'iife',
name: 'SvelteComponent'
}));
const ssr = svelte.compile(input, Object.assign(config, {
format: 'iife',
generate: 'ssr',
name: 'SvelteComponent'
}));
assert.equal(dom.css, ssr.css);
fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css);
const expected = {
html: read(`test/css/samples/${dir}/expected.html`),
css: read(`test/css/samples/${dir}/expected.css`)
};
assert.equal(actual.css.trim(), expected.css.trim());
assert.equal(dom.css.trim(), expected.css.trim());
// verify that the right elements have scoping selectors
if (expected.html !== null) {
return env().then(window => {
const Component = eval(`(function () { ${actual.code}; return SvelteComponent; }())`);
const Component = eval(`(function () { ${dom.code}; return SvelteComponent; }())`);
const target = window.document.querySelector("main");
new Component({ target, data: config.data });
@ -51,7 +60,19 @@ describe("css", () => {
fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html);
assert.equal(html.trim(), expected.html.trim());
// dom
assert.equal(
normalizeHtml(window, html),
normalizeHtml(window, expected.html)
);
// ssr
const component = eval(`(function () { ${ssr.code}; return SvelteComponent; }())`);
assert.equal(
normalizeHtml(window, component.render(config.data)),
normalizeHtml(window, expected.html)
);
});
}
});

@ -1,4 +1,4 @@
[data-foo][svelte-2966013849] {
[autoplay][svelte-240005720] {
color: red;
}

@ -1,2 +1,2 @@
<div><p svelte-2966013849="" data-foo="true">this is styled</p>
<p data-bar="true">this is unstyled</p></div>
<div><video svelte-240005720 autoplay></video>
<video></video></div>

@ -1,10 +1,10 @@
<div>
<p data-foo>this is styled</p>
<p data-bar>this is unstyled</p>
<video autoplay></video>
<video></video>
</div>
<style>
[data-foo] {
[autoplay] {
color: red;
}
</style>

@ -69,6 +69,20 @@ export function env() {
function cleanChildren(node) {
let previous = null;
// sort attributes
const attributes = Array.from(node.attributes).sort((a, b) => {
return a.name < b.name ? -1 : 1;
});
attributes.forEach(attr => {
node.removeAttribute(attr.name);
});
attributes.forEach(attr => {
node.setAttribute(attr.name, attr.value);
});
// recurse
[...node.childNodes].forEach(child => {
if (child.nodeType === 8) {
// comment
@ -114,22 +128,23 @@ function cleanChildren(node) {
}
}
export function setupHtmlEqual() {
return env().then(window => {
assert.htmlEqual = (actual, expected, message) => {
window.document.body.innerHTML = actual
export function normalizeHtml(window, html) {
const node = window.document.createElement('div');
node.innerHTML = html
.replace(/>[\s\r\n]+</g, '><')
.trim();
cleanChildren(window.document.body, '');
actual = window.document.body.innerHTML;
window.document.body.innerHTML = expected
.replace(/>[\s\r\n]+</g, '><')
.trim();
cleanChildren(window.document.body, '');
expected = window.document.body.innerHTML;
cleanChildren(node, '');
return node.innerHTML;
}
assert.deepEqual(actual, expected, message);
export function setupHtmlEqual() {
return env().then(window => {
assert.htmlEqual = (actual, expected, message) => {
assert.deepEqual(
normalizeHtml(window, actual),
normalizeHtml(window, expected),
message
);
};
});
}

Loading…
Cancel
Save