I have an interactive landing page (single page - no react router). This landing page will need to be uploaded to a CMS where you just paste in the HTML - meaning I do not need <html> <head> <body> tags and the JavaScript file is uploaded separately. Currently I can just paste <div id="react-root"></div> into the CMS and upload my bundled JavaScript file and the page works.
However, the feedback is that this page now needs be be crawlable on all search engines. This means I need to basically render the initial HTML generated by react to paste into the CMS and have the page still all work as expected.
I am trying to make my build command output the rendered HTML to an index.html file where I can copy and paste the code into the CMS. (I have got my bundle.js file working fine).
webpack.common.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
]
},
plugins: [
new CleanWebpackPlugin(['dist'])
],
};
webpack.prod.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(common, {
plugins: [
new UglifyJSPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new HtmlWebpackPlugin({
inject: false,
template: require('./src/template.js'),
})
]
});
template.js
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const LandingPage = require('./App.js');
const html = ReactDOMServer.renderToString(LandingPage);
module.exports = function () {
return ReactDOM.hydrate(html, document.getElementById('lp-root'));
};
So I have used react-dom-server to renderToString. Is this the best way to do this? If so, this way isn't working as HtmlWebpackPlugin cannot process the ES6 syntax used in the landing pages (import and exports) - anyway around this?
The approach seems correct.
However, you might need babel transpiler to convert your code from ES6 syntax.
Try adding a new rule in the webpack config for the js/jsx files.
Something like this:
{
test: /js$/,
exclude: /(node_modules)/,
loader: "babel-loader",
query: { presets: ["react", "es2016", "es2015"], plugins: ["transform-class-properties"] },
},
It will transpile your code properly before using.
Hope it helps.
Please revert for any doubts.
Related
I'm currently attempting to incorporate Bootstrap into my React project that is using Webpack. I've sorted through a couple of issues trying to get Webpack setup to use Bootstrap but currently I cannot get past this issue. I've tried searching for answers, but haven't come up with any. I even followed the BootStrap documentation, but that didn't work. When I go to compile my JSX I'm getting the following error in the console.
ERROR in ./node_modules/bootstrap/dist/css/bootstrap.min.css
Module build failed (from ./node_modules/style-loader/dist/cjs.js):
ValidationError: Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
- options should be an object:
object { injectType?, attributes?, insert?, base?, esModule? }
at validate (/home/game-app/node_modules/schema-utils/dist/validate.js:98:11)
at Object.loader (/home/game-app/node_modules/style-loader/dist/index.js:25:28)
# ./src/index.jsx 4:0-46
I think I'm fairly close to getting it setup, but Webpack doesn't like how it's being served the bootstrap data. Any help on this issue would be greatly appreciated. I can provide my webpack.config file as well if necessary.
Thanks!
edit:
Webpack Config
const HtmlWebPackPlugin = require('html-webpack-plugin');
const path = require('path');
const htmlPlugin = new HtmlWebPackPlugin({
template: './src/index.html',
filename: './index.html',
});
module.exports = {
entry: './src/index.jsx',
output: { // NEW
path: path.join(__dirname, 'dist'),
filename: '[name].js',
}, // NEW Ends
plugins: [htmlPlugin],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
resolve: {
extensions: ['.js', '.jsx'],
},
};
edit2:
.babelrc
{
"presets": ["#babel/preset-env", "#babel/preset-react"]
}
I'm trying to create JS widgets from my existing react app. So currently I have an app that looks something like this
-src
- config
- components
- containers
- lib
- assets
- widgets
- widgetOne
- widgetTwo
- components
- widget.js
- index.js
- index.js
- index.html
So, I want directories in the widgets directories to be self contained apps that I can break out into a separate js file and a client can just add the js script into their page in a script tag.
I've come close but still facing a few issues. Also, I wanted to see if someone had experience doing this following a better pattern.
Right now I'm using webpack to do this splitting. I'm just defining /src/widgets/widgetOne/index.js as an entry point and perfectly creates a separate file.
Here is my webpack:
const appConstants = function() {
switch (process.env.NODE_ENV) {
case 'local':
const localConfig = require('./config/local');
return localConfig.config();
case 'development':
const devConfig = require('./config/development');
return devConfig.config();
case 'production':
default:
const prodConfig = require('./config/production');
return prodConfig.config();
}
};
const HtmlWebPackPlugin = require("html-webpack-plugin");
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const htmlWebpackPlugin = new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "./index.html",
hash: true
});
let webpackConfig = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.css$/,
exclude: [ /assets/, /node_modules/ ],
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
modules: true,
importLoaders: 1,
localIdentName: "[name]_[local]_[hash:base64]",
sourceMap: true,
minimize: true
}
}
]
},
{
test: /\.(pdf|jpg|png|gif|svg|ico)$/,
exclude: [/node_modules/],
use: [
{
loader: 'file-loader'
},
]
},
{
test: /\.(woff|woff2|eot|ttf|svg)$/,
exclude: [/node_modules/],
use: {
loader: 'url-loader?limit100000'
}
}
]
},
entry: {
main: [ "#babel/polyfill", "./src/index.js"],
widgetOne: ["./src/widgets/widgetOne/index.js"]
},
output: {
publicPath: appConstants().BASENAME ? JSON.parse(appConstants().BASENAME) : '/'
},
optimization: {
splitChunks: {
chunks: 'all'
}
},
plugins: [
htmlWebpackPlugin,
new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /en/),
new BundleAnalyzerPlugin({
analyzerMode: 'disabled',
generateStatsFile: true,
statsOptions: { source: false }
}),
new webpack.DefinePlugin({
'process.env': appConstants()
}),
new webpack.EnvironmentPlugin(['NODE_ENV'])
],
devServer: {
historyApiFallback: true,
port: 9090
}
};
module.exports = webpackConfig;
The problem I have now is while I get the widgetOne.js:
1) I end up with a vendor~widgetOne.js file that I also need to include to make the widgetOne app to work.
2) The widgetOne.js also gets added to my index.html file for my main app which I do not want.
Is there a way to configure webpack properly to make this work?
So, this is the solution I came up with that seems to work. I still don't know if this is the best approach but it' the only one I was able to get to work for me.
I decided to build the widgets as a different environment process and and modify the webpack configs based on that environment.
So the package.json I add this line under scritps:
"build-widgets": "cross-env NODE_ENV=plugins webpack --mode development",
And I added this section to the end of my webpack.config.js file:
// Override webpack configs when building plugins
if ( process.env.NODE_ENV === 'plugins') {
webpackConfig.entry = {
widgetOne: [ "#babel/polyfill", "./src/plugins/widgetOne/index.js"]
}
webpackConfig.output = {
publicPath: appConstants().DS_BASENAME ? JSON.parse(appConstants().DS_BASENAME) : '/',
path: __dirname + '/dist/widgets',
library: 'MyApp',
libraryTarget: 'umd',
umdNamedDefine: true
}
}
Alternatively, I could have just added a second webpack.config.js exclusively to deal with my widgets build. In my case I didn't feel the need for it just yet but it's something to be considered for sure, just for the sake of keeping configs separate.
Is there a way to prevent in React.js, raw HTML display before the CSS stylesheets are completely loaded. I'm using Webpack, Semantic-UI (react version) and React.js.
Is there an equivalent of ng-cloak (angular) in React ?
Here's the content of my webpack config file :
const webpack = require('webpack')
const ManifestPlugin = require('webpack-manifest-plugin')
const path = require("path");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const DEBUG = process.env.NODE_ENV !== 'production'
const plugins = [
new webpack.DefinePlugin({
'process.env.NODE_ENV': `"${process.env.NODE_ENV}"`
})
]
const assetsDir = process.env.ASSETS_DIR
const assetMapFile = process.env.ASSETS_MAP_FILE
const outputFile = DEBUG ? '[name].js' : '[name].[chunkhash].js'
if (!DEBUG) {
plugins.push(new ManifestPlugin({
fileName: assetMapFile
}))
plugins.push(new webpack.optimize.UglifyJsPlugin({ minimize: true }))
}
const config = {
entry: {
bundle: ['babel-polyfill', './src/client/index.jsx']
},
module: {
noParse: [],
loaders: [
{ test: /\.json$/, loader: 'json' },
{ test: /\.css$/, loader: 'style!css' },
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components)/,
query: {
cacheDirectory: DEBUG
}
},
{test:/.svg$/,loader:'url-loader',query:{mimetype:'image/svg+xml',
name:'/semantic/themes/default/assets/fonts/icons.svg'}},
{test:/.woff$/,loader:'url-loader',query:{mimetype:'application/font-woff',
name:'/semantic/themes/default/assets/fonts/icons.woff'}},
{test:/.woff2$/,loader:'url-loader',query:{mimetype:'application/font-woff2',
name:'/semantic/themes/default/assets/fonts/icons.woff2'}},
{test:/.[ot]tf$/,loader:'url-loader',query:{mimetype:'application/octet-stream',
name:'/semantic/themes/default/assets/fonts/icons.ttf'}},
{test:/.eot$/,loader:'url-loader',query:{mimetype:'application/vnd.ms-fontobject',
name:'/semantic/themes/default/assets/fonts/icons.eot'}},
{ test: /\.png/, loader: "url-loader?limit=100000&minetype=image/png" },
{ test: /\.jpg/, loader: "file-loader" }
]
},
node: {
fs: "empty"
},
resolve: {
alias: {
"semantic-ui" : path.resolve( __dirname, "../semantic/dist/semantic.min.css")
},
extensions: ['', '.js', '.jsx', ".css"]
},
plugins,
output: {
filename: outputFile,
path: DEBUG ? '/' : assetsDir,
publicPath: '/assets/'
}
}
if (DEBUG) {
config.devtool = '#inline-source-map'
} else if (process.env.NODE_ENV === 'production') {
config.devtool = 'source-map'
}
module.exports = config
I got this error when I try to load my css from my component :
Cannot find module '/semantic/dist/semantic.min.css'
and the module exists.
I tried the exact same configuration without Webpack and import from component worked.
This is not the correct setup for a production deploy.
By default, Webpack turns your CSS into Javascript code that injects CSS tags in the page. This allows for hot CSS reloading. It's only appropriate for the development environment, obviously. You should be using this default behavior in dev, and should not be using it in production.
In production, you need to build a separate CSS file and load it normally with a <style> tag in your production HTML code. To tell Webpack to pull that out into a file, use the ExtractTextPlugin, which your code requires but never uses.
You should maintain two Webpack config files, one for development which doesn't extract text (and doesn't minify/uglify, etc), and one for production, which correctly minifies, hashes names, extracts text, etc.
I've done some searching but was wondering if there's an elegant solution here. When building a Webpack app, it's common to have dependencies that don't need to be compiled/bundled, like jQuery, React, ReactDOM, Angular, or Bootstrap, to name a few. You can list these in your Webpack config file in an externals object, but externals just assumes that these libraries will be available as namespaced globals at runtime.
This means that for each entry in your externals hash, you also need to toss in a script tag in your HTML. This makes sense if you're referencing an external CDN, but I'm thinking this could be automated if all you want to do is copy some dist file from a library in node_modules.
I've been looking for examples of how to do this but I haven't seen any yet. I messed with external-loader but I haven't had any luck integrating it (the documentation doesn't seem to provide a complete example).
Essentially, this would need to happen:
Libraries that shouldn't be bundled should be added to resolve.alias, e.g. {"react": "react/dist/react.js"}
A loader copies the dist files to the public directory (maybe this could just be done with file-loader?)
An HTML loader or maybe plugin inserts the script tags before the bundle.js script tag
If something like this doesn't exist, I might look into trying to make one; I'm just posting this here to see if anyone might know of a pre-baked solution, as it seems like it'd be a common problem for building web apps and I figured I'm probably missing something.
var path = require("path");
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');
var WebpackNotifierPlugin = require('webpack-notifier');
module.exports = {
entry: {
'index-ref': './app/index-ref.ts',
'vendor': './app/vendor.ts',
'app': './app/main.ts',
},
resolve: {
extensions: ['', '.ts', '.js']
},
module: {
loaders: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file?name=assets/[name].[hash].[ext]'
},
{
test: /\.css$/,
exclude: helpers.root('app'),
loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
},
{
test: /\.css$/,
include: helpers.root('app'),
loader: 'raw'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'index-ref']
}),
new HtmlWebpackPlugin({
filename: '../index.html',
template: 'template' + '/default.html',
lib: ['jQuery'],
chunks: ['entry-name']
}),
new HtmlWebpackExternalsPlugin([
// Using a CDN for a JS library
{
name: 'jquery',
var: 'jQuery',
url: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.js'
}
],
{
basedir: 'node_modules',
dest: 'lib'
}),
new WebpackNotifierPlugin()
]
};
Am I missing anything here?
I didn't find a pre-existing solution, so I wrote a plugin to supplement the HtmlWebpackPlugin. It takes an array of externals and appends script/link tags to the HTML file, generates the externals hash, and can use CDNs or local files.
https://github.com/mmiller42/html-webpack-externals-plugin
If you don't want to add extra package bloat then HtmlWebpackPlugin has templating features, so you could do something like this:
//template.html
<html>
<head>
<%= htmlWebpackPlugin.options.externals %>
</head>
...
</html>
and then something like this in your webpack config:
//webpack.config.js
const EXTERNALS = [
{
name: 'react',
globalVarName: 'React',
src: 'https://cdn.example.com/react#18',
},
...
]
module.exports = {
...,
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'template.html',
externals: EXTERNALS.reduce(
(scripts, external) => (
`${scripts}<script src="${external.src}"></script>`
), ''
)
})
],
externals: EXTERNALS.reduce(
(res, external) => ({
...res,
[external.name]: external.globalVarName,
}), {}
),
}
(You can obviously add any environment customisations/finer details/etc. you want in the webpack config file.)
EDIT:
For some extra panache you could also get the current package versions from your node_modules rather than hard-coding them into the webpack file:
const fs = require('fs')
function getPackageVersion(packageName) {
const pkgPath = `node_modules/${packageName}`
const pkg = JSON.parse(fs.readFileSync(`${pkgPath}/package.json`, 'utf8'))
return pkg['version']
}
I'm building an app using webpack and I'm trying to add some web workers, I'm using a built-in plugin to load them, but I followed the example here, the official repository, and I couldn't make it work.
My webpack.dev.js looks like this:
import webpack from 'webpack';
import assign from 'object-assign';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import prodCfg from './webpack.prod.config.js';
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var DEBUG = process.env.NODE_ENV !== 'production' ? true : false;
var styles = 'css!less';
Object.assign = assign;
export default function (app) {
const config = Object.assign(prodCfg, {
devtool: 'cheap-module-inline-source-map',
entry:
[
'webpack-hot-middleware/client',
'./client',
'styles/main.less'
],
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
plugins: [
[
'react-transform', {
transforms: [{
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module']
}]
}
]
]
}
},
{
test: /\.css$/,
loader: DEBUG ? 'style!' + styles : ExtractTextPlugin.extract(styles)
},
{
test: /\.less$/,
loader: DEBUG ? 'style!' + styles : ExtractTextPlugin.extract(styles)
}
]
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new ExtractTextPlugin("style.css", {allChunks: true })
],
worker: {
output: {
filename: "hash.worker.js",
chunkFilename: "[id].hash.worker.js"
}
}
});
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {noInfo: true, publicPath: config.output.publicPath}));
app.use(webpackHotMiddleware(compiler));
}
and when I try to load the file I try it like this:
var Worker = require("worker!worker.js");
It would never find the file, the only files exported by webpack are bundle.js and style.css, it looks like the worker loader is not loading anything at all, or maybe I just don't understand the logic behind the loader, anyway there isn't much documentation about this nor implemented examples. So I hope someone had implemented this successfully and can shed some light on my problem.
Any help would be very welcomed! Thank you very much in advance!
You need to put it in the loaders section -
in your webpack config, something like
const config = Object.assign(prodCfg, {
...
module: {
loaders: [
...
{
test: /\.less$/,
loader: DEBUG ? 'style!' + styles : ExtractTextPlugin.extract(styles)
},
{
test: /worker\.js$/,
loader: 'worker'
}
...
then you should see it as a separate output of webpack.
Hope it helps
In the example you linked to, your line here:
var Worker = require("worker!worker.js");
Looks like this:
var Worker = require("worker!./worker");
Note that there is a ./ to indicate a relative path. Also, you do not need the .js when calling require on another file.
Do you have a file called worker.js relative to your shared code?