pull/31/head
Rich-Harris 8 years ago
parent 24e768e4d2
commit 370db7d269

@ -0,0 +1,18 @@
import deindent from '../utils/deindent.js';
import spaces from '../../utils/spaces.js';
import transform from './transform.js';
export default function process ( parsed ) {
const scoped = transform( spaces( parsed.css.content.start ) + parsed.css.content.styles, parsed.hash );
return deindent`
let addedCss = false;
function addCss () {
var style = document.createElement( 'style' );
style.textContent = ${JSON.stringify( scoped )};
document.head.appendChild( style );
addedCss = true;
}
`;
}

@ -0,0 +1,58 @@
// largely borrowed from Ractive https://github.com/ractivejs/ractive/blob/2ec648aaf5296bb88c21812e947e0e42fcc456e3/src/Ractive/config/custom/css/transform.js
const selectorsPattern = /(?:^|\})?\s*([^\{\}]+)\s*\{/g;
const commentsPattern = /\/\*.*?\*\//g;
const selectorUnitPattern = /((?:(?:\[[^\]+]\])|(?:[^\s\+\>~:]))+)((?:::?[^\s\+\>\~\(:]+(?:\([^\)]+\))?)*\s*[\s\+\>\~]?)\s*/g;
const excludePattern = /^(?:@|\d+%)/;
function transformSelector ( selector, parent ) {
const selectorUnits = [];
let match;
while ( match = selectorUnitPattern.exec( selector ) ) {
selectorUnits.push({
str: match[0],
base: match[1],
modifiers: match[2]
});
}
// For each simple selector within the selector, we need to create a version
// that a) combines with the id, and b) is inside the id
const base = selectorUnits.map( unit => unit.str );
const transformed = [];
let i = selectorUnits.length;
while ( i-- ) {
const appended = base.slice();
// Pseudo-selectors should go after the attribute selector
const unit = selectorUnits[i];
appended[i] = unit.base + parent + unit.modifiers || '';
const prepended = base.slice();
prepended[i] = parent + ' ' + prepended[i];
transformed.push( appended.join( ' ' ), prepended.join( ' ' ) );
}
return transformed.join( ', ' );
}
export default function transformCss ( css, hash ) {
const attr = `[svelte-${hash}]`;
return css
.replace( commentsPattern, '' )
.replace( selectorsPattern, ( match, $1 ) => {
// don't transform at-rules and keyframe declarations
if ( excludePattern.test( $1 ) ) return match;
const selectors = $1.split( ',' ).map( selector => selector.trim() );
const transformed = selectors
.map( selector => transformSelector( selector, attr ) )
.join( ', ' ) + ' ';
return match.replace( $1, transformed );
});
}

@ -5,13 +5,12 @@ import isReference from './utils/isReference.js';
import counter from './utils/counter.js'; import counter from './utils/counter.js';
import flattenReference from './utils/flattenReference.js'; import flattenReference from './utils/flattenReference.js';
import visitors from './visitors/index.js'; import visitors from './visitors/index.js';
import processCss from './css/process.js';
export default function generate ( parsed, template, options = {} ) { export default function generate ( parsed, template, options = {} ) {
const renderers = []; const renderers = [];
const generator = { const generator = {
code: new MagicString( template ),
addRenderer ( fragment ) { addRenderer ( fragment ) {
if ( fragment.autofocus ) { if ( fragment.autofocus ) {
fragment.initStatements.push( `${fragment.autofocus}.focus();` ); fragment.initStatements.push( `${fragment.autofocus}.focus();` );
@ -43,6 +42,10 @@ export default function generate ( parsed, template, options = {} ) {
}); });
}, },
code: new MagicString( template ),
components: {},
contextualise ( expression, isEventHandler ) { contextualise ( expression, isEventHandler ) {
const usedContexts = []; const usedContexts = [];
@ -81,18 +84,20 @@ export default function generate ( parsed, template, options = {} ) {
return usedContexts; return usedContexts;
}, },
helpers: {},
events: {},
components: {},
getName: counter(),
// TODO use getName instead of counters // TODO use getName instead of counters
counters: { counters: {
if: 0, if: 0,
each: 0 each: 0
}, },
events: {},
getName: counter(),
cssId: parsed.css ? `svelte-${parsed.hash}` : '',
helpers: {},
usesRefs: false, usesRefs: false,
template template
@ -127,6 +132,7 @@ export default function generate ( parsed, template, options = {} ) {
name: 'renderMainFragment', name: 'renderMainFragment',
namespace: null, namespace: null,
target: 'target', target: 'target',
elementDepth: 0,
initStatements: [], initStatements: [],
updateStatements: [], updateStatements: [],
@ -204,11 +210,15 @@ export default function generate ( parsed, template, options = {} ) {
dispatchObservers( observers.deferred, newState, oldState ); dispatchObservers( observers.deferred, newState, oldState );
` ); ` );
const addCss = parsed.css ? processCss( parsed ) : null;
const constructorName = options.name || 'SvelteComponent'; const constructorName = options.name || 'SvelteComponent';
const result = deindent` const result = deindent`
${parsed.js ? `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` : ``} ${parsed.js ? `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` : ``}
${parsed.css ? addCss : ``}
${renderers.reverse().join( '\n\n' )} ${renderers.reverse().join( '\n\n' )}
export default function ${constructorName} ( options ) { export default function ${constructorName} ( options ) {
@ -292,6 +302,7 @@ export default function generate ( parsed, template, options = {} ) {
this.fire( 'teardown' );${templateProperties.onteardown ? `\ntemplate.onteardown.call( this );` : ``} this.fire( 'teardown' );${templateProperties.onteardown ? `\ntemplate.onteardown.call( this );` : ``}
}; };
${parsed.css ? `if ( !addedCss ) addCss();` : ''}
let mainFragment = renderMainFragment( this, options.target ); let mainFragment = renderMainFragment( this, options.target );
this.set( ${templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data`} ); this.set( ${templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data`} );

@ -68,12 +68,15 @@ export default {
local.update.push( declarations ); local.update.push( declarations );
} }
local.init.unshift( let render = local.namespace ?
local.namespace ?
`var ${name} = document.createElementNS( '${local.namespace}', '${node.name}' );` : `var ${name} = document.createElementNS( '${local.namespace}', '${node.name}' );` :
`var ${name} = document.createElement( '${node.name}' );` `var ${name} = document.createElement( '${node.name}' );`;
);
if ( generator.cssId && !generator.current.elementDepth ) {
render += `\n${name}.setAttribute( '${generator.cssId}', '' );`;
}
local.init.unshift( render );
local.teardown.push( `${name}.parentNode.removeChild( ${name} );` ); local.teardown.push( `${name}.parentNode.removeChild( ${name} );` );
} }
@ -85,7 +88,8 @@ export default {
isComponent, isComponent,
namespace: local.namespace, namespace: local.namespace,
target: name, target: name,
parent: generator.current parent: generator.current,
elementDepth: generator.current.elementDepth + 1
}); });
}, },

@ -2,7 +2,8 @@ import { locate } from 'locate-character';
import fragment from './state/fragment.js'; import fragment from './state/fragment.js';
import { whitespace } from './patterns.js'; import { whitespace } from './patterns.js';
import { trimStart, trimEnd } from './utils/trim.js'; import { trimStart, trimEnd } from './utils/trim.js';
import spaces from './utils/spaces.js'; import spaces from '../utils/spaces.js';
import hash from './utils/hash.js';
function tabsToSpaces ( str ) { function tabsToSpaces ( str ) {
return str.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) ); return str.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) );
@ -165,6 +166,7 @@ export default function parse ( template ) {
} }
return { return {
hash: hash( template ),
html: parser.html, html: parser.html,
css: parser.css, css: parser.css,
js: parser.js js: parser.js

@ -1,5 +1,5 @@
import { parse, tokenizer } from 'acorn'; import { parse, tokenizer } from 'acorn';
import spaces from '../utils/spaces.js'; import spaces from '../../utils/spaces.js';
export default function readScript ( parser, start, attributes ) { export default function readScript ( parser, start, attributes ) {
const scriptStart = parser.index; const scriptStart = parser.index;

@ -1,3 +1,19 @@
export default function readStyle () { export default function readStyle ( parser, start, attributes ) {
throw new Error( 'TODO <style>' ); const contentStart = parser.index;
const styles = parser.readUntil( /<\/style>/ );
const contentEnd = parser.index;
parser.eat( '</style>', true );
const end = parser.index;
return {
start,
end,
attributes,
content: {
start: contentStart,
end: contentEnd,
styles
}
};
} }

@ -0,0 +1,8 @@
// https://github.com/darkskyapp/string-hash/blob/master/index.js
export default function hash ( str ) {
let hash = 5381;
let i = str.length;
while ( i-- ) hash = ( hash * 33 ) ^ str.charCodeAt( i );
return hash >>> 0;
}

@ -0,0 +1,7 @@
<p>test</p>
<style>
p {
color: red;
}
</style>

@ -0,0 +1,10 @@
import * as assert from 'assert';
export default {
test ( component, target, window ) {
const [ control, test ] = target.querySelectorAll( 'p' );
assert.equal( window.getComputedStyle( control ).color, '' );
assert.equal( window.getComputedStyle( test ).color, 'red' );
}
};

@ -0,0 +1,10 @@
<p>control</p>
<Widget/>
<script>
import Widget from './Widget.html';
export default {
components: { Widget }
};
</script>

@ -0,0 +1,7 @@
<div>foo</div>
<style>
div {
color: red;
}
</style>

@ -0,0 +1,35 @@
{
"html": {
"start": 0,
"end": 14,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 14,
"type": "Element",
"name": "div",
"attributes": [],
"children": [
{
"start": 5,
"end": 8,
"type": "Text",
"data": "foo"
}
]
}
]
},
"css": {
"start": 16,
"end": 56,
"attributes": [],
"content": {
"start": 23,
"end": 48,
"styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n"
}
},
"js": null
}

@ -35,7 +35,9 @@ describe( 'svelte', () => {
const actual = parse( input ); const actual = parse( input );
const expected = require( `./parser/${dir}/output.json` ); const expected = require( `./parser/${dir}/output.json` );
assert.deepEqual( actual, expected ); assert.deepEqual( actual.html, expected.html );
assert.deepEqual( actual.css, expected.css );
assert.deepEqual( actual.js, expected.js );
} catch ( err ) { } catch ( err ) {
if ( err.name !== 'ParseError' ) throw err; if ( err.name !== 'ParseError' ) throw err;

Loading…
Cancel
Save