Отказался выполнить скрипт из ‘…/bundle.js ‘ поскольку его тип MIME (‘text / html’) не является исполняемым, и включена строгая проверка типа MIME

#reactjs #heroku #webpack #webpack-dev-server

#reactjs #heroku #webpack #webpack-dev-server

Вопрос:

У меня есть приложение с интерфейсом 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"
  }
}
  

Settings.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 projects folder)

 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()

  

urls.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()),
]

  

У меня есть папка views с 9 файлами view:
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 *

  

procfile

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

  

Очень благодарен за любую помощь, которую кто-либо может предложить. Прочитав несколько похожих вопросов, я попытался установить {output: publicPath: ‘/’} и {historyApiFallback: true} в webpack.config.js но, похоже, это не имело никакого значения.

Ответ №1:

Ваши конфигурации webpack и package.json абсолютно хороши. Проблема с вашим конкретным типом MIME («text / html») заключается в вашей конфигурации Django.

  1. views.py в вашем интерфейсе React требуется content_type аргумент в HttpResponse.
  2. Heroku должен знать, где находятся ваши файлы сборки Webpack.

Вы настроили Webpack для компиляции рабочей сборки вашего приложения React с именем this ‘bundle.js ‘, и сказал Webpack поместить его в папку с именем ‘frontend / dist’ вместе с рабочей сборкой вашего index.html . На языке Django это ваши «статические» файлы. (Если вы использовали create-react-app вместо Webpack, это материал в папке ‘build’.) В вашем settings.py , вам нужно сообщить heroku, где найти эти «статические» файлы. (объяснено ниже). Но сначала исправьте frontend/views.py .

Ваш Home(View) сообщает Heroku (или любому серверу) ответить, отправив index.html . Ваш веб-пакет ‘HtmlWebpackPlugin’ внедрил скрипт в index.html который просит вас Assets(View) : искать bundle.js (и любые другие файлы, хранящиеся в той же папке на сервере), откройте его как ‘rb’ (что означает ‘read binary’) и верните его пользователю как HttpResponse.

Ваш «отказался выполнить скрипт … Тип MIME (‘text / html’)» проблема связана с content_type настройкой Django по умолчанию для HttpResponse, которая text/html .https://docs.djangoproject.com/en/3.1/ref/request-response/#id3

Исправьте это, включив content_type='application/javascript' аргумент в оператор возврата 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 файловая справка Конфигурация Django Heroku правильно обслуживает эти файлы.

Кроме того, процесс развертывания Django в Heroku рекомендует установить пакет промежуточного программного обеспечения («Whitenoise») для сжатия и управления вашими статическими файлами, а также интерфейс wsgi («Gunicorn»), чтобы помочь Heroku взаимодействовать с Django. Ваше отсутствие этого не вызывает вашей проблемы, но они являются частью текущей рекомендуемой конфигурации Heroku, поэтому я включаю их ниже.

Во-первых, в ваших настройках.пожалуйста, укажите адрес вашего приложения heroku в разрешенных хостах, а также ваш порт и локальный хост, например:

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

Затем вам нужно добавить промежуточное ПО whitenoise (не забудьте его установить). Убедитесь, что вы указали его вторым, сразу после промежуточного ПО безопасности django:

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

Затем вам нужно добавить путь к тому месту, где Webpack хранит ваши ресурсы, в каталоги, в вашем случае 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’

Не забудьте изменить настройки отладки на False для развертывания.

Наконец, вам нужно установить 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
  3. Настройте виртуальную среду с помощью или pipenv shell или 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 для локальной разработки и создания второго файла (heroku.py ) с помощью пакета django-environ для импорта ваших настроек env для развертывания. Это намного проще, но вы прекрасно справляетесь со своей конфигурацией env, поэтому я пропущу это здесь. Проверьте modularize-your-settings и setting-up-code-heroku-py для рекомендуемой настройки]
  2. Установите django-heroku и добавьте import django_heroku в начало settings.py файл и django_heroku.settings(locals()) в нижней части settings.py файл
  3. Установите whitenoise и добавьте его в качестве промежуточного программного обеспечения в settings.py
  4. Установить psycopg2 http://initd.org/psycopg/docs / Общеизвестно, что это сложно установить, но установка raw psycopg2-binary , как вы сделали здесь, кажется, работает в крайнем случае.
  5. Создайте requirements.txt файл и запустите pip freeze > requirements.txt , создайте runtime.txt файл и укажите текущую версию сборки python, чтобы все не ломалось по дороге. Поскольку вы используете pipenv, который генерирует Pipfile.lock Я не уверен, что это строго необходимо, поскольку pipenv заменяет, но вы должны заморозить свой текущий пакет и версии сборки python в вашем Pipfile, чтобы все не ломалось по дороге.
  6. Установите процесс интерфейса шлюза веб-серверов (WSGI), который фактически обслуживает ваше приложение heroku: 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 в качестве ссылки modularize-your-settings, предложенной выше, затем используйте 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 и run git push heroku master
    или, если вы работаете из другой ветки, зафиксируйте в git и запустите git push heroku yourbranch:master замену ‘yourbranch’ на имя вашей ветки.
  11. heroku run python manage.py migrate
  12. Если у вас есть приспособления.файл json, содержащий исходные данные вашей базы данных, запустите heroku запустите python manage.py загружайте данные вашего-django-project-name/fixtures.json
  13. heroku ps:scale web=1
  14. heroku open
  15. Если есть какие-либо ошибки, запустите heroku logs --tail для расследования

Комментарии:

1. Большое спасибо mtcolvard — такой четкий и исчерпывающий ответ и, что более важно, он сработал!!! Удивительно!!