Build once, deploy many
I have a React app that references a config file with variables that are different for each environment. In keeping with the "build once, deploy many" concept, I'd like to exclude the config file from my webpack build process and instead import a different config file for each environment.
How can I accomplish this?
Here's my webpack.config.js as it stands.
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const srcRoot = path.resolve(__dirname, 'src');
const appRoot = path.resolve(srcRoot, 'app');
const configRoot = path.resolve(srcRoot, 'fs_config.js');
module.exports = (env) => {
const isDev = env == 'development';
return {
context: path.resolve(__dirname),
entry: {
main: './src/app/main.js',
vendor: [
'react', 'react-dom', 'jquery', 'moment',
'react-bootstrap', 'lodash'
]
},
output: {
path: path.resolve(__dirname, './dist'),
filename: isDev ? 'js/[name].bundle.js' : 'js/[name].[hash].bundle.js',
sourceMapFilename: isDev ? 'js/[name].bundle.map' : 'js/[name].[chunkhash].bundle.map',
chunkFilename: isDev ? 'js/[id].chunk.js' : 'js/[id].[chunkhash].chunk.js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
query:{
"presets": [
["es2015", {"modules": false}],
"stage-2",
"react"
],
"plugins": [
"react-hot-loader/babel"
]
},
exclude: [
/node_modules/
],
},
{test: /\.css$/, loader: "style-loader!css-loader"},
{test: /\.json$/, loader: "json-loader"},
{
test: /\.(jpe?g|png|gif)$/,
loader: 'file-loader',
query:{
name: 'assets/img/[name].[ext]'
}
},
]
},
resolve: {
extensions: [".js", ".jsx"],
modules: [
appRoot,
'node_modules'
],
},
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 2200,
disableHostCheck: true,
compress:true,
publicPath: '/',
stats: "minimal"
},
stats: "minimal",
performance: {
hints: false
},
devtool: isDev ? 'eval' : 'cheap-source-map',
plugins: [
new CleanWebpackPlugin(['dist']),
new CopyWebpackPlugin([
{from: './src/index.html'},
{from: './src/assets', to: './assets'},
]),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new HtmlWebpackPlugin({
template: path.resolve(srcRoot, 'index.html'),
chunksSortMode: 'dependency'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'js/[hash].vendor.js',
minChunks: Infinity,
}),
new webpack.DefinePlugin({
process: {
env: {
NODE_ENV: isDev ? '"development"' : '"production"'
}
}
}),
].concat(
!isDev
?
[
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
]
:
[]
),
node: {
console: true,
fs: 'empty',
tls: 'empty',
net: 'empty',
},
externals: [{
xmlhttprequest: '{XMLHttpRequest:XMLHttpRequest}'
}
]
}
};
Related
I have spent days getting to my current Webpack configuration setup.
When Webpack runs the build, the file size of my minified output file "react_customer.uniqueid.js" ends up being 2.53 MB, I would be satisfied if the file size would be under 1 MB.
Here is a visualization of the bundled JavaScript.
Could this setup be optimized further so the file size gets reduced?
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const nodeExternals = require("webpack-node-externals");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const { EnvironmentPlugin } = require("webpack");
module.exports = {
mode: "production",
entry: {
react_customer: path.resolve(__dirname, "react-customer/index.js"),
react_admin: path.resolve(__dirname, "react-admin/index.js"),
},
output: {
path: path.resolve(__dirname, "production-build"),
filename: "[name].[contenthash].js",
assetModuleFilename: "[name][ext]",
clean: true,
publicPath: "/",
},
//externals: [nodeExternals()],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
minify: TerserPlugin.uglifyJsMinify,
// `terserOptions` options will be passed to `uglify-js`
// Link to options - https://github.com/mishoo/UglifyJS#minify-options
terserOptions: {},
}),
],
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, /*"style-loader",*/ "css-loader"],
},
{ test: /\.(svg|ico|png|webp|jpg|gif|jpeg)$/, type: "asset/resource" },
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
["#babel/preset-env", { modules: false }],
["#babel/preset-react", { runtime: "automatic" }],
],
},
},
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
//"style-loader",
MiniCssExtractPlugin.loader,
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
],
},
plugins: [
new EnvironmentPlugin({
backend_server: "https://www.example.com",
frontend_customer: "https://www.example.com",
}),
new BundleAnalyzerPlugin(),
new MiniCssExtractPlugin(),
new HtmlWebpackPlugin({
filename: "index.html",
template: path.resolve(
__dirname,
"server/templates/other/react-customer-template.html"
),
chunks: ["react_customer"],
}),
new HtmlWebpackPlugin({
filename: "admin.html",
template: path.resolve(
__dirname,
"server/templates/other/react-admin-template.html"
),
chunks: ["react_admin"],
}),
],
};
Building TS/SASS with Webpack works fine, running this through VSCode Live Server plugin renders the index.html perfectly to the app folder.
Running webpack-dev-server with it looking at the same app folder does not. The page opens but there is a Javascript error Uncaught SyntaxError: Unexpected token '<'
And the page does not render the JS/CSS.
webpack.config.js
// Imports
var path = require("path");
// Plugins
const HtmlWebPackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// Exports
module.exports = {
mode: 'development',
entry: './src/main.ts',
devtool: "inline-source-map",
resolve: {
extensions: [".ts", ".tsx", ".js"]
},
output: {
filename: 'main.min.js',
path: path.resolve(__dirname, 'app')
},
devServer: {
open: 'http://localhost',
port: 80,
publicPath: path.resolve(__dirname, 'app'),
hot: true
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: { minimize: true }
}
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.scss$/,
use: [
"style-loader",
"css-loader",
"sass-loader"
]
},
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "./index.html"
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
]
}
tsconfig.json
{
"compilerOptions": {
"sourceMap": true
}
}
main.ts
console.log('Main.ts has loaded')
import './styles/main.scss'
Any help with this would be appreciated, I'm losing my mind lol.
My issue was with syntax in the webpack.config.js file.
I was neglecting to insert a comma at the end of the last entry in every object and array, believing it not to be necessary.
Here is a working config:
// Imports
var path = require("path");
// Plugins
const HtmlWebPackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// Exports
module.exports = {
mode: 'development',
entry: './src/main.ts',
devtool: "inline-source-map",
output: {
filename: 'main.min.js',
path: path.resolve(__dirname, 'app'),
},
devServer: {
open: 'http://localhost',
port: 80,
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: { minimize: true },
}
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader',
]
},
{
test: /\.scss$/,
use: [
"style-loader",
"css-loader",
"sass-loader",
]
},
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "./index.html",
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css",
}),
]
}
I have a problems with souce maps. They are generated but apparently not used.
I use webpack-dev-server, react hot module replacement and Webpack v3.x if that is important.
Inside of my webpack-config i use devtool: 'source-map' and I run my script like this:
"webpack": "cross-env NODE_ENV=development webpack-dev-server -d --config webpack.config.js"
Example of error that I get
When I click on this client?e36c:157 it doesn't take me to source code but:
If I remove devtools from webpack.config and -d tag from script I get the same output
**webpack.config.js**
const path = require('path')
const webpack = require('webpack')
const publicPath = path.resolve(__dirname, './src/client')
const buildPath = path.resolve(__dirname, './src')
// const BrowserSyncPlugin = require('browser-sync-webpack-plugin')
const Write = require('write-file-webpack-plugin')
process.noDeprecation = true
module.exports = {
devtool: 'source-map',
performance: {
hints: false,
},
devServer: {
hot: true,
port: 3001,
host: 'localhost',
/* Needed only if using Browsersync */
headers: { 'Access-Control-Allow-Origin': '*', },
proxy: {
'**': {
target: 'http://localhost:3000',
secure: false,
changeOrigin: true,
},
},
},
context: publicPath,
entry: {
bundle: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:3001',
'webpack/hot/only-dev-server',
'script-loader!jquery/dist/jquery.min.js',
'script-loader!tether/dist/js/tether.min.js',
'script-loader!bootstrap/dist/js/bootstrap.min.js',
'./app.js',
],
},
output: {
path: path.join(buildPath, 'dist'),
filename: '[name].js',
publicPath: 'http://localhost:3001/',
},
resolve: {
extensions: [ '.js', '.jsx', ],
alias: {
// Container
Container: path.resolve(__dirname, 'src/client/scenes/Container.jsx'),
// Components
FormComponent: path.resolve(
__dirname,
'src/client/scenes/feature/components/FormComponent.jsx'
),
Feature: path.resolve(__dirname, 'src/client/scenes/feature/Feature.jsx'),
TitleComponent: path.resolve(
__dirname,
'src/client/scenes/home/components/TitleComponent.jsx'
),
Home: path.resolve(__dirname, 'src/client/scenes/home/Home.jsx'),
Navigation: path.resolve(__dirname, 'src/client/scenes/shared/navigation/Navigation.jsx'),
},
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules|dist|build/,
loader: 'babel-loader',
options: {
babelrc: true,
},
},
{
test: /\.local\.(css|scss)$/,
use: [
'style-loader',
'css-loader?sourceMap&modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
'postcss-loader?sourceMap',
'sass-loader?sourceMap',
{
loader: 'sass-resources-loader',
options: {
resources: [
path.resolve(__dirname, './src/client/styles/scss/variables.scss'),
],
},
},
],
},
{
test: /^((?!\.local).)+\.(css|scss)$/,
use: [
'style-loader',
'css-loader?sourceMap',
'postcss-loader?sourceMap',
'sass-loader?sourceMap',
],
},
{
test: /\.(gif|png|jpg)$/,
/* We can specify custom publicPath if needed */
loader: 'url-loader',
},
],
},
plugins: [
new Write(),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
jquery: 'jquery',
}),
],
}
I've read multiple threads regarding similar issues and tried some propositions, but had no results.
I've followed few tutorials related to React.js and WebPack 3. As the result the application is working well on all browsers (at this moment) except IE 10 and below. The error points to bundle.js (once I'm using the configuration Nr.1):
SCRIPT1002: Syntax error and the line - const url = __webpack_require__(83);
With configuration Nr2., on local server - : SCRIPT1002: Syntax error - line with eval()
And the same configuration, but running on remote server produces a bit different error:
SCRIPT5009: 'Set' is undefine
WebPack configuration Nr1.:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
template: './index.html',
filename: 'index.html',
inject: 'body'
})
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.json$/,
exclude: /node_modules/,
loader: 'json-loader'
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
}
],
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react']
}
}
}
]
},
devServer: {
historyApiFallback: true,
contentBase: './'
},
plugins: [HtmlWebpackPluginConfig]
}
WebPack configuration Nr2.:
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PreloadWebpackPlugin = require('preload-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const StyleExtHtmlWebpackPlugin = require('style-ext-html-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const autoprefixer = require('autoprefixer');
const staticSourcePath = path.join(__dirname, 'static');
const sourcePath = path.join(__dirname);
const buildPath = path.join(__dirname, 'dist');
module.exports = {
stats: {
warnings: false
},
devtool: 'cheap-eval-source-map',
devServer: {
historyApiFallback: true,
contentBase: './'
},
entry: {
app: path.resolve(sourcePath, 'index.js')
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].[chunkhash].js',
publicPath: '/'
},
resolve: {
extensions: ['.webpack-loader.js', '.web-loader.js', '.loader.js', '.js', '.jsx'],
modules: [
sourcePath,
path.resolve(__dirname, 'node_modules')
]
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.[chunkhash].js',
minChunks: Infinity
}),
new webpack.LoaderOptionsPlugin({
options: {
postcss: [
autoprefixer({
browsers: [
'last 3 version',
'ie >= 10'
]
})
],
context: staticSourcePath
}
}),
new webpack.HashedModuleIdsPlugin(),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'index.html'),
path: buildPath,
excludeChunks: ['base'],
filename: 'index.html',
minify: {
collapseWhitespace: true,
collapseInlineTagWhitespace: true,
removeComments: true,
removeRedundantAttributes: true
}
}),
new PreloadWebpackPlugin({
rel: 'preload',
as: 'script',
include: 'all',
fileBlacklist: [/\.(css|map)$/, /base?.+/]
}),
new webpack.NoEmitOnErrorsPlugin(),
new CompressionPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/,
threshold: 10240,
minRatio: 0.8
})
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react', 'es2015'],
plugins: ["transform-es2015-arrow-functions"]
}
},
include: sourcePath
},
{
test: /\.css$/,
exclude: /node_modules/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{ loader: 'css-loader', options: { minimize: true } },
'postcss-loader',
'sass-loader'
]
})
},
{
test: /\.(eot?.+|svg?.+|ttf?.+|otf?.+|woff?.+|woff2?.+)$/,
use: 'file-loader?name=assets/[name]-[hash].[ext]'
},
{
test: /\.(png|gif|jpg|svg)$/,
use: [
'url-loader?limit=20480&name=assets/[name]-[hash].[ext]'
],
include: staticSourcePath
}
]
}
};
Here additionally I've added the es2015 to presets: ['env', 'react', 'es2015'] and plugins: ["transform-es2015-arrow-functions"] but it made no sense.
Well in case when the babel loader won't work at all of misconfiguration or something else, I think that the whole application won't start. I believe that something should be done with presets or their order... Need advice from experienced developer
UPDATE
I've changed devtool to inline-cheap-module-source-map and got error point to overlay.js -> const ansiHTML = require('ansi-html');
In your package.json file
change the version of webpack-dev-server to version "2.7.1" (or earlier).
"webpack-dev-server": "2.7.1"
Then do a npm install et voilĂ .
That solved the problem for me.
All versions after 2.7.1 gives me an error similar to yours.
Simply add devtools : "source-map" to your Webpack config like this:
const path = require('path');
module.exports = {
devtool: "source-map",
entry: ['babel-polyfill', path.resolve(__dirname, './js/main.js')],
mode: 'production',
output: {
path: __dirname+'/js/',
filename: 'main-webpack.js'
}
};
This will remove eval and change your source map to be supported by IE.
UPDATE I've changed devtool to inline-cheap-module-source-map and got error point to overlay.js -> const ansiHTML = require('ansi-html');
And to support ES6 syntax you have to compile your JavaScript code with Babel.
I have been working on ReactJS for sometime now,I have come over quite a lot of modules eco-system (Webpack,Babel,React) etc..
I have some knowledge on webpack configuration,I have done basic configuration. See below
const Path = require('path');
const Webpack = require('webpack');
const envs = require('../utils/env');
const Possibles = envs.possibles;
const NODE_ENV = process.env.NODE_ENV;
const miniOps = {
development: Possibles.dev.indexOf(NODE_ENV) > -1,
production: Possibles.prod.indexOf(NODE_ENV) > -1
};
const configuration = {
target: 'web',
output: {
path: Path.join(__dirname, '..', 'public'),
filename: 'bundle.js',
publicPath: '/assets/'
},
module: {
loaders: []
},
resolve: {
extensions: ['', '.js', '.jsx']
},
plugins: [
new Webpack.DefinePlugin({ NODE_ENV: JSON.stringify(NODE_ENV) })
]
};
if (miniOps.development) {
configuration.resolve.alias = {};
configuration.module.noParse = [];
configuration.cache = true;
configuration.devtool = 'cheap-eval-source-map';
configuration.entry = [
Path.join(__dirname, '..', 'app/main.js')
];
const minideps = [
'react/dist/react.min.js'
];
const NODE_MODULES_DIR = Path.join(__dirname, '..', 'node_modules');
minideps.forEach(dep => {
const depPath = Path.resolve(NODE_MODULES_DIR, dep);
configuration.resolve.alias[dep.split(Path.sep)[0]] = depPath;
configuration.module.noParse.push(depPath);
});
configuration.plugins.push(
new Webpack.optimize.OccurenceOrderPlugin(),
new Webpack.NoErrorsPlugin(),
new Webpack.HotModuleReplacementPlugin()
);
configuration.module.loaders.push({
test: Path.resolve(NODE_MODULES_DIR, minideps[0]),
loader: 'expose?React'
});
configuration.module.loaders.push({
test: /\.(js|jsx)$/,
include: Path.join(__dirname, '..', 'app'),
exclude: /node_modules/,
loader: 'babel',
query: {
cacheDirectory: true
}
});
}
if (miniOps.production) {
configuration.entry = {
app: [
Path.join(__dirname, '..', 'app/main.js')
],
vendor: ['react']
};
configuration.output.path = Path.join(__dirname, '..', 'dist');
configuration.module.loaders.push({
test: /\.(js|jsx)$/,
include: Path.join(__dirname, '..', 'app'),
exclude: /node_modules/,
loader: 'babel'
});
configuration.plugins.push(
new Webpack.optimize.UglifyJsPlugin({
compress: {
unused: true,
dead_code: true,
warnings: false,
screw_ie8: true
}
})
);
configuration.plugins.push(
new Webpack.optimize.OccurrenceOrderPlugin(),
new Webpack.optimize.DedupePlugin(),
new Webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js')
);
}
console.log(`Bundling modules...... NODE_ENV: ${NODE_ENV}`);
module.exports = configuration;
The babelrc configuration is,
{
"presets": ["es2015", "react"],
"env": {
"development": {
"plugins": [
[ "transform-runtime" ],
[
"react-transform", {
"transforms": [
{
"transform": "react-transform-hmr",
"imports": ["react"],
"locals": ["module"]
},
{
"transform": "react-transform-catch-errors",
"imports": ["react", "redbox-react"]
}
]
}
]
]
}
}
}
I have planned not to use dev-server, instead I am planning to write Webserver which runs on Hapi.JS.
Questions:
Now that I am using react-transform-hmr. Do I need to use webpack-dev-middleware and webpack-hot-middleware ?
How should I implement / connect react-transform-hmr with my Hapi webserver ?
If I am gonna use webpack-dev-server and webpack-hot-middleware once again with this plugin
https://www.npmjs.com/package/hapi-webpack-plugin
Then what is the use of react-transform-hmr ?
& I see one of the boilerplate is also using this
https://github.com/Dindaleon/hapi-react-starter-kit/blob/master/webpack/hapiWebpack.js
Can someone explain what is the use of webpack-hot-middleware,webpack-dev-middleware,babel-transform-hmr ?