Como permitir que o webpack-dev-server permita pontos de entrada do react-router


117

Estou criando um aplicativo que usa webpack-dev-server no desenvolvimento junto com o react-router.

Parece que webpack-dev-server é construído em torno da suposição de que você terá um ponto de entrada público em um lugar (ou seja, "/"), enquanto o react-router permite uma quantidade ilimitada de pontos de entrada.

Eu quero os benefícios do webpack-dev-server, especialmente o recurso de recarregamento a quente que é ótimo para produtividade, mas ainda quero ser capaz de carregar rotas definidas no react-router.

Como alguém poderia implementá-lo de forma que eles trabalhassem juntos? Você poderia executar um servidor expresso na frente do webpack-dev-server de forma a permitir isso?


Tenho uma versão extremamente hacky de algo aqui, mas é frágil e só permite a correspondência de rotas simples: github.com/natew/react-base (consulte make-webpack-config) e (app / routes.js)
— Nathan Wienert

Você conseguiu resolver esse problema Nathan? Se sim, como? Tente responder minha pergunta aqui stackoverflow.com/questions/31091702/… . Obrigado..!
— SudoPlz

Respostas:


69

Eu configurei um proxy para conseguir isso:

Você tem um servidor da web expresso regular que serve o index.html em qualquer rota, exceto se for uma rota de ativo. se for um ativo, a solicitação é enviada por proxy para o web-dev-server

seus pontos de entrada react hot ainda apontarão diretamente para o servidor de desenvolvimento webpack, então o hot recarregamento ainda funciona.

Vamos supor que você execute webpack-dev-server em 8081 e seu proxy em 8080. Seu arquivo server.js será semelhante a este:

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

agora faça seus pontos de entrada na configuração do webpack assim:

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

observe a chamada direta para 8081 para hotreload

também certifique-se de passar um url absoluto para a output.publicPathopção:

 output: {
     publicPath: "http://localhost:8081/assets/",
     // ...
 }

1
Ei, isso é incrível. Na verdade, cheguei a essa configuração pouco antes disso e postaria uma resposta, mas acho que você fez um trabalho melhor.
— Nathan Wienert

1
Uma pergunta, meio sem relação, então posso abrir uma nova pergunta se necessário, mas percebi que agora a saída do console do servidor de desenvolvimento webpack não é transmitida. Antes, você podia vê-lo compilar e ver a porcentagem aumentar, agora ele apenas bloqueia as saídas após a compilação.
— Nathan Wienert

Bom trabalho. É exatamente assim que deve ser feito. Eu adicionei uma observação sobre a output.publicPathopção, que também deve ser um url absoluto.
— Tobias K.

5
Seria mais fácil usar apenas um proxy webpack integrado . Assim você não interfere no próprio servidor, você deixa o servidor puro . Em vez disso, basta adicionar um pouco (3-5 linhas) à configuração do webpack. Graças a isso, você modifica apenas os scripts de desenvolvimento para fins de desenvolvimento e deixa o código de produção (server.js) em paz (ao contrário da sua versão) e isso é o caminho certo a seguir.
— jalooc

3
Esta resposta ainda está correta, embora um pouco datada. Maneiras mais simples estão disponíveis agora, procure historyApiFallback.
— Eugene Kulabuhov

102

Você deve definir historyApiFallbackde WebpackDevServercomo verdadeiro para que isso funcione. Aqui está um pequeno exemplo (ajuste para se adequar aos seus objetivos):

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

Você perderá a barra de status no topo do seu index.html, mas funciona muito bem :)
— swennemen

7
Esta deve ser a resposta aceita. Dos documentos do servidor de desenvolvimento do webpack: "Se você estiver usando a API de histórico HTML5, provavelmente precisará veicular seu index.html no lugar de 404 respostas, o que pode ser feito configurando historyApiFallback: true" Se eu entendi a questão corretamente, isso resolverá o problema.
— Sebastian

tão simples ... obrigado!
— smnbbrv

1
@smnbbrv Sem probs. Na verdade, ele usa connect-history-api-fallback por baixo e você pode passar um objeto com as opções específicas de middleware se quiser, em vez de apenas true.
— Juho Vepsäläinen

1
OU se você estiver usando o cli,webpack-dev-server --history-api-fallback
— Levi

27

Para qualquer pessoa que ainda esteja procurando por essa resposta. Eu criei um bypass de proxy simples que consegue isso sem muito trabalho e a configuração vai para o webpack.config.js

Tenho certeza de que existem maneiras muito mais elegantes de testar o conteúdo local usando regex, mas isso funciona para minhas necessidades.

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';
        }
      }
    }
  }
} 

Funcionou bem para mim
— Nath,

Funcionou muito bem! .. Obrigado!
— Dhrumil Bhankhar

Esta é a resposta perfeita, rápida e fácil.
— dominó de

12

Se você estiver executando webpack-dev-server usando CLI, poderá configurá-lo por meio de webpack.config.js passando o objeto devServer:

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

Isso irá redirecionar para index.html toda vez que 404 for encontrado.

NOTA: Se você estiver usando publicPath, você precisará passá-lo para devServer também:

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

Você pode verificar se tudo está configurado corretamente observando as primeiras linhas da saída (a parte com "404s retrocederá para: caminho ").

insira a descrição da imagem aqui


11

Para uma resposta mais recente, a versão atual do webpack (4.1.1), você pode apenas definir isso em seu webpack.config.js da seguinte forma:

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
    }
  };

A parte importante é historyApiFallback: true. Não há necessidade de executar um servidor personalizado, basta usar o cli:

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

2

Eu gostaria de acrescentar à resposta para o caso de quando você executa um aplicativo isomórfico (ou seja, renderizando o componente React do lado do servidor).

Nesse caso, você provavelmente também deseja recarregar automaticamente o servidor ao alterar um dos componentes do React. Você faz isso com o pipingpacote. Tudo que você precisa fazer é instalá-lo e adicionar require("piping")({hook: true})em algum lugar no início de seu server.js . É isso aí. O servidor será reiniciado após você alterar qualquer componente usado por ele.

Isso levanta outro problema - se você executar o servidor webpack a partir do mesmo processo que seu servidor expresso (como na resposta aceita acima), o servidor webpack também será reiniciado e recompilará seu pacote todas as vezes. Para evitar isso, você deve executar o servidor principal e o servidor webpack em processos diferentes para que a tubulação reinicie apenas o servidor expresso e não toque no webpack. Você pode fazer isso com o concurrentlypacote. Você pode encontrar um exemplo disso no react-isomorphic-starterkit . No package.json ele tem:

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

que executa os dois servidores simultaneamente, mas em processos separados.


Isso significa que alguns arquivos estão sendo assistidos duas vezes? Como os arquivos isomórficos / universais compartilhados?
— David Sinclair

1

historyApiFallback também pode ser um objeto em vez de um booleano, contendo as rotas.

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


-1

Isso funcionou para mim: basta adicionar os middlewares do webpack primeiro e o app.get('*'...resolvedor index.html depois,

assim, o expresso verificará primeiro se a solicitação corresponde a uma das rotas fornecidas pelo webpack (como: /dist/bundle.jsou /__webpack_hmr_) e, se não, moverá para o index.htmlcom o* resolvedor.

ie:

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)
})
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.