Comparing version 0.0.1 to 0.0.2
@@ -1,205 +0,606 @@ | ||
import { getLocator } from 'locate-character'; | ||
import MagicString from 'magic-string'; | ||
import { walk } from 'estree-walker'; | ||
import deindent from './utils/deindent.js'; | ||
import walkHtml from './utils/walkHtml.js'; | ||
import isReference from './utils/isReference.js'; | ||
import contextualise from './utils/contextualise.js'; | ||
import counter from './utils/counter.js'; | ||
import attributeLookup from './attributes/lookup.js'; | ||
import createBinding from './binding/index.js'; | ||
const ROOT = 'options.target'; | ||
function createRenderer ( fragment ) { | ||
if ( fragment.autofocus ) { | ||
fragment.initStatements.push( `${fragment.autofocus}.focus();` ); | ||
} | ||
return deindent` | ||
function ${fragment.name} ( component, target${fragment.useAnchor ? ', anchor' : ''} ) { | ||
${fragment.initStatements.join( '\n\n' )} | ||
return { | ||
update: function ( ${fragment.contextChain.join( ', ' )} ) { | ||
${fragment.updateStatements.join( '\n\n' )} | ||
}, | ||
teardown: function () { | ||
${fragment.teardownStatements.join( '\n\n' )} | ||
} | ||
}; | ||
} | ||
`; | ||
} | ||
export default function generate ( parsed, template ) { | ||
const code = new MagicString( template ); | ||
function addSourcemapLocations ( node ) { | ||
walk( node, { | ||
enter ( node ) { | ||
code.addSourcemapLocation( node.start ); | ||
code.addSourcemapLocation( node.end ); | ||
} | ||
}); | ||
} | ||
const templateProperties = {}; | ||
if ( parsed.js ) { | ||
addSourcemapLocations( parsed.js.content ); | ||
const defaultExport = parsed.js.content.body.find( node => node.type === 'ExportDefaultDeclaration' ); | ||
if ( defaultExport ) { | ||
code.overwrite( defaultExport.start, defaultExport.declaration.start, `const template = ` ); | ||
defaultExport.declaration.properties.forEach( prop => { | ||
templateProperties[ prop.key.name ] = prop.value; | ||
}); | ||
} | ||
} | ||
const helpers = {}; | ||
if ( templateProperties.helpers ) { | ||
templateProperties.helpers.properties.forEach( prop => { | ||
helpers[ prop.key.name ] = prop.value; | ||
}); | ||
} | ||
const renderers = []; | ||
const getName = counter(); | ||
// TODO use getName instead of counters | ||
const counters = { | ||
element: 0, | ||
text: 0, | ||
anchor: 0, | ||
if: 0 | ||
if: 0, | ||
each: 0 | ||
}; | ||
const initStatements = []; | ||
const setStatements = [ deindent` | ||
const oldState = state; | ||
state = Object.assign( {}, oldState, newState ); | ||
` ]; | ||
const teardownStatements = []; | ||
// TODO (scoped) css | ||
// TODO add contents of <script> tag, with `export default` replaced with `var template =` | ||
// TODO css | ||
let current = { | ||
useAnchor: false, | ||
name: 'renderMainFragment', | ||
target: 'target', | ||
const locator = getLocator( template ); | ||
initStatements: [], | ||
updateStatements: [], | ||
teardownStatements: [], | ||
parsed.html.children.forEach( child => { | ||
const declarations = []; | ||
contexts: {}, | ||
indexes: {}, | ||
let current = { | ||
target: ROOT, | ||
conditions: [], | ||
children: [], | ||
renderBlocks: [], | ||
removeBlocks: [], | ||
anchor: null | ||
}; | ||
contextChain: [ 'root' ], | ||
indexNames: {}, | ||
listNames: {}, | ||
const stack = [ current ]; | ||
counter: counter(), | ||
parent: null | ||
}; | ||
let usesRefs = false; | ||
parsed.html.children.forEach( child => { | ||
walkHtml( child, { | ||
enter ( node ) { | ||
if ( node.type === 'Element' ) { | ||
current = { | ||
target: `element_${counters.element++}`, | ||
conditions: current.conditions, | ||
children: current.children, | ||
renderBlocks: current.renderBlocks, | ||
removeBlocks: current.removeBlocks, | ||
anchor: current.anchor | ||
}; | ||
Comment: { | ||
// do nothing | ||
}, | ||
stack.push( current ); | ||
Element: { | ||
enter ( node ) { | ||
const name = current.counter( node.name ); | ||
declarations.push( `var ${current.target};` ); | ||
const initStatements = [ | ||
`var ${name} = document.createElement( '${node.name}' );` | ||
]; | ||
if ( current.anchor ) { | ||
current.renderBlocks.push( deindent` | ||
${current.target} = document.createElement( '${node.name}' ); | ||
${current.anchor}.parentNode.insertBefore( ${current.target}, ${current.anchor} ); | ||
const updateStatements = []; | ||
const teardownStatements = []; | ||
const allUsedContexts = new Set(); | ||
node.attributes.forEach( attribute => { | ||
if ( attribute.type === 'Attribute' ) { | ||
let metadata = attributeLookup[ attribute.name ]; | ||
if ( metadata && metadata.appliesTo && !~metadata.appliesTo.indexOf( node.name ) ) metadata = null; | ||
if ( attribute.value === true ) { | ||
// attributes without values, e.g. <textarea readonly> | ||
if ( metadata ) { | ||
initStatements.push( deindent` | ||
${name}.${metadata.propertyName} = true; | ||
` ); | ||
} else { | ||
initStatements.push( deindent` | ||
${name}.setAttribute( '${attribute.name}', true ); | ||
` ); | ||
} | ||
// special case – autofocus. has to be handled in a bit of a weird way | ||
if ( attribute.name === 'autofocus' ) { | ||
current.autofocus = name; | ||
} | ||
} | ||
else if ( attribute.value.length === 1 ) { | ||
const value = attribute.value[0]; | ||
let result = ''; | ||
if ( value.type === 'Text' ) { | ||
// static attributes | ||
result = JSON.stringify( value.data ); | ||
if ( metadata ) { | ||
initStatements.push( deindent` | ||
${name}.${metadata.propertyName} = ${result}; | ||
` ); | ||
} else { | ||
initStatements.push( deindent` | ||
${name}.setAttribute( '${attribute.name}', ${result} ); | ||
` ); | ||
} | ||
} | ||
else { | ||
// dynamic – but potentially non-string – attributes | ||
contextualise( code, value.expression, current.contexts, current.indexes, helpers ); | ||
result = `[✂${value.expression.start}-${value.expression.end}✂]`; | ||
if ( metadata ) { | ||
updateStatements.push( deindent` | ||
${name}.${metadata.propertyName} = ${result}; | ||
` ); | ||
} else { | ||
updateStatements.push( deindent` | ||
${name}.setAttribute( '${attribute.name}', ${result} ); | ||
` ); | ||
} | ||
} | ||
} | ||
else { | ||
const value = ( attribute.value[0].type === 'Text' ? '' : `"" + ` ) + ( | ||
attribute.value.map( chunk => { | ||
if ( chunk.type === 'Text' ) { | ||
return JSON.stringify( chunk.data ); | ||
} else { | ||
addSourcemapLocations( chunk.expression ); | ||
contextualise( code, chunk.expression, current.contexts, current.indexes, helpers ); | ||
return `( [✂${chunk.expression.start}-${chunk.expression.end}✂] )`; | ||
} | ||
}).join( ' + ' ) | ||
); | ||
if ( metadata ) { | ||
updateStatements.push( deindent` | ||
${name}.${metadata.propertyName} = ${value}; | ||
` ); | ||
} else { | ||
updateStatements.push( deindent` | ||
${name}.setAttribute( '${attribute.name}', ${value} ); | ||
` ); | ||
} | ||
} | ||
} | ||
else if ( attribute.type === 'EventHandler' ) { | ||
// TODO verify that it's a valid callee (i.e. built-in or declared method) | ||
addSourcemapLocations( attribute.expression ); | ||
code.insertRight( attribute.expression.start, 'component.' ); | ||
const usedContexts = new Set(); | ||
attribute.expression.arguments.forEach( arg => { | ||
const contexts = contextualise( code, arg, current.contexts, current.indexes, helpers, true ); | ||
contexts.forEach( context => { | ||
usedContexts.add( context ); | ||
allUsedContexts.add( context ); | ||
}); | ||
}); | ||
// TODO hoist event handlers? can do `this.__component.method(...)` | ||
const declarations = [...usedContexts].map( name => { | ||
if ( name === 'root' ) return 'var root = this.__svelte.root;'; | ||
const listName = current.listNames[ name ]; | ||
const indexName = current.indexNames[ name ]; | ||
return `var ${listName} = this.__svelte.${listName}, ${indexName} = this.__svelte.${indexName}, ${name} = ${listName}[${indexName}]`; | ||
}); | ||
const handlerName = current.counter( `${attribute.name}Handler` ); | ||
const handlerBody = ( declarations.length ? declarations.join( '\n' ) + '\n\n' : '' ) + `[✂${attribute.expression.start}-${attribute.expression.end}✂];`; | ||
const customEvent = templateProperties.events && templateProperties.events.properties.find( prop => prop.key.name === attribute.name ); | ||
if ( customEvent ) { | ||
initStatements.push( deindent` | ||
const ${handlerName} = template.events.${attribute.name}( ${name}, function ( event ) { | ||
${handlerBody} | ||
}); | ||
` ); | ||
teardownStatements.push( deindent` | ||
${handlerName}.teardown(); | ||
` ); | ||
} else { | ||
initStatements.push( deindent` | ||
function ${handlerName} ( event ) { | ||
${handlerBody} | ||
} | ||
${name}.addEventListener( '${attribute.name}', ${handlerName}, false ); | ||
` ); | ||
teardownStatements.push( deindent` | ||
${name}.removeEventListener( '${attribute.name}', ${handlerName}, false ); | ||
` ); | ||
} | ||
} | ||
else if ( attribute.type === 'Binding' ) { | ||
createBinding( node, name, attribute, current, initStatements, updateStatements, teardownStatements, allUsedContexts ); | ||
} | ||
else if ( attribute.type === 'Ref' ) { | ||
usesRefs = true; | ||
initStatements.push( deindent` | ||
component.refs.${attribute.name} = ${name}; | ||
` ); | ||
teardownStatements.push( deindent` | ||
component.refs.${attribute.name} = null; | ||
` ); | ||
} | ||
else { | ||
throw new Error( `Not implemented: ${attribute.type}` ); | ||
} | ||
}); | ||
if ( allUsedContexts.size ) { | ||
initStatements.push( deindent` | ||
${name}.__svelte = {}; | ||
` ); | ||
const declarations = [...allUsedContexts].map( contextName => { | ||
if ( contextName === 'root' ) return `${name}.__svelte.root = root;`; | ||
const listName = current.listNames[ contextName ]; | ||
const indexName = current.indexNames[ contextName ]; | ||
return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`; | ||
}).join( '\n' ); | ||
updateStatements.push( declarations ); | ||
} | ||
teardownStatements.push( `${name}.parentNode.removeChild( ${name} );` ); | ||
current.initStatements.push( initStatements.join( '\n' ) ); | ||
if ( updateStatements.length ) current.updateStatements.push( updateStatements.join( '\n' ) ); | ||
current.teardownStatements.push( teardownStatements.join( '\n' ) ); | ||
current = Object.assign( {}, current, { | ||
target: name, | ||
parent: current | ||
}); | ||
}, | ||
leave () { | ||
const name = current.target; | ||
current = current.parent; | ||
if ( current.useAnchor && current.target === 'target' ) { | ||
current.initStatements.push( deindent` | ||
anchor.parentNode.insertBefore( ${name}, anchor ); | ||
` ); | ||
} else { | ||
current.renderBlocks.push( deindent` | ||
${current.target} = document.createElement( '${node.name}' ); | ||
options.target.appendChild( ${current.target} ); | ||
current.initStatements.push( deindent` | ||
${current.target}.appendChild( ${name} ); | ||
` ); | ||
} | ||
} | ||
}, | ||
current.removeBlocks.push( deindent` | ||
${current.target}.parentNode.removeChild( ${current.target} ); | ||
Text: { | ||
enter ( node ) { | ||
current.initStatements.push( deindent` | ||
${current.target}.appendChild( document.createTextNode( ${JSON.stringify( node.data )} ) ); | ||
` ); | ||
} | ||
}, | ||
else if ( node.type === 'Text' ) { | ||
if ( current.target === ROOT ) { | ||
const identifier = `text_${counters.text++}`; | ||
MustacheTag: { | ||
enter ( node ) { | ||
const name = current.counter( 'text' ); | ||
declarations.push( `var ${identifier};` ); | ||
current.initStatements.push( deindent` | ||
var ${name} = document.createTextNode( '' ); | ||
var ${name}_value = ''; | ||
${current.target}.appendChild( ${name} ); | ||
` ); | ||
current.renderBlocks.push( deindent` | ||
${identifier} = document.createTextNode( ${JSON.stringify( node.data )} ); | ||
${current.target}.appendChild( ${identifier} ); | ||
` ); | ||
addSourcemapLocations( node.expression ); | ||
current.removeBlocks.push( deindent` | ||
${identifier}.parentNode.removeChild( ${identifier} ); | ||
${identifier} = null; | ||
const usedContexts = contextualise( code, node.expression, current.contexts, current.indexes, helpers ); | ||
const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`; | ||
if ( isReference( node.expression ) ) { | ||
const reference = `${template.slice( node.expression.start, node.expression.end )}`; | ||
const qualified = usedContexts[0] === 'root' ? `root.${reference}` : reference; | ||
current.updateStatements.push( deindent` | ||
if ( ${snippet} !== ${name}_value ) { | ||
${name}_value = ${qualified}; | ||
${name}.data = ${name}_value; | ||
} | ||
` ); | ||
} | ||
} else { | ||
const temp = getName( 'temp' ); | ||
else { | ||
current.renderBlocks.push( deindent` | ||
${current.target}.appendChild( document.createTextNode( ${JSON.stringify( node.data )} ) ); | ||
current.updateStatements.push( deindent` | ||
var ${temp} = ${snippet}; | ||
if ( ${temp} !== ${name}_value ) { | ||
${name}_value = ${temp}; | ||
${name}.data = ${name}_value; | ||
} | ||
` ); | ||
} | ||
} | ||
}, | ||
else if ( node.type === 'MustacheTag' ) { | ||
const identifier = `text_${counters.text++}`; | ||
const expression = node.expression.type === 'Identifier' ? node.expression.name : 'TODO'; // TODO handle block-local state | ||
IfBlock: { | ||
enter ( node ) { | ||
const i = counters.if++; | ||
const name = `ifBlock_${i}`; | ||
const renderer = `renderIfBlock_${i}`; | ||
declarations.push( `var ${identifier};` ); | ||
current.renderBlocks.push( deindent` | ||
${identifier} = document.createTextNode( '' ); | ||
${current.target}.appendChild( ${identifier} ); | ||
current.initStatements.push( deindent` | ||
var ${name}_anchor = document.createComment( ${JSON.stringify( `#if ${template.slice( node.expression.start, node.expression.end )}` )} ); | ||
${current.target}.appendChild( ${name}_anchor ); | ||
var ${name} = null; | ||
` ); | ||
setStatements.push( deindent` | ||
if ( state.${expression} !== oldState.${expression} ) { // TODO and conditions | ||
${identifier}.data = state.${expression}; | ||
} | ||
` ); | ||
addSourcemapLocations( node.expression ); | ||
current.removeBlocks.push( deindent` | ||
${identifier}.parentNode.removeChild( ${identifier} ); | ||
${identifier} = null; | ||
` ); | ||
} | ||
const usedContexts = contextualise( code, node.expression, current.contexts, current.indexes, helpers ); | ||
const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`; | ||
else if ( node.type === 'IfBlock' ) { | ||
const anchor = `anchor_${counters.anchor++}`; | ||
const suffix = `if_${counters.if++}`; | ||
let expression; | ||
declarations.push( `var ${anchor};` ); | ||
if ( isReference( node.expression ) ) { | ||
const reference = `${template.slice( node.expression.start, node.expression.end )}`; | ||
expression = usedContexts[0] === 'root' ? `root.${reference}` : reference; | ||
const expression = node.expression.type === 'Identifier' ? node.expression.name : 'TODO'; // TODO handle block-local state | ||
current.updateStatements.push( deindent` | ||
if ( ${snippet} && !${name} ) { | ||
${name} = ${renderer}( component, ${current.target}, ${name}_anchor ); | ||
} | ||
` ); | ||
} else { | ||
expression = `${name}_value`; | ||
current.renderBlocks.push( deindent` | ||
${anchor} = document.createComment( '#if ${template.slice( node.expression.start, node.expression.end)}' ); | ||
${current.target}.appendChild( ${anchor} ); | ||
` ); | ||
current.updateStatements.push( deindent` | ||
var ${expression} = ${snippet}; | ||
current.removeBlocks.push( deindent` | ||
${anchor}.parentNode.removeChild( ${anchor} ); | ||
${anchor} = null; | ||
` ); | ||
if ( ${expression} && !${name} ) { | ||
${name} = ${renderer}( component, ${current.target}, ${name}_anchor ); | ||
} | ||
` ); | ||
} | ||
current = { | ||
renderName: `render_${suffix}`, | ||
removeName: `remove_${suffix}`, | ||
target: current.target, | ||
conditions: current.conditions.concat( expression ), | ||
renderBlocks: [], | ||
removeBlocks: [], | ||
anchor | ||
}; | ||
current.updateStatements.push( deindent` | ||
else if ( !${expression} && ${name} ) { | ||
${name}.teardown(); | ||
${name} = null; | ||
} | ||
setStatements.push( deindent` | ||
// TODO account for conditions (nested ifs) | ||
if ( state.${expression} && !oldState.${expression} ) ${current.renderName}(); | ||
else if ( !state.${expression} && oldState.${expression} ) ${current.removeName}(); | ||
if ( ${name} ) { | ||
${name}.update( ${current.contextChain.join( ', ' )} ); | ||
} | ||
` ); | ||
teardownStatements.push( deindent` | ||
// TODO account for conditions (nested ifs) | ||
if ( state.${expression} ) ${current.removeName}(); | ||
current.teardownStatements.push( deindent` | ||
if ( ${name} ) ${name}.teardown(); | ||
${name}_anchor.parentNode.removeChild( ${name}_anchor ); | ||
` ); | ||
stack.push( current ); | ||
} | ||
current = Object.assign( {}, current, { | ||
useAnchor: true, | ||
name: renderer, | ||
target: 'target', | ||
else { | ||
throw new Error( `Not implemented: ${node.type}` ); | ||
initStatements: [], | ||
updateStatements: [], | ||
teardownStatements: [], | ||
counter: counter(), | ||
parent: current | ||
}); | ||
}, | ||
leave () { | ||
renderers.push( createRenderer( current ) ); | ||
current = current.parent; | ||
} | ||
}, | ||
leave ( node ) { | ||
if ( node.type === 'IfBlock' ) { | ||
const { line, column } = locator( node.start ); | ||
EachBlock: { | ||
enter ( node ) { | ||
const i = counters.each++; | ||
const name = `eachBlock_${i}`; | ||
const renderer = `renderEachBlock_${i}`; | ||
initStatements.push( deindent` | ||
// (${line}:${column}) {{#if ${template.slice( node.expression.start, node.expression.end )}}}...{{/if}} | ||
function ${current.renderName} () { | ||
${current.renderBlocks.join( '\n\n' )} | ||
const listName = `${name}_value`; | ||
current.initStatements.push( deindent` | ||
var ${name}_anchor = document.createComment( ${JSON.stringify( `#each ${template.slice( node.expression.start, node.expression.end )}` )} ); | ||
${current.target}.appendChild( ${name}_anchor ); | ||
var ${name}_iterations = []; | ||
const ${name}_fragment = document.createDocumentFragment(); | ||
` ); | ||
addSourcemapLocations( node.expression ); | ||
contextualise( code, node.expression, current.contexts, current.indexes, helpers ); | ||
const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`; | ||
current.updateStatements.push( deindent` | ||
var ${name}_value = ${snippet}; | ||
for ( var i = 0; i < ${name}_value.length; i += 1 ) { | ||
if ( !${name}_iterations[i] ) { | ||
${name}_iterations[i] = ${renderer}( component, ${name}_fragment ); | ||
} | ||
const iteration = ${name}_iterations[i]; | ||
${name}_iterations[i].update( ${current.contextChain.join( ', ' )}, ${listName}, ${listName}[i], i ); | ||
} | ||
function ${current.removeName} () { | ||
${current.removeBlocks.join( '\n\n' )} | ||
for ( var i = ${name}_value.length; i < ${name}_iterations.length; i += 1 ) { | ||
${name}_iterations[i].teardown(); | ||
} | ||
${name}_anchor.parentNode.insertBefore( ${name}_fragment, ${name}_anchor ); | ||
${name}_iterations.length = ${listName}.length; | ||
` ); | ||
stack.pop(); | ||
current = stack[ stack.length - 1 ]; | ||
} | ||
current.teardownStatements.push( deindent` | ||
for ( let i = 0; i < ${name}_iterations.length; i += 1 ) { | ||
${name}_iterations[i].teardown(); | ||
} | ||
else if ( node.type === 'Element' ) { | ||
stack.pop(); | ||
current = stack[ stack.length - 1 ]; | ||
${name}_anchor.parentNode.removeChild( ${name}_anchor ); | ||
` ); | ||
const indexNames = Object.assign( {}, current.indexNames ); | ||
const indexName = indexNames[ node.context ] = ( node.index || `${node.context}__index` ); | ||
const listNames = Object.assign( {}, current.listNames ); | ||
listNames[ node.context ] = listName; | ||
const contexts = Object.assign( {}, current.contexts ); | ||
contexts[ node.context ] = true; | ||
const indexes = Object.assign( {}, current.indexes ); | ||
if ( node.index ) indexes[ indexName ] = node.context; | ||
const contextChain = current.contextChain.concat( listName, node.context, indexName ); | ||
current = { | ||
useAnchor: false, | ||
name: renderer, | ||
target: 'target', | ||
expression: node.expression, | ||
context: node.context, | ||
contexts, | ||
indexes, | ||
indexNames, | ||
listNames, | ||
contextChain, | ||
initStatements: [], | ||
updateStatements: [ Object.keys( contexts ).map( contextName => { | ||
const listName = listNames[ contextName ]; | ||
const indexName = indexNames[ contextName ]; | ||
return `var ${contextName} = ${listName}[${indexName}];`; | ||
}).join( '\n' ) ], | ||
teardownStatements: [], | ||
counter: counter(), | ||
parent: current | ||
}; | ||
}, | ||
leave () { | ||
renderers.push( createRenderer( current ) ); | ||
current = current.parent; | ||
} | ||
} | ||
}); | ||
initStatements.push( ...current.renderBlocks ); | ||
teardownStatements.push( ...current.removeBlocks ); | ||
}); | ||
teardownStatements.push( deindent` | ||
state = {}; | ||
renderers.push( createRenderer( current ) ); | ||
const setStatements = [ deindent` | ||
const oldState = state; | ||
state = Object.assign( {}, oldState, newState ); | ||
` ]; | ||
if ( templateProperties.computed ) { | ||
const dependencies = new Map(); | ||
templateProperties.computed.properties.forEach( prop => { | ||
const key = prop.key.name; | ||
const value = prop.value; | ||
const deps = value.params.map( param => param.name ); | ||
dependencies.set( key, deps ); | ||
}); | ||
const visited = new Set(); | ||
function visit ( key ) { | ||
if ( !dependencies.has( key ) ) return; // not a computation | ||
if ( visited.has( key ) ) return; | ||
visited.add( key ); | ||
const deps = dependencies.get( key ); | ||
deps.forEach( visit ); | ||
setStatements.push( deindent` | ||
if ( ${deps.map( dep => `( '${dep}' in newState && typeof state.${dep} === 'object' || state.${dep} !== oldState.${dep} )` ).join( ' || ' )} ) { | ||
state.${key} = newState.${key} = template.computed.${key}( ${deps.map( dep => `state.${dep}` ).join( ', ' )} ); | ||
} | ||
` ); | ||
} | ||
templateProperties.computed.properties.forEach( prop => visit( prop.key.name ) ); | ||
} | ||
setStatements.push( deindent` | ||
dispatchObservers( observers.immediate, newState, oldState ); | ||
mainFragment.update( state ); | ||
dispatchObservers( observers.deferred, newState, oldState ); | ||
` ); | ||
const code = deindent` | ||
const result = deindent` | ||
${parsed.js ? `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` : ``} | ||
${renderers.reverse().join( '\n\n' )} | ||
export default function createComponent ( options ) { | ||
var component = {}; | ||
var component = ${templateProperties.methods ? `Object.create( template.methods )` : `{}`};${usesRefs ? `\ncomponent.refs = {}` : ``} | ||
var state = {}; | ||
@@ -212,6 +613,7 @@ | ||
// universal methods | ||
function dispatchObservers ( group, state, oldState ) { | ||
function dispatchObservers ( group, newState, oldState ) { | ||
for ( const key in group ) { | ||
const newValue = state[ key ]; | ||
if ( !( key in newState ) ) continue; | ||
const newValue = newState[ key ]; | ||
const oldValue = oldState[ key ]; | ||
@@ -234,2 +636,6 @@ | ||
component.set = function set ( newState ) { | ||
${setStatements.join( '\n\n' )} | ||
}; | ||
component.observe = function ( key, callback, options = {} ) { | ||
@@ -249,14 +655,15 @@ const group = options.defer ? observers.deferred : observers.immediate; | ||
// component-specific methods | ||
component.set = function set ( newState ) { | ||
${setStatements.join( '\n\n' )} | ||
}; | ||
component.teardown = function teardown () { | ||
mainFragment.teardown(); | ||
mainFragment = null; | ||
component.teardown = function teardown () { | ||
${teardownStatements.join( '\n\n' )} | ||
state = {}; | ||
${templateProperties.onteardown ? `template.onteardown.call( component );` : ``} | ||
}; | ||
${initStatements.join( '\n\n' )} | ||
let mainFragment = renderMainFragment( component, options.target ); | ||
component.set( ${templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data`} ); | ||
component.set( options.data ); | ||
${templateProperties.onrender ? `template.onrender.call( component );` : ``} | ||
@@ -267,3 +674,41 @@ return component; | ||
return { code }; | ||
const pattern = /\[✂(\d+)-(\d+)$/; | ||
const parts = result.split( '✂]' ); | ||
const finalChunk = parts.pop(); | ||
const sortedByResult = parts.map( ( str, index ) => { | ||
const match = pattern.exec( str ); | ||
return { | ||
index, | ||
chunk: str.replace( pattern, '' ), | ||
start: +match[1], | ||
end: +match[2] | ||
}; | ||
}); | ||
const sortedBySource = sortedByResult | ||
.slice() | ||
.sort( ( a, b ) => a.start - b.start ); | ||
let c = 0; | ||
sortedBySource.forEach( part => { | ||
code.remove( c, part.start ); | ||
code.insertRight( part.start, part.chunk ); | ||
c = part.end; | ||
}); | ||
code.remove( c, template.length ); | ||
code.append( finalChunk ); | ||
sortedByResult.forEach( part => { | ||
code.move( part.start, part.end, 0 ); | ||
}); | ||
return { | ||
code: code.toString(), | ||
map: code.generateMap() | ||
}; | ||
} |
@@ -1,5 +0,8 @@ | ||
export default function walkHtml ( html, { enter, leave } ) { | ||
export default function walkHtml ( html, visitors ) { | ||
function visit ( node ) { | ||
enter( node ); | ||
const visitor = visitors[ node.type ]; | ||
if ( !visitor ) throw new Error( `Not implemented: ${node.type}` ); | ||
if ( visitor.enter ) visitor.enter( node ); | ||
if ( node.children ) { | ||
@@ -11,3 +14,3 @@ node.children.forEach( child => { | ||
leave( node ); | ||
if ( visitor.leave ) visitor.leave( node ); | ||
} | ||
@@ -14,0 +17,0 @@ |
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'; | ||
const whitespace = /\s/; | ||
function tabsToSpaces ( str ) { | ||
return str.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) ); | ||
} | ||
function ParseError ( message, template, index ) { | ||
const { line, column } = locate( template, index ); | ||
const lines = template.split( '\n' ); | ||
const frameStart = Math.max( 0, line - 2 ); | ||
const frameEnd = Math.min( line + 3, lines.length ); | ||
const digits = String( frameEnd + 1 ).length; | ||
const frame = lines | ||
.slice( frameStart, frameEnd ) | ||
.map( ( str, i ) => { | ||
const isErrorLine = frameStart + i === line; | ||
let lineNum = String( i + frameStart + 1 ); | ||
while ( lineNum.length < digits ) lineNum = ` ${lineNum}`; | ||
if ( isErrorLine ) { | ||
const indicator = spaces( digits + 2 + tabsToSpaces( str.slice( 0, column ) ).length ) + '^'; | ||
return `${lineNum}: ${tabsToSpaces( str )}\n${indicator}`; | ||
} | ||
return `${lineNum}: ${tabsToSpaces( str )}`; | ||
}) | ||
.join( '\n' ); | ||
this.message = `${message} (${line + 1}:${column})\n${frame}`; | ||
this.loc = { line, column }; | ||
this.shortMessage = message; | ||
} | ||
export default function parse ( template ) { | ||
@@ -16,7 +51,10 @@ const parser = { | ||
error ( message ) { | ||
const { line, column } = locate( this.template, this.index ); | ||
throw new Error( `${message} (${line}:${column})` ); | ||
acornError ( err ) { | ||
parser.error( err.message.replace( /\(\d+:\d+\)$/, '' ), err.pos ); | ||
}, | ||
error ( message, index = this.index ) { | ||
throw new ParseError( message, this.template, index ); | ||
}, | ||
eat ( str, required ) { | ||
@@ -67,17 +105,18 @@ if ( this.match( str ) ) { | ||
this.allowWhitespace(); | ||
} | ||
}; | ||
}, | ||
const html = { | ||
start: 0, | ||
end: template.length, | ||
type: 'Fragment', | ||
children: [] | ||
html: { | ||
start: null, | ||
end: null, | ||
type: 'Fragment', | ||
children: [] | ||
}, | ||
css: null, | ||
js: null | ||
}; | ||
let css = null; | ||
let js = null; | ||
parser.stack.push( parser.html ); | ||
parser.stack.push( html ); | ||
let state = fragment; | ||
@@ -89,3 +128,42 @@ | ||
return { html, css, js }; | ||
// trim unnecessary whitespace | ||
while ( parser.html.children.length ) { | ||
const firstChild = parser.html.children[0]; | ||
parser.html.start = firstChild.start; | ||
if ( firstChild.type !== 'Text' ) break; | ||
const length = firstChild.data.length; | ||
firstChild.data = trimStart( firstChild.data ); | ||
if ( firstChild.data === '' ) { | ||
parser.html.children.shift(); | ||
} else { | ||
parser.html.start += length - firstChild.data.length; | ||
break; | ||
} | ||
} | ||
while ( parser.html.children.length ) { | ||
const lastChild = parser.html.children[ parser.html.children.length - 1 ]; | ||
parser.html.end = lastChild.end; | ||
if ( lastChild.type !== 'Text' ) break; | ||
const length = lastChild.data.length; | ||
lastChild.data = trimEnd( lastChild.data ); | ||
if ( lastChild.data === '' ) { | ||
parser.html.children.pop(); | ||
} else { | ||
parser.html.end -= length - lastChild.data.length; | ||
break; | ||
} | ||
} | ||
return { | ||
html: parser.html, | ||
css: parser.css, | ||
js: parser.js | ||
}; | ||
} |
import { parseExpressionAt } from 'acorn'; | ||
export default function readExpression ( parser ) { | ||
const node = parseExpressionAt( parser.template, parser.index ); | ||
parser.index = node.end; | ||
try { | ||
const node = parseExpressionAt( parser.template, parser.index ); | ||
parser.index = node.end; | ||
// TODO check it's a valid expression. probably shouldn't have | ||
// [arrow] function expressions, etc | ||
// TODO check it's a valid expression. probably shouldn't have | ||
// [arrow] function expressions, etc | ||
return node; | ||
return node; | ||
} catch ( err ) { | ||
parser.acornError( err ); | ||
} | ||
} |
@@ -6,4 +6,2 @@ import tag from './tag.js'; | ||
export default function fragment ( parser ) { | ||
parser.allowWhitespace(); | ||
if ( parser.match( '<' ) ) { | ||
@@ -10,0 +8,0 @@ return tag; |
import readExpression from '../read/expression.js'; | ||
import { whitespace } from '../patterns.js'; | ||
import { trimStart, trimEnd } from '../utils/trim.js'; | ||
@@ -13,8 +15,8 @@ const validIdentifier = /[a-zA-Z_$][a-zA-Z0-9_$]*/; | ||
if ( parser.eat( '/' ) ) { | ||
const current = parser.current(); | ||
const block = parser.current(); | ||
let expected; | ||
if ( current.type === 'IfBlock' ) { | ||
if ( block.type === 'IfBlock' ) { | ||
expected = 'if'; | ||
} else if ( current.type === 'EachBlock' ) { | ||
} else if ( block.type === 'EachBlock' ) { | ||
expected = 'each'; | ||
@@ -29,3 +31,21 @@ } else { | ||
current.end = parser.index; | ||
// strip leading/trailing whitespace as necessary | ||
if ( !block.children.length ) parser.error( `Empty block`, block.start ); | ||
const firstChild = block.children[0]; | ||
const lastChild = block.children[ block.children.length - 1 ]; | ||
const charBefore = parser.template[ block.start - 1 ]; | ||
const charAfter = parser.template[ parser.index ]; | ||
if ( firstChild.type === 'Text' && !charBefore || whitespace.test( charBefore ) ) { | ||
firstChild.data = trimStart( firstChild.data ); | ||
if ( !firstChild.data ) block.children.shift(); | ||
} | ||
if ( lastChild.type === 'Text' && !charAfter || whitespace.test( charAfter ) ) { | ||
lastChild.data = trimEnd( lastChild.data ); | ||
if ( !lastChild.data ) block.children.pop(); | ||
} | ||
block.end = parser.index; | ||
parser.stack.pop(); | ||
@@ -68,4 +88,12 @@ } | ||
block.context = parser.read( validIdentifier ); // TODO check it's not a keyword | ||
if ( !block.context ) parser.error( `Expected name` ); | ||
parser.allowWhitespace(); | ||
if ( parser.eat( ',' ) ) { | ||
parser.allowWhitespace(); | ||
block.index = parser.read( validIdentifier ); | ||
if ( !block.index ) parser.error( `Expected name` ); | ||
parser.allowWhitespace(); | ||
} | ||
} | ||
@@ -72,0 +100,0 @@ |
import readExpression from '../read/expression.js'; | ||
import readScript from '../read/script.js'; | ||
import readStyle from '../read/style.js'; | ||
import { readEventHandlerDirective, readBindingDirective } from '../read/directives.js'; | ||
import { trimStart, trimEnd } from '../utils/trim.js'; | ||
@@ -6,5 +10,31 @@ const validTagName = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; | ||
const specials = { | ||
script: { | ||
read: readScript, | ||
property: 'js' | ||
}, | ||
style: { | ||
read: readStyle, | ||
property: 'css' | ||
} | ||
}; | ||
export default function tag ( parser ) { | ||
const start = parser.index++; | ||
if ( parser.eat( '!--' ) ) { | ||
const data = parser.readUntil( /-->/ ); | ||
parser.eat( '-->' ); | ||
parser.current().children.push({ | ||
start, | ||
end: parser.index, | ||
type: 'Comment', | ||
data | ||
}); | ||
return null; | ||
} | ||
const isClosingTag = parser.eat( '/' ); | ||
@@ -16,6 +46,26 @@ | ||
parser.allowWhitespace(); | ||
if ( isClosingTag ) { | ||
if ( !parser.eat( '>' ) ) parser.error( `Expected '>'` ); | ||
parser.current().end = parser.index; | ||
const element = parser.current(); | ||
// strip leading/trailing whitespace as necessary | ||
if ( element.children.length ) { | ||
const firstChild = element.children[0]; | ||
const lastChild = element.children[ element.children.length - 1 ]; | ||
if ( firstChild.type === 'Text' ) { | ||
firstChild.data = trimStart( firstChild.data ); | ||
if ( !firstChild.data ) element.children.shift(); | ||
} | ||
if ( lastChild.type === 'Text' ) { | ||
lastChild.data = trimEnd( lastChild.data ); | ||
if ( !lastChild.data ) element.children.pop(); | ||
} | ||
} | ||
element.end = parser.index; | ||
parser.stack.pop(); | ||
@@ -31,2 +81,3 @@ | ||
attributes.push( attribute ); | ||
parser.allowWhitespace(); | ||
} | ||
@@ -36,2 +87,16 @@ | ||
// special cases – <script> and <style> | ||
if ( name in specials ) { | ||
const special = specials[ name ]; | ||
if ( parser[ special.id ] ) { | ||
parser.index = start; | ||
parser.error( `You can only have one <${name}> tag per component` ); | ||
} | ||
parser.eat( '>', true ); | ||
parser[ special.property ] = special.read( parser, start, attributes ); | ||
return; | ||
} | ||
const element = { | ||
@@ -50,5 +115,3 @@ start, | ||
if ( !parser.eat( '>' ) ) { | ||
parser.error( `Expected >` ); | ||
} | ||
parser.eat( '>', true ); | ||
@@ -77,2 +140,4 @@ if ( selfClosing ) { | ||
function readAttribute ( parser ) { | ||
const start = parser.index; | ||
const name = parser.readUntil( /(\s|=|\/|>)/ ); | ||
@@ -83,5 +148,30 @@ if ( !name ) return null; | ||
if ( /^on:/.test( name ) ) { | ||
parser.eat( '=', true ); | ||
return readEventHandlerDirective( parser, start, name.slice( 3 ) ); | ||
} | ||
if ( /^bind:/.test( name ) ) { | ||
parser.eat( '=', true ); | ||
return readBindingDirective( parser, start, name.slice( 5 ) ); | ||
} | ||
if ( /^ref:/.test( name ) ) { | ||
return { | ||
start, | ||
end: parser.index, | ||
type: 'Ref', | ||
name: name.slice( 4 ) | ||
}; | ||
} | ||
const value = parser.eat( '=' ) ? readAttributeValue( parser ) : true; | ||
return { name, value }; | ||
return { | ||
start, | ||
end: parser.index, | ||
type: 'Attribute', | ||
name, | ||
value | ||
}; | ||
} | ||
@@ -100,3 +190,3 @@ | ||
end: null, | ||
type: 'AttributeText', | ||
type: 'Text', | ||
data: '' | ||
@@ -115,4 +205,5 @@ }; | ||
else { | ||
if ( parser.match( '{{' ) ) { | ||
const index = parser.index; | ||
const index = parser.index; | ||
if ( parser.eat( '{{' ) ) { | ||
currentChunk.end = index; | ||
@@ -124,3 +215,3 @@ | ||
const expression = readExpression(); | ||
const expression = readExpression( parser ); | ||
parser.allowWhitespace(); | ||
@@ -141,3 +232,3 @@ if ( !parser.eat( '}}' ) ) { | ||
end: null, | ||
type: 'AttributeText', | ||
type: 'Text', | ||
data: '' | ||
@@ -147,3 +238,3 @@ }; | ||
else if ( parser.match( '\\' ) ) { | ||
else if ( parser.eat( '\\' ) ) { | ||
escaped = true; | ||
@@ -153,7 +244,11 @@ } | ||
else if ( parser.match( quoteMark ) ) { | ||
if ( currentChunk.data ) { | ||
chunks.push( currentChunk ); | ||
return chunks; | ||
} | ||
currentChunk.end = parser.index++; | ||
if ( currentChunk.data ) chunks.push( currentChunk ); | ||
return chunks; | ||
} | ||
else { | ||
currentChunk.data += parser.template[ parser.index++ ]; | ||
} | ||
} | ||
@@ -160,0 +255,0 @@ } |
@@ -6,3 +6,3 @@ export default function text ( parser ) { | ||
while ( !parser.match( '<' ) && !parser.match( '{{' ) ) { | ||
while ( parser.index < parser.template.length && !parser.match( '<' ) && !parser.match( '{{' ) ) { | ||
data += parser.template[ parser.index++ ]; | ||
@@ -9,0 +9,0 @@ } |
{ | ||
"name": "svelte", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "The magical disappearing UI framework", | ||
"main": "dist/svelte-compiler.js", | ||
"main": "dist/svelte.umd.js", | ||
"module": "dist/svelte.es.js", | ||
"scripts": { | ||
"test": "mocha --opts mocha.opts --recursive ./**/__test__.js test/test.js", | ||
"lint": "eslint compiler" | ||
"lint": "eslint compiler", | ||
"build": "rollup -c", | ||
"prebuild": "npm test", | ||
"prepublish": "npm run lint && npm run build" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://gitlab.com/Rich-Harris/svelte.git" | ||
"url": "https://github.com/sveltejs/svelte.git" | ||
}, | ||
@@ -23,5 +27,5 @@ "keywords": [ | ||
"bugs": { | ||
"url": "https://gitlab.com/Rich-Harris/svelte/issues" | ||
"url": "https://github.com/sveltejs/svelte/issues" | ||
}, | ||
"homepage": "https://gitlab.com/Rich-Harris/svelte#README", | ||
"homepage": "https://github.com/sveltejs/svelte#README", | ||
"devDependencies": { | ||
@@ -32,8 +36,12 @@ "eslint": "^3.10.2", | ||
"mocha": "^3.1.2", | ||
"reify": "^0.4.0" | ||
"node-resolve": "^1.3.3", | ||
"reify": "^0.4.0", | ||
"rollup-plugin-node-resolve": "^2.0.0" | ||
}, | ||
"dependencies": { | ||
"acorn": "^4.0.3", | ||
"locate-character": "^2.0.0" | ||
"estree-walker": "^0.3.0", | ||
"locate-character": "^2.0.0", | ||
"magic-string": "^0.16.0" | ||
} | ||
} |
@@ -1,3 +0,3 @@ | ||
# svelte | ||
# Svelte | ||
Coming soon... |
172
test/test.js
import { compile } from '../compiler/index.js'; | ||
import parse from '../compiler/parse/index.js'; | ||
import * as assert from 'assert'; | ||
@@ -16,97 +17,124 @@ import * as path from 'path'; | ||
function exists ( path ) { | ||
try { | ||
fs.statSync( path ); | ||
return true; | ||
} catch ( err ) { | ||
return false; | ||
} | ||
} | ||
describe( 'svelte', () => { | ||
function loadConfig ( dir ) { | ||
try { | ||
return require( `./samples/${dir}/_config.js` ).default; | ||
} catch ( err ) { | ||
if ( err.code === 'E_NOT_FOUND' ) { | ||
return {}; | ||
} | ||
describe( 'parser', () => { | ||
fs.readdirSync( 'test/parser' ).forEach( dir => { | ||
if ( dir[0] === '.' ) return; | ||
throw err; | ||
} | ||
} | ||
const solo = exists( `test/parser/${dir}/solo` ); | ||
function env () { | ||
return new Promise( ( fulfil, reject ) => { | ||
jsdom.env( '<main></main>', ( err, window ) => { | ||
if ( err ) { | ||
reject( err ); | ||
} else { | ||
global.document = window.document; | ||
fulfil( window ); | ||
} | ||
( solo ? it.only : it )( dir, () => { | ||
const input = fs.readFileSync( `test/parser/${dir}/input.svelte`, 'utf-8' ).trim(); | ||
const actual = parse( input ); | ||
const expected = require( `./parser/${dir}/output.json` ); | ||
assert.deepEqual( actual, expected ); | ||
}); | ||
}); | ||
} | ||
}); | ||
fs.readdirSync( 'test/samples' ).forEach( dir => { | ||
if ( dir[0] === '.' ) return; | ||
const config = loadConfig( dir ); | ||
( config.solo ? it.only : it )( dir, () => { | ||
let compiled; | ||
describe( 'compiler', () => { | ||
function loadConfig ( dir ) { | ||
try { | ||
const source = fs.readFileSync( `test/samples/${dir}/main.svelte`, 'utf-8' ); | ||
compiled = compile( source ); | ||
return require( `./compiler/${dir}/_config.js` ).default; | ||
} catch ( err ) { | ||
if ( config.compileError ) { | ||
config.compileError( err ); | ||
return; | ||
} else { | ||
throw err; | ||
if ( err.code === 'E_NOT_FOUND' ) { | ||
return {}; | ||
} | ||
throw err; | ||
} | ||
} | ||
const { code } = compiled; | ||
const withLineNumbers = code.split( '\n' ).map( ( line, i ) => { | ||
i = String( i + 1 ); | ||
while ( i.length < 3 ) i = ` ${i}`; | ||
function env () { | ||
return new Promise( ( fulfil, reject ) => { | ||
jsdom.env( '<main></main>', ( err, window ) => { | ||
if ( err ) { | ||
reject( err ); | ||
} else { | ||
global.document = window.document; | ||
fulfil( window ); | ||
} | ||
}); | ||
}); | ||
} | ||
return `${i}: ${line}`; | ||
}).join( '\n' ); | ||
fs.readdirSync( 'test/compiler' ).forEach( dir => { | ||
if ( dir[0] === '.' ) return; | ||
cache[ path.resolve( `test/samples/${dir}/main.svelte` ) ] = code; | ||
const config = loadConfig( dir ); | ||
let factory; | ||
( config.solo ? it.only : it )( dir, () => { | ||
let compiled; | ||
try { | ||
factory = require( `./samples/${dir}/main.svelte` ).default; | ||
} catch ( err ) { | ||
console.log( withLineNumbers ); // eslint-disable-line no-console | ||
throw err; | ||
} | ||
try { | ||
const source = fs.readFileSync( `test/compiler/${dir}/main.svelte`, 'utf-8' ); | ||
compiled = compile( source ); | ||
} catch ( err ) { | ||
if ( config.compileError ) { | ||
config.compileError( err ); | ||
return; | ||
} else { | ||
throw err; | ||
} | ||
} | ||
if ( config.show ) { | ||
console.log( withLineNumbers ); // eslint-disable-line no-console | ||
} | ||
const { code } = compiled; | ||
const withLineNumbers = code.split( '\n' ).map( ( line, i ) => { | ||
i = String( i + 1 ); | ||
while ( i.length < 3 ) i = ` ${i}`; | ||
return env() | ||
.then( window => { | ||
const target = window.document.querySelector( 'main' ); | ||
return `${i}: ${line.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) )}`; | ||
}).join( '\n' ); | ||
const component = factory({ | ||
target, | ||
data: config.data | ||
}); | ||
cache[ path.resolve( `test/compiler/${dir}/main.svelte` ) ] = code; | ||
if ( config.html ) { | ||
assert.equal( target.innerHTML, config.html ); | ||
} | ||
let factory; | ||
if ( config.test ) { | ||
config.test( component, target ); | ||
} else { | ||
component.teardown(); | ||
assert.equal( target.innerHTML, '' ); | ||
} | ||
}) | ||
.catch( err => { | ||
if ( !config.show ) console.log( withLineNumbers ); // eslint-disable-line no-console | ||
try { | ||
factory = require( `./compiler/${dir}/main.svelte` ).default; | ||
} catch ( err ) { | ||
console.log( withLineNumbers ); // eslint-disable-line no-console | ||
throw err; | ||
}); | ||
} | ||
if ( config.show ) { | ||
console.log( withLineNumbers ); // eslint-disable-line no-console | ||
} | ||
return env() | ||
.then( window => { | ||
const target = window.document.querySelector( 'main' ); | ||
const component = factory({ | ||
target, | ||
data: config.data | ||
}); | ||
if ( config.html ) { | ||
assert.equal( target.innerHTML, config.html ); | ||
} | ||
if ( config.test ) { | ||
config.test( component, target, window ); | ||
} else { | ||
component.teardown(); | ||
assert.equal( target.innerHTML, '' ); | ||
} | ||
}) | ||
.catch( err => { | ||
if ( !config.show ) console.log( withLineNumbers ); // eslint-disable-line no-console | ||
throw err; | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
86509
128
2752
4
7
4
1
+ Addedestree-walker@^0.3.0
+ Addedmagic-string@^0.16.0
+ Addedestree-walker@0.3.1(transitive)
+ Addedmagic-string@0.16.0(transitive)
+ Addedvlq@0.2.3(transitive)