implement AMD and CommonJS outout formats (#27)

pull/31/head
Rich-Harris 8 years ago
parent 6b677f61ec
commit fa5bbbee9f

@ -4,10 +4,13 @@ import deindent from './utils/deindent.js';
import isReference from './utils/isReference.js';
import counter from './utils/counter.js';
import flattenReference from './utils/flattenReference.js';
import getIntro from './utils/getIntro.js';
import getOutro from './utils/getOutro.js';
import visitors from './visitors/index.js';
import processCss from './css/process.js';
export default function generate ( parsed, source, options ) {
const format = options.format || 'es';
const renderers = [];
const generator = {
@ -155,7 +158,8 @@ export default function generate ( parsed, source, options ) {
while ( /[ \t]/.test( source[ a - 1 ] ) ) a -= 1;
while ( source[b] === '\n' ) b += 1;
imports.push( source.slice( a, b ).replace( /^\s/, '' ) );
//imports.push( source.slice( a, b ).replace( /^\s/, '' ) );
imports.push( node );
generator.code.remove( a, b );
}
}
@ -278,9 +282,35 @@ export default function generate ( parsed, source, options ) {
dispatchObservers( observers.deferred, newState, oldState );
` );
const importBlock = imports
.map( ( declaration, i ) => {
if ( format === 'es' ) {
return source.slice( declaration.start, declaration.end );
}
const defaultImport = declaration.specifiers.find( x => x.type === 'ImportDefaultSpecifier' || x.type === 'ImportSpecifier' && x.imported.name === 'default' );
const namespaceImport = declaration.specifiers.find( x => x.type === 'ImportNamespaceSpecifier' );
const namedImports = declaration.specifiers.filter( x => x.type === 'ImportSpecifier' && x.imported.name !== 'default' );
const name = ( defaultImport || namespaceImport ) ? ( defaultImport || namespaceImport ).local.name : `__import${i}`;
declaration.name = name; // hacky but makes life a bit easier later
const statements = namedImports.map( specifier => {
return `var ${specifier.local.name} = ${name}.${specifier.imported.name}`;
});
if ( defaultImport ) {
statements.push( `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` );
}
return statements.join( '\n' );
})
.filter( Boolean )
.join( '\n' );
if ( parsed.js ) {
if ( imports.length ) {
topLevelStatements.push( imports.join( '' ).trim() );
topLevelStatements.push( importBlock );
}
topLevelStatements.push( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` );
@ -343,7 +373,7 @@ export default function generate ( parsed, source, options ) {
const initialState = templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data || {}`;
topLevelStatements.push( deindent`
export default function ${constructorName} ( options ) {
function ${constructorName} ( options ) {
var component = this;${generator.usesRefs ? `\nthis.refs = {}` : ``}
var state = ${initialState};${templateProperties.computed ? `\napplyComputations( state, state, {} );` : ``}
@ -451,13 +481,19 @@ export default function generate ( parsed, source, options ) {
const compiled = new Bundle({ separator: '' });
parts.forEach( str => {
const match = pattern.exec( str );
function addString ( str ) {
compiled.addSource({
filename: options.filename,
content: new MagicString( str.replace( pattern, '' ) )
content: new MagicString( str )
});
}
addString( getIntro( format, options, imports ) );
parts.forEach( str => {
const match = pattern.exec( str );
addString( str.replace( pattern, '' ) );
compiled.addSource({
filename: options.filename,
@ -467,6 +503,8 @@ export default function generate ( parsed, source, options ) {
compiled.append( finalChunk );
addString( '\n\n' + getOutro( format, constructorName, imports ) );
return {
code: compiled.toString(),
map: compiled.generateMap()

@ -0,0 +1,36 @@
export default function getIntro ( format, options, imports ) {
const dependencies = imports.map( declaration => {
return {
source: declaration.source.value,
name: declaration.name
};
});
if ( format === 'es' ) return '';
if ( format === 'amd' ) return getAmdIntro( options.amd, dependencies );
if ( format === 'cjs' ) return getCjsIntro( dependencies );
throw new Error( `Not implemented: ${format}` );
}
function getAmdIntro ( options = {}, dependencies ) {
const sourceString = dependencies.length ?
`[ ${dependencies.map( dep => `'${dep.source}'` ).join( ', ' )} ], ` :
'';
const paramString = dependencies.length ? ` ${dependencies.map( dep => dep.name ).join( ', ' )} ` : '';
return `define(${options.id ? ` '${options.id}', ` : ''}${sourceString}function (${paramString}) { 'use strict';\n\n`;
}
function getCjsIntro ( dependencies ) {
const requireBlock = dependencies
.map( dep => `var ${dep.name} = require( '${dep.source}' );` )
.join( '\n\n' );
if ( requireBlock ) {
return `'use strict';\n\n${requireBlock}\n\n`;
}
return `'use strict';\n\n`;
}

@ -0,0 +1,15 @@
export default function getOutro ( format, name, imports ) {
if ( format === 'es' ) {
return `export default ${name};`;
}
if ( format === 'amd' ) {
return `return ${name};\n\n});`;
}
if ( format === 'cjs' ) {
return `module.exports = ${name};`;
}
throw new Error( `Not implemented: ${format}` );
}

@ -1,4 +1,5 @@
import { compile, parse, validate } from '../dist/svelte.js';
import deindent from '../compiler/generate/utils/deindent.js';
import assert from 'assert';
import * as path from 'path';
import * as fs from 'fs';
@ -26,7 +27,77 @@ function exists ( path ) {
}
}
function env () {
return new Promise( ( fulfil, reject ) => {
jsdom.env( '<main></main>', ( err, window ) => {
if ( err ) {
reject( err );
} else {
global.document = window.document;
fulfil( window );
}
});
});
}
describe( 'svelte', () => {
before( () => {
function cleanChildren ( node ) {
let previous = null;
[ ...node.childNodes ].forEach( child => {
if ( child.nodeType === 8 ) {
// comment
node.removeChild( child );
return;
}
if ( child.nodeType === 3 ) {
child.data = child.data.replace( /\s{2,}/, '\n' );
// text
if ( previous && previous.nodeType === 3 ) {
previous.data += child.data;
previous.data = previous.data.replace( /\s{2,}/, '\n' );
node.removeChild( child );
}
}
else {
cleanChildren( child );
}
previous = child;
});
// collapse whitespace
if ( node.firstChild && node.firstChild.nodeType === 3 ) {
node.firstChild.data = node.firstChild.data.replace( /^\s+/, '' );
if ( !node.firstChild.data ) node.removeChild( node.firstChild );
}
if ( node.lastChild && node.lastChild.nodeType === 3 ) {
node.lastChild.data = node.lastChild.data.replace( /\s+$/, '' );
if ( !node.lastChild.data ) node.removeChild( node.lastChild );
}
}
return env().then( window => {
assert.htmlEqual = ( actual, expected, message ) => {
window.document.body.innerHTML = actual.trim();
cleanChildren( window.document.body, '' );
actual = window.document.body.innerHTML;
window.document.body.innerHTML = expected.trim();
cleanChildren( window.document.body, '' );
expected = window.document.body.innerHTML;
assert.deepEqual( actual, expected, message );
};
});
});
describe( 'parse', () => {
fs.readdirSync( 'test/parser' ).forEach( dir => {
if ( dir[0] === '.' ) return;
@ -125,63 +196,6 @@ describe( 'svelte', () => {
});
describe( 'generate', () => {
before( () => {
function cleanChildren ( node ) {
let previous = null;
[ ...node.childNodes ].forEach( child => {
if ( child.nodeType === 8 ) {
// comment
node.removeChild( child );
return;
}
if ( child.nodeType === 3 ) {
child.data = child.data.replace( /\s{2,}/, '\n' );
// text
if ( previous && previous.nodeType === 3 ) {
previous.data += child.data;
previous.data = previous.data.replace( /\s{2,}/, '\n' );
node.removeChild( child );
}
}
else {
cleanChildren( child );
}
previous = child;
});
// collapse whitespace
if ( node.firstChild && node.firstChild.nodeType === 3 ) {
node.firstChild.data = node.firstChild.data.replace( /^\s+/, '' );
if ( !node.firstChild.data ) node.removeChild( node.firstChild );
}
if ( node.lastChild && node.lastChild.nodeType === 3 ) {
node.lastChild.data = node.lastChild.data.replace( /\s+$/, '' );
if ( !node.lastChild.data ) node.removeChild( node.lastChild );
}
}
return env().then( window => {
assert.htmlEqual = ( actual, expected, message ) => {
window.document.body.innerHTML = actual.trim();
cleanChildren( window.document.body, '' );
actual = window.document.body.innerHTML;
window.document.body.innerHTML = expected.trim();
cleanChildren( window.document.body, '' );
expected = window.document.body.innerHTML;
assert.deepEqual( actual, expected, message );
};
});
});
function loadConfig ( dir ) {
try {
return require( `./compiler/${dir}/_config.js` ).default;
@ -194,19 +208,6 @@ describe( 'svelte', () => {
}
}
function env () {
return new Promise( ( fulfil, reject ) => {
jsdom.env( '<main></main>', ( err, window ) => {
if ( err ) {
reject( err );
} else {
global.document = window.document;
fulfil( window );
}
});
});
}
fs.readdirSync( 'test/compiler' ).forEach( dir => {
if ( dir[0] === '.' ) return;
@ -277,4 +278,89 @@ describe( 'svelte', () => {
});
});
});
describe( 'formats', () => {
describe( 'amd', () => {
it( 'generates an AMD module', () => {
const source = deindent`
<div>{{answer}}</div>
<script>
import answer from 'answer';
export default {
data () {
return { answer };
}
};
</script>
`;
const { code } = compile( source, {
format: 'amd',
amd: { id: 'foo' }
});
const fn = new Function( 'define', code );
return env().then( window => {
fn( ( id, dependencies, factory ) => {
assert.equal( id, 'foo' );
assert.deepEqual( dependencies, [ 'answer' ]);
const SvelteComponent = factory( 42 );
const main = window.document.body.querySelector( 'main' );
const component = new SvelteComponent({ target: main });
assert.htmlEqual( main.innerHTML, `<div>42</div>` );
component.teardown();
});
});
});
});
describe( 'cjs', () => {
it( 'generates a CommonJS module', () => {
const source = deindent`
<div>{{answer}}</div>
<script>
import answer from 'answer';
export default {
data () {
return { answer };
}
};
</script>
`;
const { code } = compile( source, {
format: 'cjs'
});
const fn = new Function( 'module', 'require', code );
return env().then( window => {
const module = {};
const require = id => {
if ( id === 'answer' ) return 42;
};
fn( module, require );
const SvelteComponent = module.exports;
const main = window.document.body.querySelector( 'main' );
const component = new SvelteComponent({ target: main });
assert.htmlEqual( main.innerHTML, `<div>42</div>` );
component.teardown();
});
});
});
});
});

Loading…
Cancel
Save