Adding react to existing web page (legacy) - imports not working - javascript

I have imported react dev to a webpage (legacy HTML+JQuery) as follows:
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<script type="module" src="dist/Menu.js"></script>
and I'm trying to use the Menu component, which has the following code:
import menuData from "./menuData";
import MenuItem from "./MenuItem";
export const Menu = () => {
return (
<p>Some code..</p>
)
};
let domContainer = document.querySelector('#main_menu');
ReactDOM.render(<Menu />, domContainer);
In my legacy app, before importing I had to transpile the JSX to JS using:
npx babel --watch src --out-dir dist --presets react-app/prod,
And in my legacy app, in addition to the imports (above), I wrote the following element:
<div id="main_menu"></div>
but my legacy app shows an 404 Error for my imports:
404 https://mywebpage.com/dist/menuData
404 https://mywebpage.com/dist/MenuItem
Any idea what I'm missing? How I can start using React components (with imports of another components) in my legacy HTML+JQuery app?

Do the imported paths really exist on your site? Open a browser and go to
https://mywebpage.com/dist/menuData
If they don't resolve to plain .js source code - which it sounds like they don't - they won't work. You probably need to add the file extension, since it sounds like your server isn't doing it:
import menuData from "./menuData.js";
import MenuItem from "./MenuItem.js";
such that going to
https://mywebpage.com/dist/menuData.js
shows you the JS file.
But an even better option you might also consider would be to bundle the code together during a build process, so that only one .js file has to be served to the client. As an app grows larger and larger, if the client has to request 20 or 30 or more modules from your server, that can be a significant performance hit if you aren't using HTTP/2. Consider a module bundler like Webpack. You may be able to turn your HTML into
<script src="dist/bundle.js"></script>
by bundling all of your modules (and React, and any other libraries you happen to be using here) together into a single file.

Related

Natively import ES module dependencies from npm without bundling/transpiling first-party source

Background
I'm trying to create a "buildless" JavaScript app, one where I don't need a watch task running to transpile JSX, re-bundle code, etc every time I save any source file.
It works fine with just first-party code, but I'm stuck when I try to import dependencies from npm.
Goal
I want to achieve this kind of workflow:
npm install foo (assume it's an ES module, not CommonJS)
Edit source/index.js and add import { bar } from 'foo'
npm run build. Something (webpack, rollup, a custom script, whatever) runs, and bundles foo and its dependencies into ./build/vendor.js (without anything from source/).
Edit index.html to add <script src="build/vendor.js" type="module"...
I can reload source/index.js in my browser, and bar will be available. I won't have to run npm run build until the next time I add/remove a dependency.
I've gotten webpack to split dependencies into a separate file, but to import from that file in a buildless context, I'd have to import { bar } from './build/vendor.js. At that point webpack will no longer bundle bar, since it's not a relative import.
I've also tried Snowpack, which is closer to what I want conceptually, but I still couldn't configure it to achieve the above workflow.
I could just write a simple script to copy files from node_modules to build/, but I'd like to use a bundled in order to get tree shaking, etc. It's hard to find something that supports this workflow, though.
I figured out how to do this, using Import Maps and Snowpack.
High-Level Explanation
I used Import Maps to translate bare module specifiers like import { v4 } from 'uuid' into a URL. They're currently just a drafted standard, but are supported in Chrome behind an experimental flag, and have a shim.
With that, you can use bare import statements in your code, so that a bundler understands them and can work correctly, do tree-shaking, etc. When the browser parses the import, though, it'll see it as import { v4 } from 'http://example.org/vendor/uuid.js', and download it like a normal ES module.
Once those are setup, you can use any bundler to install the packages, but it needs to be configured to build individual bundles, instead of combining all packages into one. Snowpack does a really good job at this, because it's designed for an unbundled development workflow. It uses esbuild under the hood, which is 10x faster than Webpack, because it avoids unnecessarily re-building packages that haven't changed. It still does tree-shaking, etc.
Implementation - Minimal Example
index.html
<!doctype html>
<!-- either use "defer" or load this polyfill after the scripts below-->
<script defer src="es-module-shims.js"></script>
<script type="importmap-shim">
{
"imports": {
"uuid": "https://example.org/build/uuid.js"
}
}
</script>
<script type="module-shim">
import { v4 } from "uuid";
console.log(v4);
</script>
snowpack.config.js
module.exports = {
packageOptions: {
source: 'remote',
},
};
packageOptions.source = remote tells Snowpack to handle dependencies itself, rather than expecting npm to do it.
Run npx snowpack add {module slug - e.g., 'uuid'} to register a dependency in the snowpack.deps.json file, and install it in the build folder.
package.json
"scripts": {
"build": "snowpack build"
}
Call this script whenever you add/remove/update dependencies. There's no need for a watch script.
Implementation - Full Example
Check out iandunn/no-build-tools-no-problems/f1bb3052. Here's direct links to the the relevant lines:
snowpack.config.js
snowpack.deps.json
package.json
core.php outputs the shim
plugin.php - outputs the import map
passphrase-generator.js - imports the modules. (They're commented out in this example, for reasons outside the scope of this answer, just uncomment them, run the bundle script, and they'll work).
If you are willing to use an online service, the Skypack CDN seems to work nicely for this. For instance I wanted to use the sample-player NPM module and I've chosen to use a bundle-less workflow for my project using only ES6 modules as I'm targeting embedded Chromium latest version so don't need to worry about legacy browser support, so all I needed to do was:
import SamplePlayer from "https://cdn.skypack.dev/sample-player#^0.5.5";
// init() once the page has finished loading.
window.onload = init;
function init() {
console.log('hello sampler', SamplePlayer)
}
and in my html:
<script src="./src/sampler/sampler.js" type="module"></script>
And of course you could just look inside the JS file the CDN generates at the above url and download the generated all-in-one js file it points to, in order to use it offline as well if needed.

React-loadable warning text content did not match

I use react-loadable for dynamically loading JS modules. I also use server-side rendering, which is set up and works for react-loadable. On the client side, however, there seems to be a problem, because when I load the page a warning appears in the console:
Warning: Text content did not match. Server: "Choose a name and enter passwordNamePasswordLogin" Client: "Loading..."
I use preloadReady on the client side, which should prevent this error.
My index.jsx looks like this:
import { preloadReady } from 'react-loadable';
window.addEventListener('load', async () => {
await preloadReady();
hydrate(
<App />,
document.getElementById('root')
);
});
Package versions:
Node: v8.12.0
webpack: 4.19.1
webpack-cli: 3.1.1
#babel/core: 7.1.0
babel-loader: 8.0.2
react-loadable: 5.5.0
Edit:
So I realized, that the server-side part of react-loadable doesn't actually work, because it can't get the list of bundles to include. The cause of this is that Loadable.Capture doesn't fire the report callback, and so the modules array will be empty.
It turned out that I made a very basic mistake, which was that I tried to use getBundles on the server side after the react app was defined, but before it was rendered. So obviously the report callback wasn't called as it is called at render time. After I changed the code to get the bundles after rendering, it works just fine (although I still have the problem of bundle duplicates, but I can easily filter that).
If András's solution doesn't resolve the issue. Then in your served .html file, ensure that chunk's script is available before vendor and main bundle. So, reorder chunk JS before vendor and main JS.
<script src="chunk.js" defer></script>
<script src="vendor.js" defer></script>
<script src="main.js" defer></script>

Where do external dependencies live in vanilla JS web components?

I'm experimenting with using web components for a project — essentially custom elements powered by attributes, ideally imported by <link rel="import">.
Here's the problem: I can't find conclusive guidance on where to stick any external libraries my component relies on, such as moment.js or even jQuery.
Most component examples I've seen strictly use vanilla JS. When they do use an external library, they often seem to drop them in using Bower or npm and and refer to them explicitly within the component's HTML:
<script type="text/javascript"
src="/bower_components/jquery/dist/jquery.min.js></script>
These days I'm more accustomed to using webpack to bundle dependencies, so this seems a bit odd.
My question: is it considered better form to include each component's library dependencies within the component directory, or have a central node_modules folder at the project level? How does webpack fit into this?
It's better to have a central node_modules folder at the project level. Most people use Webpack to bundle their code with their dependencies. They use require or import their modules for each component.
a.component.js
import React from 'react'
import $ from 'jquery'
b.component.js
import React from 'react'
app.js
import A from 'a.component.js'
import B from 'b.component.js'
Webpack will have one "entry": app.js and compile it output: app.min.js
why?
It's easier to manage (update, delete, add) dependencies with npm.
The browser will load one file instead of multiple external files.
External info:
https://webpack.js.org/concepts/
https://www.quora.com/Why-use-Bower-when-there-is-npm

Require reactjs modules without Browserify, Webpack or Babel

I'm trying to setup TypeScript HTML app in visual studio. I want to use reactjs v0.14.7
I would like to avoid using tools like Browserify.
However, how to use the react-dom module then?
Let's forget about typescript for a while. I need to get pure ES5 up and running first.
currently, I have this:
<script src="Scripts/react/react.js"></script>
<script src="Scripts/react/react-dom.js"></script>
<script>
var Button = React.createClass({
render: function () {
return (React.createElement("div", { className: "btn btn-default" }, 'hello world'));
}
});
ReactDOM.render(React.createElement('Button'), document.getElementById('container'));
</script>
however, browser complains, ReactDOM object does not exists.
I have tried:
<script src="Scripts/require.js"></script>
<script src="Scripts/react/react.js"></script>
<script src="Scripts/react/react-dom.js"></script>
<script>
var React = require('react');
var ReactDOM = require('react-dom');
....
</script>
however, it does not work with require.js: Module name "react" has not been loaded yet for context: _. Use require([])
Can someone bring a little more light into this, please? How to use react without any server side tools like bundling, transpiling etc.
Answers like "use npm" won't be accepted as answer.
RequireJS and require are very different things - more on that later.
If you want to use React without a tool like Browserify or Webpack, then you don't necessarily need a module loader. The hosted versions of React and ReactDOM will expose global variables that you can use out of the box.
<script src="https://fb.me/react-0.14.7.js"></script>
<script src="https://fb.me/react-dom-0.14.7.js"></script>
<script>
console.log(React, ReactDOM);
</script>
Just download these files if you want to work with them locally.
SystemJS
All of that out the way, it sounds like SystemJS and JSPM might be exactly what you're looking for.
First use jspm to install your packages.
jspm install systemjs react react-dom
Then link and configure SystemJS.
<script src='jspm_packages/system.js'></script>
<script>
System.import('app.js');
</script>
Now inside app.js you can write CommonJS style code.
var React = require('react');
var ReactDOM = require('react-dom');
SystemJS will handle the loading of the rest of your scripts. If you want to make a production build, then it's as simple as running jspm bundle app.
CommonJS
The calls to require that you're seeing in the React tutorials and other examples are for a module format called CommonJS.
You use require('react') to get a reference to the value exported by the React module (installed into node_modules with npm). This means you need a pre-browser build step like Browserify or Webpack, that can staple all of the modules you need together and output one big script file.
RequireJS
Confusingly, CommonJS and RequireJS use a function with the same name to specify dependencies. You're trying to use the RequireJS require function as though you were working with CommonJS modules.
If you want to import React with RequireJS instead, then you need to something like this:
<script src="js/require.js"></script>
<script>
require.config({
'baseUrl' : 'Scripts/',
});
require(["react-0.14.7", "react-dom-0.14.7"],
function(React, ReactDOM) {
console.log(React, ReactDOM);
});
</script>
When your code executes, RequireJS will go off and add script tags for the modules you've specified. Then once these scripts have loaded, it will pass the values that they export into the callback function for you to use.
Take a look at this project
https://github.com/ORESoftware/hr4r2
it is doing what you want to do - it is using RequireJS + React + TypeScript. And it is doing serverside rendering and is a SPA.
This project is not using Webpack or Babel.
Here is an example by Dan Abramov himself, the creator of Redux in which he makes a react app without using webpack, babel or browserify.
http://codepen.io/gaearon/pen/ZpvBNJ?editors=0010
In your HTML file inside your body tag write
<div id="root">
<!-- This div's content will be managed by React. -->
</div>
And In your script tag type this
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
PS Make Sure You Include These 2 Libraries At Top
https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.js
https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.js
Hope it helps.

Moving react into seperate component files without Node

I'm not using Node, how do move my react code (jsx) which is currently in a text/babel script tag into separate component files? I have tried moving them and then referencing them in the html file but as soon as it hits the first bit of HTML it throws a syntax error, I have tried both .jsx and .js extensions of the file and when I include it I give it the script type of text/babel.
<script src="components/nav.jsx" type="text/babel" ></script>
<script src="components/map.jsx" type="text/babel" ></script>
<script src="components/app.jsx" type="text/babel" ></script>
If you are transforming your JSX in the browser, you would have to attach each component to the window object.
As an example:
var Nav = React.createClass({ ... });
window.Nav = Nav;
Or more tersely:
window.Nav = React.createClass({ ... });
Jim Nielsen is right, however it is considered to be a bad practice to expose parts of you code to a global scope and to transpile JSX in the browser. Instead you should consider to use some building system like Webpack.
This way you would be able to use es2015 import syntax to import components from one file to another, bundle everything in one file and much more additional benefits like code minification, sourcemaps, livereload etc.
Setting up React for ES6 with Webpack and Babel
Using React with Webpack Tutorial
Related issue

Categories

Resources