typescript-monkey 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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