React: Good Practices For CDNs, Node_Modules and Both - javascript

Been building a React application on top of create-react-app which uses Webpack through react-scripts and has React/Redux/React-dom, etc. as dependencies in our package.json.
To use those JS dependencies, we use import statements, however, for other JS dependencies (i.e. the JS used in Bootstrap and jQuery), we import those through CDN. (We have the Bootstrap CSS files in our CSS directory, and use import statements).
Is there a generally accepted practice to use CDNs vs node_modules or both on the front-end side?
My initial thought was to use CDNs to get static JS files if you're not going to use them as imports in your JS files, but wondered if that was the right way to think about it.
Obviously this isn't a React specific question, just using React to give more context.

Here's what I generally do:
(1) Setup webpack.config.js to assume global React and ReactDOM variables are available, to prevent them from being transpiled, minified, and bundled on every build. You can still use import statements in your files, as before, but the libraries will not be in your final bundle:
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
(2) Use a CDN like http://www.jsdelivr.com/, which allows you to get multiple scripts as a single concatenated string, in a single request. This will minimize loading time.
(3) (Optional) Install React and ReactDOM as deps, and have the build process copy the minified js files from node_modules/react(-dom)/dist to your project's public files directory, and then create simple checks for the variables at the foot of the page, to inject includes pointing to your server, if the CDN calls happen to fail. For example:
if (!window.React) {
$('head').append('<script src="/js/react.min.js">')
}

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.

Using a static script tag in a React component

I'm using the default starting project from create-react-app, with the following folder structure:
app
--public
----index.html
--src
----App.js
----components
-------map.js
I have included an external CDN library in the index.html body tag. However, I'm confused about how to use the methods of this library in my map.js component.
Just writing the library methods in my map.js, returns:
Failed to compile
./src/components/map.js
Line 5: 'L' is not defined no-undef
Is there any way I can include a JS CDN library in my React components without actually importing the library as React Component via npm install?
You're loading a component in the DOM, React has no knowledge of the DOM. So you need to create a wrapper component for the DOM library and use the ref prop to access it. If you're a beginner please use React libraries exclusively, if you need third party libraries, start by reading the React docs https://reactjs.org/docs/integrating-with-other-libraries.html about third party libraries.
Btw, the error you're seeing is most likely a babel error which can't transform your ES6 classes, it can't find the library you require. I think there are options to configure global environment, so it ignores these errors (but that probably means ejecting from cra).
Take a look at this link:
https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#using-the-public-folder
To reference assets in the public folder, you need to use a special
variable called PUBLIC_URL.
Inside index.html, you can use it like this:
Only files
inside the public folder will be accessible by %PUBLIC_URL% prefix. If
you need to use a file from src or node_modules, you’ll have to copy
it there to explicitly specify your intention to make this file a part
of the build.
When you run npm run build, Create React App will substitute
%PUBLIC_URL% with a correct absolute path so your project works even
if you use client-side routing or host it at a non-root URL.
In JavaScript code, you can use process.env.PUBLIC_URL for similar
purposes:
It will show you how to do it the way create-react-app prefers.

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

import requirejs amd module with webpack

I'm using Converse.js and it's prebuilt into the RequireJS/AMD syntax. Including the file from a CDN you could use it like require(['converse'], function (converse) { /* .. */ }). How is it possible to use this with webpack? I would like to bundle converse.js with my webpack output.
I have the file on disk, and want to import it like
import converse from './converse.js';
converse.initialize({ .. });
Webpack picks up the file and bundles it correctly, although it's not useable yet as it throws 'initialize is not a function'. What am I missing?
I suspect the way their bundle is built will not work correctly with how Webpack evaluates modules in a limited context.
From their builds, taking the built AMD module via NPM without dependencies should be parsable by Webpack and it will enable you to provide the dependencies to avoid dupes in the final output.
If all else fails, using the script-loader will evaluate the script in the global context and you'd get the same experience as if you'd have followed their usage guidelines to reference it from a CDN, just don't forget to configure the globals for your linter.

Using node modules with Rollup to build web client

I'm trying to build a react application using rollup instead of browserify and babel. I realize I need to use the rollup-plugin-babel to transpile jsx, but when I tell rollup the format is iife, the final page loads with an error:
Uncaught ReferenceError: React is not defined
What do I need to add to the rollup.config.js to include the node modules I've installed in package.json in my final build?
Two options:
Include React as a separate <script> tag before your app bundle
Include rollup-plugin-node-resolve in your config file, to pull in dependencies from your node_modules folder.
If you take the second route you'll also need rollup-plugin-commonjs (to convert the CommonJS module into an ES module). I think you would also need to add import * as React from 'react' to each module that contained JSX, otherwise you'll continue to get the ReferenceError.
Note: you might be able to use rollup-plugin-buble to transpile JSX. It's similar to the Babel plugin but much faster (though it doesn't transpile every ES2015 feature)

Categories

Resources