Posted on November 2, 2017 by Sidharth Mishra
I actually had fun working with React! But, I was frustrated with Webpack. After much deliberation, I decided to use Webpack to only bundle my JS code as libraries.
The repository for the library I built is found here.
The JS library I built using React and Webpack allows the user to create cards of their GitHub repositories. The usage is as simple as:
githubLike.GitHubCards.render("sidmishraw", "projectsList", () => {
/// WIP, will make a language carousel thingy
jQuery("#languagesIDabbleIn").html(
`<ol class="pinned-repos-list mb-4">` +
githubLike.GitHubCards.getMyLanguages() +
`</ol>`
);
/// WIP, will make a language carousel thingy
stopLoader();
});
My library was exported with the name githubLike
by Webpack. githubLike
exposes 2 objects named GitHubCards
and GitHubLangColors
. GitHubCards
has a render
function that can be used to render the GitHub project cards which I made with React. I also exposed a function of the GitHubCards
called getMyLanguages
. getMyLanguages
fetches the user’s programming languages from their GitHub repositories.
The signature of render
is render(userName, elementId, callback)
. userName
is the name of the user whose GitHub repositories are to be fetched. elementId
is the ID of the DOM element(div) within which React will render the components. callback
is the callback function that gets executed after the component has been rendered to the screen.
To achieve this, I had to modify my webpack.config.js
to look like the following:
/* node.js imports */
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // webpack plugin import
const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin"); // Extracts text and makes into a new file
/* node.js imports */
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin(
{
template: __dirname + '/src/app.html',
filename: './bin/app.html',
inject: 'body'
}
);
module.exports = {
entry: {
// "github-components": path.join(__dirname, "/src/github-components.js"),
// "github-colors": path.join(__dirname, "/src/github-colors.js"),
"app": path.join(__dirname, "/src/app.js")
},
output: {
filename: "./bin/github-like.bundle.js",
libraryTarget: "var", // When your library is loaded, the return value of your entry point will be assigned to a variable
library: "githubLike" // name of the variable the library is exposed as
// In this case it is exposed as "githubLike"
// So, the consumer can just include the "github-like.bundle.js" in script tag
// and then call the function like
// githubLike.GitHubCards.render("sidmishraw", "root", () => console.log("Done!"))
// to render the cards.
},
externals: [ // externalizes the dependencies, forcing the user to have these in their projects
"React", // it is `React` that we are using from react.js, so the dependency is on `React`
"Component", // and so on. So the consumer needs to have these variables in their namespace or
"ReactDOM", // JS file to use this library. In my case, not externalizing in order
//to create a fat jar like bundle
"jQuery"
],
module: {
loaders: [
// css loaders
{
test: /\.css$/,
loaders: ExtractTextWebpackPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ["env", 'react'] // babel recommends to use presets like this now
}
},
{
test: /\.jsx$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ["env", 'react'] // babel recommends to use presets like this now
}
}
]
},
plugins: [
new ExtractTextWebpackPlugin(
{
filename: "./bin/styles.css",
allChunks: true
}
),
HtmlWebpackPluginConfig
]
};
The module.exports
in the webpack.config.js
now has 1 entry point(was trying with multiple but realized that in the end I need one big fat bundle xD). I made sure that my app.js
acts as the exporter(because I was building a library).
My app.js
looked like:
import { GitHubCards } from "./github-components.js";
import { GitHubLangColors } from "./github-colors.js";
import "./app.css";
module.exports.GitHubCards = GitHubCards;
module.exports.GitHubLangColors = GitHubLangColors;
This reminded me of Python’s __init__.py
and how its usage for resolving
from module_name import *
styled imports.
Since Webpack builds a dependency graph starting from the dependencies of the entry
, once it gets my app.js
it looked for github-components.js
, github-colors.js
, and app.css
. My github-components.js
depended on jQuery
, react
, and react-dom
internally and so on.
So, when Webpack built the bundle, it jammed everything into github-like.bundle.js
(analogous to a fat jar xD)
Since I wanted to export my app.js
as a library, I used the libraryTarget: "var"
and library: "githubLike"
fields in the output
field of the Webpack configuration in webpack.config.js
.
The libraryTarget: "var"
and library: "githubLike"
add a var githubLike = (function(){...})..
to the github-like.bundle.js
file so that it can be used as a library.
A snippet of the code from github-like.bundle.js
looks like:
var githubLike =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ ....
Now, the user of my library(the consumer) can access the GitHubCards
and GitHubLangColors
by adding the github-like.bundle.js
via a script
tag and calling them like githubLike.GitHubCards
and githubLike.GitHubLangColors
respectively. For example,
by adding the following script tag to the html document’s <head>
<script src="/js/github-like.bundle.js"></script>
and the following function call in one of the included JS files,
githubLike.GitHubCards.render("sidmishraw", "projectsList", () => {
/// WIP, will make a language carousel thingy
jQuery("#languagesIDabbleIn").html(
`<ol class="pinned-repos-list mb-4">` +
githubLike.GitHubCards.getMyLanguages() +
`</ol>`
);
/// WIP, will make a language carousel thingy
stopLoader();
});
the consumer can use the githubLike.GitHubCards
object to render GitHub repository/project cards.
Note: I externalized the
jQuery
,react
andreact-dom
dependencies because I didn’t want to bundle them together. This is useful in cases where the consumer already has jQuery, react and react-dom scripts added to their projects. It also forces the consumer to use these in their project.
The thing I noticed while mentioning the externals
in the webpack.config.js
was that, they are supposed to be actual objects and functions that were imported rather than the files. In my case, it has to be React
, Component
, ReactDOM
and jQuery
since I used these objects in my JS files.
Note: When I tried to use
jQuery
,react
andreact-dom
(file/module names), Webpack was not happy and the build failed.
To bundle CSS files, I had to use the extract-text-webpack-plugin
, css-loader
and style-loader
. The extract-text-webpack-plugin
extracted the CSS files and ran them through the loaders and bundled them into a file called styles.css
. This was only used for testing, in my final project build(this blog), I already had the CSS files included in the default.html
template hence didn’t require it :)
My package.json
had also changed to accomodate my new requirements and it looked like this:
{
"name": "static-site-gen",
"version": "1.0.0",
"description": "Creating React.js card for displaying my GitHub projects",
"main": "main.js",
"author": "Sidharth Mishra",
"license": "MIT",
"private": false,
"dependencies": {
"jquery": "3.2.1",
"react": "16.0.0",
"react-dom": "16.0.0"
},
"devDependencies": {
"babel-core": "6.26.0",
"babel-loader": "7.1.2",
"babel-preset-env": "1.6.1",
"babel-preset-react": "6.24.1",
"css-loader": "0.28.7",
"extract-text-webpack-plugin": "3.0.2",
"html-webpack-plugin": "2.30.1",
"path": "0.12.7",
"style-loader": "0.19.0",
"webpack": "3.8.1",
"webpack-dev-server": "2.9.3"
},
"scripts": {
"start": "webpack-dev-server",
"build": "webpack"
}
}
I had added build
and start
script functions to the package.json
and this enabled me to call Webpack’s server and bundling through yarn
.
yarn start --watch # for calling the live reloading webpack-dev-server; for testing
yarn build # the actual bundling happens here, bin directory is generated with all bundles
In my blog, the GitHubCards
component is used in the Projects page
To conclude, all in all, React and Webpack were cool to work with. I now have a better understanding of the front-end sea.
Will continue to dive deeper into the front-end sea …
References:
[1] Calhoun, D., “What is amd, commonjs, and umd?”, https://www.davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/
[2] Webpack docs, “Authoring libraries”, https://webpack.js.org/guides/author-libraries/
[3] Webpack text extraction plugin docs, https://github.com/webpack-contrib/extract-text-webpack-plugin
[4] Nader, D., “Beginner’s guide to Webpack”, https://medium.com/javascript-training/beginner-s-guide-to-webpack-b1f1a3638460
[5] React docs, “React.Component and Lifecycle methods”, https://reactjs.org/docs/react-component.html#shouldcomponentupdate
[6] ozh, github-colors, https://github.com/ozh/github-colors/blob/master/colors.json