scalajs-react-components
scalajs-react-components copied to clipboard
Suggestion: Use JSImport annotations instead of requiring user to configure an 'mui' or similar variables
Currently, the raw components for material-ui are contained in the Mui Scala object, which pulls its definition from an mui Javascript object which the library user must configure, e.g., as
var mui = require("material-ui");
mui.Styles = require("material-ui/styles");
mui.SvgIcons = require('material-ui/svg-icons/index');
window.mui = mui;
However, depending on the tool chain used to manage Javascript dependencies, getting such an object into the global namespace may be inconvenient and/or finicky, and it is also not obvious that it needs to be done to begin with.
Scala.js has an @JSImport annotation which is designed for this use case, that of importing definitions from modules. @JSName("mui") can be replaced with @JSImport("material-ui",Namespace) It has the same semantics as the original Mui object, so the facade classes don't need to be changed. It also works out of the box with sbt plugins like scalajs-bundler that manage NPM dependencies. Making such a tool chain work with scalajs-react-components' current method of getting component definitions is something I haven't managed to do yet.
(the above also applies to the other libraries this library provides facades for).
I've made a fork for this (tgiddings/scalajs-react-components ) that makes these changes for the material-ui module (both as an example of what I'm talking about and so I can use your library with scalajs-bundler). The values in Mui that are themselves namespaces (SvgIcons,Styles) needed to be their own @JSImport-ed objects. I'm not sure how to implement Mui.Utils since it isn't used anywhere and I can't see the relationship between its type definition and the utils module in material-ui, + it's not given a definition in the demo projects.
I've been stuck days trying to guess what was wrong when updating to 0.7.0 and for sure this is the answer.
Hope this solution gets merged 👍 I'm using scalajs-bundler and 0.7.0 scalajs-react-components.
Is there any workaround maybe using webpack or declaring these variables from ScalaJS main method?
I've tried to expose material-ui to the global scope using webpack and scalajs-bundler using the following webpack.config.js
var webpack = require('webpack');
// Load the config generated by scalajs-bundler
var config = require('./scalajs.webpack.config');
// Exported modules
var globalModules = {
"material-ui": "mui",
"material-ui/styles" : "Styles",
"material-ui/svg-icons/index" : "SvgIcons"
};
Object.keys(config.entry).forEach(function(key) {
// Prepend each entry with the globally exposed JS dependencies
config.entry[key] = Object.keys(globalModules).concat(config.entry[key]);
});
// Globally expose the JS dependencies
config.module.rules = Object.keys(globalModules).map(function (pkg) {
return {
test: require.resolve(pkg),
use: [{
loader: 'expose-loader',
options: globalModules[pkg]
}]
}
});
module.exports = config;
and this client build.sbt config:
lazy val client: Project = (project in file("client"))
.settings(
name := "client",
version := Settings.version,
scalaVersion := Settings.versions.scala,
scalacOptions ++= Settings.scalacOptions,
useYarn := true,
mainClass in Compile := Some("client.SPAMain"),
libraryDependencies ++= Settings.scalajsDependencies.value,
npmDependencies in Compile := Seq(
"react" -> Settings.versions.reactVersion,
"react-dom" -> Settings.versions.reactVersion,
"react-addons-create-fragment" -> Settings.versions.reactVersion,
"react-addons-css-transition-group" -> Settings.versions.reactVersion,
"react-addons-pure-render-mixin" -> Settings.versions.reactVersion,
"react-addons-transition-group" -> Settings.versions.reactVersion,
"react-addons-update" -> Settings.versions.reactVersion,
"material-ui" -> Settings.versions.MuiVersion,
"react-tap-event-plugin" -> "2.0.1",
"jquery" -> Settings.versions.jQuery,
"bootstrap" -> Settings.versions.bootstrap
),
npmDevDependencies in Compile += "expose-loader" -> "0.7.3",
version in webpack := "2.6.1",
webpackConfigFile := Some(baseDirectory.value/"webpack.config.js"),
scalaJSUseMainModuleInitializer := true,
scalaJSUseMainModuleInitializer.in(Test) := false,
webpackEmitSourceMaps := false,
enableReloadWorkflow := false
)
.enablePlugins(ScalaJSPlugin, ScalaJSBundlerPlugin, ScalaJSWeb)
.dependsOn(sharedJS)
It does not throw errors (and if you modify something critical like the expose-loader in the config.module.rules it explodes, as it should be) but when trying to modify the mui var in global scope using val mui = js.Dynamic.global.mui the variable is undefined. So attempt failed miserably :-1:
Fixed!!!
It works using this config:
webpack.config.js
var webpack = require('webpack');
// Load the config generated by scalajs-bundler
var config = require('./scalajs.webpack.config');
// Exported modules (here, React and ReactDOM)
var globalModules = {
"material-ui": "mui",
"material-ui/styles" : "mui.Styles",
"material-ui/svg-icons/index" : "mui.SvgIcons"
};
Object.keys(config.entry).forEach(function(key) {
// Prepend each entry with the globally exposed JS dependencies
config.entry[key] = Object.keys(globalModules).concat(config.entry[key]);
});
// Globally expose the JS dependencies
config.module.loaders = Object.keys(globalModules).map(function (pkg) {
return {
test: require.resolve(pkg),
loader: "expose-loader?" + globalModules[pkg]
}
});
module.exports = config;
build.sbt
lazy val client: Project = (project in file("client"))
.settings(
name := "client",
version := Settings.version,
scalaVersion := Settings.versions.scala,
scalacOptions ++= Settings.scalacOptions,
useYarn := true,
mainClass in Compile := Some("client.SPAMain"),
libraryDependencies ++= Settings.scalajsDependencies.value,
npmDependencies in Compile := Seq(
"react" -> Settings.versions.reactVersion,
"react-dom" -> Settings.versions.reactVersion,
"react-addons-create-fragment" -> Settings.versions.reactVersion,
"react-addons-css-transition-group" -> Settings.versions.reactVersion,
"react-addons-pure-render-mixin" -> Settings.versions.reactVersion,
"react-addons-transition-group" -> Settings.versions.reactVersion,
"react-addons-update" -> Settings.versions.reactVersion,
"material-ui" -> Settings.versions.MuiVersion,
"react-tap-event-plugin" -> "2.0.1",
"jquery" -> Settings.versions.jQuery,
"bootstrap" -> Settings.versions.bootstrap
),
npmDevDependencies in Compile += "expose-loader" -> "0.7.1",
webpackConfigFile := Some(baseDirectory.value/"webpack.config.js"),
scalaJSUseMainModuleInitializer := true,
scalaJSUseMainModuleInitializer.in(Test) := false,
webpackEmitSourceMaps := false,
enableReloadWorkflow := false
)
.enablePlugins(ScalaJSPlugin, ScalaJSBundlerPlugin, ScalaJSWeb)
.dependsOn(sharedJS)
@beikern When I originally tried to solve this in webpack, I misunderstood how webpack entry points worked. See this StackOverflow question for a shorter way to achieve this in webpack. I'll leave this issue open until a maintainer of this project say yes or no to wanting this change. It is technically a breaking change since it would require users to output a CommonJS module for their project and put it together with webpack, etc (which is precisely what scalajs-bundler does) if they want to use wrappers for material-ui or other javascript libraries, but practically, to use these wrappers, it is already necessary to do that in order to define the mui variable (or the equivalent for the other JS library wrappers) with eg. webpack. Users who are not outputting CommonJS modules can still use components that do not depend on @JSImport-ed classes and will only get a compiler error when trying to use a component that does.
Hey there! Yes, yes, this is the plan! In fact me and @rleibman have been working on it for a while in what will be version 1.0, see #89 . Feel free to give it a spin!