Comment autoriser webpack-dev-server à autoriser les points d'entrée de react-router

117

Je crée une application qui utilise webpack-dev-server en développement aux côtés de react-router.

Il semble que webpack-dev-server est construit autour de l'hypothèse que vous aurez un point d'entrée public à un endroit (c'est-à-dire "/"), alors que react-router permet un nombre illimité de points d'entrée.

Je veux les avantages du serveur webpack-dev-server, en particulier la fonction de rechargement à chaud qui est excellente pour la productivité, mais je veux toujours pouvoir charger les routes définies dans react-router.

Comment pourrait-on le mettre en œuvre de manière à ce qu'ils travaillent ensemble? Pourriez-vous exécuter un serveur express devant webpack-dev-server de manière à permettre cela?

Nathan Wienert
la source
J'ai une version extrêmement hacky de quelque chose ici, mais elle est fragile et ne permet que des routes simples de correspondre: github.com/natew/react-base (voir make-webpack-config) et (app / routes.js)
Nathan Wienert
Avez-vous réussi à résoudre ce problème Nathan? Si c'est le cas, comment? Veuillez essayer de répondre à ma question ici stackoverflow.com/questions/31091702/… . Je vous remercie..!
SudoPlz

Réponses:

69

J'ai mis en place un proxy pour y parvenir:

Vous disposez d'un serveur Web express régulier qui sert l'index.html sur n'importe quel itinéraire, sauf s'il s'agit d'un itinéraire d'actif. s'il s'agit d'un actif, la requête est envoyée par proxy au serveur web-dev-server

vos points d'entrée réactifs pointeront toujours directement vers le serveur de développement webpack, donc le rechargement à chaud fonctionne toujours.

Supposons que vous exécutiez webpack-dev-server sur 8081 et votre proxy sur 8080. Votre fichier server.js ressemblera à ceci:

"use strict";
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./make-webpack-config')('dev');

var express = require('express');
var proxy = require('proxy-middleware');
var url = require('url');

## --------your proxy----------------------
var app = express();
## proxy the request for static assets
app.use('/assets', proxy(url.parse('http://localhost:8081/assets')));

app.get('/*', function(req, res) {
    res.sendFile(__dirname + '/index.html');
});


# -----your-webpack-dev-server------------------
var server = new WebpackDevServer(webpack(config), {
    contentBase: __dirname,
    hot: true,
    quiet: false,
    noInfo: false,
    publicPath: "/assets/",

    stats: { colors: true }
});

## run the two servers
server.listen(8081, "localhost", function() {});
app.listen(8080);

maintenant, créez vos points d'entrée dans la configuration du webpack comme ceci:

 entry: [
     './src/main.js',
     'webpack/hot/dev-server',
     'webpack-dev-server/client?http://localhost:8081'
 ]

notez l'appel direct au 8081 pour hotreload

assurez-vous également de transmettre une URL absolue à l' output.publicPathoption:

 output: {
     publicPath: "http://localhost:8081/assets/",
     // ...
 }
Retozi
la source
1
Hé, c'est génial. En fait, je suis arrivé à cette configuration peu de temps avant et j'allais publier une réponse, mais je pense que vous avez fait un meilleur travail.
Nathan Wienert
1
Une question, en quelque sorte sans rapport, donc je peux ouvrir une nouvelle question si nécessaire, mais je remarque que maintenant la sortie de la console du serveur de développement Webpack n'est pas diffusée. Avant, vous pouviez le regarder compiler et voir les pourcentages augmenter, maintenant il ne fait que bloquer les sorties après compilation.
Nathan Wienert
Bien joué. C'est exactement comment cela doit être fait. J'ai ajouté une note sur l' output.publicPathoption, qui devrait également être une URL absolue.
Tobias K.
5
Il serait plus simple d'utiliser à la place un proxy Webpack intégré . Ainsi, vous n'interférez pas dans le serveur lui-même, vous laissez le serveur pur . Au lieu de cela, vous faites juste un petit ajout (3-5 lignes) à la configuration du pack Web. Grâce à cela, vous ne modifiez que les scripts de développement à des fins de développement et laissez le code de production (server.js) en paix (contrairement à votre version) et imo c'est la bonne voie à suivre.
jalooc
3
Cette réponse est toujours correcte bien qu'un peu datée. Des moyens plus simples sont disponibles maintenant, recherchez historyApiFallback.
Eugene Kulabuhov
102

Vous devez définir historyApiFallbackde WebpackDevServervrai pour que cela fonctionne. Voici un petit exemple (ajustez pour répondre à vos besoins):

var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');

var config = require('./webpack.config');


var port = 4000;
var ip = '0.0.0.0';
new WebpackDevServer(webpack(config), {
    publicPath: config.output.publicPath,
    historyApiFallback: true,
}).listen(port, ip, function (err) {
    if(err) {
        return console.log(err);
    }

    console.log('Listening at ' + ip + ':' + port);
});
Juho Vepsäläinen
la source
Vous manquerez la barre d'état en haut de votre index.html, mais cela fonctionne très bien :)
swennemen
7
Cela devrait être la réponse acceptée. Extrait de la documentation du serveur de développement Webpack: "Si vous utilisez l'API d'historique HTML5, vous devrez probablement servir votre index.html à la place de 404 réponses, ce qui peut être fait en définissant historyApiFallback: true" Si je comprends correctement la question, cela résoudra le problème.
Sebastian le
si simple ... Merci!
smnbbrv
1
@smnbbrv Aucun problème. Il utilise en fait connect-history-api-fallback en dessous et vous pouvez passer un objet avec les options spécifiques au middleware si vous le souhaitez au lieu de simplement true.
Juho Vepsäläinen
1
OU si vous utilisez le cli,webpack-dev-server --history-api-fallback
Levi
27

Pour tous ceux qui recherchent encore cette réponse. J'ai mis en place un simple contournement de proxy qui réalise cela sans trop de tracas et la configuration va dans le webpack.config.js

Je suis sûr qu'il existe des moyens beaucoup plus élégants de tester le contenu local à l'aide de regex, mais cela fonctionne pour mes besoins.

devServer: {
  proxy: { 
    '/**': {  //catch all requests
      target: '/index.html',  //default target
      secure: false,
      bypass: function(req, res, opt){
        //your custom code to check for any exceptions
        //console.log('bypass check', {req: req, res:res, opt: opt});
        if(req.path.indexOf('/img/') !== -1 || req.path.indexOf('/public/') !== -1){
          return '/'
        }

        if (req.headers.accept.indexOf('html') !== -1) {
          return '/index.html';
        }
      }
    }
  }
} 
Werner Weber
la source
A bien fonctionné pour moi
Nath
A bien fonctionné! .. Merci!
Dhrumil Bhankhar
C'est juste une réponse parfaite, rapide et facile.
domino le
12

Si vous exécutez webpack-dev-server en utilisant la CLI, vous pouvez le configurer via webpack.config.js en passant l'objet devServer:

module.exports = {
  entry: "index.js",
  output: {
    filename: "bundle.js"
  },
  devServer: {
    historyApiFallback: true
  }
}

Cela redirigera vers index.html à chaque fois qu'il 404 est rencontré.

REMARQUE: Si vous utilisez publicPath, vous devrez également le transmettre à devServer:

module.exports = {
  entry: "index.js",
  output: {
    filename: "bundle.js",
    publicPath: "admin/dashboard"
  },
  devServer: {
    historyApiFallback: {
      index: "admin/dashboard"
    }
  }
}

Vous pouvez vérifier que tout est correctement configuré en regardant les premières lignes de la sortie (la partie avec "404s se repliera sur: chemin ").

entrez la description de l'image ici

Eugène Kulabuhov
la source
11

Pour une réponse plus récente, la version actuelle de webpack (4.1.1) vous pouvez simplement définir ceci dans votre webpack.config.js comme ceci:

const webpack = require('webpack');

module.exports = {
    entry: [
      'react-hot-loader/patch',
      './src/index.js'
    ],
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: ['babel-loader']
            },
            {
                test: /\.css$/,
                exclude: /node_modules/,
                use: ['style-loader','css-loader']
            }
        ]
    },
    resolve: {
      extensions: ['*', '.js', '.jsx']  
    },
    output: {
      path: __dirname + '/dist',
      publicPath: '/',
      filename: 'bundle.js'
    },
    plugins: [
      new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
      contentBase: './dist',
      hot: true,
      historyApiFallback: true
    }
  };

La partie importante est historyApiFallback: true. Pas besoin d'exécuter un serveur personnalisé, utilisez simplement le cli:

"scripts": {
    "start": "webpack-dev-server --config ./webpack.config.js --mode development"
  },
Michael Brown
la source
2

Je voudrais ajouter à la réponse pour le cas où vous exécutez une application isomorphe (c'est-à-dire le rendu côté serveur du composant React.)

Dans ce cas, vous souhaitez probablement également recharger automatiquement le serveur lorsque vous modifiez l'un de vos composants React. Vous faites cela avec le pipingpackage. Tout ce que vous avez à faire est de l'installer et de l'ajouter require("piping")({hook: true})quelque part au début de votre server.js . C'est tout. Le serveur redémarrera après avoir modifié l'un des composants qu'il utilise.

Cela pose cependant un autre problème - si vous exécutez le serveur webpack à partir du même processus que votre serveur express (comme dans la réponse acceptée ci-dessus), le serveur webpack redémarrera également et recompilera votre bundle à chaque fois. Pour éviter cela, vous devez exécuter votre serveur principal et votre serveur Webpack dans des processus différents afin que la tuyauterie ne redémarre que votre serveur express et ne touche pas Webpack. Vous pouvez le faire avec concurrentlypackage. Vous pouvez en trouver un exemple dans react-isomorphic-starterkit . Dans le package.json, il a:

"scripts": {
    ...
    "watch": "node ./node_modules/concurrently/src/main.js --kill-others 'npm run watch-client' 'npm run start'"
  },

qui exécute les deux serveurs simultanément mais dans des processus séparés.

Viacheslav
la source
Cela signifie-t-il que certains fichiers sont visionnés deux fois? Tels que les fichiers isomorphes / universels partagés?
David Sinclair
1

historyApiFallback peut également être un objet au lieu d'un booléen, contenant les routes.

historyApiFallback: navData && {
  rewrites: [
      { from: /route-1-regex/, to: 'route-1-example.html' }
  ]
}
Tom Roggero
la source
-1

Cela a fonctionné pour moi: ajoutez simplement les middlewares webpack en premier et le app.get('*'...résolveur index.html plus tard,

so express vérifiera d'abord si la requête correspond à l'une des routes fournies par webpack (comme: /dist/bundle.jsou /__webpack_hmr_) et sinon, il se déplacera vers le index.htmlavec le *résolveur.

c'est à dire:

app.use(require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
}))
app.use(require('webpack-hot-middleware')(compiler))
app.get('*', function(req, res) {
  sendSomeHtml(res)
})
Graham Norton
la source