Router on vanilla JS for SPA that uses history api? - javascript

I need a vanilla js router for a spa site. Here on the github I found it seems like an ideal implementation, but there is one problem. This implementation, and everything that I found on the Internet, works well only with hash, through the history api that I need, they refuse to work, tell me what I'm doing wrong, or tell me another working implementation.
So I just take the Router class code, and set the routes I need:
const router = new Router({
mode: 'history',
root: '/'
});
router
.add('', () => {
console.log('deafult page');
}).add('settings', () => {
console.log('settings page');
}).add(/task-list/, () => {
console.log('task-list page');
})
And only the console.log('deafult page') is executed, if I enter http://localhost:3000/settings in the line, then it will give me Cannot GET /settings
if i use click event
document.addEventListener('click',()=>router.navigate('settings'))
then the link will become http://localhost:3000/settings, but the code console.log('settings page'); will not work

Just need to add webpack settings
devServer: {
contentBase: './dist',
port: 3000,
historyApiFallback: true, // its enable SPA routing through historyApi
}

Related

Write to disk option for Vite

recently I have started working with vite on a couple of small projects and found it very interesting, however got a blocker once tried to work on ExpressJS + Svelte coupled project.
I usually use Express as BFF (Backend For Frontend) when it comes to working on rather more serious projects since it allows me to go for HTTPOnly cookies as well as proxy gateway for the frontend. However for development (specially when it comes to oauth2) it is hard to develop the spa separated form the server so what I usually do with webpack is activating the WriteToDisk option for devserver which then allows me to have my development build in the dist folder.
Example with webpack will be something like the webpack config below for the frontend:
module.exports = {
devServer: {
devMiddleware: {
writeToDisk: true,
},
},
//...
}
and then on the server basically rendering the dist as static folder:
app.get(
"*",
(req, res, next) => {
if (req.session.isAuth) return next();
else return res.redirect(staticURL);
},
(req, res) => {
return res.sendFile(staticProxyPage());
}
);
My problem
I can not find in vite's documentation any APIs to do something like this, does anyone have any experience with such cases?
if it is possible with the help of plugins, can you please provide references to the plugin or dev logs of it?
Many Thanks :)
Here is an example plugin I was able to hack together. You might need to modify it to suit your needs:
// https://vitejs.dev/guide/api-plugin.html#universal-hooks=
import {type Plugin} from 'vite';
import fs from 'fs/promises';
import path from 'path';
const writeToDisk: () => Plugin = () => ({
name: 'write-to-disk',
apply: 'serve',
configResolved: async config => {
config.logger.info('Writing contents of public folder to disk', {timestamp: true});
await fs.cp(config.publicDir, config.build.outDir, {recursive: true});
},
handleHotUpdate: async ({file, server: {config, ws}, read}) => {
if (path.dirname(file).startsWith(config.publicDir)) {
const destPath = path.join(config.build.outDir, path.relative(config.publicDir, file));
config.logger.info(`Writing contents of ${file} to disk`, {timestamp: true});
await fs.access(path.dirname(destPath)).catch(() => fs.mkdir(path.dirname(destPath), {recursive: true}));
await fs.writeFile(destPath, await read());
}
},
});
In short, it copies the content of the publicDir (public) to the outDir (dist).
Only thing missing is the ability to watch the public folder and copy whatever changed to the dist folder again. It will also re-write the file if any of the files within the public folder changes.
Note: The use of fs.cp relies on an experimental API from node 16.
Feel free to modify as you please. Would be nice to be able to specify file globs to include/exclude, etc.

Do I remove webpack-dev-server and hot-module middleware code during production build, or when I get ready for production?

As the title suggests, when I'm ready to host the code for production, should I remove all usages of webpack-dev-middleware and webpack-hot-middleware from my server code as they are dev-dependencies? What's the best way to set this up so maybe I don't have to worry about this?
This is a snapshot of my server code:
// webpack -> HMR
const webpack = require("webpack");
const webpackConfig = require("../webpack.config");
const compiler = webpack(webpackConfig);
// webpack HMR init
app.use(
require("webpack-dev-middleware")(compiler, {
noInfo: false,
publicPath: webpackConfig.output.publicPath,
})
);
app.use(require("webpack-hot-middleware")(compiler));
...
app.get("/", async (req, res) => {
const initialContent = await serverRender();
res.render("index", {
...initialContent,
});
});
app.listen(port, () => {
console.log(`Express application listening on port ${port}`);
});
You should wrap your HMR code (or really, any development/environment specific setting) into it's own area. I wouldn't recommend taking it out of your code as you may come back to the application and want to update something. Having HMR is a pretty nice luxery, so I would just have you code sniff out the environment, and if it's development, run the associated code. Otherwise, don't run it.
How do you detect the environment in an express.js app?

Vue router on page refresh gets 404

I am using Vue router with history mode. On button click on the current page I route it to next page. On second page when i reload i get a 404. Is there a way to handle this in Vue and redirect it to home page.
export default new Router({
mode: "history",
routes: [
{
path: "/",
name: "first",
component: First
},
{
path: "/abc",
name: "abc",
component: Second,
props: true
},
you can use hash mode inested of history mode to resolve the problem on your router
let router = new Router({
mode: "hash",
routes: [
{
//and the urls with path on here ...
if any one of is facing the issue even after trying above solution, please find below method.
If you have vue.config.js which has
module.exports = {
publicPath: process.env.PUBLIC_URL || "", // <-- this is wrong
...
};
either remove the vue.config.js or try removing the publicPath from the exports object.
Also you can try below method if you dont want to remove the publicPath
module.exports = {
publicPath: process.env.PUBLIC_URL || "/", // <-- this is correct now (and default)
transpileDependencies: ["vuetify"],
};
This is related to how the history mode in Vue Router works. I have created a writeup about it, with a solution as well.
Depends on your server implementation you need to enable URL Rewrites there. Here's how this would be implemented using Express/Node.js:
const express = require('express');
const app = express();
const port = 80;
const buildLocation = 'dist';
app.use(express.static(`${buildLocation}`));
app.use((req, res, next) => {
if (!req.originalUrl.includes(buildLocation)) {
res.sendFile(`${__dirname}/${buildLocation}/index.html`);
} else {
next();
}
});
app.listen(port, () => console.info(`Server running on port ${port}`));
For more info, check out https://blog.fullstacktraining.com/404-after-refreshing-the-browser-for-angular-vue-js-app/. HTH
This usually happens, when you deploy the page to a hosting server. Check the official docs for more background info and specifiy tipps for different hosting entvironments
https://router.vuejs.org/guide/essentials/history-mode.html
We have resolved this on backend, we have filter where we intercept the request and redirect it to home page. We have made it configurable.

cannot GET error 404 on reload?

my react router is working fine with dev env, this is what I did in webpack dev server:
historyApiFallback: {
index: 'index.html',
}
so in production mode I wanted to do the same, I did it in express like this:
const indexPath = path.join(__dirname, '../public/index.html')
const publicPath = express.static(path.join(__dirname, '../public'))
app.use('/public', publicPath)
app.use('/graphql', graphQLHTTP(async (req) => {
let { user } = await getUser(req.headers.authorization);
if(!user) {
user = 'guest'
}
return {
schema,
pretty: true,
graphiql: true,
context: {
user,
}
}
}));
app.get('/', function (_, res) { res.sendFile(indexPath) });
I did not change anything with react-router-dom so I am am assuming the error is in my express config. so what's the equivalent of historyApiFallback in production mode? below is my webpack bundle config:
output: {
path: path.join(__dirname, 'public'),
filename: 'bundle.js',
publicPath: '/public/'
},
in my html I reference the bundle like this:
<script type="text/javascript" src="/public/bundle.js"></script>
I think a have the right config but when I reload I get cannot GET Error 404?
You should add this line to your app:
app.get('*', function (_, res) { res.sendFile(indexPath) });
Or you should use this package better: https://github.com/bripkens/connect-history-api-fallback
You should read more about history mode:
To get rid of the hash, we can use the router's history mode, which leverages the history.pushState API to achieve URL navigation without a page reload:
When using history mode, the URL will look "normal," e.g. http://oursite.com/user/id. Beautiful!
Here comes a problem, though: Since our app is a single page client-side app, without a proper server configuration, the users will get a 404 error if they access http://oursite.com/user/id directly in their browser. Now that's ugly.
Not to worry: To fix the issue, all you need to do is add a simple catch-all fallback route to your server. If the URL doesn't match any static assets, it should serve the same index.html page that your app lives in. Beautiful, again!

Webpack dev server hot mode not working

Heres my config:
devServer: {
contentBase: '/web/dist/',
hot: true,
stats: {colors: true},
inline: true
}
And here's the gulp task im running:
gulp.task('build', ['clean', 'styles', 'bower', 'media', 'data', 'homepage'], function(done) {
es6promise.polyfill();
console.log('STARTING DEV SERVER...');
server = new WebpackDevServer(webpack(webpackDevConfig), webpackDevConfig.devServer);
server.listen(8080, '0.0.0.0', function (err, stats) {
if (err) {
throw new gutil.PluginError("webpack-dev-server", err);
}
console.log('DEV SERVER STARTED');
done();
});
});
Everything works as expected except the hot loading (no refresh or change when I make changes to files). What am I doing wrong here?
You need to add <script src="http://localhost:8080/webpack-dev-server.js"></script> to your index.html It is not added when you use the API
"Notice that webpack configuration is not passed to WebpackDevServer API, thus devServer option in webpack configuration is not used in this case. Also, there is no inline mode for WebpackDevServer API. <script src="http://localhost:8080/webpack-dev-server.js"></script> should be inserted to HTML page manually."
(http://webpack.github.io/docs/webpack-dev-server.html)
maybe you also need to add 'webpack/hot/dev-server' as an entrypoint to your webpack config
be sure to set
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
in the webpackConfig as well
If you are using redux can try this.
For some random reason redux-devtools was not allowing hot reload for me. Try removing it from root component and redux compose config.
Note: Use redux devtool browser extension with this config in your store configuration: window.devToolsExtension ? window.devToolsExtension() : f => f
Also, must read: https://medium.com/#rajaraodv/webpacks-hmr-react-hot-loader-the-missing-manual-232336dc0d96#.ejpsmve8f
Or try hot reload 3:
example: https://github.com/gaearon/redux-devtools/commit/64f58b7010a1b2a71ad16716eb37ac1031f93915

Categories