React Router URL Throws Error With Parameter - javascript

I am trying to use react router to have dynamic profile pages, preferably having it in slashes rather than like a ? parameter wildcard in the URL. I'm looking for like /profile/{username}/ rather than /profile?user={username} if that makes sense.
Here's the route that I'm using to try and achieve this;
<Route path="/profile/:name/" component={Profile}/>
But when I try and go to this route as in `/profile/jeff/' or anything it returns a bundle.js (webpack'd) that is a blank HTML template, which is unexpected to be in the bundle.js and throws an error. Any idea's how I can fix this? Thanks.
Here is the bundle.js that gets returned;
<html>
<body style="margin:0px; padding: 0px;">
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css?family=Ubuntu" rel="stylesheet">
<div id = "root" style="margin:0px; padding: 0px;"></div>
<script src="bundle.js"></script>
</body>
Profile component;
import React from 'react';
import styles from './profile.scss';
import astyles from './allpages.scss';
export default class Profile extends React.Component{
render(){
console.log("hello!");
const { match, location, history } = this.props
console.log(location);
console.log(match);
console.log(history);
return(
<div className = {styles.borderContainer}>
<p> {this.props.param.name} </p>
</div>
)
}
}
Webpack config;
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var path = require('path');
require('style-loader');
require('css-loader');
const loaders = {
css: {
loader: 'css-loader'
},
postcss: {
loader: 'postcss-loader',
options: {
plugins: (loader) => [
autoprefixer({
browsers: ['last 2 versions']
})
]
}
},
sass: {
loader: 'sass-loader',
options: {
indentedSyntax: true,
includePaths: [path.resolve(__dirname, './src/app')]
}
}
}
module.exports = {
context: __dirname,
entry: './src/app/index.js',
output: {
path: __dirname + '/dist/',
filename: 'bundle.js',
libraryTarget: 'umd'
},
devtool: "sourceMap",
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
exclude: /node_modules/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
// Could also be write as follow:
// use: 'css-loader?modules&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader'
use: [
{
loader: 'css-loader',
query: {
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]'
}
},
'postcss-loader'
]
}),
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
// Could also be write as follow:
// use: 'css-loader?modules&importLoader=2&sourceMap&localIdentName=[name]__[local]___[hash:base64:5]!sass-loader'
use: [
{
loader: 'css-loader',
query: {
modules: true,
sourceMap: true,
importLoaders: 2,
localIdentName: '[name]__[local]___[hash:base64:5]'
}
},
'sass-loader'
]
}),
},
],
},
plugins: [
new ExtractTextPlugin('styles.css'),
],
}

When you request /profile/jeff/ you serve the index.html you posted, and presumably that is done for every resource that doesn't exist on your server (as a fallback). In the index.html you have the following script tag:
<script src="bundle.js"></script>
This is a relative path. You are actually requesting /profile/jeff/bundle.js at this point and because that doesn't exist, you end up serving the index.html as the bundle, which is problematic because it's not valid JavaScript.
You should always use /bundle.js regardless of the current URL. Similarly you'd always want /styles.css for your CSS.
<link rel="stylesheet" href="/styles.css">
<script src="/bundle.js"></script>

I don't have a better source for this, but the development server needs to be configured to handle React Routers dynamic routes because it should always serve the same html file (index.html) because it's an SPA.
https://redux.js.org/docs/advanced/UsageWithReactRouter.html
Edit:
Particularly, I think you are missing this in your webpack config
devServer: {
historyApiFallback: true
}
Edit 2:
For ExpressJS you would need something like this,
app.get('/*', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'))
})

It looks like it's your stylesheet inclusion. My understanding is that you should be using a babel import statement in your component, not link tags, or they will 404 causing the error you are seeing.

Related

How to include styles when rendering page on server?

I have a problem implementing server side rendering. I'm using react + typescript frontend and backend written in node.js with typescript as well. I cannot find a way to include styles in my page when it's send from the server for the first time (before js loads). What I've tried is bundling all my styles into single main.css file and then using link tag in html file send from server, but in this scenario it takes as much time to load css, as it takes to load js (what is the purpose of SSR here ?). Also i'm trying to avoid styling my component inline. Any help would be apprecieted. Here I'm sharing some of my current code in case it might be helpful in some way.
App Component <- optimally i would like to see styled app as soon as browser reads .html file (i guess that's
the purpose of SSR)
export default class App extends React.Component<State, Props> {
state: State = {
}
render = () => {
return (
<div className="app">
APP COMPONENT !!!
</div>
)
}
}
Client code entry (ClientEntry.tsx)
import React from "react";
import ReactDOM from "react-dom";
import App from "../shared/containers/App";
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("app")
);
Webpack config
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const autoprefixer = require("autoprefixer");
const WebpackShellPlugin = require('webpack-shell-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const clientConfig = {
stats:'errors-only',
entry: "./src/client/ClientEntry.tsx",
output: {
path: __dirname,
filename: "./build/bundle.js"
},
devtool: "cheap-module-source-map",
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
module: {
rules: [
{
test: [/\.svg$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: "file-loader",
options: {
name: "build/media/[name].[ext]",
publicPath: url => url.replace(/build/, "")
}
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: [
{
loader: "css-loader",
options: { importLoaders: 1 }
},
{
loader: "postcss-loader",
options: { plugins: [autoprefixer()] }
}
]
})
},
{
test: [/tsx?$/],
exclude: /(node_modules)/,
loader: "ts-loader",
}
]
},
plugins: [
new ExtractTextPlugin({
filename: "build/css/[name].css"
}),
new WebpackShellPlugin({
onBuildEnd: ['nodemon build/server.js']
})
]
};
const serverConfig = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin({ terserOptions: { mangle: false } })],
},
stats:'errors-only',
entry: "./src/server/index.tsx",
target: "node",
output: {
path: __dirname,
filename: "./build/server.js",
libraryTarget: "commonjs2"
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
devtool: "cheap-module-source-map",
module: {
rules: [
{
test: [/\.svg$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: "file-loader",
options: {
name: "build/media/[name].[ext]",
publicPath: url => url.replace(/build/, ""),
emit: false
}
},
{
test: /\.css$/,
use: [
{
loader: "css-loader/locals"
}
]
},
{
test: [/tsx?$/],
exclude: /(node_modules)/,
loader: "ts-loader",
}
]
}
};
module.exports = [clientConfig, serverConfig];
Part of index.tsx (express main file)
server.get('/*', (req, res) => {
const body = renderToString(
<StaticRouter location={req.url}>
<App />
</StaticRouter>
);
res.send(
html({
body
})
);
})
And function that converts to html
const html = ({ body }: { body: string }) => `
<!DOCTYPE html>
<title> my app</title>
<html>
<head>
<link rel="stylesheet" href="/css/main.css">
</head>
<body style="margin:0">
<div id="app">${body}</div>
</body>
<script src="/bundle.js" defer></script>
</html>
`;
export default html;
I'm assuming you're using express, which has a built in method to serve static files (such as images, css, etc...) you just need to show it the path as follow
server.use('static', express.static('css'))
which basically serves all files inside the css folder under the link
http://${host}/static/
if you want to serve static images you can do the following.
server.use('images', express.static('images'))
and then you can refer to any image file inside your code using the following.
<img src="/images/<image_name>" />
You can read more about it here.
express static files

Import a SCSS "exported" variable into the Javascript code within a .vue file that is loaded by vue-loader and bundled by webpack

I have a variable in my vars.scss that I want to access from Javascript in root/app/app.vue.
root/app/scss/vars.scss
:export {
cursor: #fff;
}
root/app/app.vue
<template>
<div id="yes">
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import colors from '#/scss/vars.scss';
export default Vue.extend({
mounted() {
console.log(colors.cursor);
},
});
</script>
<style >
</style>
I have read approximately 30 different stackoverflow questions that appear to be dealing with the similar problem of importing variables into the style block of the .vue file, as well as the identical problem of importing the variables directly into the Javascript code. As a result, my webpack.config.js looks like the following:
root/webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const env = process.env.NODE_ENV
module.exports = {
entry: './app/index.ts',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'staticfiles')
},
resolve: {
extensions: [ '.ts', '.js', '.vue', '.scss', '.sass'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'#': path.resolve(__dirname, '/app/')
}
},
plugins: [
new HtmlWebpackPlugin(),
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin(),
new VueLoaderPlugin()
],
module: {
rules: [
{
enforce: 'pre',
test: /\.(js|vue|ts)$/,
loader: 'eslint-loader',
exclude: /node_modules/
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
}
// other vue-loader options go here
}
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
{
test: /\.s(a|c)ss$/,
use: [ {
loader: "style-loader",
options: {
sourceMap: env === 'development',
}
}, {
loader: "css-loader",
options: {
sourceMap: env === 'development',
}
}, {
loader: "sass-loader",
options: {
sourceMap: env === 'development',
}
},
'vue-style-loader'],
}]
}
};
I have also tried, in the test: /\.s(a|c)ss$/ section, to put vue-style-loader at the beginning of the array.
I have tried many combinations of filenames when attempting to import the .scss file, such as relative (../scss/vars.scss), removing the extension, using .css as an extension, etc.
The error I get is:
ERROR in /home/Documents/application/app/app.vue.ts
[tsl] ERROR in /home/Documents/application/app/app.vue.ts(10,28)
TS2307: Cannot find module '#/scss/vars.scss'.
My question:
In a project that uses vue-style-loader and vue-loader to build .vue files with webpack, how can I import .scss variables into the <script> portion of a .vue file? (please note - I am NOT attempting to import them into the <style> section of the .vue file)
An example based on my comment:
SCSS fragment:
$foo: #333;
body {
--variable-foo: $foo;
}
And then anywhere in the JavaScript
const value = document.body.style.getPropertyValue("--variable-foo");
console.log(value); // outputs "#333"

How to access webpack generated js file from index.html

I have a Flask app that is serving a template index.html, which in turn access a javascript file generated by webpack. The issue is that I am generating a hash for the webpack generated file, to prevent the browser from caching, and I cannot figure out how to access the webpack generated file, as the hash name can change. For example, if the webpack generates a file called bundle-cdcf74127a4e321fbcf0.js, I would not know the hash function cdcf74127a4e321fbcf0 ahead of time, and so I could not access it in index.html.
Here is my webpack config file:
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: "./js/main.js",
output: {
filename: "static/bundle-[hash].js",
},
resolveLoader: {
moduleExtensions: ['-loader']
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['react', 'es2015', 'stage-0']
}
},
{
test: /\.css$/,
loader: 'style-loader',
},
{
test: /\.css$/,
loader: 'css-loader',
query: {
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]'
}
}
]
},
plugins: [new CleanWebpackPlugin(['static/bundle*.js'])]
};
The code used to call the webpack generated file in index.html is below (this code does not work, as it appears that asterisk search does not work in the file name here):
<body>
<div id="app"></div>
<script src="bundle*.js"></script>
</body>
The flask app code goes like:
app = Flask(__name__, static_url_path='')
#app.route('/')
def default():
return render_template('index.html')
How would I fix this code so that it serves the webpack generated file?
You need to use the webpack plugin htmlWebpackPlugin, refer to https://github.com/jantimon/html-webpack-plugin#configuration.
You can provide a html template for this plugin to inject the js files generated by webpack, pay close attention to these configuration options: template , inject, chunks, 'hash'.
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
template: './index.html'
favicon: './favicon.ico',
filename: './dist/index.html'
inject: 'body',
chunks: ['vendor', 'app'],
minify: {
collapseWhitespace: true,
removeComments: true,
removeAttributeQuotes: true
}
})

How to include this node_module's .html asset in webpack?

I am using the package electron-notifications and it relies on a .html and .css file in its assets folder. This assets folder is not included in webpack (1.14.0) though.
I know I should not add a module as an entry point. I have come across a concept called code splitting, but I'm not clear on how that works and if that is what I need to be looking into further. Any advice you can give would be greatly appreciated.
webpack.config.production.js
import path from 'path';
import webpack from 'webpack';
import validate from 'webpack-validator';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import merge from 'webpack-merge';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import BabiliPlugin from 'babili-webpack-plugin';
import baseConfig from './webpack.config.base';
export default validate(merge(baseConfig, {
devtool: 'inline-source-map',
entry: [
'babel-polyfill',
'./app/index'
],
output: {
path: path.join(__dirname, 'app/dist'),
publicPath: '../dist/'
},
module: {
loaders: [
// Extract all .global.css to style.css as is
{
test: /\.global\.css$/,
// loaders: [
loader: ExtractTextPlugin.extract(
'style-loader',
'css-loader?sourceMap'
)
// ]
},
// Pipe other styles through css modules and append to style.css
{
test: /^((?!\.global).)*\.css$/,
// loaders: [
loader: ExtractTextPlugin.extract(
'style-loader',
'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
)
},
// Fonts
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff' },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff' },
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream' },
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file' },
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml' },
// Images
{
test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
loader: 'url-loader'
}
]
},
plugins: [
// https://webpack.github.io/docs/list-of-plugins.html#occurrenceorderplugin
// https://github.com/webpack/webpack/issues/864
new webpack.optimize.OccurrenceOrderPlugin(),
// NODE_ENV should be production so that modules do not perform certain development checks
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new BabiliPlugin(),
new ExtractTextPlugin('style.css', { allChunks: true }),
new HtmlWebpackPlugin({
filename: '../app.html',
template: 'app/app.html',
inject: false
})
],
// https://github.com/chentsulin/webpack-target-electron-renderer#how-this-module-works
target: 'electron-renderer'
}));
If you want that packages' CSS to be recognised by the webpack, you just add it to the style's(CSS's) loader block, as an include attribute along with "test" and "loader". In the include attribute point it to the node_modules/electron_notification path.
HTML of that package need not be included, since your Single Page Application, has it's own HTML, if needed try to replicate the class names there. But I doubt if you need to do that.

Node+React+Babel+Webpack, Unexpected token error

First of all, I REALLY tried to fix it myself, I found several similar questions here, but none helped me.
Getting this error:
ERROR in ./src/components/App.jsx
Module parse failed: D:\JS projects\habr-app\src\components\App.jsx Unexpected token (54:6)
You may need an appropriate loader to handle this file type.
| render() {
| return (
| <div className='App'>
| <h1>Hello World!</h1>
| <div>
# ./src/client.js 3:0-42
# multi (webpack)-dev-server/client?http://0.0.0.0:8050 webpack/hot/dev-server babel-polyfill ./src/client.js
client.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
ReactDOM.render ("<App />", document.getElementById('react-view'));
render function in App.jsx looks like this:
render() {
return (
<div className='App'>
<h1>Hello World!</h1>
<div>
<p>Введите Ваше имя:</p>
<div><input onChange={this.handleNameChange} /></div>
{this.renderGreetingWidget()}
</div>
</div>
);
}
webpack.config.js file:
global.Promise = require('bluebird');
var webpack = require('webpack');
var path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin');
var publicPath = 'http://localhost:8050/public/assets';
var cssName = process.env.NODE_ENV === 'production' ? 'styles-[hash].css' : 'styles.css';
var jsName = process.env.NODE_ENV === 'production' ? 'bundle-[hash].js' : 'bundle.js';
var plugins = [
new webpack.DefinePlugin({
'process.env': {
BROWSER: JSON.stringify(true),
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
}
}),
new ExtractTextPlugin(cssName)
];
if (process.env.NODE_ENV === 'production') {
plugins.push(
new CleanWebpackPlugin([ 'public/assets/' ], {
root: __dirname,
verbose: true,
dry: false
})
);
plugins.push(new webpack.optimize.DedupePlugin());
plugins.push(new webpack.optimize.OccurenceOrderPlugin());
}
module.exports = {
entry: ['babel-polyfill', './src/client.js'],
resolve: {
extensions: ['.js', '.jsx']
},
plugins: [
new ExtractTextPlugin("styles.css"),
new webpack.LoaderOptionsPlugin({
debug: true,
options: {
eslint: { configFile: '.eslintrc' }
}
})
],
output: {
path: `${__dirname}/public/assets/`,
filename: jsName,
publicPath
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
],
loaders: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!postcss-loader'})
},
{
test: /\.less$/,
loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!postcss-loader!less-loader'})
},
{ test: /\.gif$/, loader: 'url-loader?limit=10000&mimetype=image/gif' },
{ test: /\.jpg$/, loader: 'url-loader?limit=10000&mimetype=image/jpg' },
{ test: /\.png$/, loader: 'url-loader?limit=10000&mimetype=image/png' },
{ test: /\.svg/, loader: 'url-loader?limit=26000&mimetype=image/svg+xml' },
{ test: /\.(woff|woff2|ttf|eot)/, loader: 'url-loader?limit=1' },
{ test: /\.jsx?$/, loaders: ['react-hot-loader', 'babel-loader?presets[]=react,presets[]=es2015'],
exclude: [/node_modules/, /public/] , query: {presets: ['es2015', 'react', 'react-hot']} },
{ test: /\.json$/, loader: 'json-loader' },
]
},
devtool: process.env.NODE_ENV !== 'production' ? 'source-map' : null,
devServer: {
headers: { 'Access-Control-Allow-Origin': '*' }
}
};
The only way I can fix this problem - is to put quotes around html in render:
render() {
return (
`<div className='App'>
<h1>Hello World!</h1>
<div>
<p>Введите Ваше имя:</p>
<div><input onChange={this.handleNameChange} /></div>
{this.renderGreetingWidget()}
</div>
</div>`
);
}
But after that I'm gettin this error in browser and nodemon:
Invariant Violation: App.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
I checked and rechecked all dependencies, modules and my files.
Still, I cant find an error. Could someone help me, please?
P.S. Sorry for my awful English.
Funny thing. When I start nodemon without those quotes in App.jsx, my page loads, but without css. After that I add quotes in file App.jsx and now webpack-devserver builds everything right, and page gets .css after refresh. JS-script still doesnt work on it, but looks almost like it should... Right until I restart nodemon... It starts to show same error "Invariant Violation: App.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object."
The error tells you that you did not define a loader that can handle JSX, although it might look like you did in your webpack config. The problem is that you define both module.rules and module.loaders. When webpack sees module.rules it ignores module.loaders completely (though it still exists for compatibility reason). The fix is simple, just put all loaders under module.rules.
And there is also a problem in your .jsx? rule, because query (which is also deprecated and replaced with options) cannot be used for an array of loaders, but instead should be defined per loader in the array. Since you did it inline as string you don't need it at all.
To get a working config replace the module section with:
module: {
rules: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!postcss-loader'})
},
{
test: /\.less$/,
loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!postcss-loader!less-loader'})
},
{ test: /\.gif$/, loader: 'url-loader?limit=10000&mimetype=image/gif' },
{ test: /\.jpg$/, loader: 'url-loader?limit=10000&mimetype=image/jpg' },
{ test: /\.png$/, loader: 'url-loader?limit=10000&mimetype=image/png' },
{ test: /\.svg/, loader: 'url-loader?limit=26000&mimetype=image/svg+xml' },
{ test: /\.(woff|woff2|ttf|eot)/, loader: 'url-loader?limit=1' },
{ test: /\.jsx?$/, use: ['react-hot-loader', 'babel-loader?presets[]=react,presets[]=es2015'],
exclude: [/node_modules/, /public/] },
{ test: /\.json$/, loader: 'json-loader' },
]
},
In case you decide to use options, which is definitely more readable, your .jsx? rule would look like this:
{
test: /\.jsx?$/,
use: [
'react-hot-loader',
{ loader: 'babel-loader', options: { presets: ['react', 'es2015'] } },
],
exclude: [/node_modules/, /public/]
}
As shown in the docs for use.

Categories