Automatic script conversion js to jsx in react app - javascript

I'm new to REACT so be patient if I confuse terms.
The following html document starts an react app:
index.html
<html>
<head>
<script data-require="react#*" data-semver="15.5.0" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.0/react.min.js"></script>
<script data-require="react#*" data-semver="15.5.0" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.0/react-dom.min.js"></script>
<script data-require="redux#*" data-semver="3.2.1" src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.2.1/redux.js"></script>
<script data-require="react-redux#*" data-semver="4.4.5" src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.5/react-redux.js"></script>
<title>Hello world</title>
</head>
<body>
<h1>Share a sport activity</h1>
<div id="appMount"></div>
<script src="app.js"></script>
</body>
</html>
In the same folder as this index.html on the server there is an app.jsx file but no app.js file (see the src attribute of the script tag).
app.jsx
class Communicator extends React.Component {
constructor(props) {
super(props);
this.state = {message: this.props.message};
}
resetComm() {
this.setState({message: ''});
}
(...)
Magically, the index.html document sources the app.jsx file anyway. If I look at dev tools what has been delivered for the app.js file the ouput starts with
'use strict';
var _createClass = function () { function definePr...
which is not the content of the actual app.jsx. So, there is some server-side compiling involved.
So, I understand that the app.jsx file needs pre-compiling to js. I think (after some investigation) this server-side tranlation is done by babeljs and done on-the-fly.
The problem
If I run the index.html locally it gives me errors in the console that (of course) app.js is not found.
Question
How can I make my localhost environment (WAMP) behave like the server (it is the online editor platform plunkr.co). I have npm installed. I want it on-the-fly, even if that's slow and not recommended for production.
Everything that I find involves at least the setting of a proper script type
<script type="text/babel" src="app.js"></script>
but I would like to reproduce the exact same behavior of the server. Basically, I want the index.html (which seems to me a proper standalone app as it works on the server) to run on my local server. I think there is some babeljs setup required which I don't seem to understand.
Edit
By the way. The example was in a plunker at http://plnkr.co/
Edit 2
If I add babel.min.js to the header and add a .babelrc as suggested by #Eric it doesn't find the app.js still. If I source app.jsx it finds it but sources it as is. Also, the react app doesn't start.

Without knowing the rest of your project setup, I can't tell how the current app.js file is getting created, but you're right: what you need is to transpile the code via Babel. This can be done two ways in development: on the dev server (recommended), or in the dev browser using standalone Babel.
On the Dev Server (Recommended)
React's recommended way to start with React is using a tool called Create React App. Since you said you're new to React, this will take care of Babel behind the scenes and give you a development server (so you don't need WAMP, only Node and NPM), and a build process to create the assets like app.js to deliver to the production server.
If you don't have time to immediately learn Babel and Webpack, I'd recommend using Create React App first.
In the Dev Browser
If you don't want to introduce a new tool, and you just want to transpile JSX with minimal configuration, you can do this in the browser via the instructions. This used to be demonstrated in the React.js tutorials until they switched to recommending Create React App. The instructions for installing "in the browser" require two things: adding babel.min.js to your <head>, and adding the type="text/babel" attribute to your .jsx file (working code example below). However, the documentation gives this advice:
Compiling in the browser has a fairly limited use case, so if you are
working on a production site you should be precompiling your scripts
server-side. See setup build systems for more information.
Your HTML and JavaScript should look as follows (I've omitted a lot of your code for brevity):
index.html
<!doctype html>
<head>
<script src=src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.0/react-dom.js"></script>
</head>
<body>
<h1>Browser in JSX Demo</h1>
<div id="appRoot">If you're seeing this, it's not working.</div>
<script type="text/babel" src="app.jsx"></script>
</body>
</html>
app.jsx
class App extends React.Component {
render() {
return <div>It works!</div>;
}
}
const el = document.getElementById('appRoot');
ReactDOM.render(<App />, el);
Please note that the extensions used in the script src attribute and the JavaScript file must match. You can use .jsx or .js, but they must match. I don't know how your server is working its magic, but that's beyond the scope of the question, and is not expected behavior by default.

You can use Webpack to achieve what you want.
You can download every dependencies like this (if you have npm installed):
npm install --save-dev webpack
npm install --save-dev webpack-dev-server
npm install --save-dev loader-utils html-webpack-plugin extract-text-webpack-plugin
npm install --save-dev babel-core babel-loader babel-register
npm install --save-dev babel-preset-es2015 babel-preset-react
Then you can create the file webpack.config.js at the root of your folder with this code inside:
import path from 'path';
import HtmlWebpackPlugin from 'html-webpack-plugin';
export default () => ({
entry: [
path.join(__dirname, 'src/index.jsx'),
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './src/index.html'
}),
]
module: {
rules: [
{
test: /.jsx?$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
use: [
{
loader: 'babel',
options: {
babelrc: false,
presets: [
['es2015', { modules: false }],
'react',
],
}
}
]
},
]
},
});
And in your package.json, add those lines:
{
...
"scripts": {
"dev": "webpack-dev-server",
"build": "webpack"
}
...
}
You will now be able to run npm run dev for development and npm run build to compile the code ready for a production server.
You can init npm in your project if it's not already done like this npm init
You can also follow this guide: Setting Up A React Workflow with Babel and Webpack 2
Webpack is basically a tool that takes every .jsx file and convert it to a .js file. In the code above, I use index.jsx as the main file. Every react component that will be imported in this file will be automatically computed.

Related

How to import shared typescript code using create-react-app (no eject)?

I'm trying to achieve TypeScript code sharing in a create-react-app in non-eject mode, but I'm running into the infamous restriction that imports outside of src are not allowed:
You attempted to import ../../common/src/lib.ts which falls outside of the project src/ directory. [...]
For the non-TypeScript case this has been asked & answered here, but I can't get any of the solutions to work with TypeScript. Specifically the issues with the proposed solutions are:
Setting baseDir in ts-config.json: Here create-react-app complains about: Your project's baseUrl can only be set to src or node_modules. Create React App does not support other values at this time.
Approaches based on react-app-rewired: More promising. Disabling the ModuleScopePlugin gets me past the "attempted import outside src" error, but the problem now is that the loader of typescript files doesn't play along:
I have verified that the .ts itself is fine: Copying to ./src and importing it from there works fine.
I have also added ../../common/src folder to the includes list in ts-config.json.
My guess is that somehow the webpack loader configuration has a rule that prevents the TypeScript loader to transpile files that don't match its expected path patterns. How can this be fixed using react-app-rewired?
Symlinking sources doesn't work either -- again with the same problem. Probably because webpack internally resolves the symlinks and sees the file in a path where the normal loader rules don't apply.
Eject based solutions: I'd like not to eject for now, but I tried and I'm basically running into the same problem again.
I've also found other questions that sounded related but didn't answer the problem:
Create React App + Typescript In monorepo code sharing: Sounds basically like the same question, but it is not, because it is asking for the case of an ejected React app.
Sharing code between projects using TypeScript and webpack: Also addresses the code sharing problem, but not create-react-app specific, and I don't know if the solution can be transferred to create-react-app, because it requires manual webpack config control.
Re-using TypeScript typings in a mono repo seems to be a fairly reasonable pattern. Am I missing something or why is the create-react-app making this so difficult?
To reproduce: My code is basically 100% what you get from
npx create-react-app my-app --template typescript
with one import to external added.
You could use craco (create-react-app config override) to override the webpack config (abstracted as part of CRA) without ejecting.
Additionally you could use ts-loader to reference non-transpiled ts code directly in external projects (e.g. if you wanted to reference/use shared code/lib as part of a mono-repo).
Assuming your CRA app is in the client directory your project structure is like the following:
client/
|--src/
|--package.json
shared/
|--package.json
|--(ts files)
package.json
cd client
yarn add -D #craco/craco ts-loader
create a craco.config.js file in the client/ (CRA) directory
ensure the craco.config.js has the following content
const path = require("path");
module.exports = {
webpack: {
configure: webpackConfig => {
// ts-loader is required to reference external typescript projects/files (non-transpiled)
webpackConfig.module.rules.push({
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
transpileOnly: true,
configFile: 'tsconfig.json',
},
})
return webpackConfig;
}
}
};
Replace react-scripts commands in client/package.json with craco
/* client/package.json */
"scripts": {
- "start": "react-scripts start",
+ "start": "craco start",
- "build": "react-scripts build",
+ "build": "craco build"
- "test": "react-scripts test",
+ "test": "craco test"
}
UPDATE: Since react-app-rewired is only maintained passively and doesn't support CRA versions 2+ (we are are three major versions later at the time of writing), I would no longer recommend this approach.
After more hours of experimenting and reading up on GitHub issues, I finally have a working solution. Big thanks to BirukDmitry who made this very helpful post on GitHub. Step-by-step guide:
Install react-app-rewired and customize-cra
npm i react-app-rewired customize-cra --save-dev
Configure react-app-rewird with a minimal config-overrides.js like this:
const { removeModuleScopePlugin, override, babelInclude } = require("customize-cra");
const path = require("path");
module.exports = override(
removeModuleScopePlugin(), // (1)
babelInclude([
path.resolve("src"),
path.resolve("../common/src"), // (2)
])
);
Setting (1) is similar to what is needed for getting around the import-outside-src limitation in general (see linked question).
Setting (2) is crucial though to enabled babel-transpilation (and thus, including TS type stripping I presume) for other paths as well. This is where you have to put add your paths from which you want to import.
No adaptations needed for tsconfig.json.
Import using relative paths, e.g., import * as mymodule from '../../common/src/mymodule'.
#bluenote10 solution did the trick!
There was only one issue in step #2 that prevents Babel from transpilling ts files from ../../common/src
That is modified version of #bluenote10's code (that finally worked for me)
Install react-app-rewired and customize-cra
npm i react-app-rewired customize-cra --save-dev
Configure react-app-rewird with a minimal config-overrides.js like this:
const { removeModuleScopePlugin, override, babelInclude } = require("customize-cra");
const path = require("path");
module.exports = function (config, env) {
return Object.assign( // We need Object.assign() to not to loose initial config
config,
override(
removeModuleScopePlugin(), //1
babelInclude([
path.resolve('src'),
path.resolve('../common/src'), //2
])
)(config, env)
)
}
Setting (1) is similar to what is needed for getting around the import-outside-src limitation in general (see linked question).
Setting (2) is crucial though to enabled babel-transpilation (and thus, including TS type stripping I presume) for other paths as well. This is where you have to put add your paths from which you want to import.
No adaptations needed for tsconfig.json.
Import using relative paths, e.g., import * as mymodule from '../../common/src/mymodule'.
The alias solution for craco or rewired create-react-app is react-app-alias for systems as: craco, react-app-rewired, customize-cra
According docs of mentioned systems replace react-scripts in package.json and configure next:
react-app-rewired
// config-overrides.js
const {aliasWebpack, aliasJest} = require('react-app-alias')
const options = {} // default is empty for most cases
module.exports = aliasWebpack(options)
module.exports.jest = aliasJest(options)
craco
// craco.config.js
const {CracoAliasPlugin} = require('react-app-alias')
module.exports = {
plugins: [
{
plugin: CracoAliasPlugin,
options: {}
}
]
}
all
Configure aliases in json like this:
// tsconfig.paths.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"example/*": ["example/src/*"],
"#library/*": ["library/src/*"]
}
}
}
And add this file in extends section of main typescript config file:
// tsconfig.json
{
"extends": "./tsconfig.paths.json",
// ...
}
question where does ../../common/src/mymodule lives at ?
is it another project ?
If it is another project why dont you link'em
inside common code project run npm link
inside the project that will use the common code: npm link common-project
If there are not two different projects. why does it has to be outside of src ?
The link you posted The create-react-app imports restriction outside of src directory
and your case/problem are two totally different thing, plus they be tripping so hard. let me give you and idea of that problem and yours.
As we already know CRA creates a SPA single page app. which means that there is only one single html file. everything happens at the index.html which is located at <project>/public/index.html. If we compile the code we'll notice something the end bundle might look some like this
build
--static
----js
----css
----media
--index.html
--...more hashed crap...
public
src
and by default process.env.PUBLIC_URL is set to "/"
WHAT?? 🤨 yes look let me show you. there is an old say a picture explains more than 1000 words.
if we look at the image that according to its path it is located at ./src/styles/imageSlack.jpg or some.
So what if we console.log it.
WHAAAT!! where they do that at ? one thing you can do to test my theory is if you console.log(process.env.PUBLIC_URL) any where in yow code. now here is the big diference between this and that.
Browsers natively do not know Typescript.
So either you set yow code inside src and we end happy or
follow the separate of concerns principle and we create an npm package with the shared code and just imported as any other module.

webpack: how to transpile code in node_modules

I want to import nano-memoize in my web application that uses Typescript and Webpack to transpile to ES5.
npm install nano-memoize
add import 'nano-memoize/browser/nano-memoize' to the source file.
These steps do not work in IE 11. The page simply doesn't load.
On inspecting the code for nano-memoize/browser/nano-memoize.js in IE 11 developer console, I noticed it uses arrow function. If I copy nano-memoize/browser/nano-memoize.js directly to my source folder, they get transpiled to ES5 and everything works in IE 11 too.
So why is nano-memoize/browser/nano-memoize.js not getting transpiled?
Build setup:
webpack : ^4.40.2,
#babel/core : ^7.4.0,
awesome-typescript-loader : ^5.2.1,
UPDATE:
This was a webpack configuration issue where the node_modules are excluded from compilation. The skeleton setup is created by a script and my bad I never check this. The following in webpack.config.js for babel-loader can selectively compile the required node_modules. Replace with required module name.
{
test: /\.js$/,
exclude: function(modulePath) {
return /node_modules/.test(modulePath) &&
!/node_modules\/<MY_MODULE>/.test(modulePath);
}
}
Try it by using polyfills in your project , Below is the script tag to import in your index.html file
<script src="https://cdn.polyfill.io/v3/polyfill.min.js"></script>

How can I import React Components as a package/module from some other(non-project) directory?

I have a couple of React Components in a folder, which is not a react project. The Directory Structure i am following for components is:
~/components/Component1/index.jsx
~/components/Component2/index.jsx
Now I have a React project (built with create-react-app), named "myapp" I want to import those React Components as a package or module in my project.
I have tried mentioning a dependency in my package.json, but gives an error, because I can't mention absolute paths in package.json.
I don't want to publish these components as a npm package.
Kindly help me with this
The problem was:
I was trying to wrap material-ui components like iconButton, iconMenu, etc. to make the components easy to use programatically. To put them into a git repository I need a example directory with a seperate react project using the components I developed. So, I need to develop a package that hold components' definitions and exporting them to be used in other project. I want to keep my implementations private so I cannot even publish it to npm.js.
[you can see the question statement for thorough understanding of thr ptoblem.]
Coming to the solution help me doing the needed, I created a new project with yarn adding minimal dependencies. i.e.
babel
babel-cli
babel-core
babel-loader
babel-preset-es2015
babel-preset-react
babel-preset-stage-2
html-webpack-plugin
raw-loader
webpack
webpack-dev-server
and then devDependencies
react
react-dom
material-ui [occasional]
After the installations, [HERE COMES THE PART] I created a webpack.config.js with following script:
const path = require('path')
const webpack = require('webpack')
module.exports = {
devtool: 'cheap-eval-source-map',
entry: './docs/entry.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
resolve: {
alias: {
genericcomponents: path.join(__dirname, 'src')
}
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
}
]
},
devServer: {
contentBase: 'docs/'
}
}
In the code above, after mentioning the output the entry, devtool and output of the transpilation process. I actually have another thing to define, which is alias, I defined the alias with the directory holding the components.
Now this package will hold all the components with the name I provided in the alias parameter.
After that I mentioned loaders for transpilation and file format to jsx, so It will accept the source with JSX Syntax.
And at last I mentioned my the directory where I placed my index.html and entry.js file.
Following is the directory structure of my project:
I have my App.jsx Component in docs folder. which can now import any of the components in the components folder by the giving the package name.
You are welcome to discuss of any of the problem occurred in the above solution.
I know the answer might sound very basic but is there any reason why don't you just copy paste the components into the new app and just use them as any other component ?

Why are we requiring React and ReactDOM as modules when working with Webpack? It takes long to produce output

EDIT: I WAS WRONG. I'M USING GULP, WEBPACK AND BABEL, SEE MY ANSWER.
I've just begun learning React. Basically all tutorials I've seen use Webpack and babel.
Webpack and babel are great but why do most require react and react-dom as modules to be packaged with the webpack bundle file?:
var React = require('react');
var ReactDOM = require('react-dom');
The cdn files that we can load instead of making react go through webpack found right at the bottom of react's installation page https://facebook.github.io/react/docs/installation.html are much smaller than the react modules that webpack outputs (at least for me and I've done recommended optimizations).
The page has these script tags:
Development files (react 29kb, react-dom 137kb):
<script src="https://unpkg.com/react#15/dist/react.js"></script>
<script src="https://unpkg.com/react-dom#15/dist/react-dom.js"></script>
Production files (.min.js) (react 7kb, react-dom 36kb):
<script src="https://unpkg.com/react#15/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom#15/dist/react-dom.min.js"></script>
The production files total 43kb and the development files total 166kb.
Using webpack I've managed to get react and react-dom down to 220kb.
For me it's not so much about the size of the files when they are loaded into the browser, at least not during development.
The cdn files will make React and ReactDOM global variables, which really is fine but I guess it would be nicer to have them be local to a module function like the tutorials, it's not like there's going to be a global identifier collision though. The problem is the fact that when we require the react.js npm module it itself has many requires in it and so on and it gets run through webpack and babel which takes a few seconds just to produce the bundle file each time we make a change during development because react is very big. The fact that we can minimize the output of react with webpack's config doesn't change the fact that webpack will take a while to produce the minimized output. When I make a simple change to my app.js and want the bundle file to be ready as quickly as possible.
If I use the react cdn files and only have my own app code be bundled with webpack a change in my app.js is pretty much bundled instantly as opposed to requiring react which will then take about 4-5 seconds to bundle.
Does anyone have a solution to this other than using the cdn? Could I possibly be using webpack in the wrong way?
Thanks!
Solved it!
As Lucas Katayama pointed out in a comment under my question, webpack dev server will only reprocess the files that have changed. Therefore it won't have to process React and ReactDOM more than once I believe.
I'm not using the webpack dev server. I don't want to. I'm simply using my own node server file and I'm using Gulp to do all the build of various things in my project. The bundling happens though webpack-stream https://www.npmjs.com/package/webpack-stream
What I was doing wrong was that I was watching all my javascript files for any changes and then running the whole webpack process every time (watching file changes using gulp-watch). This is what I had in my gulp file when it was running slow (reprocessing react):
var gulp = require('gulp');
var watch = require('gulp-watch');
var webpack = require('webpack-stream');
var webpackConfig = require('./webpack.config');
var browserSync = require('browser-sync').create();
gulp.task('js', function() {
return watch('./src/js/**/*.js', function () {
return gulp.src('./src/js/app.js')
.pipe(webpack(webpackConfig))
.pipe(gulp.dest('./dist/js/'))
.pipe(browserSync.reload({stream: true}));
});
});
What I had to do was stop watching my js files with gulp watch and I had to put a new option in my webpack.config.js file, the option's name is "watch" it has to be set to true. Now my gulp js task looks like this:
gulp.task('js', function() {
return gulp.src('./src/js/app.js')
.pipe(webpack(webpackConfig))
.pipe(gulp.dest('./dist/js/'))
.pipe(browserSync.reload({stream: true}));
});
My webpack.config.js file looks like this:
module.exports = {
watch: true,
output: {
filename: "bundle.js"
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: "babel-loader",
query: {
presets: ["latest", "react"],
plugins: [
"react-html-attrs",
"transform-class-properties",
"transform-decorators-legacy"
]
}
}
]
}
};
It is MUCH faster now. Thanks for the comment Lucas!

How to run SystemJS/React demo locally with JSX?

I'm following this video tutorial at the moment and I'm stuck with the simple HelloWorld App. At the time position 12m:31s is where I'm stuck it should show HelloWorld but it doesn't.
The App is using SystemJs, React and JSX.
To create the app do these steps in your terminal (node and jspm required):
npm init (enter to all)
jspm init (enter to almost all, except use babel)
jspm install fetch=npm:whatwg-fetch
jspm install react
create an app sub-folder create main.js and copy my code into it
create index.html into root dir.
Then run it with serve
I think the problem is my local server. I'm running it with nodejs http-server and I think the JSX is not transpiled to JS. Also the mentioned serve server is not working.
I'm getting this error message in the browser console:
Potentially unhandled rejection [3] SyntaxError: Error loading "app/main"
[...] Unexpected token <
How do I make it work?
Here is my code, exactly the code from the video (it doesn't run here because no js files added):
//app/main.js
import 'fetch';
import React from 'react';
console.log('Hello world');
class HelloWorld extends React.Component {
render() {
return <p>hello world</p>;
}
}
React.render(<HelloWorld />, document.body);
<!-- index.html -->
<!doctype html>
<html>
<head>
<title>First jspm</title>
<script type="text/javascript" src="jspm_packages/system.js"></script>
<script type="text/javascript" src="config.js"></script>
<script>
System.import('app/main');
</script>
</head>
<body>
</body>
</html>
get similar issue when i work on a es6+react demo on browser, figure out finally
<!doctype html>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.20.12/system.js"></script>
<script>
SystemJS.config({
baseURL:'https://unpkg.com/',
defaultExtension: true,
meta: {
'*.jsx': {
'babelOptions': {
react: true
}
}
},
map: {
'plugin-babel': 'systemjs-plugin-babel#latest/plugin-babel.js',
'systemjs-babel-build': 'systemjs-plugin-babel#latest/systemjs-babel-browser.js',
'react': 'react#15.3.2/dist/react.min.js',
'react-dom': 'react-dom#15.3.2/dist/react-dom.min.js'
},
transpiler: 'plugin-babel'
});
SystemJS.import('./comp-a.jsx').then(function (m) {
console.log(m);
});
</script>
Ok, I've figured it out.
It was mentioned in the video tutorial but I thought it is not needed. Anyway, it is required.
Add blacklist: [] to the configuration of babelconfig inside of config.js!!
From Babel homepage:
JSX support is currently disabled by jspm. To re-enable it, add "blacklist": [] to babelOptions in the jspm configuration file.
With this added Babel will automatically check the imports / exports and transpils the jsx.
i have no idea specifically about this tutorial, but JSX needs to have the JSX transpiler included before your JSX files are loaded. it then looks for any script tags that have type="text/jsx" to compile to regular JavaScript.
It sound's like this tutorial may have complicated this matter.
It seems like the current way to use jsx with jspm (with jspm 0.16.* anyway) is to install:
jspm install npm:jspm-loader-jsx
This is the only way I've managed to get it working. I didn't have to set the blacklist to []. I'm using the .js extension.
I learned about jspm-loader-jsx from this blog post after many hours trying other things and searching the web for help.
In jspm 0.17.* (currently in beta), it seems like there may be a different way, but at least it's actually covered in the jspm docs.

Categories

Resources