Skip to content
This repository has been archived by the owner on Aug 4, 2021. It is now read-only.

Commit

Permalink
Merge pull request #18 from rollup/export-class-name
Browse files Browse the repository at this point in the history
Handle TypeScript's `export class Name`
  • Loading branch information
Victorystick committed Jan 20, 2016
2 parents 99c72ca + 02c9f64 commit fd0bdb7
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 6 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@
"dependencies": {
"object-assign": "^4.0.1",
"rollup-pluginutils": "^1.1.0",
"tippex": "^1.1.0",
"typescript": "1.7.5"
},
"devDependencies": {
"mocha": "^2.3.3",
"rollup": "^0.25.0",
"rollup-plugin-typescript": "^0.2.0"
"rollup-plugin-typescript": "^0.3.0"
},
"repository": {
"type": "git",
Expand Down
1 change: 1 addition & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default {
'fs',
'object-assign',
'rollup-pluginutils',
'tippex',
'typescript'
],

Expand Down
70 changes: 70 additions & 0 deletions src/fixExportClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as tippex from 'tippex';

// Hack around TypeScript's broken handling of `export class` with
// ES6 modules and ES5 script target.
//
// It works because TypeScript transforms
//
// export class A {}
//
// into something like CommonJS, when we wanted ES6 modules.
//
// var A = (function () {
// function A() {
// }
// return A;
// }());
// exports.A = A;
//
// But
//
// class A {}
// export { A };
//
// is transformed into this beauty.
//
// var A = (function () {
// function A() {
// }
// return A;
// }());
// export { A };
//
// The solution is to replace the previous export syntax with the latter.
export default function fix ( code: string, id: string ): string {

// Erase comments, strings etc. to avoid erroneous matches for the Regex.
const cleanCode = tippex.erase( code );

const re = /export\s+(default\s+)?class(?:\s+(\w+))?/g;
let match;

while ( match = re.exec( cleanCode ) ) {
// To keep source maps intact, replace non-whitespace characters with spaces.
code = erase( code, match.index, match[ 0 ].indexOf( 'class' ) );

let name = match[ 2 ];

if ( match[ 1 ] ) { // it is a default export

// TODO: support this too
if ( !name ) throw new Error( `TypeScript Plugin: cannot export an un-named class (module ${ id })` );

// Export the name ` as default`.
name += ' as default';
}

// To keep source maps intact, append the injected exports last.
code += `\nexport { ${ name } };`
}

return code;
}

function erase ( code: string, start: number, length: number ): string {
const end = start + length;

return code.slice( 0, start ) +
code.slice( start, end ).replace( /[^\s]/g, ' ' ) +
code.slice( end );
}
7 changes: 5 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { createFilter } from 'rollup-pluginutils';
import { statSync } from 'fs';
import assign from 'object-assign';

import fixExportClass from './fixExportClass';

const resolveHost = {
fileExists ( filePath: string ): boolean {
try {
Expand Down Expand Up @@ -47,7 +49,8 @@ export default function typescript ( options ) {
transform ( code: string, id: string ): { code: string, map: any } {
if ( !filter( id ) ) return null;

const transformed = ts.transpileModule( code, {
const transformed = ts.transpileModule( fixExportClass( code, id ), {
fileName: id,
reportDiagnostics: true,
compilerOptions: options
});
Expand All @@ -61,7 +64,7 @@ export default function typescript ( options ) {
if ( diagnostic.file ) {
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition( diagnostic.start );

console.error( `${diagnostic.file.fileName}(${line + 1},${character + 1}): error ES${diagnostic.code}: ${message}` );
console.error( `${diagnostic.file.fileName}(${line + 1},${character + 1}): error TS${diagnostic.code}: ${message}` );
} else {
console.error( `Error: ${message}` );
}
Expand Down
3 changes: 3 additions & 0 deletions test/sample/export-class-fix/default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// the odd spacing is intentional
export default
class A {}
4 changes: 4 additions & 0 deletions test/sample/export-class-fix/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import A from './default';
import { B } from './named';

export { A, B };
2 changes: 2 additions & 0 deletions test/sample/export-class-fix/named.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// the odd spacing is intentional
export class B {}
3 changes: 3 additions & 0 deletions test/sample/export-class/Foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class Foo {
foo = 'bar';
}
3 changes: 3 additions & 0 deletions test/sample/export-class/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {Foo} from './Foo.ts';

export default new Foo();
43 changes: 40 additions & 3 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ var typescript = require( '..' );

process.chdir( __dirname );

// Evaluate a bundle (as CommonJS) and return its exports.
function evaluate( bundle ) {
const module = { exports: {} };

new Function( 'module', 'exports', bundle.generate({ format: 'cjs' }).code )(module, module.exports);

return module.exports;
}

describe( 'rollup-plugin-typescript', function () {
this.timeout( 5000 );

Expand All @@ -22,6 +31,23 @@ describe( 'rollup-plugin-typescript', function () {
});
});

it( 'transpiles `export class A` correctly', function () {
return rollup.rollup({
entry: 'sample/export-class-fix/main.ts',
plugins: [
typescript()
]
}).then( function ( bundle ) {
const generated = bundle.generate();
const code = generated.code;

assert.equal( code.indexOf( 'class' ), -1, code );
assert.ok( code.indexOf( 'var A = (function' ) !== -1, code );
assert.ok( code.indexOf( 'var B = (function' ) !== -1, code );
assert.ok( code.indexOf( 'export { A, B };' ) !== -1, code );
});
});

it( 'transpiles ES6 features to ES5 with source maps', function () {
return rollup.rollup({
entry: 'sample/import-class/main.ts',
Expand All @@ -32,9 +58,9 @@ describe( 'rollup-plugin-typescript', function () {
const generated = bundle.generate();
const code = generated.code;

assert.ok( code.indexOf( 'class' ) === -1, code );
assert.ok( code.indexOf( '...' ) === -1, code );
assert.ok( code.indexOf( '=>' ) === -1, code );
assert.equal( code.indexOf( 'class' ), -1, code );
assert.equal( code.indexOf( '...' ), -1, code );
assert.equal( code.indexOf( '=>' ), -1, code );
});
});

Expand All @@ -48,4 +74,15 @@ describe( 'rollup-plugin-typescript', function () {
assert.ok( error.message.indexOf( 'There were TypeScript errors' ) === 0, 'Should reject erroneous code.' );
});
});

it( 'should use named exports for classes', function () {
return rollup.rollup({
entry: 'sample/export-class/main.ts',
plugins: [
typescript()
]
}).then( function ( bundle ) {
assert.equal( evaluate( bundle ).foo, 'bar' );
});
});
});

0 comments on commit fd0bdb7

Please sign in to comment.