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?

Nathan Wienert
fonte
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/",
     // ...
 }
Retozi
fonte
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);
});
Juho Vepsäläinen
fonte
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';
        }
      }
    }
  }
} 
Werner Weber
fonte
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

Eugene Kulabuhov
fonte
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"
  },
Michael Brown
fonte
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.

Viacheslav
fonte
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' }
  ]
}
Tom Roggero
fonte
-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)
})
Graham Norton
fonte