How to config minified React+ReactDOM with Webpack - reactjs

After too many unsuccessful trials my question is: What is the proper way to setup Webpack so that:
Use react.min.js + react-dom.min.js - not the npm installed sources
Don't parse/com them again, just bundle with my own components.
"React" and "ReactDOM" variables can be used from all .jsx files.
The tutorials and guides I found didn't work - or maybe I did some errors. Usually I got error in browser developer tools about missing variable React.
My aim is just to save parsing/bundling time. Now I parse React from scratch every time I bundle my app. And it takes tens of seconds on a slowish computer. In watch mode it is faster, but I find I'm doing unnecessary work.
Any ideas with recent React versions?

Assuming you have a webpack.config.js that looks something like this:
module.exports = {
entry: "./entry.js",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [
...
]
}
};
You just need to specify React and ReactDOM as external dependencies (from the docs):
module.exports = {
entry: "./entry.js",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [
...
]
},
externals: {
// "node/npm module name": "name of exported library variable"
"react": "React",
"react-dom": "ReactDOM"
}
};
The key point about the externals section is that the key is the name of the module you want to reference, and the value is the name of the variable that the library exposes when used in a <script> tag.
In this example, using the following two script tags:
<script src="https://fb.me/react-0.14.6.js"></script>
<script src="https://fb.me/react-dom-0.14.6.js"></script>
results in two top-level variables being created: React and ReactDOM.
With the above externals configuration, anytime in your source code you have a require('react'), it will return the value of the global variable React instead of bundling react with your output.
However, in order to do this the page that includes your bundle must include the referenced libraries (in this case react and react-dom) before including your bundle.
Hope that helps!
*edit*
Okay I see what you're trying to do. The webpack configuration option you want is module.noParse.
This disables parsing by webpack. Therefore you cannot use dependencies. This may be useful for prepackaged libraries.
For example:
{
module: {
noParse: [
/XModule[\\\/]file\.js$/,
path.join(__dirname, "web_modules", "XModule2")
]
}
}
So you'd have your react.min.js, react-dom.min.js, and jquery.min.js files in some folder (say ./prebuilt), and then you'd require them like any other local module:
var react = require('./prebuilt/react.min');
And the entry in webpack.config.js would look something like this (untested):
{
module: {
noParse: [
/prebuilt[\\\/].*\.js$/
]
}
}
The [\\\/] mess is for matching paths on both Windows and OSX/Linux.

Related

Do web pack import lib source into every single module?

I started a FE app with react-create-app. later, I moved the exact same source code to a django project of mine with a custom webpack configuration (so it can be loaded with django's static files).
Long story short, my custom webpack build is almost twice as big as the react-create-app build. 278kb vs 478kb
Most of the modules are importing jquery and bootstrap js. So my guess is that my configuration is importing said libraries into each module.
Most of my modules import look like :
let React = require('react');
import $ from 'jquery/src/jquery';
import 'bootstrap/dist/js/bootstrap';
And my webpack config looks like
const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
entry:[
'./app.jsx'
],
output:{
filename:'../app.bundle.js'
},
module:{
loaders:[
{
test:/\.js[x]?$/,
loader:'babel-loader',
exclude:/(node_modules)/,
query:{
presets:['es2015','react']
}
}
]
},
plugins: [
new UglifyJsPlugin()
]
};
Granted is a very basic webpack config. Hence, my guess is that I'm missing a existing plugin in react-create-app, that avoids to import the same library over and over. I have look for information in the docs to wether Webpack would do this, but can't seem to find any.
I've look into commons chunks, but that seems to solve other problem, and I don't really need or want to have an independent chunks bundle.
After further research I changed my webpack config file to :
plugins: [
new UglifyJsPlugin(),
//enable production build:
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
//expose jquery window.$ global
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
tether: 'tether',
Tether: 'tether',
'window.Tether': 'tether',
})
]
So bootstrap.js is able to find the $ object in window, instead of importing the whole source library into the module as before.
I also changed:
import $ from 'jquery/src/jquery';
to
let $ = require('jquery');
It reduced the filsize from 480kb to 300 kbs, which seems in line with the react-create-app bundle.

How to use react as a component library rather than application

I'm trying to learn react and also introduce it in already existing project. Thus I want to use react in a few places where it makes sense, instead of using it everywhere.
The problem is the entry-point concept that I can't wrap my head around. I'd prefer to have:
ReactDOM.render(
<MyComponent/>,
document.getElementById("componentExample")
);
inside my cshtml near <div id='componentExample'></div> rather than in a separate jsx. I understand that I can't use jsx syntax in cshtml, but if I write
<script src="#Url.Content("~/Scripts/dist/bundle.js")"></script>
<script>
ReactDOM.render(
React.createElement(MyComponent, {}, null), document.getElementById("componentExample")
);
</script>
This doesn't really work because MyComponent is undefined. It is included in my bundle.js (I had to add it as entry point), but it is inside the scope of some webpack stuff and is not exposed.
What is the proper way of doing this?
My goal is to write some components for my app where it is more suitable and use it here and there, without completely going into react-based architecture.
My setup is webpack + babel polyfill + typescript + react and this is in ASP.NET MVC.
My webpack.config.js is:
var config = {
entry: ['babel-polyfill', "./SiteScripts/React/index.tsx", "./SiteScripts/React/Components/MyComponent.tsx"],
output: {
filename: "./Scripts/dist/bundle.js",
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
module: {
rules: [
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{
enforce: 'pre',
test: /\.js$/,
loader: "source-map-loader"
},
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
{
test: /\.tsx?$/, loaders: ['babel-loader', "ts-loader"], exclude: /node_modules/
}
]
},
plugins : []
}
if (process.env.NODE_ENV === "production") {
config.devtool = "cheap-module-source-map";
}
module.exports = config;
Also this is somewhat related to the question (as question about architecture): I'd like to make my bundle.js smaller - it is 30k lines now. It is probably because I'm trying to use react in a wrong way?
I finally found the problem. I had a few mistakes (and didn't completely understand the whole theory behind webpack) and the answer is definetely beyound the code I gave in the question - so I do not have any answers.
First I had to make by bundle a library, in order to export stuff defined in it (webpack.config.js):
entry: ['babel-polyfill', "./SiteScripts/React/index.tsx"],
output: {
filename: "./Scripts/dist/bundle.js",
libraryTarget: "var",
library: "WebPack"
},
This will make types/variables defined in index.tsx (I think only last file from entry list is taken) and that are exported - available via WebPack.<name>
I needed to export my component. I've done it like this - but there are probably better ways (index.tsx):
import { MyComponent} from "./Components/mycomponent";
export var myComponent = MyComponent;
I also needed to ensure all my subcomponents are modules. This means using import everywhere, and adding a keyword export to all element, props and states.
After that I was able to render my component from:
<script src="#Url.Content("~/Scripts/dist/bundle.js")"></script>
<script>
ReactDOM.render(
React.createElement(WebPack.myComponent,
{
PropA = 2, PropB = "3"
}, null),
document.getElementById("componentExample")
);
</script>
One more thing to watch for if I am doing this is that the line <script src="#... does not happen more than once in my result html (this is very likely as I am using many components embedded in a single page). I use this javascript function to ensure I load bundle.js only once:
function loadJsOnce(url, whenFinish) {
if (!window.loadedScripts) {
window.loadedScripts = {};
}
if (!window.loadedScripts[url]) {
var script = document.createElement('script');
//script.async = true; // remove this if you don't want the script to be async
script.src = url;
script.onload = function() {
whenFinish();
}
document.getElementsByTagName('head')[0].appendChild(script);
window.loadedScripts[url] = true;
} else {
whenFinish();
}
}
In url I specify the path to bundle.js and in whenFinish function I put a function that calls ReactDOM.render(... stuff

Using Webpack on an existing angular 1.x application

I have an existing, very large, angular 1.x application which runs today ES5 code.
Almost all of the application runs on the same module. My main module is defined in the file "dashboardApp.js".
I want to start using ES6 with modules per component as the app is component structured. For it to run in develpment, I want to start using Webpack.
I tried adding Webpack so I added all the needed npm dependencies and added the following webpack.config.js
var webpack = require('webpack');
module.exports = {
entry: '../app/dashboardApp.js',
output:{
path: __dirname + '/../dst/dist',
filename: 'my.bundle.js'
},
module:{
rules: [{
test: /\.js$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components)/
}]
}
};
Also, I added to package.json the following property:
"scripts": {
"build": "webpack --config webpack.config.js"
},
and was able to successfully run build and create my.bundle.js. However, when trying to load the app using just the my.bundle.js script, I got an exception:
Uncaught Error: [$injector:modulerr] Failed to instantiate module dashboardApp due to:
Error: [$injector:unpr] Unknown provider: myConsts
myConsts is an angular constant which was included before using Webpack by loading the script and hence my question:
Whats needed in order to transform an existing angular 1.x app that used to load all scripts explicitly to be one Webpack generated script app. What changes I need to do in all my files, that are all defined on the same module, in order to be included in the generated file. I understand that webpack is a module bundler, but I lack the understanding on what I need to do in order to make the old app work with Webpack. Do I need to transform all the files to ES6 module import/export syntax? How does Webpack knows what files to load when the old angular syntax (1 controller/service/constant... per file when all on the same module)? What does it do given the entry point.
Thanks
If your app is using requirejs, then you could achieve it using webpack2. Just configure it properly using rules and aliases. My app too uses requirejs and I successfully managed to replace Grunt with webpack2 after a lot of struggle.
Below is the webpack.config.js file:
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
let basePath = path.join(__dirname, '/');
let config = {
// Entry, file to be bundled
entry: {
'main': basePath + '/src/main.js',
},
devtool: 'source-map',
output: {
// Output directory
path: basePath + '/dist/',
library: '[name]',
// [hash:6] with add a SHA based on file changes if the env is build
filename: env === EnvEnum.BUILD ? '[name]-[hash:6].min.js' : '[name].min.js',
libraryTarget: 'amd',
umdNamedDefine: true
},
module: {
rules: [{
test: /(\.js)$/,
exclude: /(node_modules|bower_components)/,
use: {
// babel-loader to convert ES6 code to ES5 + amdCleaning requirejs code into simple JS code, taking care of modules to load as desired
loader: 'babel-loader',
options: {
presets: ['es2015'],
plugins: []
}
}
}, { test: /jQuery/, loader: 'expose-loader?$' },
{ test: /application/, loader: 'expose-loader?application' },
{ test: /base64/, loader: 'exports-loader?Base64' }
]
},
resolve: {
alias: {
'jQuery': 'bower_components/jquery/dist/jquery.min',
'application': 'main',
'base64': 'vendor/base64'
},
modules: [
// Files path which will be referenced while bundling
'src/**/*.js',
'src/bower_components',
path.resolve('./src')
],
extensions: ['.js'] // File types
},
plugins: [
]
};
module.exports = config;
Let me know if you have any more queries. I still remember how hard I had to try to make things work. WIll be happy to help you!
Putting this here in case anyone else runs into this problem. Essentially what webpack is trying to do is build a dependency graph. Meaning there is an entry point, and then webpack will look at that file and see what it depends on by seeing if there are any imports or require statements in it. It will then travel to the dependency file and bundle that while also looking for more dependencies and so on. In this way, it knows what things need to be loaded before others.
It sounds like you didn't alter your source code to import or require any of the module's dependencies, so Webpack simply built that one file you pointed it to instead of all of the files of your app.
Lets say ModuleA depends on ModuleB and ModuleC.
in ModuleA.js, you'll import (or require) moduleB as well as ModuleC.
In both ModuleB and ModuleC, you'll need to export them and make sure your exporting the .name property from the module since AngularJS wants strings for its dependencies.
The tricky thing about using AngularJS with Webpack, is that Angular has its own Module system which is different from the commonJS pattern or ESModules, so its a bit of an odd combination.
Softvar's solution above works because he told webpack what to bundle when defining his modules under the resolve property. If all of your sub modules are exported, another solution to bundling all of your angular files into one parent module to export, is like this, where the file is index.js and webpack looks here as its entry point:
const modules = [];
function importAll(webpackContext) {
// the webpackContext parameter is a function returned after invoking require.context() that has
// access to all of the resolved paths defined in the require.context call.
// The keys will be an array of all of the resolved module paths returned from the initial
// require.context invocation within the importAll invocation a number of lines below this declaration.
webpackContext.keys()
// this will fetch each module itself and give us access to all of the exports from that module.
// Since we are exporting the angular modules as the default export from all of our index files,
// we are just pushing the default property into the modules array. In this case the default property
// is the string name of the angular module.
.forEach(modulePath => modules.push( webpackContext(modulePath).default) );
}
// recurse through all sub directories in ./src and find the path for each index.js file.
importAll(require.context("./src/", true, /index\.js$/));
// take all of the module's name strings and spread them out as module dependencies.
// export the single module all glued together.
export default angular.module("YOUR_MODULE_NAME", [...modules]).name;

How to import from different bundle created with webpack

I have two project using webpack. Now I want to bring one project as module of other project. I can get the two bundle created but don't know how to import from the other bundle.
Elaborating a bit:-
Lets say the other file from which i want to import looks like as follows:-
index2.js (Bundled as bundleTwo)
import SomeCompoent from "./components/SomeCompoent/SomeCompoent";
module.exports = {SomeCompoent}
and in the file (is in another bundle - bundleOne) below I want to import the component (somecomponent):-
index1.js (in bundleOne)
import {SomeCompoent} from "bundleTwo";
but here bundleTwo is undefiend
Any help is highly appreciated
One way that I have figured out myself, is that using alias this can be achieved.
To make this line import {SomeCompoent} from "bundleTwo"; work, bundleTwo can be defined in alias :-
config:{
resolve: {
alias: {
"bundleTwo": path.join(__dirname, "<path_to_the_bundleTwo>")
}
....
If you want to use webpack only,then just set the libraryTarget to 'umd' in bundletwo webpack configuration.
In order to be able to import this module, you need to export your bundle.
output: {
libraryTarget: 'umd',// make the bundle export
filename: "index.js",
path: path.resolve(__dirname, "dist"),
}
However, this can also be achieved by just using Babel to transpile your ES6 code to ES5 code.
babel index2.js --out-file dist/index2.js
Now set the main in package.json to "dist/index2.js"
Now you can use it like
import {SomeCompoent} from "bundleTwo";
You can also create a gulp script for that
gulp.task('js', function () {
return gulp.src(['packages/**/*.js', "!**/*.test.js"])
.pipe(babel({
plugins: ['transform-runtime']
}))
.pipe(gulp.dest('dist'));
});

import CSS and JS files using Webpack

I have a directory structure like this:
and inside node_modules:
>node_modules
>./bin
>webpack.config.js
>bootstrap
>bootstrap.css
>bootstrap.js
I need to generate separate CSS and JS bundles like this:
custom-styles.css, custom-js.js, style-libs.css, js-libs.js
where style-libs and js-libs should contain syles and js files of all libraries like bootstrap and jQuery. Here's what I have done so far:
webpack.config.js:
const path = require('path');
const basedir = path.join(__dirname, '../../client');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const stylesPath = path.join(__dirname, '../bootstrap/dist/css');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
watch: true,
// Script to bundle using webpack
entry: path.join(basedir, 'src', 'Client.js'),
// Output directory and bundled file
output: {
path: path.join(basedir, 'dist'),
filename: 'app.js'
},
// Configure module loaders (for JS ES6, JSX, etc.)
module: {
// Babel loader for JS(X) files, presets configured in .babelrc
loaders: [
{
test: /\.jsx?$/,
loader: 'babel',
babelrc: false,
query: {
presets: ["es2015", "stage-0", "react"],
cacheDirectory: true // TODO: only on development
}
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader")
},
]
},
// Set plugins (for index.html, optimizations, etc.)
plugins: [
// Generate index.html
new HtmlWebpackPlugin({
template: path.join(basedir, 'src', 'index.html'),
filename: 'index.html'
}),
new ExtractTextPlugin(stylesPath + "/bootstrap.css", {
allChunks: true,
})
]
};
Client.js
import * as p from 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';
ReactDOM.render(<App />, document.getElementById('app'));
I am able to run the app and render all of the components correctly except loading the external JS and CSS file using webpack.
I'm not much experienced with webpack and find it really difficult it wrap my hear around it. There're are a few simple questions:
1- Is this configuration correct? If yes, then how can I include my CSS and JS files in components using ES6. Something like import keyword.
2- Should I even be using webpack for CSS files?
3- How to specify individual directories for input and their respective output files in webpack? Something like all-custom.js should be output for custom1.js and custom2.js?
I know these are some very basic question and I tried Google but didn't find a single tutorial for Webpack that is simple and targets beginners.
After playing out with Webpack in multiple projects, I figured out how Webpack loads stuff. Since the question is still unanswered, I decided to do it myself for anybody with same need.
Directory structure
->assets
->css
->my-style-1.css //custom styling file 1
->my-style-2.css //custom styling file 2
->src
->app
->app.js
->variables.js
->libs.js //require all js libraries here
->styles-custom.js //require all custom css files here
->styles-libs.js //require all style libraries here
->node_modules
->index.html
->package.json
->webpack.config.js
Bundle 1 (main code of app)
app.js: assuming this is main file and app starts from here
var msgs = require('./variables');
//similarly import other js files you need in this bundle
//your application code here...
document.getElementById('heading').innerText = msgs.foo;
document.getElementById('sub-heading').innerText = msgs.bar;
Bundle 2 (js modules)
libs.js: this file will require all modules needed
require('bootstrap');
//similarly import other js libraries you need in this bundle
Bundle 3 (external css files)
styles-libs.js: this file will require all external css files
require('bootstrap/dist/css/bootstrap.css');
//similarly import other css libraries you need in this bundle
Bundle 4 (custom css files)
styles-custom.js: this file will require all custom css files
require('../assets/css/my-style-1.css');
require('../assets/css/my-style-2.css');
//similarly import other css files you need in this bundle
webpack.config.js
const path = require('path');
const webpack = require('webpack');
const extractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: {
'app': './src/app/app.js', //specifying bundle with custom js files
'libs': './src/libs.js', //specifying bundle with js libraries
'styles-custom': './src/styles-custom.js', //specifying bundle with custom css files
'styles-libs': './src/styles-libs.js', //specifying bundle with css libraries
},
module: {
loaders: [
//used for loading css files
{
test: /\.css$/,
loader: extractTextPlugin.extract({ fallbackLoader: 'style-loader', loader: 'css-loader?sourceMap' })
},
//used for loading fonts and images
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file-loader?name=assets/[name].[hash].[ext]'
}
]
},
output: {
path: path.resolve(__dirname, 'dist'), //directory for output files
filename: '[name].js' //using [name] will create a bundle with same file name as source
},
plugins: [
new extractTextPlugin('[name].css'), //is used for generating css file bundles
//use this for adding jquery
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jQuery'
})
]
}
index.html
<head>
<link rel="stylesheet" href="dist/styles-libs.css" />
<link rel="stylesheet" href="dist/styles-custom.css" />
</head>
<body>
<h2 id="heading"></h2>
<h3>
<label id="sub-heading" class="label label-info"></label>
</h3>
<script src="dist/libs.js"></script>
<script src="dist/app.js"></script>
</body>
You can include css & JS files using import in es6 in the source files in your project. example:
import './style.css';
import Style from './path/style.js';
NB. Generally You need to code in es5 in webpack.config.js file. If you want to use es6 just follow the link How can I use ES6 in webpack.config.js?
You can use https://github.com/webpack/css-loader for CSS configuration.
You can use code splitting in webpack and specify multiple entry point but that will generate multiple output files. Have a look at multiple entry point section of that following link.
https://webpack.github.io/docs/code-splitting.html

Resources