├── project ├── build.properties ├── plugins.sbt └── bintray.sbt ├── version.sbt ├── .jvmopts ├── src ├── sbt-test │ └── sbt-reactjs │ │ ├── reactfail │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ ├── build.sbt │ │ ├── test │ │ └── src │ │ │ └── main │ │ │ └── assets │ │ │ ├── hello.jsx │ │ │ └── js │ │ │ └── more.jsx │ │ └── reacttask │ │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ │ ├── test │ │ ├── src │ │ └── main │ │ │ └── assets │ │ │ ├── hello.jsx │ │ │ └── js │ │ │ └── more.jsx │ │ ├── build.sbt │ │ └── out │ │ └── more.js └── main │ ├── resources │ └── jsx.js │ └── scala │ └── com │ └── github │ └── ddispaltro │ └── reactjs │ └── SbtReactJs.scala ├── .gitignore ├── scripted.sbt ├── .travis.yml ├── LICENSE ├── .github └── workflows │ └── scala.yml └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.9.1 2 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.6.9-SNAPSHOT" 2 | -------------------------------------------------------------------------------- /.jvmopts: -------------------------------------------------------------------------------- 1 | -Dfile.encoding=UTF8 2 | -Xms1g 3 | -Xmx2g 4 | -Xss2m 5 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reactfail/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.9.1 2 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reacttask/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.9.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | .bloop 4 | .bsp 5 | .metals 6 | .vscode 7 | metals.sbt 8 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reactfail/build.sbt: -------------------------------------------------------------------------------- 1 | lazy val root = (project in file(".")).enablePlugins(SbtWeb) -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reactfail/test: -------------------------------------------------------------------------------- 1 | -> assets 2 | $ absent target/web/reactjs/main/js/more.js 3 | $ absent target/web/reactjs/main/hello.js 4 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-web-build-base" % "1.2.3") 2 | 3 | addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.6") -------------------------------------------------------------------------------- /scripted.sbt: -------------------------------------------------------------------------------- 1 | enablePlugins(SbtWebBase) 2 | scriptedLaunchOpts := { 3 | scriptedLaunchOpts.value ++ 4 | Seq("-Xmx1024M", "-Dplugin.version=" + version.value) 5 | } 6 | 7 | scriptedBufferLog := false -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | jdk: openjdk8 2 | language: scala 3 | scala: 4 | - 2.12.11 5 | cache: 6 | directories: 7 | - $HOME/.ivy2/cache 8 | - $HOME/.sbt 9 | 10 | script: 11 | - sbt test scripted 12 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reacttask/test: -------------------------------------------------------------------------------- 1 | > assets 2 | $ exists target/web/reactjs/main/js/more.js 3 | $ exists target/web/reactjs/main/hello.js 4 | $ must-mirror target/web/reactjs/main/js/more.js out/more.js 5 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reactfail/src/main/assets/hello.jsx: -------------------------------------------------------------------------------- 1 | var HelloMessage = React.createClass({ 2 | render: function() { 3 | return
Hello {this.props.name}, mountNode); 8 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reacttask/src/main/assets/hello.jsx: -------------------------------------------------------------------------------- 1 | class HelloMessage extends React.Component { 2 | render() { 3 | return
Hello {this.props.name}
; 4 | } 5 | } 6 | 7 | ReactDOM.render(, mountNode); 8 | -------------------------------------------------------------------------------- /project/bintray.sbt: -------------------------------------------------------------------------------- 1 | /*resolvers += Resolver.url( 2 | "bintray-sbt-plugin-releases", 3 | url("http://dl.bintray.com/content/sbt/sbt-plugin-releases"))( 4 | Resolver.ivyStylePatterns)*/ 5 | 6 | //addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.6") 7 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reactfail/src/main/assets/js/more.jsx: -------------------------------------------------------------------------------- 1 | export var MoreMessage = React.createClass({ 2 | render: function() { 3 | return
Hello {this.props.name}
; 4 | } 5 | }); 6 | 7 | ReactDOM.render(, mountNode); 8 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reacttask/build.sbt: -------------------------------------------------------------------------------- 1 | lazy val root = (project in file(".")).enablePlugins(SbtWeb) 2 | 3 | ReactJsKeys.harmony := true 4 | 5 | ReactJsKeys.stripTypes := true 6 | 7 | ReactJsKeys.sourceMapInline := true 8 | 9 | ReactJsKeys.es6module := true 10 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reacttask/src/main/assets/js/more.jsx: -------------------------------------------------------------------------------- 1 | export var MoreMessage = React.createClass({ 2 | render () { 3 | var names: Array = this.props.names; 4 | return
{names.map((name) => {name})}
; 5 | } 6 | }); 7 | 8 | ReactDOM.render(, mountNode); 9 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reactfail/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | { 2 | val pluginVersion = System.getProperty("plugin.version") 3 | if(pluginVersion == null) 4 | throw new RuntimeException("""|The system property 'plugin.version' is not defined. 5 | |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) 6 | else addSbtPlugin("com.github.ddispaltro" % "sbt-reactjs" % pluginVersion) 7 | } 8 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reacttask/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | { 2 | val pluginVersion = System.getProperty("plugin.version") 3 | if(pluginVersion == null) 4 | throw new RuntimeException("""|The system property 'plugin.version' is not defined. 5 | |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) 6 | else addSbtPlugin("com.github.ddispaltro" % "sbt-reactjs" % pluginVersion) 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the Apache 2 license, quoted below. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with 4 | the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 5 | 6 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 7 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 8 | language governing permissions and limitations under the License. 9 | -------------------------------------------------------------------------------- /.github/workflows/scala.yml: -------------------------------------------------------------------------------- 1 | name: Scala CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up JDK 1.8 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: 1.8 20 | - name: Run tests 21 | run: sbt scripted 22 | - name: Cache 23 | uses: actions/cache@v2.0.0 24 | with: 25 | # A list of files, directories, and wildcard patterns to cache and restore 26 | path: | 27 | ~/.ivy2/cache 28 | ~/.sbt 29 | # An explicit key for restoring and saving the cache 30 | key: sbt-cache 31 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-reactjs/reacttask/out/more.js: -------------------------------------------------------------------------------- 1 | export var MoreMessage = React.createClass({displayName: "MoreMessage", 2 | render:function () { 3 | var names = this.props.names; 4 | return React.createElement("div", null, names.map(function(name) {return React.createElement("span", null, name);})); 5 | } 6 | }); 7 | 8 | ReactDOM.render(React.createElement(MoreMessage, {names: ["John", "Mary"]}), mountNode); 9 | 10 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbbnVsbF0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sSUFBSSxpQ0FBaUMsMkJBQUE7RUFDMUMsTUFBTSxDQUFBLFFBQUEsSUFBSSxDQUFDO0lBQ1QsSUFBSSxLQUFLLGtCQUFrQixJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQztJQUM1QyxPQUFPLG9CQUFBLEtBQUksRUFBQSxJQUFDLEVBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxRQUFBLENBQUMsSUFBSSxDQUFBLEtBQUssQ0FBQSxPQUFBLG9CQUFBLE1BQUssRUFBQSxJQUFDLEVBQUMsSUFBWSxDQUFBLEVBQUEsQ0FBUSxDQUFBLENBQUM7R0FDOUQ7QUFDSCxDQUFDLENBQUMsQ0FBQzs7QUFFSCxRQUFRLENBQUMsTUFBTSxDQUFDLG9CQUFDLFdBQVcsRUFBQSxDQUFBLENBQUMsS0FBQSxFQUFLLENBQUUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFFLENBQUEsQ0FBRyxDQUFBLEVBQUUsU0FBUyxDQUFDLENBQUMiLCJmaWxlIjoidHJhbnNmb3JtZWQuanMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgdmFyIE1vcmVNZXNzYWdlID0gUmVhY3QuY3JlYXRlQ2xhc3Moe1xuICByZW5kZXIgKCkge1xuICAgIHZhciBuYW1lczogQXJyYXk8c3RyaW5nPiA9IHRoaXMucHJvcHMubmFtZXM7XG4gICAgcmV0dXJuIDxkaXY+e25hbWVzLm1hcCgobmFtZSkgPT4gPHNwYW4+e25hbWV9PC9zcGFuPil9PC9kaXY+O1xuICB9XG59KTtcblxuUmVhY3RET00ucmVuZGVyKDxNb3JlTWVzc2FnZSBuYW1lcz17W1wiSm9oblwiLCBcIk1hcnlcIl19IC8+LCBtb3VudE5vZGUpO1xuIl19 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactJS Source Compiler 2 | 3 | ![Scala CI](https://github.com/dispalt/sbt-reactjs/workflows/Scala%20CI/badge.svg) 4 | 5 | - The master series is for React 0.14.x 6 | - The [0.4.x](https://github.com/ddispaltro/sbt-reactjs/tree/0.4.x) series is for React 0.12 7 | - The [0.3.x](https://github.com/ddispaltro/sbt-reactjs/tree/0.3.x) series is for React 0.11 8 | - The [0.2.x](https://github.com/ddispaltro/sbt-reactjs/tree/0.2.x) series is for React 0.10 9 | 10 | This plugin hooks your JSX files in to the Asset compilation phase. 11 | It uses the autoPlugin feature with `0.13.5` to make the setup dead-simple. 12 | 13 | To use this plugin use the addSbtPlugin command within your project's 14 | plugins.sbt (or as a global setting) i.e.: 15 | 16 | ```scala 17 | addSbtPlugin("com.github.ddispaltro" % "sbt-reactjs" % "0.6.8") 18 | ``` 19 | 20 | Your project's build file also needs to enable sbt-web plugins. For example with build.sbt: 21 | 22 | ```scala 23 | lazy val root = (project in file(".")).enablePlugins(SbtWeb) 24 | ``` 25 | 26 | ## Options 27 | 28 | - `harmony`: Turns on JS transformations such as ES6 Classes etc. 29 | - `stripTypes`: Strips out type annotations. 30 | - `sourceMapInline`: Embed inline sourcemap in transformed source 31 | 32 | The artifact is hosted as part of the [community plugins](http://www.scala-sbt.org/0.13.5/docs/Community/Bintray-For-Plugins.html) 33 | via [bintray service](https://bintray.com/ddispaltro/sbt-plugins/sbt-reactjs/view). 34 | 35 | 36 | ## License 37 | 38 | `sbt-reactjs` is licensed under the [Apache License, Version 2.0](./LICENSE) 39 | -------------------------------------------------------------------------------- /src/main/resources/jsx.js: -------------------------------------------------------------------------------- 1 | /*global process, require */ 2 | 3 | (function () { 4 | 5 | "use strict"; 6 | 7 | var args = process.argv, 8 | fs = require("fs"), 9 | mkdirp = require("mkdirp"), 10 | path = require("path"), 11 | jsx = require('react-tools'); 12 | 13 | var SOURCE_FILE_MAPPINGS_ARG = 2; 14 | var TARGET_ARG = 3; 15 | var OPTIONS_ARG = 4; 16 | 17 | var sourceFileMappings = JSON.parse(args[SOURCE_FILE_MAPPINGS_ARG]); 18 | var target = args[TARGET_ARG]; 19 | var options = JSON.parse(args[OPTIONS_ARG]); 20 | 21 | var sourcesToProcess = sourceFileMappings.length; 22 | var results = []; 23 | var problems = []; 24 | 25 | function compileDone() { 26 | if (--sourcesToProcess === 0) { 27 | console.log("\u0010" + JSON.stringify({results: results, problems: problems})); 28 | } 29 | } 30 | 31 | function throwIfErr(e) { 32 | if (e) throw e; 33 | } 34 | 35 | function endsWith(str, suffix) { 36 | return str.indexOf(suffix, str.length - suffix.length) !== -1; 37 | } 38 | 39 | sourceFileMappings.forEach(function (sourceFileMapping) { 40 | 41 | var input = sourceFileMapping[0]; 42 | var outputFile = sourceFileMapping[1].replace(".jsx", ".js"); 43 | var output = path.join(target, outputFile); 44 | 45 | fs.readFile(input, "utf8", function (e, contents) { 46 | throwIfErr(e); 47 | 48 | try { 49 | var compileResult = jsx.transform(contents, options); 50 | 51 | mkdirp(path.dirname(output), function (e) { 52 | throwIfErr(e); 53 | 54 | var js = compileResult.code; 55 | if (js === undefined) { 56 | js = compileResult; 57 | } 58 | 59 | fs.writeFile(output, js, "utf8", function (e) { 60 | throwIfErr(e); 61 | 62 | results.push({ 63 | source: input, 64 | result: { 65 | filesRead: [input], 66 | filesWritten: [output] 67 | } 68 | }); 69 | 70 | compileDone(); 71 | }); 72 | }); 73 | 74 | } catch (err) { 75 | problems.push({ 76 | message: err.message, 77 | severity: "error", 78 | lineNumber: err.lineNumber, 79 | characterOffset: err.column - 1, 80 | lineContent: contents.split("\n")[err.lineNumber - 1], 81 | source: input 82 | }); 83 | results.push({ 84 | source: input, 85 | result: null 86 | }); 87 | 88 | compileDone(); 89 | } 90 | }); 91 | }); 92 | })(); 93 | -------------------------------------------------------------------------------- /src/main/scala/com/github/ddispaltro/reactjs/SbtReactJs.scala: -------------------------------------------------------------------------------- 1 | package com.github.ddispaltro.reactjs 2 | 3 | import sbt._ 4 | import sbt.Keys._ 5 | import com.typesafe.jse._ 6 | 7 | import scala.collection.{JavaConversions, immutable} 8 | import com.typesafe.npm.NpmLoader 9 | import com.typesafe.sbt.web.pipeline.Pipeline 10 | import akka.util.Timeout 11 | 12 | import scala.concurrent.{Await, ExecutionContext, Future} 13 | import com.typesafe.sbt.web.{CompileProblems, SbtWeb, incremental} 14 | 15 | import scala.concurrent.duration._ 16 | import com.typesafe.sbt.jse.{SbtJsEngine, SbtJsTask} 17 | import com.typesafe.jse.Engine.JsExecutionResult 18 | import akka.actor.ActorRef 19 | import akka.pattern.ask 20 | 21 | import scala.util.control.Exception._ 22 | import com.typesafe.sbt.web.incremental.{OpInputHash, OpInputHasher, OpSuccess} 23 | import spray.json._ 24 | import DefaultJsonProtocol._ 25 | 26 | import scala.collection.mutable.ListBuffer 27 | import org.mozilla.javascript.Context 28 | import org.mozilla.javascript.Function 29 | import org.mozilla.javascript.NativeObject 30 | import org.mozilla.javascript.Scriptable 31 | import org.mozilla.javascript.commonjs.module.Require 32 | import org.mozilla.javascript.commonjs.module.RequireBuilder 33 | import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider 34 | import org.mozilla.javascript.commonjs.module.provider.UrlModuleSourceProvider 35 | import com.typesafe.jse.Engine.JsExecutionResult 36 | import com.typesafe.sbt.web.Import.WebKeys 37 | 38 | 39 | object Import { 40 | 41 | 42 | 43 | object ReactJsKeys { 44 | 45 | val reactJs = TaskKey[Seq[File]]("reactjs", "Perform ReactJS JSX compilation on the asset pipeline.") 46 | 47 | val timeout = SettingKey[FiniteDuration]("reactjs-timeout", "How long before timing out JS runtime.") 48 | val harmony = SettingKey[Boolean]("reactjs-harmony", "Support harmony features.") 49 | val es6module = SettingKey[Boolean]("reactjs-es6module", "Support ES6 modules.") 50 | val stripTypes = SettingKey[Boolean]("reactjs-strip-types", "Strips out type annotations.") 51 | val sourceMapInline = SettingKey[Boolean]("reactjs-source-map-inline", "Embed inline sourcemap in transformed source.") 52 | } 53 | 54 | } 55 | 56 | object SbtReactJs extends AutoPlugin { 57 | 58 | override def requires = SbtJsTask 59 | 60 | override def trigger = AllRequirements 61 | 62 | val autoImport = Import 63 | 64 | import SbtWeb.autoImport._ 65 | import WebKeys._ 66 | import SbtJsTask.autoImport.JsTaskKeys._ 67 | import autoImport.ReactJsKeys._ 68 | 69 | 70 | 71 | val reactJsScriptUnscopedSettings = Seq( 72 | 73 | includeFilter := "*.jsx", 74 | 75 | jsOptions := JsObject( 76 | "harmony" -> JsBoolean(harmony.value), 77 | "es6module" → JsBoolean(es6module.value), 78 | "stripTypes" -> JsBoolean(stripTypes.value), 79 | "sourceMap" -> JsBoolean(sourceMapInline.value) 80 | ).toString() 81 | ) 82 | 83 | override def buildSettings = inTask(reactJs)( 84 | SbtJsTask.jsTaskSpecificUnscopedBuildSettings ++ Seq( 85 | moduleName := "reactjs", 86 | shellFile := getClass.getClassLoader.getResource("jsx.js") 87 | ) 88 | ) 89 | 90 | override def projectSettings = Seq( 91 | harmony := false, 92 | es6module := false, 93 | stripTypes := false, 94 | sourceMapInline := false 95 | ) ++ inTask(reactJs)( 96 | SbtJsTask.jsTaskSpecificUnscopedProjectSettings ++ 97 | inConfig(Assets)(reactJsScriptUnscopedSettings) ++ 98 | inConfig(TestAssets)(reactJsScriptUnscopedSettings) ++ 99 | Seq( 100 | Assets / taskMessage := "ReactJS compiling", 101 | TestAssets / taskMessage := "ReactJS test compiling" 102 | ) 103 | ) ++ SbtJsTask.addJsSourceFileTasks(reactJs) ++ Seq( 104 | Assets / reactJs := (Assets / reactJs).dependsOn(Assets / webModules, Assets / nodeModules).value, 105 | TestAssets / reactJs := (TestAssets / reactJs).dependsOn(TestAssets / webModules, TestAssets / nodeModules).value 106 | ) 107 | 108 | 109 | 110 | } 111 | --------------------------------------------------------------------------------