首页 > 解决方案 > 拒绝执行来自 '.../bundle.js' 的脚本,因为它的 MIME 类型 ('text/html') 不可执行,并且启用了严格的 MIME 类型检查

问题描述

我有一个带有 React/webpack 前端和 Django/python 后端的应用程序。过去它已成功部署到heroku。自从我在应用程序中做任何事情以来已经有一段时间了,在最近的一些更新之后,我想更新已部署的版本。Heroku 报告应用程序部署成功,但是当我尝试访问它时,出现错误:

拒绝执行来自“https://pairshead-2020.herokuapp.com/bundle.js”的脚本,因为它的 MIME 类型(“text/html”)不可执行,并且启用了严格的 MIME 类型检查。

这是我的 webpack.config.js:

const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/app.js',
  context: path.resolve(__dirname, 'frontend'),
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'frontend/dist'),
    publicPath: '/'
  },
  devtool: 'source-maps',
  module: {
    rules: [
      { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
      { test: /\.css$/, loader: ['style-loader', 'css-loader'] },
      { test: /\.s(a|c)ss$/, loader: ['style-loader', 'css-loader', 'sass-loader'] },
      { test: /\.woff2?$/, loader: 'file-loader' },
      { test: /\.(jpg|png|gif)$/, loader: 'file-loader' }
    ]
  },
  devServer: {
    contentBase: 'src',
    hot: true,
    open: true,
    port: 8000,
    watchContentBase: true,
    historyApiFallback: true,
    proxy: {
      '/api': 'http://localhost:4000'
    }
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'index.html',
      inject: 'body'
    })
  ]
}

这是我的 package.json:

{
  "name": "results",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack -p",
    "serve:backend": "python manage.py runserver 4000",
    "serve:frontend": "webpack-dev-server",
    "seed": "python manage.py loaddata results/fixtures.json"
  },
  "dependencies": {
    "@babel/core": "^7.5.5",
    "@babel/plugin-transform-runtime": "^7.6.0",
    "@babel/preset-env": "^7.6.0",
    "@babel/preset-react": "^7.0.0",
    "@babel/runtime": "^7.6.0",
    "@fortawesome/fontawesome-free": "^5.10.2",
    "axios": "^0.19.0",
    "babel-loader": "^8.0.6",
    "bulma": "^0.7.5",
    "copy-webpack-plugin": "^5.0.4",
    "css-loader": "^3.2.0",
    "file-loader": "^4.2.0",
    "html-webpack-plugin": "^3.2.0",
    "jsonwebtoken": "^8.5.1",
    "moment": "^2.24.0",
    "moment-duration-format": "^2.3.2",
    "node-sass": "^4.12.0",
    "react": "^16.9.0",
    "react-dom": "^16.9.0",
    "react-image": "^2.2.0",
    "react-router-dom": "^5.0.1",
    "react-select": "^3.0.4",
    "react-select-async-paginate": "^0.3.13",
    "react-toastify": "^5.4.0",
    "sass-loader": "^7.3.1",
    "style-loader": "^1.0.0",
    "webpack": "^4.39.2",
    "webpack-cli": "^3.3.7"
  },
  "devDependencies": {
    "babel-eslint": "^10.0.3",
    "eslint": "^6.3.0",
    "eslint-plugin-react": "^7.14.3",
    "webpack-dev-server": "^3.8.0"
  },
  "engines": {
    "node": "12.11.1",
    "python": "3.7.5"
  }
}

设置.py

"""
Django settings for project project.

Generated by 'django-admin startproject' using Django 2.2.5.

For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
# import dj_database_url
from dotenv import load_dotenv
import django_heroku
load_dotenv()
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/


SECRET_KEY = os.environ['SECRET_KEY']


# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['0.0.0.0', 'localhost', 'pairshead-results.herokuapp.com']


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'rest_framework',
    'results',
    'django_filters',
    'computed_property',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = [
    'http://localhost:3030',
]
CORS_ORIGIN_REGEX_WHITELIST = [
    'http://localhost:3030',
]

ROOT_URLCONF = 'project.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'project.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/

LANGUAGE_CODE = 'en-gb'

TIME_ZONE = 'Europe/London'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/

STATIC_URL = '/static/'

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 25,
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
    'PAGE_SIZE_QUERY_PARAM': 'page_size',
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication'
    ],
}

django_heroku.settings(locals())
# DATABASES['default'] = dj_database_url.config(conn_max_age=600, ssl_require=True)

urls.py(来自项目文件夹)

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('django-admin/', admin.site.urls),
    path('auth/', include('rest_framework.urls')),
    path('api/', include('results.urls')),
    path('api/', include('jwt_auth.urls')),
    path('', include('frontend.urls')),
]

wsgi.py

"""
WSGI config for project project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')

application = get_wsgi_application()

网址.py

from django.urls import path
from .views import clubs
from .views import events
from .views import bands
from .views import crews
from .views import competitors
from .views import times
from .views import results
from .views import masters_adjustments
from .views import original_event_category


urlpatterns = [
path('clubs/', clubs.ClubListView.as_view()),
path('club-data-import/', clubs.ClubDataImport.as_view()),
path('events/', events.EventListView.as_view()),
path('event-data-import/', events.EventDataImport.as_view()),
path('band-data-import/', bands.BandDataImport.as_view()),
path('bands/', bands.BandListView.as_view()),
path('crews/', crews.CrewListView.as_view(), name='crews-list'),
path('crews/<int:pk>', crews.CrewDetailView.as_view(), name='crews-detail'),
path('', crews.CrewListView.as_view()),
path('results/', results.ResultsListView.as_view()),
path('results-export/', results.ResultDataExport.as_view()),
path('crew-update-rankings/', crews.CrewUpdateRankings.as_view()),
path('crew-data-import/', crews.CrewDataImport.as_view()),
path('crew-data-export/', crews.CrewDataExport.as_view()),
path('competitor-data-export/', competitors.CompetitorDataExport.as_view()),
path('competitor-data-import/', competitors.CompetitorDataImport.as_view()),
path('race-times/', times.RaceTimeListView.as_view()),
path('race-times/<int:pk>', times.RaceTimeDetailView.as_view()),
path('crew-race-times/', times.CrewRaceTimesImport.as_view()),
path('masters-adjustments-import/', masters_adjustments.MastersAdjustmentsImport.as_view()),
path('original-event-import/', original_event_category.OriginalEventCategoryImport.as_view()),
]

我有一个包含 9 个视图文件的视图文件夹: init .py

from .bands import *
from .clubs import *
from .competitors import *
from .crews import *
from .events import *
from .times import *
from .results import *
from .masters_adjustments import *

过程文件

web: python manage.py runserver 0.0.0.0:$PORT --noreload

非常感谢任何人可以提供的任何帮助。在阅读了一些类似的问题后,我尝试在 webpack.config.js 中设置 {output: publicPath: '/'} 和 {historyApiFallback: true} 但似乎没有任何区别。

标签: reactjsherokuwebpackwebpack-dev-server

解决方案


您的 webpack 和 package.json 配置绝对没问题。您的特定 MIME 类型(“text/html”)问题存在于您的 Django 配置中。

  1. React 前端中的 views.py 需要content_type在 HttpResponse 中添加一个参数。
  2. Heroku 需要知道你的 Webpack 构建文件在哪里。

你已经将 Webpack 配置为编译你的 React 应用程序的生产版本,命名为“bundle.js”,并告诉 Webpack 将它与 index.html 的生产版本一起放在一个名为“frontend/dist”的文件夹中。用 Django 的说法,这些是你的“静态”文件。(如果您使用 create-react-app 而不是 Webpack,这是“build”文件夹中的内容。)在您的 settings.py 中,您需要告诉 heroku 在哪里可以找到这些“静态”文件。(解释如下)。但首先,修复你的前端/views.py。

Home(View)正在告诉 Heroku(或任何服务器)通过发送 index.html 来响应。您的 Webpack 'HTMLWebpackPlugin' 已将一个脚本注入到 index.html 中,它要求您Assets(View):查找 bundle.js(以及存储在服务器上同一文件夹中的任何其他文件),将其打开为 'rb',(代表 ' read binary'),并将其作为 HttpResponse 返回给用户。

您的“拒绝执行脚本 ... MIME 类型('text/html')”问题源于 Djangocontent_type对 HttpResponse 的默认设置,即text/html. https://docs.djangoproject.com/en/3.1/ref/request-response/#id3

content_type='application/javascript'通过在 return 语句中包含一个参数来解决这个问题,Assets(View)如下所示:

class Assets(View):

    def get(self, _request, filename):
        path = os.path.join(os.path.dirname(__file__), 'dist', filename)

        if os.path.isfile(path):
            with open(path, 'rb') as file:
                return HttpResponse(file.read(), content_type='application/javascript')
        else:
            return HttpResponseNotFound()

排序后,您需要对 settings.py 文件进行一些更改,以帮助 Heroku 的 Django 配置正确地为这些文件提供服务。

顺便说一句,Heroku 的 Django 部署过程建议安装一个中间件包(“Whitenoise”)来压缩和管理您的静态文件,以及一个 wsgi 接口(“Gunicorn”)来帮助 Heroku 与 Django 通信。您缺少这些不会导致您的问题,但它们是 Heroku 当前推荐配置的一部分,因此我将它们包括在下面。

首先,在您的 settings.py 中,您需要在允许的主机中包含您的 heroku 应用程序的地址,以及您的端口和本地主机,如下所示:

ALLOWED_HOSTS = ['your-app-name.herokuapp.com/', '127.0.0.1', 'localhost']

然后您需要添加 whitenoise 中间件(不要忘记 pip 安装它。)确保将它列在第二个,直接在 django 的安全中间件之后:

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
... 

然后,您需要添加 Webpack 将资产存储到 DIRS 的路径,在您的情况下为 frontend/dist:

TEMPLATES = [
{
    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    'DIRS': [os.path.join(BASE_DIR, 'frontend/dist')],
    'APP_DIRS': True,
     ...

最后也是最重要的,您需要配置存储静态文件的位置。在你的情况下:

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_STORAGE = 
"whitenoise.storage.CompressedManifestStaticFilesStorage"
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'frontend/dist')
]

STATIC_ROOT 和 STATICFILES_DIRS 都有设置的原因是 STATIC_ROOT 是 Heroku 自动调用命令时收集所有静态文件的位置:manage.py collectstatic在部署期间。

STATICFILES_DIRS 用于包含 collectstatic 需要查找的其他目录。确保引用 Webpack 正确存储构建文件的文件夹的路径!对于您的特定目录设置,应该是 'frontend/dist'

不要忘记将 DEBUG 设置更改为 False 以进行部署。

最后,您需要 pip install gunicorn 并将您的 Procfile 更改为如下所示:

release: python manage.py migrate
web: gunicorn project.wsgi --preload --log-file -

您真的应该查看 Heroku 链接:https ://blog.heroku.com/from-project-to-productionized-python ,因为它会引导您完成 Heroku 和 Heroku 的环境设置。它还指出 Heroku 需要一个 requirements.txt 文件来指定您已安装的 python 包的版本,以及一个 runtime.txt 文件来指定您的 Django 版本。但是由于您使用的是 Pipenv 而不是 venv,我认为您的 Pipfile 使这些变得不必要。

下面是按照 Heroku 的从产品到生产的说明将 Django 部署到 Heroku 的大纲,其中包含按正确顺序插入 React + Webpack 前端的必要步骤:--------部署前- ------------

  1. 注册Heroku
  2. 安装Heroku CLI
  3. pipenv shell使用or 或 venv https://docs.python.org/3/tutorial/venv.html设置虚拟环境
  4. 更新 .gitignore 以包括:
/your-venv-directory      
__pycache__
db.sqlite3         
your-app-name/static/
frontend/dist
.env
  1. [Heroku 建议您使用 settings.py 进行本地开发,并使用包 django-environ 创建第二个文件 (heroku.py) 以导入您的环境设置以进行部署。他们的方式要容易得多,但你的环境配置管理得很好,所以我会在这里跳过。检查modulesize-your-settingssetting-up-code-heroku-py以获得推荐的设置]
  2. 安装django-heroku并添加 import django_heroku 到 settings.py 文件的顶部和 settings.py 文件 django_heroku.settings(locals()) 的底部
  3. 安装whitenoise并将其作为中间件添加到 settings.py
  4. 安装psycopg2 http://initd.org/psycopg/docs/ 这是出了名的难安装,但是psycopg2-binary像你在这里做的那样安装 raw 似乎在紧要关头。
  5. 创建一个requirements.txt文件并运行pip freeze > requirements.txt并创建一个runtime.txt文件并指定您当前的 python 构建版本,这样事情就不会中断。当您使用生成 Pipfile.lock 的 pipenv 时,我不确定这是绝对必要的,因为 pipenv 会取代但您应该将当前包和 python 构建版本冻结到您的 Pipfile 中,这样事情就不会中断。
  6. 安装实际服务于您的 heroku 应用程序的 Web 服务器网关接口 (WSGI) 进程:gunicorn https ://devcenter.heroku.com/articles/python-gunicorn https://docs.gunicorn.org/en/latest/settings.html
  7. 创建一个名为Procfile(确保“P”大写!!!)的文件并添加:
    release: python manage.py migrate
    web: gunicorn gettingstarted.wsgi --preload --log-file -
  1. 确保你的 package.json 中有一个构建脚本
"scripts": {
  "build": "webpack -p" }
  1. 运行此构建脚本 ( yarn build) 并确认您的应用在本地运行。

--------部署------- 从您的终端运行:

  1. heroku login
  2. heroku create --region=eu your-app-name
  3. heroku buildpacks:add heroku/nodejs
  4. heroku buildpacks:add heroku/python (确保按该顺序添加构建包!)
  5. heroku config:set ALLOWED_HOSTS=your-app-name.herokuapp.com
  6. heroku config:set DJANGO_SETTINGS_MODULE=project.settings(如果您添加了部署设置文件以使用 django-environ 作为上面建议的模块化设置链接,则使用heroku config:set DJANGO_SETTINGS_MODULE=project.settings.herokuDotPyOrWhateverYouNamedIt
  7. heroku config:set SECRET_KEY=INSERTASECURESECRETKEYPASSWORD
  8. heroku config:set WEB_CONCURRENCY=1
  9. heroku addons:create heroku-postgresql:hobby-dev
  10. 确保您在您的主分支上,并且所有更改都已提交到 git 并运行 git push heroku master
    ,或者如果您在另一个分支上工作,请提交到 git 并运行 git push heroku yourbranch:master用您的分支名称替换“yourbranch”。
  11. heroku run python manage.py migrate
  12. 如果您有一个包含数据库种子的 fixtures.json 文件,请运行 heroku run python manage.py loaddata your-django-project-name/fixtures.json
  13. heroku ps:scale web=1
  14. heroku open
  15. 如果有任何错误,请运行 heroku logs --tail 调查

推荐阅读