Legacy JS bundling & minification with Webpack - javascript

My project has multiple JS-Files that contain function declarations without any exporting and uses jquery, jquery-ui and other libraries that need to be accessible globally.
Functions are often called by inline JavaScript in html-elements (<button onclick="doStuff()">...)
My goal is now to bundle the app files into bundle.js and the vendor files into vendor.js. I've managed to accomplish the second task but the first one is not working as I would have hoped.
The bundle.js file should only contain all .js files concatenated + minified and then be executed in the global context.
What I tried so far:
scripts-loader (no minification...)
exports-loader (context problem?)
expose-loader (doesn't really expose functions globally outside of the bundle and needs exports)
For easier discussion I give a simplified scenario:
src/a.js
function doStuffA() {
alert("A");
}
src/b.js
function doStuffB() {
alert("B");
}
src/index.js
require("script!./a");
require("script!./b");
index.html
<body>
<button onclick="doStuffA()">A</button>
<button onclick="doStuffB()">B</button>
<script src="bundle.js"></script>
<script src="vendor.js"></script>
</body>
webpack.config.js
var webpack = require("webpack");
module.exports = {
entry: {
app: "./src/index.js",
vendor: [
"jquery",
"jquery-ui"
]
},
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [
{test: /\.json$/, loader: "json"},
{test: /\.css$/, loader: "style!css"}
]
},
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
"jQuery": "jquery",
"window.jQuery": "jquery"
}),
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.bundle.js', minChunks: Infinity }),
]
}

Related

Webpack configuration vs multiple bundles and ordering

I have a big javascript (angularjs) application which I have bootstrapped in a Angular 8 project. Now I also have many vendor libraries that are javascript files. For example Kendo libs, JQuery, foundation (zurb).
I have these vendor files, and my app files included in my webpack configuration. Which looks like this:
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
'ang': './chunks/angularjs.ts',
'fou': './chunks/foundation.ts',
'ken': './chunks/kendo.ts',
'vws': './chunks/views.ts',
'dir': './chunks/directives.ts',
'ser': './chunks/services.ts',
'con': './chunks/controllers.ts',
'app': './main.ts',
'toastr': './app-old/scripts/toastr.js'
},
output: {
path: helpers.root('dist/dev'),
publicPath: '/',
filename: '[name].bundle.js'
},
// Currently we need to add '.ts' to the resolve.extensions array.
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
// Add the loader for .ts files.
module: {
rules: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader?keepUrl=true'],
exclude: [/\.(spec|e2e)\.ts$/]
},
{
test: /\.less$/,
loader: 'less-loader', // compiles Less to CSS
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|tff|otf|eot|ico)$/,
loader: 'file-loader?name=assets/[name].[hash].[ext]'
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
// Exposes jQuery for use outside Webpack build
test: require.resolve(path.resolve(__dirname, './app-old/scripts/jquery-2.1.4.js')),
use: [{
loader: 'expose-loader',
options: 'jQuery'
}, {
loader: 'expose-loader',
options: '$'
}]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: '../config/index.html'
}),
// new webpack.ProvidePlugin({
// foundation: 'foundation-sites/js/foundation/foundation'
// }),
new webpack.ProvidePlugin({
$: path.resolve(__dirname, './app-old/scripts/jquery-2.1.4.js'),
jQuery: path.resolve(__dirname, './app-old/scripts/jquery-2.1.4.js'),
"window.jQuery": path.resolve(__dirname, './app-old/scripts/jquery-2.1.4.js')
}),
new webpack.ProvidePlugin({
toastr: 'toastr',
"window.toastr": "toastr"
})
]
}
You see that I have created different entry points, because I want smaller chunks (because of performance).
It compiles perfectly, but when I start up the application the troubles begin. I get al kinds of erros. It keeps telling my that JQuery needs to be loaded before Kendo is being loaded. And the foundation files need don't see the JQuery files.
But I made sure that they are globally available, as you can see in the file.
Not the strange thing.
When I throw everything in 1 TS file and use just only 1 entry point everything runs ok and I have no errors. :-)
But then my bundled file is huge and this is not good for the performance.
So my main question is?
How can I get multiple chunks for my vendor javascript files so that they can see each other?
I am starting to think that I don't need to use the entry points for smaller chunks.

How to bundle JS and CSS file independently with Webpack?

I have a few JS and SCSS files. I need Webpack 4 to bundle each JS entry to one JS file and each SCSS entry to one CSS file. The JS files don't import the SCSS files. I try to do it with the following webpack.config.js:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
scriptFoo: './src/js/scriptFoo.js',
scriptBar: './src/js/scriptBar.js',
// ...
styleBaz: './src/css/styleBaz.scss',
styleBaq: './src/css/styleBaq.scss'
// ...
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.(scss|sass)$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
]
}
]
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
})
]
};
It works fine, Webpack puts the compiled files to the dist directory. But it also creates an excess dummy JS file for each SCSS file in the dist directory:
webpack.config.js
src/
js/
scriptFoo.js
scriptBar.js
...
css/
styleBaz.scss
styleBaq.scss
...
dist/
scriptFoo.js
scriptBar.js
...
styleBaz.css
styleBaz.js // Excess
styleBaq.css
styleBaq.js // Excess
...
How to make Webpack not to create the excess JS files?
Use the ignore-emit-webpack-plugin Webpack plugin to not create the excess file. First install it by running in a console:
npm install --save-dev ignore-emit-webpack-plugin
Then add it to your Webpack configuration:
const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin');
module.exports = {
// ...
plugins: [
// ...
new IgnoreEmitPlugin(['styleBaz.js', 'styleBaq.js']) // Or simply: new IgnoreEmitPlugin(/^style.*\.js$/)
]
};
It is because for each property in the entry object ,The js file is created in output destinations.
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
Webpack creating dummy js when css is an entry point is a known bug, which has not been fixed yet.
Also having multiple entry files in the entry configuration will also affect treeshaking capabilties

Combining SCSS and CSS Into A Single File With WebPack

I'm a newbie to webpack and I'm having trouble understanding how I can take a bunch of scss files and css files and merge them together with webpack (After transpiling the sass of course).
With gulp, it was really obvious, as I can have 1 step the transpile the sass to css and then a step after that to concatenate them together.
However with webpack, it looks like everything happens at the same time.
This is a pretty basic requirement that I'm sure has an obvious answer to those more experienced.
I've got to the point where I can successfully output a transpiled scss to css and seperately output a css file from css input, but I can't figure out how to stick them together using webpack.
Below is my webpack.config.js file:
const path = require('path');
const webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractCSS = new ExtractTextPlugin('extractedCSS.css');
const extractSass = new ExtractTextPlugin({
filename: "extractedSASS.css",
disable: process.env.NODE_ENV === "development"
});
module.exports = function (env) {
return {
context: path.resolve(__dirname, './wwwroot/app'),
entry: {
main: './index.js',
vendor: 'moment'
},
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, './wwwroot/mytempdist')
},
module: {
rules: [{
test: /\.css$/,
use: extractCSS.extract({
use: 'css-loader'
})
},
{
test: /\.scss$/,
use: extractSass.extract({
use: [{
loader: "css-loader"
}, {
loader: "sass-loader"
}],
// use style-loader in development
fallback: "style-loader"
})
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
// this assumes your vendor imports exist in the node_modules directory
return module.context && module.context.indexOf('node_modules') !== -1;
}
}),
//CommonChunksPlugin will now extract all the common modules from vendor and main bundles
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest' //But since there are no more common modules between them we end up with just the runtime code included in the manifest file
}),
//new webpack.optimize.UglifyJsPlugin({
// sourceMap: options.devtool && (options.devtool.indexOf("sourcemap") >= 0 || options.devtool.indexOf("source-map") >= 0)
//}),
extractSass,
extractCSS
],
devtool: 'inline-source-map'
}
}
How can I modify the above to make both the sass and css go into the file css output file?
Incase it makes a difference, below is an exerpt from my index.js file (entry point) toastr is an npm package and pace is just a normal downloaded css file:
var toastr = require('toastr');
import 'toastr/toastr.scss';
import pace from './../lib/pace/pace.min.js';
import './../lib/pace/pace.css';
You have 2 instances of ExtractTextPlugin defined with explicit names and you use those separate instances to load css and scss files respectively.
What you need is only 1 instance of the plugin which will accumulate all the CSS and only one rule for both scss and css files.
{
test: /\.s?css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
})
}
This will handle both scss and css files and put it all in one single output CSS file.

Import Javascript library globally via Webpack

I am trying to remove script tags for javascript libraries from my html, and so have removed underscore.js from a template page.
To replace this, within my index.js (webpack entry point), I have the following
import 'underscore';
The size of the webpack outputted bundle.js file increases by 50k when I do this, so I know that the library is in bundle.js. However, underscore is not available when I try to use it in the console on a page which has the bundle.js included.
Any thoughts would be appreciated.
const webpack = require('webpack');
const path = require('path');
const precss = require('precss');
const autoprefixer = require('autoprefixer');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const postcssImport = require('postcss-import');
module.exports = {
context: __dirname + '/frontend',
devtool: 'source-map',
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, './static'),
},
module: {
loaders: [
{ test: /\.js$/, loader: 'babel', exclude: /node_modules/, query: { presets: ['es2015'] } },
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style', 'css?sourceMap&importLoaders=1!postcss') },
],
},
vendor: [
'underscore',
],
plugins: [
new ExtractTextPlugin('si-styles.css'),
new webpack.ProvidePlugin({
underscore: 'underscore',
}),
],
postcss: function(webpack) {
return [
postcssImport({ addDependencyTo: webpack }), // Must be first item in list
precss,
autoprefixer({ browsers: ['last 2 versions'] }),
];
},
};
In order to achieve that you can use this webpack plugin:
new webpack.ProvidePlugin({
underscore: "underscore"
})
by the way it is not necessary that you import the library in the index file of your directory. You will have access to the library also specifying a new entry point in your webpack config file.. You could then put all your vendor code in a vendor.js boundle like so:
entry: {
main: [
'./app/js/main.js'
],
vendor: [
'underscore',
'lodash',
'my-awesome-library!'
]
}
UPDATE: There is a very good tutorial in how to use webpack in production on egghead.io.. Try to check it out!

require not defined error on bundled JS reactjs

I'm new to Django and ReactJS, was trying to compile a simple JSX code to JS using this tutorial : http://geezhawk.github.io/2016/02/02/using-react-with-django-rest-framework.html
Didn't work, so I used npm run dev to compile, now it worked but giving error in browser console : Uncaught ReferenceError: react is not defined
Here is my webpack.config.js
var path = require('path');
var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');
var nodeExternals = require('webpack-node-externals');
module.exports = {
//the base directory (absolute path) for resolving the entry option
context: __dirname,
//the entry point we created earlier. Note that './' means
//your current directory. You don't have to specify the extension now,
//because you will specify extensions later in the `resolve` section
entry: './assets/js/index',
output: {
//where you want your compiled bundle to be stored
path: path.resolve('./assets/bundles/'),
//naming convention webpack should use for your files
filename: '[name]-[hash].js',
},
target: 'node', // in order to ignore built-in modules like path, fs, etc.
externals: {
react: 'react'
}, // in order to ignore all modules in node_modules folder
plugins: [
//tells webpack where to store data about your bundles.
new BundleTracker({filename: './webpack-stats.json'}),
//makes jQuery available in every module
new webpack.ProvidePlugin({
//React: "react",
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
})
],
module: {
loaders: [
//a regexp that tells webpack use the following loaders on all
//.js and .jsx files
{test: /\.jsx?$/,
//we definitely don't want babel to transpile all the files in
//node_modules. That would take a long time.
/*exclude: /node_modules/,*/
//use the babel loader
loader: 'babel-loader',
query: {
//specify that we will be dealing with React code
presets: ['react']
}
}
]
},
resolve: {
//tells webpack where to look for modules
modulesDirectories: ['node_modules'],
//extensions that should be used to resolve modules
extensions: ['', '.js', '.jsx']
}
}
And assets/bundles/index.js
var React = require('react')
var ReactDOM = require('react-dom')
//snaha//
var BooksList = React.createClass({
loadBooksFromServer: function(){
console.log(123454657);
$.ajax({
url: this.props.url,
datatype: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this)
})
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadBooksFromServer();
setInterval(this.loadBooksFromServer,
this.props.pollInterval)
},
render: function() {
if (this.state.data) {
console.log('DATA!')
var bookNodes = this.state.data.map(function(book){
return <li> {book.title} </li>
})
}
return (
<div>
<h1>Hello React!</h1>
<ul>
{bookNodes}
</ul>
</div>
)
}
})
ReactDOM.render(<BooksList url='/api/' pollInterval={1000} />,
document.getElementById('container'))
And templates/body.html
{% load render_bundle from webpack_loader %}
<!doctype html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.js"></script>
<meta charset="UTF-8">
<title>Hello React
{% block content %}
{{ id }}
{% endblock %}
</title>
</head>
<body>
<div id="container"></div>
{% render_bundle 'main' %}
</body>
</html>
Anything I'm missing? here is my Django project structure
Finally I've solved it!
Problem was : it was trying to get variable react where as React.js on browser was providing variable React!
So I simple change of externals of webpack.config.js to
externals: {
React: 'react'
},
solved the issue!
Next Problem Faced :
"process was not defined"
Solution : added
var env = process.env.WEBPACK_ENV;
to top of webpack.config.js
and
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': '"production"'
}
})
into the plugins part of model.export
So Final webpack.config.js would be :
var path = require('path');
var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');
var nodeExternals = require('webpack-node-externals');
var env = process.env.WEBPACK_ENV;
module.exports = {
//the base directory (absolute path) for resolving the entry option
context: __dirname,
//the entry point we created earlier. Note that './' means
//your current directory. You don't have to specify the extension now,
//because you will specify extensions later in the `resolve` section
entry: './assets/js/index',
output: {
//where you want your compiled bundle to be stored
path: path.resolve('./assets/bundles/'),
//naming convention webpack should use for your files
filename: '[name]-[hash].js',
},
target: 'node', // in order to ignore built-in modules like path, fs, etc.
externals: {
React: 'react'
}, // in order to ignore all modules in node_modules folder
plugins: [
//tells webpack where to store data about your bundles.
new BundleTracker({filename: './webpack-stats.json'}),
//makes jQuery available in every module
new webpack.ProvidePlugin({
//React: "react",
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': '"production"'
}
})
],
module: {
loaders: [
//a regexp that tells webpack use the following loaders on all
//.js and .jsx files
{test: /\.jsx?$/,
//we definitely don't want babel to transpile all the files in
//node_modules. That would take a long time.
/*exclude: /node_modules/,*/
//use the babel loader
loader: 'babel-loader',
query: {
//specify that we will be dealing with React code
presets: ['react']
}
}
]
},
resolve: {
//tells webpack where to look for modules
modulesDirectories: ['node_modules'],
//extensions that should be used to resolve modules
extensions: ['', '.js', '.jsx']
}
}
Now Enjoy React! Happy Coding :-)
Can you look if you have all the requirements installed.
Look inside package.json. You should have react noted in requirements if you do run.
npm install
If you don't, then run
npm install react --save
ps: in my option if you are running Webpack try to add babel to Webpack presets and write javascript in ES2015 specification.

Categories