avalon-loader
Version:
A single-file webpack loader for avalon.js
138 lines (118 loc) • 3.8 kB
JavaScript
var parse5 = require( 'parse5' );
var deindent = require( './deindent' );
var hash = require( 'hash-sum' );
var cache = require( 'lru-cache' )( 100 );
var SourceMapGenerator = require( 'source-map' ).SourceMapGenerator;
var splitRE = /\r?\n/g
var emptyRE = /^\s*$/
function getCommentSymbol() {
return '//';
}
function getAttribute( node, name ) {
if ( node.attrs ) {
var i = node.attrs.length
var attr
while ( i-- ) {
attr = node.attrs[ i ]
if ( attr.name === name ) {
return attr.value
}
}
}
}
function commentScript( content ) {
var symbol = getCommentSymbol();
var lines = content.split( splitRE )
return lines.map( function( line, index ) {
// preserve EOL
if ( index === lines.length - 1 && emptyRE.test( line ) ) {
return ''
} else {
return symbol + ( emptyRE.test( line ) ? '' : ' ' + line )
}
} )
.join( '\n' )
}
function parse( content, filename, needMap ) {
var cacheKey = hash( filename + content )
// source-map cache busting for hot-reloadded modules
var filenameWithHash = filename + '?' + cacheKey
var output = cache.get( cacheKey )
if ( output ) {
return output
}
output = {
template: [],
style: [],
script: [],
}
var fragment = parse5.parseFragment( content, {
locationInfo: true
} );
fragment.childNodes.forEach( function( node ) {
var tagName = node.tagName;
var lang = getAttribute( node, 'lang' );
var scoped = getAttribute( node, 'scoped' ) != null
var map = null
if ( !output[ tagName ] ) {
return;
}
if ( tagName === 'template' ) {
node = node.content;
}
var start = node.childNodes[ 0 ].__location.startOffset;
var end = node.childNodes[ node.childNodes.length - 1 ].__location.endOffset;
var result;
if ( tagName === 'script' ) {
result = commentScript( content.slice( 0, start ) ) +
deindent( content.slice( start, end ) ) +
commentScript( content.slice( end ) );
} else {
var lineOffset = content.slice( 0, start ).split( splitRE ).length - 1
result = deindent( content.slice( start, end ) )
result = Array( lineOffset + 1 ).join( '\n' ) + result
}
if ( needMap ) {
// generate source map
map = new SourceMapGenerator()
map.setSourceContent( filenameWithHash, content )
// do not add mappings for comment lines - babel's source map
// somehow gets messed up because of it
var isCommentLine = function( line ) {
return tagName === 'script' &&
line.indexOf( getCommentSymbol() ) === 0
}
result.split( splitRE ).forEach( function( line, index ) {
if ( !emptyRE.test( line ) && !isCommentLine( line ) ) {
map.addMapping( {
source: filenameWithHash,
original: {
line: index + 1,
column: 0
},
generated: {
line: index + 1,
column: 0
}
} )
}
} )
// workaround for Webpack eval-source-map bug
// https://github.com/webpack/webpack/pull/1816
// in case the script was piped through another loader
// that doesn't pass down the source map properly.
if ( tagName === 'script' && !lang ) {
result += '\n/* generated by avalon-loader */\n'
}
}
output[ tagName ].push( {
content: result,
lang: lang,
scoped: scoped,
map: map && map.toJSON()
} );
} );
cache.set( cacheKey, output )
return output
}
module.exports = parse;