Permalink
Browse files

Generate google closurer compiler externs from type file

If using any external libraries in you javascript code, you
need to declare those APIs in the externs file, so that the
Google Closure Compiler will not rename the symbols of the APIs.
https://developers.google.com/closure/compiler/docs/api-tutorial3#externs

Add an ext.ts to generate the externs file from typescript type
file(http://www.typescriptlang.org/Handbook#writing-dts-files)
to make typescript work better with google closure compiler.
To compile the ext.ts:
tsc ext.ts --out ext.js

To generate externs file:
./ext a.d.ts a.d.externs
  • Loading branch information...
1 parent 5129d7c commit 540689f6191d55e751199686ca345109185305d3 @raphaelfeng committed May 8, 2015
Showing with 152 additions and 0 deletions.
  1. +2 −0 src/extensions/ext
  2. +150 −0 src/extensions/ext.ts
View
@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+require('./ext.js')
View
@@ -0,0 +1,150 @@
+/// <reference path="..\compiler\program.ts"/>
+/// <reference path="..\compiler\types.ts"/>
+/// <reference path="..\compiler\commandLineParser.ts"/>
+/// <reference path="..\services\services.ts"/>
+
+module ts {
+ export module ext {
+ declare var require: any;
+
+ class ExternWriter {
+ private output: string = "";
+
+ public writeLine(str: string): void {
+ this.output += str + '\n';
+ }
+
+ public getText(): string {
+ return this.output;
+ }
+ }
+
+ var fs = require('fs');
+ var writer = new ExternWriter();
+ var checker: TypeChecker = null;
+
+ export function execCmdLine(args: string[]) {
+ var commandLine = parseCommandLine(args);
+ var fileNames = commandLine.fileNames;
+
+ if (! fileNames || fileNames.length < 1) {
+ printUsage();
+ return;
+ }
+
+ if (fileNames.length == 1) {
+ let fileName = fileNames[0];
+ let lastDotIndex = fileName.lastIndexOf('.');
+ fileNames.push(fileName.substring(0, lastDotIndex) + '.externs');
+ }
+
+ exportExterns(commandLine.fileNames);
+ }
+
+ function printUsage(): void {
+ console.log("Usage: ext [file ...]");
+ console.log("Example: ext a.d.ts a.externs");
+ }
+
+ function exportExterns(fileNames: string[]) {
+ var exportClassAndInterface = false;
+ var typeFile = fileNames[0];
+ var externFile = fileNames[1];
+ console.log('generating ' + externFile + ' from ' + typeFile);
+
+ var compilerOptions: CompilerOptions = {
+ target: ScriptTarget.ES3,
+ module: ModuleKind.None
+ };
+
+ var compilerHost = ts.createCompilerHost(compilerOptions);
+ var program = ts.createProgram([typeFile], compilerOptions, compilerHost);
+ program.emit();
+
+ var sourceFile = program.getSourceFile(typeFile);
+ checker = ts.createTypeChecker(program, true);
+ var stack: ts.Symbol[] = [];
+ var locals = sourceFile.locals;
+ for (let key in locals) {
+ locals[key]['ext.externName'] = locals[key].getName();
+ stack.push(locals[key]);
+ }
+
+ while (stack.length != 0) {
+ var sym = stack.pop();
+ var children = getChildren(sym);
+ if (children.length == 0) {
+ visit(sym);
+ }
+ else {
+ for (var child of children) {
+ if(! child['ext.visited']) {
+ child['ext.externName'] = sym['ext.externName'] + '.' + child.getName();
+ stack.push(child);
+ }
+ }
+ }
+ sym['ext.visited'] = true;
+ }
+
+ // write the output extern file
+ compilerHost.writeFile(externFile, writer.getText(), false);
+ console.log(writer.getText());
+ }
+
+ function getChildren(sym: Symbol): Symbol[] {
+ // exports
+ var children = checker.getExportsOfModule(sym);
+ // members
+ if (sym.members) {
+ for (let name in sym.members) {
+ children.push(sym.members[name]);
+ }
+ }
+
+ // a variable or property with a class or interface
+ var type: Type = sym.valueDeclaration ?
+ checker.getTypeOfSymbolAtLocation(sym, sym.valueDeclaration) :
+ null;
+ if (sym.valueDeclaration && sym.valueDeclaration['type'] && (sym.valueDeclaration['type']['kind'] === SyntaxKind.ArrayType)) {
+ // hack way to get the element type of array
+ // because the checker.getSymbolType return an empty array without element type
+ type = checker.getTypeAtLocation(sym.valueDeclaration['type']['elementType'])
+ }
+ if ((sym.getFlags() & (SymbolFlags.Variable | SymbolFlags.Property)) &&
+ (type && (type.flags & TypeFlags.Class || type.flags & TypeFlags.Interface))) {
+ var properties = checker.getPropertiesOfType(type);
+ for (let prop of properties) {
+ let isPublic = true;
+ if (prop.valueDeclaration.modifiers) {
+ for (let modifier of prop.valueDeclaration.modifiers) {
+ if (modifier.kind === SyntaxKind.PrivateKeyword ||
+ modifier.kind === SyntaxKind.ProtectedKeyword) {
+ isPublic = false;
+ }
+ }
+ }
+ if (isPublic) {
+ children.push(prop);
+ }
+ }
+ }
+
+ return children;
+ }
+
+ function visit(sym: Symbol): void {
+ // properties, members with primitive types
+ //var fullName = checker.getFullyQualifiedName(sym);
+ if (! (sym.flags & SymbolFlags.Prototype)) {
+ //writer.writeLine(fullName);
+ writer.writeLine(sym['ext.externName']);
+ }
+ }
+
+ function fileExists(path: string) {
+ return fs.existsSync(path) && fs.statSync(path).isFile();
+ }
+ }
+}
+ts.ext.execCmdLine(ts.sys.args);

0 comments on commit 540689f

Please sign in to comment.