typescript-monkey 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.editorconfig +27 -0
  4. data/.gitignore +52 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +27 -0
  8. data/.vscode/tasks.json +40 -0
  9. data/CHANGES.md +48 -0
  10. data/Gemfile +16 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +351 -0
  13. data/Rakefile +13 -0
  14. data/contrib/example_package.json +10 -0
  15. data/contrib/example_typescript.rb +14 -0
  16. data/gulpfile.js +67 -0
  17. data/lib/assets/javascripts/transpiler.ts.erb +1 -0
  18. data/lib/assets/javascripts/transpiler_pkg.js.erb +2 -0
  19. data/lib/assets/javascripts/typescript.js.erb +1 -0
  20. data/lib/assets/typescripts/transpile_once.ts +13 -0
  21. data/lib/assets/typescripts/transpiler.ts +203 -0
  22. data/lib/rails/generators/typescript/assets/assets_generator.rb +13 -0
  23. data/lib/rails/generators/typescript/assets/templates/javascript.ts +3 -0
  24. data/lib/typescript-monkey.rb +8 -0
  25. data/lib/typescript/monkey.rb +9 -0
  26. data/lib/typescript/monkey/cli.rb +22 -0
  27. data/lib/typescript/monkey/compiler.rb +177 -0
  28. data/lib/typescript/monkey/configuration.rb +49 -0
  29. data/lib/typescript/monkey/engine.rb +18 -0
  30. data/lib/typescript/monkey/js_hook.rb +15 -0
  31. data/lib/typescript/monkey/package.rb +239 -0
  32. data/lib/typescript/monkey/railtie.rb +20 -0
  33. data/lib/typescript/monkey/template.rb +32 -0
  34. data/lib/typescript/monkey/template_handler.rb +24 -0
  35. data/lib/typescript/monkey/transformer.rb +20 -0
  36. data/lib/typescript/monkey/transpiler.rb +256 -0
  37. data/lib/typescript/monkey/version.rb +3 -0
  38. data/package.json +32 -0
  39. data/test/assets_generator_test.rb +15 -0
  40. data/test/assets_test.rb +41 -0
  41. data/test/controller_generator_test.rb +19 -0
  42. data/test/fixtures/assets/javascripts/hello.js.ts +3 -0
  43. data/test/fixtures/assets/javascripts/included.ts +4 -0
  44. data/test/fixtures/assets/javascripts/reference.ts +2 -0
  45. data/test/fixtures/references/ref1_1.js.ts +3 -0
  46. data/test/fixtures/references/ref1_2.js.ts +3 -0
  47. data/test/fixtures/references/ref2_1.d.ts +1 -0
  48. data/test/fixtures/references/ref2_2.js.ts +3 -0
  49. data/test/fixtures/references/ref3_1.js.ts +5 -0
  50. data/test/fixtures/references/ref3_2.ts +3 -0
  51. data/test/fixtures/references/ref3_3.ts +3 -0
  52. data/test/fixtures/routes.rb +0 -0
  53. data/test/fixtures/site/es5.js.ts +7 -0
  54. data/test/fixtures/site/index.js.ts +1 -0
  55. data/test/fixtures/site/script_tags.html.erb +23 -0
  56. data/test/fixtures/sprockets/ref1_1.js.ts +3 -0
  57. data/test/fixtures/sprockets/ref1_2.js.ts +3 -0
  58. data/test/fixtures/sprockets/ref1_manifest.js.ts +2 -0
  59. data/test/references_test.rb +48 -0
  60. data/test/scaffold_generator_test.rb +19 -0
  61. data/test/sprockets_test.rb +36 -0
  62. data/test/support/routes.rb +1 -0
  63. data/test/template_handler_test.rb +35 -0
  64. data/test/test_helper.rb +111 -0
  65. data/tsconfig.json +23 -0
  66. data/typescript-monkey.gemspec +34 -0
  67. metadata +175 -0
@@ -0,0 +1,13 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'lib'
8
+ t.libs << 'test'
9
+ t.pattern = 'test/**/*_test.rb'
10
+ t.verbose = false
11
+ end
12
+
13
+ task :default => :test
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "rails-project",
3
+ "description": "rails-project",
4
+ "version": "0.0.1",
5
+ "dependencies": {
6
+ "@types/jquery": "^2.0.41",
7
+ "@types/node": "^7.0.14",
8
+ "typescript": "^2.3.1"
9
+ }
10
+ }
@@ -0,0 +1,14 @@
1
+ #
2
+ # initializers/typescript.rb
3
+ #
4
+ # Configure typescript-monkey gem, notably which typescript path to use.
5
+ # See: https://github.com/markeissler/typescript-monkey
6
+ #
7
+
8
+ Typescript::Monkey.configure do |config|
9
+ # Configure Typescript::Monkey concatenated compilation
10
+ # config.compile = false
11
+
12
+ # Configure Typescript::Monkey logging (for debugging your app build)
13
+ # config.logger = Rails.logger
14
+ end
@@ -0,0 +1,67 @@
1
+ //
2
+ // gulpfile.js
3
+ // typescript-monkey
4
+ //
5
+ var gulp = require("gulp");
6
+ var ts = require("gulp-typescript");
7
+ var uglify = require("gulp-uglify");
8
+ var rename = require("gulp-rename");
9
+ var concat = require("gulp-concat");
10
+ var ignore = require("gulp-ignore");
11
+ var debug = require('gulp-debug');
12
+ var runSequence = require('run-sequence');
13
+
14
+ var tsProject = ts.createProject("tsconfig.json");
15
+ var jsOutputDir = "lib/assets/javascripts";
16
+
17
+ gulp.task("transpile", function() {
18
+ var tsResult = tsProject.src()
19
+ .pipe(ignore.include(/transpiler.ts$/))
20
+ .pipe(tsProject());
21
+
22
+ return tsResult.js
23
+ .pipe(rename({ basename: "dyrt" }))
24
+ .pipe(gulp.dest(jsOutputDir));
25
+ });
26
+
27
+ gulp.task("transpile-min", function() {
28
+ var tsResult = tsProject.src()
29
+ .pipe(ignore.include(/transpiler.ts$/))
30
+ .pipe(tsProject());
31
+
32
+ return tsResult.js
33
+ .pipe(rename({ basename: "dyrt" }))
34
+ .pipe(gulp.dest(jsOutputDir))
35
+ .pipe(uglify())
36
+ .pipe(rename({ suffix: ".min" }))
37
+ .pipe(gulp.dest(jsOutputDir));
38
+ });
39
+
40
+ gulp.task("transpile-once", function() {
41
+ var tsResult = tsProject.src()
42
+ .pipe(tsProject());
43
+
44
+ return tsResult.js
45
+ .pipe(concat('dyrt_once.js'))
46
+ .pipe(gulp.dest(jsOutputDir));
47
+ });
48
+
49
+ gulp.task("transpile-once-min", function() {
50
+ var tsResult = tsProject.src()
51
+ .pipe(tsProject());
52
+
53
+ return tsResult.js
54
+ .pipe(concat('dyrt_once.js'))
55
+ .pipe(gulp.dest(jsOutputDir))
56
+ .pipe(uglify())
57
+ .pipe(rename({ suffix: ".min" }))
58
+ .pipe(gulp.dest(jsOutputDir));
59
+ });
60
+
61
+ gulp.task("all", function() {
62
+ runSequence("transpile-min", "transpile-once-min");
63
+ });
64
+
65
+ gulp.task("watch", ["transpile"], function() {
66
+ gulp.watch("lib/assets/typescripts/*.ts", ["transpile"]);
67
+ });
@@ -0,0 +1 @@
1
+ <%= Typescript::Monkey::Transpiler.dyrt_ts %>
@@ -0,0 +1,2 @@
1
+ <%= Typescript::Monkey::Package.services_js %>
2
+ <%= Typescript::Monkey::Transpiler.dyrt_once_js %>
@@ -0,0 +1 @@
1
+ <%= Typescript::Monkey::Package.services_js %>
@@ -0,0 +1,13 @@
1
+ //
2
+ // transpile_once.ts
3
+ // typescript-monkey
4
+ //
5
+ // Run the transpiler once against the DOM.
6
+ //
7
+
8
+ /// <reference path="./transpiler.ts" />
9
+
10
+ (() => {
11
+ const transpiler = new TypescriptRails.Transpiler();
12
+ transpiler.transpile();
13
+ })();
@@ -0,0 +1,203 @@
1
+ //
2
+ // transpiler.ts
3
+ // typescript-monkey
4
+ //
5
+
6
+ /// <reference types="typescript/lib/typescriptServices" />
7
+
8
+ namespace TypescriptRails {
9
+ export enum ScriptType {
10
+ Any,
11
+ Javascript,
12
+ Typescript,
13
+ };
14
+
15
+ /**
16
+ * A class to transpile typescript into javascript in the browser, at
17
+ * runtime.
18
+ *
19
+ * @export
20
+ * @class Transpiler
21
+ */
22
+ export class Transpiler {
23
+ public domBody: HTMLElement;
24
+
25
+ constructor() {
26
+ this._loadDOM();
27
+ }
28
+
29
+ /**
30
+ * Set internal DOM properties.
31
+ *
32
+ * @private
33
+ * @returns {void}
34
+ *
35
+ * @memberOf TypescriptRails
36
+ */
37
+ private _loadDOM(): void {
38
+ this.domBody = document.getElementsByTagName("body")[0];
39
+
40
+ return;
41
+ }
42
+
43
+ /**
44
+ * Transpile all typescript scripts in DOM.
45
+ *
46
+ * All previous transpiled scripts will be removed before their source
47
+ * is re-transpiled and re-appended to the DOM.
48
+ *
49
+ * @returns {void}
50
+ *
51
+ * @memberof Transpiler
52
+ */
53
+ public transpile(): void {
54
+ let typescripts: HTMLScriptElement[] = this.domScripts(ScriptType.Typescript);
55
+ let javascripts: HTMLScriptElement[] = [];
56
+
57
+ // remove all transpiled scripts from DOM
58
+ this.purgeTranspiledScripts();
59
+
60
+ // transpile all typescripts
61
+ for (const script of typescripts) {
62
+ javascripts.push(this.transpileScript(script));
63
+ }
64
+
65
+ // append transpiled scripts to DOM
66
+ this.appendScripts(javascripts);
67
+
68
+ return;
69
+ }
70
+
71
+ /**
72
+ * Transpile typescript script to javascript.
73
+ *
74
+ * @param {HTMLScriptElement} script The script object to transpile
75
+ * @returns {HTMLScriptElement} The transpiled script object
76
+ *
77
+ * @memberof Transpiler
78
+ */
79
+ public transpileScript(script: HTMLScriptElement): HTMLScriptElement {
80
+ if (script.type !== "text/typescript") return;
81
+
82
+ const compilerOptions: ts.TranspileOptions = {
83
+ compilerOptions: {
84
+ module: ts.ModuleKind.CommonJS,
85
+ },
86
+ fileName: undefined,
87
+ reportDiagnostics: false,
88
+ moduleName: undefined,
89
+ renamedDependencies: undefined,
90
+ } as ts.TranspileOptions;
91
+
92
+ // transpile script content
93
+ const jsContent = ts.transpileModule(script.text, compilerOptions);
94
+
95
+ // create a new script node, insert transpiled content
96
+ const element = document.createElement("script");
97
+ element.type = "text/javascript";
98
+ element.innerHTML = "// Transpiled TypeScript\n\n" + jsContent.outputText;
99
+
100
+ return element;
101
+ }
102
+
103
+ /**
104
+ * Append scripts to DOM.
105
+ *
106
+ * Script objects will be assigned an id that is a combination of the
107
+ * prefix plus an incremented value.
108
+ *
109
+ * @param {HTMLScriptElement[]} scripts
110
+ * @param {string} [prefix="drty-"] Object id prefix to filter targets
111
+ * @returns {number} Number of scripts appended
112
+ *
113
+ * @memberof Transpiler
114
+ */
115
+ public appendScripts(scripts: HTMLScriptElement[], prefix: string = "drty-"): number {
116
+ let counter: number = 0;
117
+
118
+ for (const script of scripts) {
119
+ script.id = `${prefix + counter}`
120
+ this.domBody.appendChild(script);
121
+ counter++;
122
+ }
123
+
124
+ return counter;
125
+ }
126
+
127
+ /**
128
+ * Remove transpiled scripts from DOM.
129
+ *
130
+ * @param {string} [prefix="drty-"] Object id prefix to filter targets
131
+ * @returns {number} Number of scripts removed
132
+ *
133
+ * @memberof Transpiler
134
+ */
135
+ public purgeTranspiledScripts(prefix: string = "drty-"): number {
136
+ let counter: number = this.purgeScripts(ScriptType.Javascript, prefix);
137
+
138
+ return counter;
139
+ }
140
+
141
+ /**
142
+ * Remove scripts from DOM.
143
+ *
144
+ * @param {ScriptType} type Object script type
145
+ * @param {string} prefix Object id prefix to filter targets
146
+ * @returns {number} Number of scripts removed
147
+ *
148
+ * @memberof Transpiler
149
+ */
150
+ public purgeScripts(type: ScriptType, prefix: string): number {
151
+ let scripts: HTMLScriptElement[] = this.domScripts(type, prefix);
152
+ let counter: number = 0;
153
+
154
+ for (const script of scripts) {
155
+ this.domBody.removeChild(script);
156
+ counter++;
157
+ }
158
+
159
+ return counter;
160
+ }
161
+
162
+ /**
163
+ * Returns an array of script objects from DOM.
164
+ *
165
+ * This method returns all scripts if no idPrefix is provided.
166
+ *
167
+ * @private
168
+ * @param {ScriptType} [type=ScriptType.Typescript] Object script type
169
+ * @param {string} [prefix=""] Object id prefix to filter results
170
+ * @returns {HTMLScriptElement[]} Array of script objects
171
+ *
172
+ * @memberof Transpiler
173
+ */
174
+ private domScripts(type: ScriptType = ScriptType.Typescript, prefix: string = ""): HTMLScriptElement[] {
175
+ let nodes: NodeList = document.getElementsByTagName("script");
176
+ let scripts: HTMLScriptElement[] = [];
177
+
178
+ Array.prototype.forEach.call(nodes, (node, key, listObj, argument) => {
179
+ const script = node as HTMLScriptElement;
180
+
181
+ switch(type) {
182
+ case ScriptType.Javascript:
183
+ if (script.type !== "text/javascript" && script.type.length > 0) return;
184
+ break;
185
+
186
+ case ScriptType.Typescript:
187
+ if (script.type !== "text/typescript") return;
188
+ break;
189
+
190
+ default:
191
+ break;
192
+ }
193
+
194
+ const regex = new RegExp("^" + `${prefix}`);
195
+ if (prefix.length === 0 || (script.id.length > 0 && regex.test(script.id))) {
196
+ scripts.push(script);
197
+ }
198
+ });
199
+
200
+ return scripts;
201
+ }
202
+ } // class Transpiler
203
+ } // namespace TypescriptRails
@@ -0,0 +1,13 @@
1
+ require "rails/generators/named_base"
2
+
3
+ module Typescript
4
+ module Generators
5
+ class AssetsGenerator < ::Rails::Generators::NamedBase
6
+ source_root File.expand_path("../templates", __FILE__)
7
+
8
+ def copy_typescript
9
+ template "javascript.ts", File.join('app/assets/javascripts', class_path, "#{file_name}.ts")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
3
+ // You can use TypeScript in this file: www.typescriptlang.org
@@ -0,0 +1,8 @@
1
+ require 'typescript/monkey'
2
+ require 'typescript/monkey/configuration'
3
+ require 'typescript/monkey/railtie'
4
+ require 'typescript/monkey/engine'
5
+ require 'typescript/monkey/template'
6
+ require 'typescript/monkey/template_handler'
7
+ require 'typescript/monkey/transformer'
8
+ require 'typescript/monkey/transpiler'
@@ -0,0 +1,9 @@
1
+ module Typescript
2
+ module Monkey
3
+ require_relative 'monkey/version'
4
+
5
+ #
6
+ # define the module Typescript::Monkey
7
+ #
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ module Typescript::Monkey
2
+ require 'open3'
3
+
4
+ #
5
+ # The CLI class.
6
+ #
7
+ # A class that implements a basic interface to command line programs.
8
+ #
9
+ class CLI
10
+ # Run a command with arguments
11
+ #
12
+ # @return [String, String, Process::Status] stdout, stderr, and the status
13
+ # of the command results.
14
+ #
15
+ # @see Process::Status
16
+ #
17
+ def self.run_command(command, args=[])
18
+ args_string = args.join(" ")
19
+ _stdout, _stderr, _status = Open3.capture3("\"#{command}\" #{args_string}")
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,177 @@
1
+ require 'typescript/monkey'
2
+ require 'typescript/monkey/package'
3
+
4
+ module Typescript::Monkey::Compiler
5
+ class TypescriptCompileError < RuntimeError; end
6
+
7
+ class << self
8
+ # Replace relative paths specified in /// <reference path="..." /> with absolute paths.
9
+ #
10
+ # @param [String] ts_path Source .ts path
11
+ # @param [String] source. It might be pre-processed by erb.
12
+ # @return [String] replaces source
13
+ #
14
+ def replace_relative_references(ts_path, source)
15
+ ts_dir = File.dirname(File.expand_path(ts_path))
16
+ escaped_dir = ts_dir.gsub(/["\\]/, '\\\\\&') # "\"" => "\\\"", '\\' => '\\\\'
17
+
18
+ # Why don't we just use gsub? Because it display odd behavior with File.join on Ruby 2.0
19
+ # So we go the long way around.
20
+ (source.each_line.map do |l|
21
+ if l.starts_with?('///') && !(m = %r!^///\s*<reference\s+path=(?:"([^"]+)"|'([^']+)')\s*/>\s*!.match(l)).nil?
22
+ matched_path = m.captures.compact[0]
23
+ l = l.sub(matched_path, File.join(escaped_dir, matched_path))
24
+ end
25
+ next l
26
+ end).join
27
+ end
28
+
29
+ # Get all references
30
+ #
31
+ # @param [String] path Source .ts path
32
+ # @param [String] source. It might be pre-processed by erb.
33
+ # @yieldreturn [String] matched ref abs_path
34
+ #
35
+ def get_all_reference_paths(path, source, visited_paths=Set.new, &block)
36
+ visited_paths << path
37
+ source ||= File.read(path)
38
+ source.each_line do |l|
39
+ if l.starts_with?('///') && !(m = %r!^///\s*<reference\s+path=(?:"([^"]+)"|'([^']+)')\s*/>\s*!.match(l)).nil?
40
+ matched_path = m.captures.compact[0]
41
+ abs_matched_path = File.expand_path(matched_path, File.dirname(path))
42
+ unless visited_paths.include? abs_matched_path
43
+ block.call abs_matched_path
44
+ get_all_reference_paths(abs_matched_path, nil, visited_paths, &block)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ # Compile source
51
+ #
52
+ # @param [String] ts_path
53
+ # @param [String] source TypeScript source code
54
+ # @param [Sprockets::Context] sprockets context object
55
+ # @return [String] compiled JavaScript source code
56
+ #
57
+ def compile(ts_path, source, context=nil, *options)
58
+ if context
59
+ get_all_reference_paths(File.expand_path(ts_path), source) do |abs_path|
60
+ context.depend_on abs_path
61
+ end
62
+ end
63
+ begin
64
+ command_path = Typescript::Monkey::Package.compiler_bin()
65
+ if command_path.nil?
66
+ raise RuntimeError, "Failed to find typescript compiler in local or global node environment."
67
+ end
68
+
69
+ log("#{module_name} processing: #{ts_path}")
70
+
71
+ # compile file
72
+ s = replace_relative_references(ts_path, source)
73
+ source_file = Tempfile.new(["typescript-monkey", ".ts"])
74
+ source_file.write(s)
75
+ source_file.close
76
+ args = Typescript::Monkey.configuration.options.map(&:dup)
77
+ # _args = [ "--out /dev/stdout", "--noResolve" ]
78
+ # if self.tsconfig && File.exist?(self.tsconfig)
79
+ # _args.push("--project #{self.tsconfig}")
80
+ # end
81
+ args.push(source_file.path)
82
+ compiled_source, _, status = Typescript::Monkey::CLI.run_command(command_path, args)
83
+
84
+ filtered_output = nil
85
+
86
+ # Parse errors from output: there is no way (currently) to suppress the
87
+ # errors emitted when passing --noResolve argument to tsc.
88
+ #
89
+ # Status values:
90
+ # Success = 0
91
+ # DiagnosticsPresent_OutputsSkipped = 1
92
+ # DiagnosticsPresent_OutputsGenerated = 2
93
+ #
94
+ # See: https://github.com/Microsoft/TypeScript/blob/master/src/compiler/types.ts
95
+ #
96
+ # Ignore the following error codes:
97
+ # TS2304: Cannot find name ...
98
+ # TS2307: Cannot find module ...
99
+ # TS2318: Cannot find global type ...
100
+ # TS2339: Property ... does not exist on type ... **
101
+ # TS2468: Cannot find global value ...
102
+ # TS2503: Cannot find namespace ...
103
+ # TS2662: Cannot find name ... Did you mean the static member ...
104
+ # TS2663: Cannot find name ... Did you mean the instance member ...
105
+ # TS2688: Cannot find type definition file for ...
106
+ # TS2694: Namespace ... has no exported member ... **
107
+ #
108
+ # See: https://github.com/Microsoft/TypeScript/blob/master/src/compiler/diagnosticMessages.json
109
+ #
110
+ unless status.success?
111
+ filtered_output = ""
112
+ ignore_errors = [
113
+ "TS2304",
114
+ "TS2307",
115
+ "TS2318",
116
+ "TS2339",
117
+ "TS2468",
118
+ "TS2503",
119
+ "TS2662",
120
+ "TS2663",
121
+ "TS2688",
122
+ "TS2694"
123
+ ]
124
+ regex = /#{Regexp.escape(File.basename(source_file))}\(([\d]+,[\d]+)\):[\s]+error[\s]+(TS[\d]+):[\s]+(.*)$/
125
+ errors = []
126
+ compiled_source.split("\n").each do |line|
127
+ if (matches = line.match(regex))
128
+ errors << {
129
+ code: matches[2],
130
+ message: matches[3],
131
+ line: line,
132
+ line_position: matches[1]
133
+ }
134
+ next
135
+ end
136
+ filtered_output << line << "\n"
137
+ end
138
+ # iterate over errors and log ignored, raise exception for all others
139
+ errors.each do |error|
140
+ log("#{module_name} parsing error for file: #{ts_path}, #{error[:code]}@(#{error[:line_position]}): #{error[:message]}")
141
+ unless ignore_errors.include?(error[:code])
142
+ raise TypescriptCompileError, "#{error[:code]}@(#{error[:line_position]}): #{error[:message]}"
143
+ end
144
+ end
145
+ end
146
+ filtered_output ||= compiled_source
147
+ rescue StandardError => e
148
+ raise "Typescript error in file '#{ts_path}':\n#{e.message}"
149
+ ensure
150
+ source_file.unlink
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ # Log a message
157
+ #
158
+ # Checks if a logger has been configured before attempting to log.
159
+ #
160
+ # @param [String] message to be logged
161
+ #
162
+ def log(message)
163
+ if Typescript::Monkey.configuration.logger
164
+ Typescript::Monkey.configuration.logger.debug(message)
165
+ end
166
+ end
167
+
168
+ # Returns module name
169
+ #
170
+ # @return [String] module name
171
+ #
172
+ def module_name
173
+ Module.nesting.last
174
+ end
175
+ end
176
+
177
+ end