Cómo permitir que webpack-dev-server permita puntos de entrada desde react-router

117

Estoy creando una aplicación que usa webpack-dev-server en desarrollo junto con react-router.

Parece que webpack-dev-server se basa en la suposición de que tendrá un punto de entrada público en un lugar (es decir, "/"), mientras que react-router permite una cantidad ilimitada de puntos de entrada.

Quiero los beneficios de webpack-dev-server, especialmente la función de recarga en caliente que es excelente para la productividad, pero aún quiero poder cargar rutas configuradas en react-router.

¿Cómo podría uno implementarlo para que trabajen juntos? ¿Podría ejecutar un servidor rápido frente a webpack-dev-server de tal manera que permita esto?

Nathan Wienert
fuente
Tengo una versión extremadamente hacky de algo aquí, pero es frágil y solo permite que las rutas simples coincidan: github.com/natew/react-base (ver make-webpack-config) y (app / route.js)
Nathan Wienert
¿Conseguiste solucionar este problema Nathan? ¿Si es así, cómo? Intente responder mi pregunta aquí stackoverflow.com/questions/31091702/… . Gracias..!
SudoPlz

Respuestas:

69

Configuré un proxy para lograr esto:

Tiene un servidor web expreso regular que sirve el index.html en cualquier ruta, excepto si es una ruta de activos. si es un activo, la solicitud se envía al servidor web-dev-server

sus puntos de entrada de react hot seguirán apuntando directamente al servidor de desarrollo webpack, por lo que la recarga en caliente aún funciona.

Supongamos que ejecuta webpack-dev-server en 8081 y su proxy en 8080. Su archivo server.js se verá así:

"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);

ahora haga sus puntos de entrada en la configuración del paquete web así:

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

tenga en cuenta la llamada directa al 8081 para hotreload

también asegúrese de pasar una URL absoluta a la output.publicPathopción:

 output: {
     publicPath: "http://localhost:8081/assets/",
     // ...
 }
Retozi
fuente
1
Oye, esto es asombroso. De hecho, llegué a esta configuración poco antes de esto e iba a publicar una respuesta, pero creo que hiciste un mejor trabajo.
Nathan Wienert
1
Una pregunta, algo no relacionada, así que puedo abrir una nueva pregunta si es necesario, pero me doy cuenta de que ahora la salida de la consola del servidor de desarrollo del paquete web no se transmite. Antes, podía verlo compilar y ver cómo aumentaban los porcentajes, ahora solo bloquea las salidas después de la compilación.
Nathan Wienert
Bien hecho. Así es exactamente como debe hacerse. Agregué una nota sobre la output.publicPathopción, que también debería ser una URL absoluta.
Tobias K.
5
En su lugar, sería más fácil usar un proxy de paquete web integrado . Por lo tanto, no interfiere en el servidor en sí, deja el servidor puro . En su lugar, solo agrega una pequeña (3-5 líneas) a la configuración del paquete web. Gracias a eso, modifica solo los scripts de desarrollo para fines de desarrollo y deja el código de producción (server.js) en paz (a diferencia de su versión) y, en mi opinión, ese es el camino correcto a seguir.
jalooc
3
Esta respuesta sigue siendo correcta, aunque un poco anticuada. Ahora hay disponibles formas más sencillas, busque historyApiFallback.
Eugene Kulabuhov
102

Se debe ajustar historyApiFallbackde WebpackDevServertan cierto para que esto funcione. Aquí hay un pequeño ejemplo (modifíquelo para que se ajuste a sus propósitos):

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
fuente
Se perderá la barra de estado en la parte superior de su index.html, pero esto funciona muy bien :)
swennemen
7
Esta debería ser la respuesta aceptada. De los documentos del servidor de desarrollo de paquete web: "Si está utilizando la API de historial HTML5, probablemente necesite entregar su index.html en lugar de respuestas 404, lo que se puede hacer configurando historyApiFallback: true" Si entiendo la pregunta correctamente, esto se resolverá el problema.
Sebastian
tan simple ... ¡Gracias!
smnbbrv
1
@smnbbrv No hay problemas. En realidad, usa connect-history-api-fallback debajo y puede pasar un objeto con las opciones específicas de middleware si lo desea en lugar de solo true.
Juho Vepsäläinen
1
O si está usando el cli,webpack-dev-server --history-api-fallback
Levi
27

Para cualquier otra persona que todavía esté buscando esta respuesta. Reuní un bypass de proxy simple que logra esto sin mucha molestia y la configuración entra en webpack.config.js

Estoy seguro de que hay formas mucho más elegantes de probar el contenido local usando expresiones regulares, pero esto funciona para mis necesidades.

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
fuente
Funcionó bien para mí
Nath
¡Funcionó muy bien! .. ¡Gracias!
Dhrumil Bhankhar
Esta es la respuesta perfecta, rápida y sencilla.
domino
12

Si está ejecutando webpack-dev-server usando CLI, puede configurarlo a través de webpack.config.js pasando el objeto devServer:

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

Esto redirigirá a index.html cada vez que se encuentre el 404.

NOTA: Si está utilizando publicPath, también deberá pasarlo a devServer:

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

Puede verificar que todo esté configurado correctamente observando las primeras líneas de la salida (la parte con "404s volverá a: ruta ").

ingrese la descripción de la imagen aquí

Eugene Kulabuhov
fuente
11

Para una respuesta más reciente, la versión actual de webpack (4.1.1) puede configurar esto en su webpack.config.js como tal:

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 parte importante es historyApiFallback: true. No es necesario ejecutar un servidor personalizado, solo use el cli:

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

Me gustaría agregar a la respuesta para el caso cuando ejecuta una aplicación isomorfa (es decir, renderizando el componente React del lado del servidor).

En este caso, probablemente también desee volver a cargar automáticamente el servidor cuando cambie uno de sus componentes de React. Haz esto con el pipingpaquete. Todo lo que tienes que hacer es instalarlo y agregarlo require("piping")({hook: true})en algún lugar al principio de tu server.js . Eso es. El servidor se reiniciará después de que cambie cualquier componente utilizado por él.

Sin embargo, esto plantea otro problema: si ejecuta el servidor webpack desde el mismo proceso que su servidor express (como en la respuesta aceptada anteriormente), el servidor webpack también se reiniciará y volverá a compilar su paquete cada vez. Para evitar esto, debe ejecutar su servidor principal y el servidor de paquete web en diferentes procesos para que la tubería reinicie solo su servidor express y no toque el paquete web. Puede hacer esto con concurrentlypackage. Puede encontrar un ejemplo de esto en react-isomorphic-starterkit . En el package.json tiene:

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

que ejecuta ambos servidores simultáneamente pero en procesos separados.

Viacheslav
fuente
¿Significa esto que algunos archivos se están viendo dos veces? ¿Como los archivos compartidos isomórficos / universales?
David Sinclair
1

historyApiFallback también puede ser un objeto en lugar de un booleano, que contiene las rutas.

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

Esto funcionó para mí: simplemente agregue el middlewares del paquete web primero y el solucionador app.get('*'...index.html más tarde,

por lo que express primero verificará si la solicitud coincide con una de las rutas proporcionadas por el paquete web (como: /dist/bundle.jso /__webpack_hmr_) y si no, se moverá a la index.htmlcon el *solucionador.

es decir:

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
fuente