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

@ -68,12 +68,15 @@ export default {
local.update.push( declarations );
}
local.init.unshift(
local.namespace ?
`var ${name} = document.createElementNS( '${local.namespace}', '${node.name}' );` :
`var ${name} = document.createElement( '${node.name}' );`
);
let render = local.namespace ?
`var ${name} = document.createElementNS( '${local.namespace}', '${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} );` );
}
@ -85,7 +88,8 @@ export default {
isComponent,
namespace: local.namespace,
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 { whitespace } from './patterns.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 ) {
return str.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) );
@ -165,6 +166,7 @@ export default function parse ( template ) {
}
return {
hash: hash( template ),
html: parser.html,
css: parser.css,
js: parser.js

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

@ -1,3 +1,19 @@
export default function readStyle () {
throw new Error( 'TODO <style>' );
export default function readStyle ( parser, start, attributes ) {
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 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 ) {
if ( err.name !== 'ParseError' ) throw err;

Loading…
Cancel
Save