Proper way to package a react component library? - javascript

Right now, I'm working on a React component library, which I want to deliver via npm to as many people as possible. I use webpack and babel for packaging and processing of my code. However, being fairly new to webpack, I don't know what the best way would be to go about packaging my library.
I'm planning to have a list of files in the src folder that will be individual components. How do I go about packaging them for people to grab from npm? My guess is to output them individually, so that people can import whatever they need. However, I want them to work with ES5 (which I think is what babel does with the es2015 preset which I have installed). My files are as follows:
webpack.config.js (a couple of things were removed for brevity)
var webpack = require('webpack');
module.exports = {
entry: {
Component1: __dirname + '/src/Component1.js',
Component2: __dirname + '/src/Component2.js'
},
output: {
path: __dirname + '/dist',
filename: '[name].js'
},
module: {
loaders: [{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['react', 'es2015']
}
}]
}
};
Component1.js (sample component, written to showcase an example)
import React from 'react';
export default class Component1 extends React.Component {
render() {
return React.createElement('p',{className : 'Component1'}, 'This is a test component.');
}
}
After running through webpack, I get a huge file with lots of overhead code added by it, but, from what I can tell, the code is compiled to ES5, which is my intention. Is this the proper way to do this? Can I avoid the overhead added by webpack?
I tried googling for answers, but the articles I found (this and this mainly) were a bit outdated and/or required me to use some plugin for webpack, which I'm not very comfortable with yet. I'd like to understand what I should be doing and why. Thanks in advance!

This is a great question and something that I agree should be covered a lot more. For your specific problem at hand:
react-npm-boilerplate on githhub
This article covers the idea of the github site in detail

You can do as modules with vendors.
var webpack = require('webpack');
module.exports = {
entry: {
Component1: __dirname + '/src/Component1.js',
Component2: __dirname + '/src/Component2.js',
vendor: ['react'],
},
output: {
path: __dirname + '/dist',
filename: '[name].js'
},
module: {
loaders: [{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['react', 'es2015']
}
}]
}
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: Infinity
})
]
};
You get a file vendor.js where will be react
more detail here https://webpack.github.io/docs/code-splitting.html

Related

webpack live hot reload for sass

I am building a workflow for a react starter and would like to have my browser auto reload when I make a change to my scss files.
Currently, webpack will hot reload when I make a change in my index.js file (set as my entry point). However when I change/add scss code in my scss file, it gets compiled, but the css doesn't get output anywhere and does not trigger a browser reload.
I am new to webpack would really appreciate some insight here.
Here is my webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: ['./src/js/index.js', './src/scss/style.scss'],
output: {
path: path.join(__dirname, 'dist'),
filename: 'js/index_bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.scss$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].css',
outputPath: 'css/'
}
},
{
loader: 'extract-loader'
},
{
loader: 'css-loader'
},
{
loader: 'postcss-loader'
},
{
loader: 'sass-loader'
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
}
My index.js entry point file
import React from 'react';
import ReactDOM from 'react-dom';
import App from '../components/App';
ReactDOM.render(
<App/>,
document.getElementById('App')
);
And my App component
import React, {Component} from 'react';
import '../../dist/css/style.css';
class App extends Component {
render() {
return (
<div>
<p>Test</p>
</div>
)
}
}
export default App;
Actually, style-loader is the one that is responsible for CSS HMR.
You should add it at the end of the style pipeline, only for dev.
For production, you can remain your config.
It should look something like that:
const devMode = process.env.NODE_ENV !== 'production'
{
test: /\.scss$/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader'
},
{
loader: 'postcss-loader'
},
{
loader: 'sass-loader'
}
]
}
Pay attention, the best practice of extracting css into a separate file is to use MiniCssExtractPlugin if you are using webpack 4, or ExtractTextWebpackPlugin, if you are using webpack < 4.
Try using Parcel instead of Webpack. I used to spend hours configuring Webpack to get things like hot reload working. With Parcel, most things just "work" without a configuration file. For example, I wanted to start using Pug templates. Parcel recognized the .pug extension and automatically downloaded the required NPM dependencies!
In your case, just include the SCSS file in your app like this: import '../scss/style.scss' (notice the path is to the .scss source file relative to index.js). Parcel will automatically do the "sensible" thing.
Here are some references to get started with Parcel + React + SASS:
Build a React web app with Parcel.js lightning fast
Parcel SCSS documentation
Notable advantages and disadvantages of Parcel vs WebPack:
Parcel requires minimal configuration; often no configuration.
Parcel usually builds much faster than WebPack.
The WebPack dev server seems more stable. (The Parcel dev server needs to restarted once in a while and doesn't play nice with Dropbox. Apparently this should be fixed in version 2.0.)
When (an uncommon) configuration is required, it might not be obvious how to do that in Parcel; at least in WebPack, all the configuration is in one place.
Sometimes Parcel's automatic configuration does thing people don't expect, confusing them.

creating javascript library with webpack

I don't understand why this is being so complicated I want my project to have 2 separate work spaces where one is a library that will be distributed and the other will be used for testing... this is how i have the file structure
project
--engine
---math
----vec2.js
---dist
----library.js
---main.js
--sandbox
---main.js
I want to build the "engine" project with webpack and es6 modules so I get a "library" file that can be used in "sandbox".
The "engine" main file would look something like this
import vec2 from './math/vec2';
export default class Library {
constructor() {
this.vec2 = vec2;
}
}
An then the sandbox main file would look something like this
import lib from '../engine/dist/library';
const game = new lib();
The problem is when I build the "library.js" file with webpack and import it in the "sandbox" main file I can't call any of the classes therein. I get this error.
Uncaught TypeError: o.default is not a constructor
at Object.<anonymous> (library.js:1)
at e (library.js:1)
at library.js:1
at library.js:1
My webpack.config.js file looks like this
var webpack = require('webpack');
module.exports = {
context: __dirname,
entry: __dirname+"/main.js",
output: {
path: __dirname+"/dist",
filename: "library.js"
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /(node_modules)/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
};
I must be missing some configuration webpack needs or some plugin that will make this work. I simply want to build the library with webpack using es6 modules so it can be used in another project but I have no idea how to configure it. I'm using babel for transpilling es6 to es5
You need to configure output.libraryTarget. In this case the target commonjs-module is appropriate. So your output would be:
output: {
path: __dirname+"/dist",
filename: "library.js",
libraryTarget: "commonjs-module"
},
The different targets are described in the docs. And you might also want to read Guides - Authoring Libraries.

Compiling Sass with Webpack (and local scope class names)

I've spent hours attempting to get my Webpack config to compile Sass; it's kinda ridiculous. During my research I found dozens of Github issues, Stackoverflow posts, and blogs talking about how to use Sass with Webpack, and they all do it differently. Also, there are so many people with problems. I just think Webpack needs to be better documented. Ugh.
I figured out how to compile Sass and have Webpack serve it in memory from /static, but I want the class names to be locally scoped. Isn't that one of the benefits of modular CSS with React components?
Example of locally scoped: .foo__container___uZbLx {...}
So, this is my Webpack config file:
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
devtool: 'source-map',
entry: {
bundle: './src/js/app'
},
output: {
path: __dirname,
filename: '[name].js',
publicPath: '/static'
},
plugins: [
new webpack.optimize.OccurrenceOrderPlugin(),
new ExtractTextPlugin('[name].css', {allChunks: true})
],
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
loader: 'babel'
},
{
test: /\.scss$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
loader: ExtractTextPlugin.extract('style', 'css?sourceMap!sass')
}
]
}
};
I managed to get it to work for vanilla CSS:
{
test: /\.css$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]')
}
I don't really understand the parameter-like syntax with all the ? marks, and I don't know what to search for to find documentation pertaining to that.
This is what my React component looks like; just incase you want to see how I am importing the style:
import React, { Component } from 'react';
import s from './foo.css';
class Foo extends Component {
render() {
return (
<div className={s.container}>
<h1 className="title">Welcome!</h1>
<p className="body">This is a dummy component for demonstration purposes.</p>
</div>
);
}
}
export default Foo;
Also, I have three unrelated questions:
What's the point of output.path property if Webpack merely serves the file from memory by means of /static?
What's the point of webpack-dev-server if what I am doing here is adequate? From my understanding, webpack-dev-server is just for hot module replacement stuff, right? Just automatic refreshing?
Are my exclude and include properties redundant? From my understanding, excluding node_modules decreases the compilation time making it work quicker; less files to process.
I got it to work with this:
loader: ExtractTextPlugin.extract('style', 'css?modules&localIdentName=[name]__[local]___[hash:base64:5]!sass')
All I had to do was put !sass at the end of the query. I wish this stuff was better documented; can't find adequate docs anywhere...

Making a library importable using webpack and babel

I am trying to publish a package on npm (this one) that I am developing using webpack and babel. My code is written in ES6. I have a file in my sources, index.js, that (for the moment) exports one of my library's core components, it simply goes like this:
import TheGamesDb from './scrapers/thegamesdb';
export { TheGamesDb };
I am using webpack and babel to create a dist index.js that is my package's main file. My webpack.config.js goes like this:
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
module.exports = {
entry: {
index: ['babel-polyfill', './src/index.js'],
development: ['babel-polyfill', './src/development.js']
},
output: {
path: '.',
filename: '[name].js',
library: 'rom-scraper',
libraryTarget: 'umd',
umdNamedDefine: true
},
devtool: 'source-map',
module: {
loaders: [
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }
]
},
target: 'node',
externals: [nodeExternals()]
};
Now when I load my package in another project and try to import my export TheGamesDb simply like this
import { TheGamesDb } from 'rom-scraper';
I get the error
Uncaught TypeError: Path must be a string. Received undefined
It is to be noted that I am importing my library in electron.
Update: Electron seems to be the main problem here and it is not even my library but a dependency that throws this error (only in Electron)
The problem wasn't any of the things in my question but node-expat not working in electron. I switched to an alternative library and it's all right now.

How do you extract css imports from submodules with webpack?

I'm trying to create a React application with multiple entries using webpack and extract-text-webpack-plugin.
My config file looks like this,
const commonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const extractTextPlugin = require('extract-text-webpack-plugin');
let config = {
entry: {
app: './client/app.entry.js',
signIn: './client/sign-in.entry.js',
},
output: {
path: './server/public',
filename: '[name].js'
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['react', 'es2015']
}
},
{
test: /\.css$/,
loader: extractTextPlugin.extract('style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]')
}
]
},
resolve: {
modulesDirectories: ['node_modules', 'client'],
extensions: ['', '.js']
},
plugins: [
new commonsChunkPlugin('common', 'common.js'),
new extractTextPlugin('styles.css', { allChunks: true })
]
};
module.exports = config;
My problem is that extract-text-webpack-plugin only includes imported css files from the entry chunks, and not from submodules of the entry chunks.
So if app.entry.js has
import "./app-style.css";
import "./sub-module"; // This module has import "./sub-style.css";
then the styles from app-style.css gets bundled but not the styles from sub-style.css.
I haven't had this issue before when there's only been one entry file, so I'm wondering if having multiple entries requires another setup?
Something to also take into consideration is the use of CSSModules by the way the css-loader is used, which also could be a factor.
Any ideas?
I'm trying to solve similar problem, and i think it will be nice idea to document the solution and thoughts for those who has the same questions.
TextExtract plugin can work with chunks that have to be configured with commonchunks plugin, enable chunks support:
// Configuration of the extract plugin with chunks and naming
new ExtractTextPlugin("[name].css", { allChunks: true })
It's all ) Next thing is just configuration of the chunks (webpack is very flexible tool, everyone configure it for own needs. For an instance i'll show how i configure "vendour.css" and "application.css" build configuration based on "imports")
// Vendour chunks definition for vendor css
entry: {
vendor : ['./css/vendour.sass']
Example of entrypoint file.js
import "./css/vendor.sass"
import "./css/application.sass"
After build, webpack will create vendor.css (where you export vendour things with #import "~vendormodules/sass/alla") and application.css files.
Thanks,

Categories