質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.22%

webpack-dev-serverでCORSが回避できない。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 118

yu-imu

score 31

実現したいこと

webpack-dev-serverで開発できるように環境を構築したい。
webpackへ置き換えてwebpack --progress --mode=developmentでは正常に動作するが、
webpack-dev-serverでassetsを取得するとCORSが発生し画面ロードが途中で消えてしまう。

背景

CORS対策として、webpack.config.jsに'Access-Control-Allow-Origin': '*',等を記入しても効果がなく
調べても良さそうなアイディアがなかったので質問しました。

構成

元々webpackerをベースにしたruby on rails × reactで動くアプリケーションを
http://localhost:3000/ にrailsのアプリケーションを起動
http://localhost:3035/ にwebpack-dev-serverを起動

発生している問題・エラーメッセージ

chromeでhttp://localhost:3000/をアクセスした場合です。
http://localhost:3035/には、CORSを対策したheaderが付与されリクエストは通るのですが、
rails側のhttp://localhost:3000/にアクセスすると headerが付与されず失敗となります。(※添付画像参照)

scheduler.development.js:178 Uncaught Error: A cross-origin error was thrown. React doesn't have access to the actual error object in development. See https://fb.me/react-crossorigin-error for more information.

原因画像
※webpackおよびrails側のログにはエラーはありません。

ソースコード

webpack.config.js
→devServer箇所を追記

const path = require('path')
const glob = require('glob')
const webpack = require('webpack')
const ManifestPlugin = require('webpack-manifest-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

let entries = {}
glob.sync('./frontend/**/*.{js,jsx,css,scss,sass}').map(function (file) {
  let name = file.split('/')[2].split('.')[0]
  entries[name] = file
})

module.exports = (env, argv) => {
  const IS_DEV = argv.mode === 'development'
  return {
    entry: entries,
    output: {
      filename: 'javascripts/[name]-[hash].js',
      path: path.resolve(__dirname, 'public/assets')
    },
    plugins: [
      new MiniCssExtractPlugin({
        filename: 'stylesheets/[name]-[hash].css'
      }),
      new ManifestPlugin({
        writeToFileEmit: true
      }),
      new webpack.HotModuleReplacementPlugin(),
    ],
    module: {
      rules: [
        {
          test: /\.(c|sc)ss$/,
          use: [
            {
              loader: MiniCssExtractPlugin.loader,
              options: {
                publicPath: path.resolve(__dirname, 'public/assets/stylesheets')
              }
            },
            'css-loader',
            'sass-loader'
          ]
        },

        {
          test: /\.jsx$/,
          use: [
            {
              loader: 'babel-loader?cacheDirectory',
              query: {
                presets: [
                  "@babel/preset-env",
                  "@babel/preset-react",
                ]
              }
            }
          ],
          exclude: /node_modules/,
        },
        {
          test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
          loader: 'file-loader',
          options: {
            name: '[name]-[hash].[ext]',
            outputPath: 'images/',
            publicPath: function (path) {
              return 'images/' + path
            }
          }
        }
      ],
    },
    resolve: {
      extensions: ['.js', '.scss', 'css', '.jsx', '.jpg', '.png', '.gif', ' ']
    },
    devServer: {
      host: 'localhost',
      port: 3035,
      publicPath: 'http://localhost:3035/public/assets/',
      contentBase: path.resolve(__dirname, 'public/assets'),
      hot: true,
      disableHostCheck: true,
      historyApiFallback: true,
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
        "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
      }
    }
  }
}

/app/helpers/webpack_bundle_helper.rb
→manifest.jsonを修正

require "open-uri"
module WebpackBundleHelper
  class BundleNotFound < StandardError; end

  def javascript_bundle_tag(entry, **options)
    path = asset_bundle_path("#{entry}.js")

    options = {
      src: path,
      defer: true
    }.merge(options)

    # async と defer を両方指定した場合、ふつうは async が優先されるが、
    # defer しか対応してない古いブラウザの挙動を考えるのが面倒なので、両方指定は防いでおく
    options.delete(:defer) if options[:async]

    javascript_include_tag '', **options
  end

  def stylesheet_bundle_tag(entry, **options)
    path = asset_bundle_path("#{entry}.css")

    options = {
      href: path
    }.merge(options)

    stylesheet_link_tag '', **options
  end

  def image_bundle_tag(entry, **options)
    raise ArgumentError, "Extname is missing with #{entry}" unless File.extname(entry).present?
    image_tag asset_bundle_path(entry), **options
  end

  private

  def asset_server
    port = Rails.env === "development" ? "3035" : "3000"
    "http://#{request.host}:#{port}"
  end

  def pro_manifest
    File.read("public/assets/manifest.json")
  end

  def dev_manifest
    OpenURI.open_uri("#{asset_server}/public/assets/manifest.json").read
  end

  def manifest
    return @manifest ||= JSON.parse(pro_manifest) if Rails.env.production?
    return @manifest ||= JSON.parse(dev_manifest) if Rails.env.development?
  end

  def valid_entry?(entry)
    return true if manifest.key?(entry)
    raise BundleNotFound, "Could not find bundle with name #{entry}"
  end

  def asset_bundle_path(entry, **options)
    valid_entry?(entry)
    asset_path("#{asset_server}/public/assets/" + manifest.fetch(entry), **options)
  end
end

補足情報(FW/ツールのバージョンなど)

railsにcors対策でgem 'rack-cors'を入れたこともありますが、効果がなかったです。

参考にした資料
Rails環境でJS , CSSをwebpackで完全に管理する

フロントエンド原理主義者が目論んだ脱webpacker

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

check解決した方法

0

Rails側でdev-serverへプロキシを通す実装を行いました。
実装は、「フロントエンド原理主義者が目論んだ脱webpacker」を参考にmiddlwareを作成しました。

自分の場合は、jsとimageと lazy loadしたjsの3つがlocalhost:3000からlocalhost:3035の任意のパスにアクセスできるように力技で実装しました。(※lazy loadしたreactがうまくproxyを通らず別実装にしましたがそこに気づくまでが大変でした。)

require "rack/proxy"

class DevServerProxy < Rack::Proxy

  def initialize(app)
    @app = app
  end

  def perform_request(env)
    if env["PATH_INFO"].include?("/images/") 
      #画像関連
      change_http_host(env)
      env["PATH_INFO"] = "/images/" + env["PATH_INFO"].split("/").last
      super
    elsif env["PATH_INFO"].include?("/packs/")
      #通常のReactコード
      change_http_host(env)
      super
    elsif env["PATH_INFO"].match("/\.+\-.+\.js")
      #lazy loadしているReactコード
      change_http_host(env)
      env["PATH_INFO"] = "/packs/" + env["PATH_INFO"].split("/").last
      super
    else
      @app.call(env)
    end
  end

  private

  def change_http_host(env)
    dev_server = env["HTTP_HOST"].gsub(":3000", ":3035")
    env["HTTP_HOST"] = dev_server
    env["HTTP_X_FORWARDED_HOST"] = dev_server
    env["HTTP_X_FORWARDED_SERVER"] = dev_server
  end
end

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.22%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる