babel and reactjs: how to use es2015 style imports - reactjs

I'm new to javascript and javascript build scripts, and I'm trying to build a "future-proof" build script for building a ReactJS/Redux app. I'm having trouble with getting imports to work between javascript files.
My question is what is the recommended approach to add support for es2016-style import statements?
As I've been trying to get this working, these are the questions and comments that are rolling around in my head that help color where I'm coming from.
I've just been getting a little more comfortable with Gulp. Is it possible to use just Gulp, Babel, and npm to add support for es2016-style import statements?
I'm wondering if Gulp still the recommended way to go for building javascript bundles, or should I learn WebPack instead.
In the past, I've used to use Browserify for including other javascript files, but I've heard people mention that you can do what Browserify does with pure npm and that Browserify may be falling out of favor.
I've noticed a lot of ReactJS examples using WebPack. I'm not sure where WebPack fits in or if it's necessary. I'm wondering if WebPack takes the place of Browserify and if I need WebPack or if I can do without it.
I'd prefer to use whatever import syntax is the recommended. I believe that Browserify uses require() and es2015 syntax uses "import ... from". I'm wondering if the "import ... from" is the recommended syntax to use for imports now or if I should be using something else.
I've been trying to use Babel 6 to use es2015-style code. I've noticed that it doesn't pre-process the import statements and I think I read somewhere that Babel 6 removed support for import statements. I'm wondering what to use in place of that to pre-process import statements.
I'd be interested in minimizing the amount of configuration (dot files and such) to build a basic project.
Below is a simple example that I've been trying to get working, using Gulp. Currently, when Gulp runs, is creates a bundle, but the import statement doesn't seem to work. When I try to load index.html, everything looks concated together and I get javascript errors.
more package.json
{
"name": "test_jsx",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"babel-cli": "^6.14.0",
"babel-plugin-transform-react-jsx": "^6.8.0"
},
"devDependencies": {
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.11.1",
"babel-preset-stage-0": "^6.5.0",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-cli": "^1.2.2",
"gulp-concat": "^2.6.0",
"gulp-print": "^2.0.1",
"gulp-sourcemaps": "^1.6.0"
}
}
more gulpfile.js
var gulp = require("gulp");
var print = require('gulp-print');
var sourcemaps = require("gulp-sourcemaps");
var babel = require("gulp-babel");
var concat = require("gulp-concat");
const paths = {
src: 'src/**/*js',
dest: 'build'
}
gulp.task("default", function () {
return gulp.src(paths.src)
.pipe(print())
.pipe(sourcemaps.init())
.pipe(babel({ presets: ['react', 'es2015', ]}))
.pipe(concat("bundle.js"))
.pipe(sourcemaps.write("."))
.pipe(gulp.dest("dist"));
});
more src/test.js
// This import statement is what I'm trying to get working.
import { square } from './lib';
var profile = <div>
<img src="avatar.png" className="profile" />
<h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>;
more src/lib.js
// This is just a example function that I want to try to import
export function square(x) {
return x * x;
}
more index.html
<script src="dist/bundle.js"></script>
TEST
Build steps
npm install
./node_modules/.bin/gulp

You can use either webpack or browserify to build your bundle, but they'll both be leveraging babel to provide ES6 support. I am not sure where you read that Babel 6 removed the import statement - I use Babel 6 and have had no such issue. You can build the bundle with gulp too but I find it's more work and tends to be harder to maintain. But, you might be getting a lot of opinionated answers here.
There is a tool that was provided by Facebook recently to bootstrap a React app without having to configure build tools: Create React App. You might want to either try that or one of the available boilerplate starters on Github, unless you like tinkering with build scripts. It's less flexible but if you are looking to reduce the amount of configuration it does the job.

Related

general questions and setting webpack entry point as a jsx file using babel loader

Context: I'm somewhat new to web development; I started about 2-3 weeks ago and jumped right into the MERN stack to understand the bigger picture of how everything works together. And as I encountered problems or questions, I gradually filled in knowledge gaps. After researching how to get started, I noticed that many tutorials used create-react-app, which I looked into and discovered that many discouraged its use. So I decided to use Webpack and Babel to set up React myself. I know it's recommended for beginners to use create-react-app, but I don't want to use it; I don't want to get into the habit of using create-react-app.
My first question is: how do Next.JS, Gatsby, and Vite set up a React project? The videos I came across recommended using one of those instead of create-react-app. I used Webpack and Babel instead because I had a resource available (Pro MERN Stack: Full Stack Web App Development with Mongo, Express, React, and Node) that used these instead of Next.JS, Gatsby, and Vite. I didn't want to get into the details of how Next.JS, Gatsby, and Vite worked with the MERN stack because it would make me indecisive on which path to follow.
The second (and related) question is: what is the difference between a Webpack/Babel react project setup and a Next.JS, Gatsby, or Vite setup?
Third question related to Webpack and Babel setup,
import { join } from 'path';
import HtmlWebpackPlugin from 'html-webpack-plugin';
module.exports = {
entry: './src/index.jsx',
output: {
path: './dist',
filename: 'app.bundle.js'
},
module: {
rules: [
{
test:/\.js$/,
use:'babel-loader',
exclude:/node_modules/
},
{
test:/\.jsx$/,
use:'babel-loader',
exclude:/node_modules/
}
]
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './src/index.html'
})
]
};
I want my entry point to be a .jsx file instead of a .js file, so I used a babel-loader, but the setup isn't working.
{
"name": "reactsetup",
"version": "1.0.0",
"description": "",
"main": "index.jsx",
"scripts": {
"start": "webpack-dev-server --mode development --open --hot",
"build": "webpack --mode production"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"#babel/core": "^7.20.12",
"#babel/preset-env": "^7.20.2",
"#babel/preset-react": "^7.18.6",
"babel-core": "^6.26.3",
"babel-loader": "^9.1.2",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"html-webpack-plugin": "^5.5.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
},
"browserslist": [
">0.3%",
"defaults",
"supports es6-module",
"maintained node versions"
]
}
My package.json file includes Babel-loader version 9.1.2.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';
ReactDOM.render(<App/>, document.getElementById('app'));
This is what my index.jsx file looks like for now. I'm not sure if this file must be a .jsx file, but I want to be able to set up the entry point as a .jsx file anyway since the book I'm using as a reference uses a .jsx file as its entry point. This is the webpack.config.js file of the book tutorial, (https://github.com/vasansr/pro-mern-stack/blob/master/webpack.config.js). I'm not 100% following the book; I'm just following sections as I need to learn them. Maybe my inconsistency in following the tutorial is why I'm struggling here.
This is what my .bablerc file looks like.
{ "presets": ["#babel/preset-env", "#babel/preset-react"] }
My error:
**Compiled with problems:
ERROR
Module not found: Error: Can't resolve './src/index.js' in
I was expecting the Babel loader to allow Webpack to figure out how to load .jsx files, but that's not how it went.
EDIT: My problem is solved. I followed this article (https://dev.to/alexeagleson/understanding-the-modern-web-stack-webpack-devserver-react-typescript-4b9b) which got rid of my original error. After which another error popped up:
Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
configuration.output.path: The provided value "./dist" is not an absolute path!
-> The output directory as absolute path (required).
To solve this, I changed the output path to:
path: path.resolve(__dirname, 'dist'),
After this, I got another error
Error: For the selected environment is no default script chunk format available:
JSONP Array push can be chosen when 'document' or 'importScripts' is available.
CommonJs exports can be chosen when 'require' or node builtins are available.
Make sure that your 'browserslist' includes only platforms that support these features or select an appropriate 'target' to allow selecting a chunk format by default. Alternatively specify the 'output.chunkFormat' directly.
To solve this, I removed
"maintained node versions"
from my package.json file in the "browserslist" folder.
I'm typing this in case anyone comes across any of these problems.
Please disregard my third question; I'd appreciate it if you could answer my first two. Thank you ahead of time.

Module parse failed? I need help setting up Babel

I'm writing my first JavaScript library and I'm testing it locally with a React project. I'm not seeing any Typescript errors when I'm writing my code but when I run yarn start for the React project that it linked to I'm getting the following error:
ERROR in ../test-lib/src/add.ts 3:29
Module parse failed: Unexpected token (3:29)
File was processed with these loaders:
* ./node_modules/#pmmmwh/react-refresh-webpack-plugin/loader/index.js
* ./node_modules/source-map-loader/dist/cjs.js
You may need an additional loader to handle the result of these loaders.
| __webpack_require__.$Refresh$.runtime = require('/home/aaron/projects/react-projects/test-app/node_modules/react-refresh/runtime.js');
|
> export default function add(a: number, b: number) {
| return a + b
| }
I edited this question to simplify the problem after I did some research - which is it looks like I need to compile the typescript code in my library and export it to a dist folder? Then the React app that depends on it can use it.
I'm seeing articles about Babel and ones about Rollup and I'm confused about which I need (or both?) and why. Lots of outdated and incomplete/lazy tutorial articles that don't seem to help.
Can you help me set up what I need to get this to work? Here is the current file structure of my test library, test-lib:
node_modules/
src/
add.ts
index.js
package.json
tsconfig.ts
yarn.lock
Here are the contents of the pertinent files:
src/add.ts:
export default function add(a: number, b: number) {
return a + b
}
src/index.js:
import add from './add'
export default add
package.json:
{
"name": "test-lib",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
tsconfig.json:
{
"include": ["src/**/*"],
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
I'm aware I'll have to install #babel/... packages and the plugin for #babel/preset-typescript but I've left this out in the example because I wanted a freshly untouched typescript library for simplicity's sake. Help me start from scratch here.
I have additional questions as well:
Can I have all my files in *.ts format or does the entry point need to be a javascript file? (I'm assuming any *.config.* files probably need to be *.js as you need to start with javascript first so you can set up typescript for the rest of the project, right?
I've heard that I should compile my code for commonjs as well as esnext. Does my choice affect the needed libraries or steps to run their React app by the user that depends on my library?
The library I'm writing depends on React but it won't have any jsx/tsx files. It will have some custom React hooks though. Do I need to include the #babel/preset-react plugin as well?
Rollup or Webpack are module bundler and they handle files bundling only.
To transpile Javascript latest code to browser compatible code, we need transpilers like babel. There are plugins available for Rollup and Webpack to handle this transpiling of code, Babel is such a plugin.
So Babel and Rollup are different tools, we can use Babel inside Rollup for transpiling of Javascript.
Let's take Webpack as bundler for your questions
What about typescript ?
We need some plugin to convert it into js, there are plugins like ts-loader for Webpack
What about entry point ?
You can use ts or js, it does not matter as module bundler will assign each file to it's corresponding plugin to handle based on the Webpack configuration during bundling.
Can I have all my files in *.ts format or does the entry point need to be a javascript file?...
Yes, you can have all files in .ts. You may need .tsx if you have jsx code.
I've heard that I should compile my code for commonjs as well as esnext...
Yes, it will affect the user as their browser can be old and will not support latest code.
The library I'm writing depends on React but it won't have any jsx/tsx files...
Do you have jsx code?, if yes then you will need this.
Note: If your full code is in typescript, you do not need babel, you can simply use ts-loader following below link.
You may want to follow this simple Webpack config for typescript - https://webpack.js.org/guides/typescript/

Upgrade to Webpack 5 breaking Storybook 5

In process of upgrading a webpack 4/storybook 5 project to webpack 5 to hopefully take advantage of federated modules. I have regular webpack --config webpack.config.js building working atfer some struggle, but I can't seem to overcome this storybook config issue to get that working. there's not a lot in the storybook webpack.config.js - just some module rules for testing for less files and using the appropriate loaders. It seems the error I'm encountering is typical when upgrading webpack majors, as I've found a number of folks that encountered the same thing going from 3-4, but anything I've tried has failed so far. The specific stacktrace is:
Cannot read property 'tapAsync' of undefined
at ExternalModuleFactoryPlugin.apply (/Users/tbullard/Workspace/unify/node_modules/webpack/lib/ExternalModuleFactoryPlugin.js:29:39)
at compiler.hooks.compile.tap (/Users/tbullard/Workspace/unify/node_modules/webpack/lib/ExternalsPlugin.js:24:63)
at SyncHook.eval [as call] (eval at create (/Users/tbullard/Workspace/unify/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:7:1)
at SyncHook.lazyCompileHook (/Users/tbullard/Workspace/unify/node_modules/tapable/lib/Hook.js:154:20)
at hooks.beforeCompile.callAsync.err (/Users/tbullard/Workspace/unify/node_modules/#storybook/core/node_modules/webpack/lib/Compiler.js:665:23)
at _err0 (eval at create (/Users/tbullard/Workspace/unify/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:11:1)
at compiler.inputFileSystem.readFile (/Users/tbullard/Workspace/unify/node_modules/#storybook/core/node_modules/webpack/lib/DllReferencePlugin.js:72:15)
at process.nextTick (/Users/tbullard/Workspace/unify/node_modules/#storybook/core/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:85:15)
at process._tickCallback (internal/process/next_tick.js:61:11)
Leads me to believe there's a plugin incompatibility 🤷🏻‍♂️ TIA!
As of 6.2.0, Storybook's preview builder is officially Webpack 5 compatible and the manager builder is unofficially Webpack 5 compatible. See this explanation of the builders and Webpack 5 compatibility. Also check out this gist/comments section with more detailed installation instructions.
If I understand correctly, setting the builder to Webpack 5 (as per these instructions) will force the preview builder to use Webpack 5, allowing you to expose your UI components for cool, new Webpack 5 features such as module federation.
However, if you also want to force the manager builder to use Webpack 5 (so that you can just finish breaking up with Webpack 4), you'll need to make use of Yarn selective dependency resolutions. Here is what I did specifically (in a package.json):
"resolutions": {
"webpack": "^5.27.2",
"css-loader": "^5.0.1",
"dotenv-webpack": "^7.0.2",
"html-webpack-plugin": "^5.0.0",
"style-loader": "^2.0.0",
"terser-webpack-plugin": "^5.1.1",
"webpack-dev-middleware": "^4.1.0",
"webpack-virtual-modules": "^0.4.2"
}
With these resolutions, Yarn detects that Webpack 4 is no longer used and removes it. The SB build gives this warning:
info #storybook/react v6.2.0-rc.10
info
info => Loading presets
WARN Unexpected webpack version in manager-builder
WARN - Received: 5.27.2
WARN - Expected: 4.x
One of the tasks of migrating from Webpack 4 to 5 involves manually providing browser polyfills for node packages which were automatically provided by Webpack 4. I want to note that if you find yourself manually providing a ton of polyfills while upgrading Storybook to Webpack 5, you have probably gotten off in the wrong direction. The Storybook dev-server builds get cached in a local (to the package where Storybook is installed) node_modules directory (whatever-package/node_modules/.cache/storybook/dev-server). Deleting the dev-server sub-directory regularly can help you debug your build and potentially spare you from building out long lists of unnecessary node polyfills. (I learned this the hard way).
With that said, for a cleanish install of Storybook you might not actually need any polyfills. On the other hand, some cases do require the node polyfills (e.g. #storybook/addon-docs requires "assert" (see below)). Here is one way to add polyfills (and addons, if you want) to Storybook's Webpack config in main.js:
module.exports = {
core: {
builder: 'webpack5',
},
stories: ['../whatever/src/**/*.stories.#(ts|tsx)'],
addons: [
'#storybook/addon-actions',
'#storybook/addon-controls',
'#storybook/addon-docs',
],
webpackFinal: (config) => {
return {
...config,
resolve: {
...config.resolve,
fallback: {
...config.fallback,
assert: require.resolve('assert-browserify/'),
},
},
};
},
};
Re: addons, I had serious issues with node polyfills when attempting to use addon-essentials. I've been... adding... addons piecemeal instead (standalone packages via npm), and that seems to be working without any polyfills (#storybook/actions and #storybook/controls are good oob; #storybook/docs requires the assert polyfill (above), #storybook/addons is also working fine with theming in manager.ts---that is:
import { addons } from '#storybook/addons';
import { themes } from '#storybook/theming';
addons.setConfig({
theme: themes.dark,
});
I also want to note that adding sass-loader to Webpack config.module.rules works as expected. Some people were running into problems with some scss preset with Storybook and Webpack 5. Here's the relevant portion of a proper Storybook Webpack config for Sass:
module: {
...config.module,
rules: [
...config.module.rules,
{
test: /\.(scss)$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: function () {
return [require('precss'), require('autoprefixer')];
},
},
},
},
{
loader: require.resolve('sass-loader'),
options: {
// using sass (Dart) instead of node-sass because node-sass (Javascript) cannot resolve Yarn 2's Plug'N'Play synthetic node_modules directory
// Evidently, sass is a bit slower to compile than node-sass, but I think I prefer sass anyway for latest features (such as #use)
implementation: require('sass'),
},
},
],
},
],
},
Hope that will get you off the ground 🛫
Storybook is not yet ready to work with Webpack 5 but it is on their roadmap for version 7.0.
More context in this GitHub issue.
Upgrading webpack v4 to v5 in storybook v6.
check the below link for detailed explanation.
https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#webpack-5
"#storybook/builder-webpack5": "^6.5.15",
"#storybook/manager-webpack5": "^6.5.15",
It happened to me as well, in the end I solved it setting the dependency for storybook webpack5 but using webpack4:
"#storybook/addon-actions": "^6.2.9",
"#storybook/addon-controls": "^6.2.9",
"#storybook/addon-storysource": "^6.2.9",
"#storybook/builder-webpack5": "^6.2.9",
"#storybook/vue": "^6.2.9",
As I read here: https://stackoverflow.com/a/67075112/5384339 I think it's better to wait a bit before using webpack5

Jest fails when CSS uses #import

trying to setup jest to work with css decorators.
The css file linked to my react component has:
#import './another.css'
Now, when I run jest I get:
SyntaxError: /Users/thiagofacchini/Documents/atomix/src/library/atoms/Label/styles.css: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (2:1):
Then I went to my .babelrc and added:
"env": {
"test": {
"plugins": [
"#babel/plugin-proposal-decorators", { "legacy": true },
]
}
Running jest again I get
[BABEL] /Users/thiagofacchini/Documents/atomix/src/library/protons/Animator/tests/index.test.js: The decorators plugin requires a 'decoratorsBeforeExport' option, whose value must be a boolean. If you want to use the legacy decorators semantics, you can set the 'legacy: true' option.
(While
processing:/Users/thiagofacchini/Documents/atomix/node_modules/#babel/plugin-proposal-decorators/lib/index.js")
Also tried to change my .babelrc to:
"env": {
"test": {
"plugins": [
"#babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true ,"legacy": true },
]
}
}
But get exactly the same error.
My package.json looks like:
"#babel/core": "^7.3.4",
"#babel/plugin-proposal-decorators": "^7.3.0",
"#babel/preset-env": "^7.3.4",
"#babel/preset-flow": "^7.0.0",
"#babel/preset-react": "^7.0.0",
"#logux/eslint-config": "^27.0.0",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.5",
NOTE: The error is just happening on JEST, my development build works fine.
I googled the hell but I simply cannot understand what's going on. Maybe something with versions? I'd appreciate any help.
When you run your app in development build the build process is taken care of by webpack/parcel/whichever tool you're using.
These tools allow you to (via the use of plugins) do thing like import css into javascript and then eventually spit it back out as css at the appropriate time. This is not a native feature of javascript.
Jest runs on node js which doesn't have all of the features of webpack and cannot parse raw css etc.
So when you had the error "SyntaxError: /Users/thiagofacchini/Documents/atomix/src/library/atoms/Label/styles.css: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (2:1):"
this is actually nodejs trying to parse the css as javascript! You can read more about what it though you were doing here https://www.sitepoint.com/javascript-decorators-what-they-are/
So how do you manage css in your jest environment?
in your jest configuration you set it up so that you don't import the css and instead you import a blank module.
first npm install identity-obj-proxy
then add the following to your jest.config.js
moduleNameMapper: {
"\\.css$": "identity-obj-proxy",
"^lodash-es$": "lodash"
},

How can I `npm link` a typescript dependency with peer dependencies?

I have a React/Redux typescript project A. My team decided to split out some of the React components and Redux code into an NPM module, so I created another React/Redux TS project B.
Initially, when I tried to install B from A, I got errors due to type redeclarations, since both A and B depend on the same type declarations files (react, redux, etc). So I moved all of B's #types dependencies to be peer dependencies. This allows me to properly install B from A.
However, for development purposes, I would like to npm link to B from A, so I don't constantly have to recompile and reinstall B. But because npm link creates a symlink, it points to the entire B project, including the type definitions that I need to avoid.
Does anyone know how to solve this conundrum?
This problem isn't particularly related to typescript, but is a general problem of how to link two javascript packages together and prevent libraries from loading multiple times. A big factor on how you solve this is based on what build/bundler you use. I think this stackoverflow answer is pretty good if you only care about de-duping react.
Here is an example of solving it using webpack.
The first thing is make sure any shared dependencies in your child package are devDependencies and peerDependencies and your parent package is setting them as the needed dependencies and devDependencies.
A - package.json
{
"dependencies": {
"B": "1.0.0",
"react": "x.x.x",
},
"devDependencies": {
"#types/react": "x.x.x"
}
}
B - package.json
{
"version": "1.0.0",
"peerDependencies": {
"#types/react": "x.x.x",
"react": "x.x.x"
},
"devDependencies": {
"#types/react": "x.x.x",
"react": "x.x.x"
}
}
If you are running webpack from package A, you need to make sure to resolve node_modules when applicable only from package A's node_modules.
const path = require('path')
module.exports = {
// rest of your webpack condig...
resolve: {
modules: [path.resolve(__dirname, 'node_modules'), 'node_modules']
}
}
Here is also another solution using react-app-rewired that does the same thing
const path = require('path')
module.exports = function override(config) {
config.resolve = Object.assign({}, config.resolve, {
modules: [path.resolve(__dirname, 'node_modules'), ...config.resolve.modules]
})
return config
}

Resources