可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a single app site (NodeJS) and I want to migrate from Express to Hapi, what I normally do is serve static files and route everything else to a single page which contains the angularjs app and the angular routing configuration.
// Express routing, first the static files
app.use( express.static(__dirname + '/public') );
// Second the api routes
app.get('/api', function(req, res){
res.send( {api: 'response' } )
});
// Finally everything else maps to the single page app:
app.get('*', function(req, res){
res.sendfile('./public/html/controllers.index.html')
});
In HapiJS I dont know how to replicate the same code (without using express.static middleware), because:
Hapi = require('hapi');
var server = new Hapi.Server('localhost', 84);
server.route({
method: 'GET',
path: '/{p*}',
handler: function (request, reply) {
reply.file('public/html/index.html');
}
});
In the code above, every request no matter what will be mapped to my single page ('public/html/index.html'), but if I do this, then the js, css, jpg & files will be mapped to the same file instead to the scripts, styles and images (a request to '/images/bg.png' will download the single page instead the image file).
I know that if I set up the path '/' to my single page and then '{p*}' to '{directory: {path: '/public'}}' then I will have the behaviour that I need, but theres one catch, if some user copy and paste an specific url (lets say '/account/login') and then hit enter, that route will be mapped in HapiJS and the response will be 'Not Found (404)', angular routing will never be able to respond.
Does anybody know how to solve this?
The key part of the question is:
- Use only HapiJS (no express or other middleware)
- Don't route every angular route (just route everything else not already routed to single page son angular can handle the routing)
回答1:
Not sure if this will help you out but your starting code is a bit "odd" for Hapi.js. This is what i use to set up a simple hapi.js SPA.
If you wish to use specific URLS like the Account/Login, you have to direct your path to that specific section.(path: '/account/login')
The difference lies in the fact that i'm pointing to the directory as a whole, incl. the different files, and you simply reply the index.html file. The listing parameter lets you decide if you want to show directory structure or not in your urls. Default is false.
More info here: http://hapijs.com/tutorials/serving-files#directory-handler
var Hapi = require('hapi');
var Path = require('path');
var server = new Hapi.Server(8080, 'localhost');
server.route({
method: 'GET',
path: '/{path*}',
handler: {
directory: {
path: './public',
listing: false,
index: true
}
}
});
server.start(function(){
console.log('server started');
});
回答2:
Inspired by the inert documentation, I solved this by serving everything out of a static directory. Then I added an onPostHandler to return the index file whenever a 404 was about to be returned. You client side router could then send a redirect to an existing 404.html file in the static files directory.
// Static Assets
server.route({
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: ['app/static'],
listing: false,
index: ['index.html']
}
}
});
// return index.html for everything else
server.ext('onPostHandler', (request, reply) => {
console.log('WORD');
const response = request.response;
if (response.isBoom && response.output.statusCode === 404) {
return reply.file('app/static/index.html');
}
return reply.continue();
});
回答3:
This answer is invalid. As @Al jey said in the comment. Hapi have their own algorithm to sort the routes, please don't follow this answer.
Similar to expressJS, Hapi handle route by order. Just define routes order by priority:
server.route(
{ // Angular/API Route
method: 'GET',
path: '/api',
handler: function (request, reply) {
reply( {api: 'response' } )
}
});
server.route({ // Other assets If you have
method: 'GET',
path: '/assets/{param*}',
handler: {
directory: {
path: './assets',
listing: false,
index: true
}
});
server.route({ // Everything else
method: 'GET',
path: '/{p*}',
handler: function (request, reply) {
reply.file('public/html/index.html');
}
});
回答4:
i had success with inert like described here. If you scroll down, you can find the section "Directory handler".
My solution looks like that:
var Hapi = require('hapi');
var Path = require('path');
var server = new Hapi.Server();
server.connection({
host: 'localhost',
port: 8001
});
server.register(require('inert'), (err) => {
if (err) {
throw err;
}
server.route({
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: 'public'
}
}
});
server.start((err) => {
if (err) {
throw err;
}
console.log('Server running at:', server.info.uri);
});
});
回答5:
Make sure you have inert as a plugin.
In your server.js (or whatever you named it), configure the relative path to serve static files see below
const server = new Hapi.Server({
connections: {
routes: {
cors: true,
files: {
relativeTo: Path.join(__dirname, 'public')
}
},
router: {
stripTrailingSlash: true
}
}
})
then for your route, register a new route plugin like below. This is assuming you entry index.html (for React or Angular) is inside the public directory as configured above
exports.register = (server, options, next) => {
server.route([{
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: '.',
redirectToSlash: true,
listing: false,
index: true
}
}
}])
server.ext('onPostHandler', (req, res) => {
const response = req.response
if (response.isBoom && response.output.statusCode >= 404) {
return res.file('index.html')
}
return res.continue()
})
next()
}
exports.register.attributes = {
name: 'static-route',
version: '1.0.0'
}
Now everytime HapiJS throws a 404 error, the route it is forwarded to your React / Angular App, which can then handle the route if it exists.
回答6:
For people using Hapi@18 and Inert.
const server = Hapi.server({
port: 3000,
routes: {
files: {
relativeTo: Path.join(__dirname, 'build'),
}
}
});
server.route({
method: 'GET',
path: '/static/{param?}',
handler: {
directory: {
path: '.',
redirectToSlash: true,
}
}
});
server.route({
method: 'GET',
path: '/{any*}',
handler: {
file: 'index.html'
}
});
Here my ./build
contains all the JS assets and index.html
. index.html
requests entrypoint ./build/main.js
as /static/main.js
. The rest of the scripts are relative to each other.