diff --git a/.circleci/config.yml b/.circleci/config.yml index 15a9b689..05a54546 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,29 +3,72 @@ workflows: version: 2 test: jobs: - - test-3.4-18 - - test-3.4-19 - - test-3.4-110 - - test-3.4-111 - - - test-3.5-18 - - test-3.5-19 - - test-3.5-110 - - test-3.5-111 - - - test-3.6-18 - - test-3.6-19 - - test-3.6-110 - - test-3.6-111 + - test-3.5-20 + - test-3.5-21 + - test-3.5-22 + + - test-3.6-20 + - test-3.6-21 - test-3.6-22 + - test-3.6-30 + - test-3.6-31 + - test-3.6-32 - - test-3.7-18 - - test-3.7-19 - - test-3.7-110 - - test-3.7-111 + - test-3.7-20 + - test-3.7-21 - test-3.7-22 + - test-3.7-30 + - test-3.7-31 + - test-3.7-32 + - test-3.8-20 + - test-3.8-21 - test-3.8-22 + - test-3.8-30 + - test-3.8-31 + - test-3.8-32 + + - test-3.9-20 + - test-3.9-21 + - test-3.9-22 + - test-3.9-30 + - test-3.9-31 + - test-3.9-32 + + - done: + requires: + - test-3.5-20 + - test-3.5-21 + - test-3.5-22 + + - test-3.6-20 + - test-3.6-21 + - test-3.6-22 + - test-3.6-30 + - test-3.6-31 + - test-3.6-32 + + - test-3.7-20 + - test-3.7-21 + - test-3.7-22 + - test-3.7-30 + - test-3.7-31 + - test-3.7-32 + + - test-3.8-20 + - test-3.8-21 + - test-3.8-22 + - test-3.8-30 + - test-3.8-31 + - test-3.8-32 + + - test-3.9-20 + - test-3.9-21 + - test-3.9-22 + - test-3.9-30 + - test-3.9-31 + - test-3.9-32 + jobs: base: &test-template docker: @@ -71,121 +114,183 @@ jobs: source venv/bin/activate cd tests coverage run --source=webpack_loader manage.py test - test-3.4-18: - <<: *test-template - docker: - - image: circleci/python:3.4-stretch-node - environment: - DJANGO_VERSION: "18" - test-3.4-19: - <<: *test-template - docker: - - image: circleci/python:3.4-stretch-node - environment: - DJANGO_VERSION: "19" - test-3.4-110: - <<: *test-template - docker: - - image: circleci/python:3.4-stretch-node - environment: - DJANGO_VERSION: "110" - test-3.4-111: - <<: *test-template - docker: - - image: circleci/python:3.4-stretch-node - environment: - DJANGO_VERSION: "111" + coveralls + environment: + COVERALLS_PARALLEL: 1 - test-3.5-18: + test-3.5-20: <<: *test-template docker: - image: circleci/python:3.5-stretch-node environment: - DJANGO_VERSION: "18" - test-3.5-19: + DJANGO_VERSION: "20" + test-3.5-21: <<: *test-template docker: - image: circleci/python:3.5-stretch-node environment: - DJANGO_VERSION: "19" - test-3.5-110: + DJANGO_VERSION: "21" + test-3.5-22: <<: *test-template docker: - image: circleci/python:3.5-stretch-node environment: - DJANGO_VERSION: "110" - test-3.5-111: + DJANGO_VERSION: "22" + + test-3.6-20: <<: *test-template docker: - - image: circleci/python:3.5-stretch-node + - image: circleci/python:3.6-stretch-node environment: - DJANGO_VERSION: "111" - - test-3.6-18: + DJANGO_VERSION: "20" + test-3.6-21: <<: *test-template docker: - image: circleci/python:3.6-stretch-node environment: - DJANGO_VERSION: "18" - test-3.6-19: + DJANGO_VERSION: "21" + test-3.6-22: <<: *test-template docker: - image: circleci/python:3.6-stretch-node environment: - DJANGO_VERSION: "19" - test-3.6-110: + DJANGO_VERSION: "22" + test-3.6-30: <<: *test-template docker: - image: circleci/python:3.6-stretch-node environment: - DJANGO_VERSION: "110" - test-3.6-111: + DJANGO_VERSION: "30" + test-3.6-31: <<: *test-template docker: - image: circleci/python:3.6-stretch-node environment: - DJANGO_VERSION: "111" - test-3.6-22: + DJANGO_VERSION: "31" + test-3.6-32: <<: *test-template docker: - image: circleci/python:3.6-stretch-node environment: - DJANGO_VERSION: "22" + DJANGO_VERSION: "32" - test-3.7-18: + test-3.7-20: <<: *test-template docker: - image: circleci/python:3.7-stretch-node environment: - DJANGO_VERSION: "18" - test-3.7-19: + DJANGO_VERSION: "20" + test-3.7-21: <<: *test-template docker: - image: circleci/python:3.7-stretch-node environment: - DJANGO_VERSION: "19" - test-3.7-110: + DJANGO_VERSION: "21" + test-3.7-22: <<: *test-template docker: - image: circleci/python:3.7-stretch-node environment: - DJANGO_VERSION: "110" - test-3.7-111: + DJANGO_VERSION: "22" + test-3.7-30: <<: *test-template docker: - image: circleci/python:3.7-stretch-node environment: - DJANGO_VERSION: "111" - test-3.7-22: + DJANGO_VERSION: "30" + test-3.7-31: <<: *test-template docker: - image: circleci/python:3.7-stretch-node environment: - DJANGO_VERSION: "22" + DJANGO_VERSION: "31" + test-3.7-32: + <<: *test-template + docker: + - image: circleci/python:3.7-stretch-node + environment: + DJANGO_VERSION: "32" + test-3.8-20: + <<: *test-template + docker: + - image: circleci/python:3.8-buster-node + environment: + DJANGO_VERSION: "20" + test-3.8-21: + <<: *test-template + docker: + - image: circleci/python:3.8-buster-node + environment: + DJANGO_VERSION: "21" test-3.8-22: <<: *test-template docker: - image: circleci/python:3.8-buster-node environment: DJANGO_VERSION: "22" + test-3.8-30: + <<: *test-template + docker: + - image: circleci/python:3.8-buster-node + environment: + DJANGO_VERSION: "30" + test-3.8-31: + <<: *test-template + docker: + - image: circleci/python:3.8-buster-node + environment: + DJANGO_VERSION: "31" + test-3.8-32: + <<: *test-template + docker: + - image: circleci/python:3.8-buster-node + environment: + DJANGO_VERSION: "32" + + test-3.9-20: + <<: *test-template + docker: + - image: circleci/python:3.9-buster-node + environment: + DJANGO_VERSION: "20" + test-3.9-21: + <<: *test-template + docker: + - image: circleci/python:3.9-buster-node + environment: + DJANGO_VERSION: "21" + test-3.9-22: + <<: *test-template + docker: + - image: circleci/python:3.9-buster-node + environment: + DJANGO_VERSION: "22" + test-3.9-30: + <<: *test-template + docker: + - image: circleci/python:3.9-buster-node + environment: + DJANGO_VERSION: "30" + test-3.9-31: + <<: *test-template + docker: + - image: circleci/python:3.9-buster-node + environment: + DJANGO_VERSION: "31" + test-3.9-32: + <<: *test-template + docker: + - image: circleci/python:3.9-buster-node + environment: + DJANGO_VERSION: "32" + + done: + docker: + - image: circleci/python:3.9-buster-node + steps: + - run: + name: Finish Coveralls + command: | + pip install coveralls + coveralls --finish diff --git a/.gitignore b/.gitignore index 5b697119..e6e344be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ venv/ +*.sqlite3 # auto generated README.rst @@ -70,7 +71,7 @@ examples/**/ve/ examples/**/venv/ examples/**/node_modules/ examples/**/assets/bundles/ -examples/**/assets/webpack-stats.json +examples/**/webpack-stats.json tests/ve/ tests/ve3/ @@ -78,6 +79,7 @@ tests/venv/ tests/venv3/ tests/node_modules/ tests/assets/bundles/ +tests/assets/django_webpack_loader_bundles/ tests/webpack-stats.json tests/webpack-stats-app2.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ecc44fc4..bf2e8bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ For more general information, view the [readme](README.md). Releases are added to the [github release page](https://github.com/ezhome/django-webpack-loader/releases). +## [1.3.0] -- 2021-08-30 + +- Add option for rel="preload" in JS/CSS tags #203 +- Add option for extension appending in the url files #135 +- Fixes RemovedInDjango41Warning #290 +- Applies IGNORE setting before checking assets #286 +- Removed type from link and script tags per #152 + +NOTE: Skipped version 1.2.0 to match `webpack-bundle-tracker` version + + +## [1.1.0] -- 2021-06-18 + +- Added compatibility with `webpack-bundle-tracker@1.1.0` +- Removes bower references in project +- Fix jinja configuration example in README.md + +## [1.0.0] -- 2021-05-12 + +- Added support for custom loader classes +- Added compatibility with `webpack-bundle-tracker@1.0.0-alpha.1` +- Updated and corrected examples +- Updated Python and Django supported versions on tests ## [0.6.0] -- 2018-02-22 - Added support for 'Access-Control-Allow-Origin' header diff --git a/Makefile b/Makefile index 730d210d..7f1cc335 100644 --- a/Makefile +++ b/Makefile @@ -27,9 +27,9 @@ install: @$(ENV)/bin/pip install $(requirements) publish: build - @echo "Publishing to pypi..." + @echo "Publishing to $(REPOSITORY)..." @$(ENV)/bin/twine upload -r $(REPOSITORY) dist/* register: - @echo "Registering package on pypi..." + @echo "Registering package on $(REPOSITORY)..." @$(ENV)/bin/twine register -r $(REPOSITORY) diff --git a/README.md b/README.md index 6f35da1b..c60a89df 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ -# django-webpack-loader +# django-webpack-loader for gzip -[![Join the chat at https://gitter.im/owais/django-webpack-loader](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/owais/django-webpack-loader?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://circleci.com/gh/owais/django-webpack-loader/tree/master.svg?style=svg)](https://circleci.com/gh/owais/django-webpack-loader/tree/master) -[![Coverage Status](https://coveralls.io/repos/owais/django-webpack-loader/badge.svg?branch=master&service=github)](https://coveralls.io/github/owais/django-webpack-loader?branch=master) +Different from the original repository, it appends an extension in the creation of the tags (as .gzip). The append is also dynamic, and can be done for css or js + +[![Build Status](https://circleci.com/gh/django-webpack/django-webpack-loader/tree/master.svg?style=svg)](https://circleci.com/gh/django-webpack/django-webpack-loader/tree/master) +[![Coverage Status](https://coveralls.io/repos/github/django-webpack/django-webpack-loader/badge.svg?branch=master)](https://coveralls.io/github/django-webpack/django-webpack-loader?branch=master) +![pyversions](https://img.shields.io/pypi/pyversions/django-webpack-loader) +![djversions](https://img.shields.io/pypi/djversions/django-webpack-loader)
@@ -19,7 +22,7 @@ A [changelog](CHANGELOG.md) is also available. ## Compatibility -Test cases cover Django>=1.6 on Python 2.7 and Python>=3.4. 100% code coverage is the target so we can be sure everything works anytime. It should probably work on older version of django as well but the package does not ship any test cases for them. +Test cases cover Django>=2.0 on Python>=3.5. 100% code coverage is the target so we can be sure everything works anytime. It should probably work on older version of django as well but the package does not ship any test cases for them. ## Install @@ -32,6 +35,14 @@ pip install django-webpack-loader
+## Migrating from version < 1.0.0 + +In order to use `django-webpack-loader>=1.0.0`, you must ensure that `webpack-bundle-tracker@1.0.0` is being used on the JavaScript side. It's recommended that you always keep at least minor version parity across both packages, for full compatibility. + +This is necessary because the formatting of `webpack-stats.json` that `webpack-bundle-tracker` outputs has changed starting at version `1.0.0-alpha.1`. Starting at `django-webpack-loader==1.0.0`, this is the only formatting accepted here, meaning that other versions of that package don't output compatible files anymore, thereby breaking compatibility with older `webpack-bundle-tracker` releases. + +
+ ## Configuration
@@ -124,7 +135,7 @@ WEBPACK_LOADER = { If the bundle generates a file called `main-cf4b5fab6e00a404e0c7.js` and your STATIC_URL is `/static/`, then the `', - ''] +['', + ''] ``` ## How to use in Production @@ -389,12 +441,12 @@ If you need to output your assets in a jinja template we provide a Jinja2 extens To install the extension add it to the django_jinja `TEMPLATES` configuration in the `["OPTIONS"]["extension"]` list. ```python +from django_jinja.builtins import DEFAULT_EXTENSIONS TEMPLATES = [ { "BACKEND": "django_jinja.backend.Jinja2", "OPTIONS": { - "extensions": [ - "django_jinja.builtins.extensions.DjangoFiltersExtension", + "extensions": DEFAULT_EXTENSIONS + [ "webpack_loader.contrib.jinja2ext.WebpackExtension", ], } @@ -413,3 +465,14 @@ Then in your base jinja template: Enjoy your webpack with django :) + +# Alternatives to Django-Webpack-Loader + +_Below are known projects that attempt to solve the same problem:_ + +Note that these projects have not been vetted or reviewed in any way by me. +These are not recommendation. +Anyone can add their project to this by sending a PR. + +* [Django Manifest Loader](https://github.com/shonin/django-manifest-loader) +* [Python Webpack Boilerplate](https://github.com/AccordBox/python-webpack-boilerplate) diff --git a/examples/code-splitting/app/asgi.py b/examples/code-splitting/app/asgi.py new file mode 100644 index 00000000..895c0e06 --- /dev/null +++ b/examples/code-splitting/app/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for app project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +application = get_asgi_application() diff --git a/examples/code-splitting/app/settings.py b/examples/code-splitting/app/settings.py index ff00e19b..923969f1 100644 --- a/examples/code-splitting/app/settings.py +++ b/examples/code-splitting/app/settings.py @@ -1,26 +1,27 @@ """ Django settings for app project. -Generated by 'django-admin startproject' using Django 1.8.2. +Generated by 'django-admin startproject' using Django 3.2.2. For more information on this file, see -https://docs.djangoproject.com/en/1.8/topics/settings/ +https://docs.djangoproject.com/en/3.2/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.8/ref/settings/ +https://docs.djangoproject.com/en/3.2/ref/settings/ """ -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +from pathlib import Path import os -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '+g25&y9i+-6_z$$z!ov$l2s%b#0kcmnx)n7y*2_ehy-w011p#k' +SECRET_KEY = 'django-insecure-&24ubb46zaej*fd9jz1^mw1t0)-@zd9g74f!hcs1a48-7loo0r' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -30,34 +31,32 @@ # Application definition -INSTALLED_APPS = ( +INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'app', 'webpack_loader', -) +] -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.security.SecurityMiddleware', -) +] ROOT_URLCONF = 'app.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], + 'DIRS': [os.path.join(BASE_DIR, "templates")], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -74,18 +73,18 @@ # Database -# https://docs.djangoproject.com/en/1.8/ref/settings/#databases +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + 'NAME': BASE_DIR / 'db.sqlite3', } } # Internationalization -# https://docs.djangoproject.com/en/1.8/topics/i18n/ +# https://docs.djangoproject.com/en/3.2/topics/i18n/ LANGUAGE_CODE = 'en-us' @@ -99,7 +98,7 @@ # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.8/howto/static-files/ +# https://docs.djangoproject.com/en/3.2/howto/static-files/ STATIC_URL = '/static/' @@ -113,3 +112,8 @@ 'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json') } } + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/examples/code-splitting/app/urls.py b/examples/code-splitting/app/urls.py index b1ad5f06..00663ff3 100644 --- a/examples/code-splitting/app/urls.py +++ b/examples/code-splitting/app/urls.py @@ -1,25 +1,23 @@ """app URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.8/topics/http/urls/ + https://docs.djangoproject.com/en/3.2/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') + 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf - 1. Add an import: from blog import urls as blog_urls - 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from django.conf.urls import include, url from django.contrib import admin +from django.urls import path from django.views.generic import TemplateView - - urlpatterns = [ - url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), - url(r'^admin/', include(admin.site.urls)), + path('admin/', admin.site.urls), + path('', TemplateView.as_view(template_name='home.html'), name='home'), ] diff --git a/examples/code-splitting/app/views.py b/examples/code-splitting/app/views.py deleted file mode 100644 index 955db581..00000000 --- a/examples/code-splitting/app/views.py +++ /dev/null @@ -1 +0,0 @@ -from django.views.generic.base import TemplateView diff --git a/examples/code-splitting/app/wsgi.py b/examples/code-splitting/app/wsgi.py index ea9ff951..768c6e41 100644 --- a/examples/code-splitting/app/wsgi.py +++ b/examples/code-splitting/app/wsgi.py @@ -4,13 +4,13 @@ It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') application = get_wsgi_application() diff --git a/examples/code-splitting/assets/js/app.jsx b/examples/code-splitting/assets/js/app.jsx index c729eb78..8ef06334 100644 --- a/examples/code-splitting/assets/js/app.jsx +++ b/examples/code-splitting/assets/js/app.jsx @@ -1,7 +1,7 @@ -var React = require('react'); +import React from 'react'; -module.exports = React.createClass({ - render: function(){ - return

Hello, world.

- } -}); +const App = () => { + return

Hello, world.

; +} + +export default App; diff --git a/examples/code-splitting/assets/js/index.jsx b/examples/code-splitting/assets/js/index.jsx index 8b167dfe..1795be50 100644 --- a/examples/code-splitting/assets/js/index.jsx +++ b/examples/code-splitting/assets/js/index.jsx @@ -1,4 +1,6 @@ -var React = require('react'); -var App = require('./app'); +import React from 'react'; +import ReactDOM from 'react-dom'; -React.render(, document.getElementById('react-app')); +import App from './app'; + +ReactDOM.render(, document.getElementById('react-app')); diff --git a/examples/code-splitting/assets/js/other.js b/examples/code-splitting/assets/js/other.js new file mode 100644 index 00000000..f1d3a934 --- /dev/null +++ b/examples/code-splitting/assets/js/other.js @@ -0,0 +1 @@ +console.log("Hello from other entry point") \ No newline at end of file diff --git a/examples/code-splitting/babel.config.js b/examples/code-splitting/babel.config.js new file mode 100644 index 00000000..df08db52 --- /dev/null +++ b/examples/code-splitting/babel.config.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + presets: ['@babel/preset-env', '@babel/preset-react'], +}; diff --git a/examples/code-splitting/db.sqlite3 b/examples/code-splitting/db.sqlite3 deleted file mode 100644 index 755e4629..00000000 Binary files a/examples/code-splitting/db.sqlite3 and /dev/null differ diff --git a/examples/code-splitting/manage.py b/examples/code-splitting/manage.py index 72238252..49313893 100755 --- a/examples/code-splitting/manage.py +++ b/examples/code-splitting/manage.py @@ -1,10 +1,22 @@ #!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" import os import sys -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") - - from django.core.management import execute_from_command_line +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/examples/code-splitting/package.json b/examples/code-splitting/package.json index 00929fd4..3b4be349 100644 --- a/examples/code-splitting/package.json +++ b/examples/code-splitting/package.json @@ -6,14 +6,15 @@ "author": "Owais Lone", "license": "MIT", "devDependencies": { - "babel": "^5.4.7", - "babel-core": "^5.4.7", - "babel-loader": "^5.1.3", - "node-libs-browser": "^0.5.0", - "react": "^0.13.3", - "webpack": "^1.9.8", - "webpack-bundle-tracker": "0.0.5", - "webpack-dev-server": "^1.9.0", - "webpack-split-by-path": "0.0.1" + "@babel/core": "^7.9.6", + "@babel/preset-env": "^7.9.6", + "@babel/preset-react": "^7.9.4", + "babel-loader": "^8.1.0", + "react": "^16.11.0", + "react-dom": "^16.11.0", + "webpack": "^4.0.0", + "webpack-bundle-tracker": "1.1.0", + "webpack-cli": "^3.3.10", + "webpack-dev-server": "^3.0.0" } } diff --git a/examples/simple/app/templates/home.html b/examples/code-splitting/templates/home.html similarity index 87% rename from examples/simple/app/templates/home.html rename to examples/code-splitting/templates/home.html index 08b3cfe8..7b49d766 100644 --- a/examples/simple/app/templates/home.html +++ b/examples/code-splitting/templates/home.html @@ -8,6 +8,7 @@
+ {% render_bundle 'other' %} {% render_bundle 'main' %} diff --git a/examples/code-splitting/webpack.config.js b/examples/code-splitting/webpack.config.js index f6e70c10..7beabb88 100644 --- a/examples/code-splitting/webpack.config.js +++ b/examples/code-splitting/webpack.config.js @@ -1,37 +1,36 @@ var path = require("path"); -var webpack = require('webpack'); var BundleTracker = require('webpack-bundle-tracker'); -var SplitByPathPlugin = require('webpack-split-by-path'); module.exports = { context: __dirname, - entry: './assets/js/index', + entry: { + 'main': './assets/js/index', + 'other': './assets/js/other', + }, output: { - path: path.resolve('./assets/bundles/'), - filename: "[name]-[hash].js", - chunkFilename: "[name]-[hash].js" + path: path.resolve('./assets/bundles/'), + filename: "[name]-[hash].js", + chunkFilename: "[name]-[hash].js" }, plugins: [ new BundleTracker({filename: './webpack-stats.json'}), - new SplitByPathPlugin([ - { - name: 'vendor', - path: path.join(__dirname, './node_modules/') - } - ]) ], module: { - loaders: [ + rules: [ // we pass the output from babel loader to react-hot loader - { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel'], }, + { + test: /\.jsx?$/, + exclude: /node_modules/, + loaders: ['babel-loader'], + }, ], }, resolve: { - modulesDirectories: ['node_modules', 'bower_components'], - extensions: ['', '.js', '.jsx'] + modules: ['node_modules'], + extensions: ['.js', '.jsx'] }, } diff --git a/examples/hot-reload/app/asgi.py b/examples/hot-reload/app/asgi.py new file mode 100644 index 00000000..895c0e06 --- /dev/null +++ b/examples/hot-reload/app/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for app project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +application = get_asgi_application() diff --git a/examples/hot-reload/app/settings.py b/examples/hot-reload/app/settings.py index ff00e19b..923969f1 100644 --- a/examples/hot-reload/app/settings.py +++ b/examples/hot-reload/app/settings.py @@ -1,26 +1,27 @@ """ Django settings for app project. -Generated by 'django-admin startproject' using Django 1.8.2. +Generated by 'django-admin startproject' using Django 3.2.2. For more information on this file, see -https://docs.djangoproject.com/en/1.8/topics/settings/ +https://docs.djangoproject.com/en/3.2/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.8/ref/settings/ +https://docs.djangoproject.com/en/3.2/ref/settings/ """ -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +from pathlib import Path import os -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '+g25&y9i+-6_z$$z!ov$l2s%b#0kcmnx)n7y*2_ehy-w011p#k' +SECRET_KEY = 'django-insecure-&24ubb46zaej*fd9jz1^mw1t0)-@zd9g74f!hcs1a48-7loo0r' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -30,34 +31,32 @@ # Application definition -INSTALLED_APPS = ( +INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'app', 'webpack_loader', -) +] -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.security.SecurityMiddleware', -) +] ROOT_URLCONF = 'app.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], + 'DIRS': [os.path.join(BASE_DIR, "templates")], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -74,18 +73,18 @@ # Database -# https://docs.djangoproject.com/en/1.8/ref/settings/#databases +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + 'NAME': BASE_DIR / 'db.sqlite3', } } # Internationalization -# https://docs.djangoproject.com/en/1.8/topics/i18n/ +# https://docs.djangoproject.com/en/3.2/topics/i18n/ LANGUAGE_CODE = 'en-us' @@ -99,7 +98,7 @@ # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.8/howto/static-files/ +# https://docs.djangoproject.com/en/3.2/howto/static-files/ STATIC_URL = '/static/' @@ -113,3 +112,8 @@ 'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json') } } + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/examples/hot-reload/app/urls.py b/examples/hot-reload/app/urls.py index b1ad5f06..00663ff3 100644 --- a/examples/hot-reload/app/urls.py +++ b/examples/hot-reload/app/urls.py @@ -1,25 +1,23 @@ """app URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.8/topics/http/urls/ + https://docs.djangoproject.com/en/3.2/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') + 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf - 1. Add an import: from blog import urls as blog_urls - 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from django.conf.urls import include, url from django.contrib import admin +from django.urls import path from django.views.generic import TemplateView - - urlpatterns = [ - url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), - url(r'^admin/', include(admin.site.urls)), + path('admin/', admin.site.urls), + path('', TemplateView.as_view(template_name='home.html'), name='home'), ] diff --git a/examples/hot-reload/app/views.py b/examples/hot-reload/app/views.py deleted file mode 100644 index 955db581..00000000 --- a/examples/hot-reload/app/views.py +++ /dev/null @@ -1 +0,0 @@ -from django.views.generic.base import TemplateView diff --git a/examples/hot-reload/app/wsgi.py b/examples/hot-reload/app/wsgi.py index ea9ff951..768c6e41 100644 --- a/examples/hot-reload/app/wsgi.py +++ b/examples/hot-reload/app/wsgi.py @@ -4,13 +4,13 @@ It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') application = get_wsgi_application() diff --git a/examples/hot-reload/assets/css/app.css b/examples/hot-reload/assets/css/app.css new file mode 100644 index 00000000..4717ad4b --- /dev/null +++ b/examples/hot-reload/assets/css/app.css @@ -0,0 +1,3 @@ +h1 { + color: red; +} \ No newline at end of file diff --git a/examples/hot-reload/assets/js/app.jsx b/examples/hot-reload/assets/js/app.jsx index c729eb78..3134ca00 100644 --- a/examples/hot-reload/assets/js/app.jsx +++ b/examples/hot-reload/assets/js/app.jsx @@ -1,7 +1,8 @@ -var React = require('react'); +import React from 'react'; +import '../css/app.css'; -module.exports = React.createClass({ - render: function(){ - return

Hello, world.

- } -}); +const App = () => { + return

Hello, everyone.

; +} + +export default App; diff --git a/examples/hot-reload/assets/js/index.jsx b/examples/hot-reload/assets/js/index.jsx index 8b167dfe..1795be50 100644 --- a/examples/hot-reload/assets/js/index.jsx +++ b/examples/hot-reload/assets/js/index.jsx @@ -1,4 +1,6 @@ -var React = require('react'); -var App = require('./app'); +import React from 'react'; +import ReactDOM from 'react-dom'; -React.render(, document.getElementById('react-app')); +import App from './app'; + +ReactDOM.render(, document.getElementById('react-app')); diff --git a/examples/hot-reload/babel.config.js b/examples/hot-reload/babel.config.js new file mode 100644 index 00000000..df08db52 --- /dev/null +++ b/examples/hot-reload/babel.config.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + presets: ['@babel/preset-env', '@babel/preset-react'], +}; diff --git a/examples/hot-reload/db.sqlite3 b/examples/hot-reload/db.sqlite3 deleted file mode 100644 index 755e4629..00000000 Binary files a/examples/hot-reload/db.sqlite3 and /dev/null differ diff --git a/examples/hot-reload/manage.py b/examples/hot-reload/manage.py index 72238252..49313893 100755 --- a/examples/hot-reload/manage.py +++ b/examples/hot-reload/manage.py @@ -1,10 +1,22 @@ #!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" import os import sys -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") - - from django.core.management import execute_from_command_line +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/examples/hot-reload/package.json b/examples/hot-reload/package.json index 41cc9bd7..0da0a85c 100644 --- a/examples/hot-reload/package.json +++ b/examples/hot-reload/package.json @@ -6,14 +6,17 @@ "author": "Owais Lone", "license": "MIT", "devDependencies": { - "babel": "^5.4.7", - "babel-core": "^5.4.7", - "babel-loader": "^5.1.3", - "node-libs-browser": "^0.5.0", - "react": "^0.13.3", - "react-hot-loader": "^1.2.7", - "webpack": "^1.9.8", - "webpack-bundle-tracker": "0.0.5", - "webpack-dev-server": "^1.9.0" + "@babel/core": "^7.9.6", + "@babel/preset-env": "^7.9.6", + "@babel/preset-react": "^7.9.4", + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "mini-css-extract-plugin": "^0.9.0", + "react": "^16.11.0", + "react-dom": "^16.11.0", + "webpack": "^4.0.0", + "webpack-bundle-tracker": "1.1.0", + "webpack-cli": "^3.3.10", + "webpack-dev-server": "^3.0.0" } } diff --git a/examples/hot-reload/server.js b/examples/hot-reload/server.js index 0011b772..d4de7ab5 100644 --- a/examples/hot-reload/server.js +++ b/examples/hot-reload/server.js @@ -1,17 +1,20 @@ -var webpack = require('webpack'); -var WebpackDevServer = require('webpack-dev-server'); -var config = require('./webpack.config'); +const webpackDevServer = require('webpack-dev-server'); +const webpack = require('webpack'); -new WebpackDevServer(webpack(config), { +const config = require('./webpack.config.js'); +const options = { publicPath: config.output.publicPath, + port: 3000, hot: true, inline: true, historyApiFallback: true, - headers: { 'Access-Control-Allow-Origin': '*' } -}).listen(3000, '0.0.0.0', function (err, result) { - if (err) { - console.log(err); - } + headers: { 'Access-Control-Allow-Origin': '*' }, +}; - console.log('Listening at 0.0.0.0:3000'); +webpackDevServer.addDevServerEntrypoints(config, options); +const compiler = webpack(config); +const server = new webpackDevServer(compiler, options); + +server.listen(3000, 'localhost', () => { + console.log('dev server listening on port 3000'); }); diff --git a/examples/code-splitting/app/templates/home.html b/examples/hot-reload/templates/home.html similarity index 73% rename from examples/code-splitting/app/templates/home.html rename to examples/hot-reload/templates/home.html index 994e0581..4386de12 100644 --- a/examples/code-splitting/app/templates/home.html +++ b/examples/hot-reload/templates/home.html @@ -4,11 +4,11 @@ Example + {% render_bundle 'main' 'css' %}
- {% render_bundle 'vendor' %} - {% render_bundle 'main' %} + {% render_bundle 'main' 'js' %} diff --git a/examples/hot-reload/webpack.config.js b/examples/hot-reload/webpack.config.js index f72b212c..8a33c08e 100644 --- a/examples/hot-reload/webpack.config.js +++ b/examples/hot-reload/webpack.config.js @@ -1,36 +1,43 @@ var path = require("path"); -var webpack = require('webpack'); var BundleTracker = require('webpack-bundle-tracker'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { context: __dirname, - entry: [ - 'webpack-dev-server/client?http://localhost:3000', - 'webpack/hot/only-dev-server', - './assets/js/index' - ], + entry: './assets/js/index', output: { - path: path.resolve('./assets/bundles/'), - filename: "[name]-[hash].js", - publicPath: 'http://localhost:3000/assets/bundles/', // Tell django to use this URL to load packages and not use STATIC_URL + bundle_name + path: path.resolve('./assets/bundles/'), + publicPath: 'http://localhost:3000/dist/', + filename: "[name]-[hash].js", + chunkFilename: "[name]-[hash].js" }, plugins: [ - new webpack.HotModuleReplacementPlugin(), - new webpack.NoErrorsPlugin(), // don't reload if there is an error new BundleTracker({filename: './webpack-stats.json'}), + new MiniCssExtractPlugin({ + filename: '[name]-[hash].css', + chunkFilename: '[name]-[hash].css', + }), ], module: { - loaders: [ + rules: [ // we pass the output from babel loader to react-hot loader - { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['react-hot', 'babel'], }, + { + test: /\.jsx?$/, + exclude: /node_modules/, + loaders: ['babel-loader'], + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, 'css-loader'], + } ], }, resolve: { - modulesDirectories: ['node_modules', 'bower_components'], - extensions: ['', '.js', '.jsx'] + modules: ['node_modules'], + extensions: ['.js', '.jsx'] }, } diff --git a/examples/simple/app/asgi.py b/examples/simple/app/asgi.py new file mode 100644 index 00000000..895c0e06 --- /dev/null +++ b/examples/simple/app/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for app project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +application = get_asgi_application() diff --git a/examples/simple/app/settings.py b/examples/simple/app/settings.py index ff00e19b..923969f1 100644 --- a/examples/simple/app/settings.py +++ b/examples/simple/app/settings.py @@ -1,26 +1,27 @@ """ Django settings for app project. -Generated by 'django-admin startproject' using Django 1.8.2. +Generated by 'django-admin startproject' using Django 3.2.2. For more information on this file, see -https://docs.djangoproject.com/en/1.8/topics/settings/ +https://docs.djangoproject.com/en/3.2/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.8/ref/settings/ +https://docs.djangoproject.com/en/3.2/ref/settings/ """ -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +from pathlib import Path import os -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '+g25&y9i+-6_z$$z!ov$l2s%b#0kcmnx)n7y*2_ehy-w011p#k' +SECRET_KEY = 'django-insecure-&24ubb46zaej*fd9jz1^mw1t0)-@zd9g74f!hcs1a48-7loo0r' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -30,34 +31,32 @@ # Application definition -INSTALLED_APPS = ( +INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'app', 'webpack_loader', -) +] -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.security.SecurityMiddleware', -) +] ROOT_URLCONF = 'app.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], + 'DIRS': [os.path.join(BASE_DIR, "templates")], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -74,18 +73,18 @@ # Database -# https://docs.djangoproject.com/en/1.8/ref/settings/#databases +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + 'NAME': BASE_DIR / 'db.sqlite3', } } # Internationalization -# https://docs.djangoproject.com/en/1.8/topics/i18n/ +# https://docs.djangoproject.com/en/3.2/topics/i18n/ LANGUAGE_CODE = 'en-us' @@ -99,7 +98,7 @@ # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.8/howto/static-files/ +# https://docs.djangoproject.com/en/3.2/howto/static-files/ STATIC_URL = '/static/' @@ -113,3 +112,8 @@ 'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json') } } + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/examples/simple/app/urls.py b/examples/simple/app/urls.py index 09082233..00663ff3 100644 --- a/examples/simple/app/urls.py +++ b/examples/simple/app/urls.py @@ -1,25 +1,23 @@ """app URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.8/topics/http/urls/ + https://docs.djangoproject.com/en/3.2/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') + 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf - 1. Add an import: from blog import urls as blog_urls - 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from django.conf.urls import include, url from django.contrib import admin -from django.views.generic import TemplateView -## For Django 2+ support from django.urls import path +from django.views.generic import TemplateView urlpatterns = [ - url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), - path('admin/', admin.site.urls), ## Django 2+ admin namespace + path('admin/', admin.site.urls), + path('', TemplateView.as_view(template_name='home.html'), name='home'), ] diff --git a/examples/simple/app/views.py b/examples/simple/app/views.py deleted file mode 100644 index 955db581..00000000 --- a/examples/simple/app/views.py +++ /dev/null @@ -1 +0,0 @@ -from django.views.generic.base import TemplateView diff --git a/examples/simple/app/wsgi.py b/examples/simple/app/wsgi.py index ea9ff951..768c6e41 100644 --- a/examples/simple/app/wsgi.py +++ b/examples/simple/app/wsgi.py @@ -4,13 +4,13 @@ It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') application = get_wsgi_application() diff --git a/examples/simple/assets/css/app.css b/examples/simple/assets/css/app.css new file mode 100644 index 00000000..4717ad4b --- /dev/null +++ b/examples/simple/assets/css/app.css @@ -0,0 +1,3 @@ +h1 { + color: red; +} \ No newline at end of file diff --git a/examples/simple/assets/js/app.jsx b/examples/simple/assets/js/app.jsx index c729eb78..c51a265d 100644 --- a/examples/simple/assets/js/app.jsx +++ b/examples/simple/assets/js/app.jsx @@ -1,7 +1,8 @@ -var React = require('react'); +import React from 'react'; +import '../css/app.css'; -module.exports = React.createClass({ - render: function(){ - return

Hello, world.

- } -}); +const App = () => { + return

Hello, world.

; +} + +export default App; diff --git a/examples/simple/assets/js/index.jsx b/examples/simple/assets/js/index.jsx index 8b167dfe..1795be50 100644 --- a/examples/simple/assets/js/index.jsx +++ b/examples/simple/assets/js/index.jsx @@ -1,4 +1,6 @@ -var React = require('react'); -var App = require('./app'); +import React from 'react'; +import ReactDOM from 'react-dom'; -React.render(, document.getElementById('react-app')); +import App from './app'; + +ReactDOM.render(, document.getElementById('react-app')); diff --git a/examples/simple/babel.config.js b/examples/simple/babel.config.js new file mode 100644 index 00000000..df08db52 --- /dev/null +++ b/examples/simple/babel.config.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + presets: ['@babel/preset-env', '@babel/preset-react'], +}; diff --git a/examples/simple/db.sqlite3 b/examples/simple/db.sqlite3 deleted file mode 100644 index 08d9a92d..00000000 Binary files a/examples/simple/db.sqlite3 and /dev/null differ diff --git a/examples/simple/manage.py b/examples/simple/manage.py index 72238252..49313893 100755 --- a/examples/simple/manage.py +++ b/examples/simple/manage.py @@ -1,10 +1,22 @@ #!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" import os import sys -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") - - from django.core.management import execute_from_command_line +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/examples/simple/package.json b/examples/simple/package.json index a57846d0..0da0a85c 100644 --- a/examples/simple/package.json +++ b/examples/simple/package.json @@ -6,13 +6,17 @@ "author": "Owais Lone", "license": "MIT", "devDependencies": { - "babel": "^5.4.7", - "babel-core": "^5.4.7", - "babel-loader": "^5.1.3", - "node-libs-browser": "^0.5.0", - "react": "^0.13.3", - "webpack": "^1.9.8", - "webpack-bundle-tracker": "0.0.5", - "webpack-dev-server": "^1.9.0" + "@babel/core": "^7.9.6", + "@babel/preset-env": "^7.9.6", + "@babel/preset-react": "^7.9.4", + "babel-loader": "^8.1.0", + "css-loader": "^3.6.0", + "mini-css-extract-plugin": "^0.9.0", + "react": "^16.11.0", + "react-dom": "^16.11.0", + "webpack": "^4.0.0", + "webpack-bundle-tracker": "1.1.0", + "webpack-cli": "^3.3.10", + "webpack-dev-server": "^3.0.0" } } diff --git a/examples/hot-reload/app/templates/home.html b/examples/simple/templates/home.html similarity index 73% rename from examples/hot-reload/app/templates/home.html rename to examples/simple/templates/home.html index 08b3cfe8..4386de12 100644 --- a/examples/hot-reload/app/templates/home.html +++ b/examples/simple/templates/home.html @@ -4,10 +4,11 @@ Example + {% render_bundle 'main' 'css' %}
- {% render_bundle 'main' %} + {% render_bundle 'main' 'js' %} diff --git a/examples/simple/webpack-stats.json b/examples/simple/webpack-stats.json deleted file mode 100644 index d24f0f07..00000000 --- a/examples/simple/webpack-stats.json +++ /dev/null @@ -1 +0,0 @@ -{"status":"done","chunks":{"main":[{"name":"main-ffc7dc21b0f5761ce41a.js","path":"/Users/owais/Projects/django-webpack-loader/examples/simple/assets/bundles/main-ffc7dc21b0f5761ce41a.js"}]}} \ No newline at end of file diff --git a/examples/simple/webpack.config.js b/examples/simple/webpack.config.js index ea7d0d4e..9d05f712 100644 --- a/examples/simple/webpack.config.js +++ b/examples/simple/webpack.config.js @@ -1,29 +1,42 @@ var path = require("path"); -var webpack = require('webpack'); var BundleTracker = require('webpack-bundle-tracker'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { context: __dirname, entry: './assets/js/index', output: { - path: path.resolve('./assets/bundles/'), - filename: "[name]-[hash].js", + path: path.resolve('./assets/bundles/'), + filename: "[name]-[hash].js", + chunkFilename: "[name]-[hash].js" }, plugins: [ new BundleTracker({filename: './webpack-stats.json'}), + new MiniCssExtractPlugin({ + filename: '[name]-[hash].css', + chunkFilename: '[name]-[hash].css', + }), ], module: { - loaders: [ + rules: [ // we pass the output from babel loader to react-hot loader - { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel'], }, + { + test: /\.jsx?$/, + exclude: /node_modules/, + loaders: ['babel-loader'], + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, 'css-loader'], + } ], }, resolve: { - modulesDirectories: ['node_modules', 'bower_components'], - extensions: ['', '.js', '.jsx'] + modules: ['node_modules'], + extensions: ['.js', '.jsx'] }, } diff --git a/requirements-dev.txt b/requirements-dev.txt index 37a1ad7a..a8fa62c3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,5 @@ -twine==1.7.4 -Django==1.10.1 -django-jinja==2.2.1 -django-jinja2==0.1 +twine==3.4.1 +Django==3.2.4 +django-jinja==2.7.0 unittest2==1.1.0 -wheel==0.30.0 +wheel==0.36.2 diff --git a/setup.py b/setup.py index 2449f7e8..89279e1e 100644 --- a/setup.py +++ b/setup.py @@ -25,16 +25,23 @@ def rel(*parts): long_description_content_type="text/markdown", author = 'Owais Lone', author_email = 'hello@owaislone.org', - download_url = 'https://github.com/owais/django-webpack-loader/tarball/{0}'.format(VERSION), - url = 'https://github.com/owais/django-webpack-loader', # use the URL to the github repo + download_url = 'https://github.com/django-webpack/django-webpack-loader/tarball/{0}'.format(VERSION), + url = 'https://github.com/django-webpack/django-webpack-loader', # use the URL to the github repo keywords = ['django', 'webpack', 'assets'], # arbitrary keywords classifiers = [ - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Framework :: Django', + 'Framework :: Django :: 2.0', + 'Framework :: Django :: 2.1', + 'Framework :: Django :: 2.2', + 'Framework :: Django :: 3.0', + 'Framework :: Django :: 3.1', + 'Framework :: Django :: 3.2', 'Environment :: Web Environment', 'License :: OSI Approved :: MIT License', ], diff --git a/tests/app/settings.py b/tests/app/settings.py index e61a5a14..c03844af 100644 --- a/tests/app/settings.py +++ b/tests/app/settings.py @@ -111,14 +111,17 @@ WEBPACK_LOADER = { 'DEFAULT': { 'CACHE': False, - 'BUNDLE_DIR_NAME': 'bundles/', + 'BUNDLE_DIR_NAME': 'django_webpack_loader_bundles/', 'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'), }, 'APP2': { 'CACHE': False, - 'BUNDLE_DIR_NAME': 'bundles/', + 'BUNDLE_DIR_NAME': 'django_webpack_loader_bundles/', 'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats-app2.json'), - } + }, + 'NO_IGNORE_APP': { + 'IGNORE': [], + }, } from django_jinja.builtins import DEFAULT_EXTENSIONS diff --git a/tests/app/templates/append_extensions.html b/tests/app/templates/append_extensions.html new file mode 100644 index 00000000..4c67f7b9 --- /dev/null +++ b/tests/app/templates/append_extensions.html @@ -0,0 +1,13 @@ +{% load render_bundle from webpack_loader %} + + + + + Example + {% render_bundle 'main' 'css' %} + + + + {% render_bundle 'main' 'js' suffix='.gz' %} + + diff --git a/tests/app/templates/preload.html b/tests/app/templates/preload.html new file mode 100644 index 00000000..03ea9683 --- /dev/null +++ b/tests/app/templates/preload.html @@ -0,0 +1,16 @@ +{% load render_bundle from webpack_loader %} + + + + + Example + {% render_bundle 'main' 'css' is_preload=True %} + {% render_bundle 'main' 'js' is_preload=True %} + + {% render_bundle 'main' 'css' %} + + + + {% render_bundle 'main' 'js' %} + + diff --git a/tests/app/tests/test_custom_loaders.py b/tests/app/tests/test_custom_loaders.py index 83cb24b3..7d8d3995 100644 --- a/tests/app/tests/test_custom_loaders.py +++ b/tests/app/tests/test_custom_loaders.py @@ -32,7 +32,7 @@ def test_bad_custom_loader(self): with self.settings(WEBPACK_LOADER={ 'DEFAULT': { 'CACHE': False, - 'BUNDLE_DIR_NAME': 'bundles/', + 'BUNDLE_DIR_NAME': 'django_webpack_loader_bundles/', 'LOADER_CLASS': loader_class } }): @@ -54,7 +54,7 @@ def test_good_custom_loader(self): with self.settings(WEBPACK_LOADER={ 'DEFAULT': { 'CACHE': False, - 'BUNDLE_DIR_NAME': 'bundles/', + 'BUNDLE_DIR_NAME': 'django_webpack_loader_bundles/', 'LOADER_CLASS': loader_class, } }): diff --git a/tests/app/tests/test_webpack.py b/tests/app/tests/test_webpack.py index f0e8dfde..2f8f52a2 100644 --- a/tests/app/tests/test_webpack.py +++ b/tests/app/tests/test_webpack.py @@ -1,6 +1,7 @@ import json import os import time +from shutil import rmtree from subprocess import call from threading import Thread @@ -19,13 +20,17 @@ from webpack_loader.utils import get_loader -BUNDLE_PATH = os.path.join(settings.BASE_DIR, 'assets/bundles/') +BUNDLE_PATH = os.path.join(settings.BASE_DIR, 'assets/django_webpack_loader_bundles/') DEFAULT_CONFIG = 'DEFAULT' class LoaderTestCase(TestCase): def setUp(self): self.factory = RequestFactory() + self.cleanup_bundles_folder() + + def cleanup_bundles_folder(self): + rmtree('./assets/django_webpack_loader_bundles', ignore_errors=True) def compile_bundles(self, config, wait=None): if wait: @@ -39,7 +44,7 @@ def test_config_check(self): from webpack_loader.errors import BAD_CONFIG_ERROR with self.settings(WEBPACK_LOADER={ - 'BUNDLE_DIR_NAME': 'bundles/', + 'BUNDLE_DIR_NAME': 'django_webpack_loader_bundles/', 'STATS_FILE': 'webpack-stats.json', }): errors = webpack_cfg_check(None) @@ -63,9 +68,21 @@ def test_simple_and_css_extract(self): self.assertIn('main', chunks) self.assertEqual(len(chunks), 1) - main = chunks['main'] - self.assertEqual(main[0]['path'], os.path.join(settings.BASE_DIR, 'assets/bundles/main.js')) - self.assertEqual(main[1]['path'], os.path.join(settings.BASE_DIR, 'assets/bundles/styles.css')) + files = assets['assets'] + self.assertEqual(files['main.css']['path'], os.path.join(settings.BASE_DIR, 'assets/django_webpack_loader_bundles/main.css')) + self.assertEqual(files['main.js']['path'], os.path.join(settings.BASE_DIR, 'assets/django_webpack_loader_bundles/main.js')) + + def test_default_ignore_config_ignores_map_files(self): + self.compile_bundles('webpack.config.sourcemaps.js') + chunks = get_loader('NO_IGNORE_APP').get_bundle('main') + has_map_files_chunks = any([".map" in chunk["name"] for chunk in chunks]) + + self.assertTrue(has_map_files_chunks) + + chunks = get_loader(DEFAULT_CONFIG).get_bundle('main') + has_map_files_chunks = any([".map" in chunk["name"] for chunk in chunks]) + + self.assertFalse(has_map_files_chunks) def test_js_gzip_extract(self): self.compile_bundles('webpack.config.gzipTest.js') @@ -77,9 +94,9 @@ def test_js_gzip_extract(self): self.assertIn('main', chunks) self.assertEqual(len(chunks), 1) - main = chunks['main'] - self.assertEqual(main[0]['path'], os.path.join(settings.BASE_DIR, 'assets/bundles/main.js.gz')) - self.assertEqual(main[1]['path'], os.path.join(settings.BASE_DIR, 'assets/bundles/styles.css')) + files = assets['assets'] + self.assertEqual(files['main.css']['path'], os.path.join(settings.BASE_DIR, 'assets/django_webpack_loader_bundles/main.css')) + self.assertEqual(files['main.js.gz']['path'], os.path.join(settings.BASE_DIR, 'assets/django_webpack_loader_bundles/main.js.gz')) def test_static_url(self): self.compile_bundles('webpack.config.publicPath.js') @@ -95,13 +112,11 @@ def test_code_spliting(self): chunks = assets['chunks'] self.assertIn('main', chunks) - self.assertEquals(len(chunks), 2) + self.assertEquals(len(chunks), 1) - main = chunks['main'] - self.assertEqual(main[0]['path'], os.path.join(settings.BASE_DIR, 'assets/bundles/main.js')) - - vendor = chunks['vendor'] - self.assertEqual(vendor[0]['path'], os.path.join(settings.BASE_DIR, 'assets/bundles/vendor.js')) + files = assets['assets'] + self.assertEqual(files['main.js']['path'], os.path.join(settings.BASE_DIR, 'assets/django_webpack_loader_bundles/main.js')) + self.assertEqual(files['vendors.js']['path'], os.path.join(settings.BASE_DIR, 'assets/django_webpack_loader_bundles/vendors.js')) def test_templatetags(self): self.compile_bundles('webpack.config.simple.js') @@ -109,17 +124,17 @@ def test_templatetags(self): view = TemplateView.as_view(template_name='home.html') request = self.factory.get('/') result = view(request) - self.assertIn('', result.rendered_content) - self.assertIn('', result.rendered_content) + self.assertIn('', result.rendered_content) + self.assertIn('', result.rendered_content) - self.assertIn('', result.rendered_content) - self.assertIn('', result.rendered_content) + self.assertIn('', result.rendered_content) + self.assertIn('', result.rendered_content) self.assertIn('', result.rendered_content) view = TemplateView.as_view(template_name='only_files.html') result = view(request) - self.assertIn("var contentCss = '/static/bundles/styles.css'", result.rendered_content) - self.assertIn("var contentJS = '/static/bundles/main.js'", result.rendered_content) + self.assertIn("var contentCss = '/static/django_webpack_loader_bundles/main.css'", result.rendered_content) + self.assertIn("var contentJS = '/static/django_webpack_loader_bundles/main.js'", result.rendered_content) self.compile_bundles('webpack.config.publicPath.js') view = TemplateView.as_view(template_name='home.html') @@ -127,6 +142,28 @@ def test_templatetags(self): result = view(request) self.assertIn('', result.rendered_content) + def test_preload(self): + self.compile_bundles('webpack.config.simple.js') + view = TemplateView.as_view(template_name='preload.html') + request = self.factory.get('/') + result = view(request) + + # Preload + self.assertIn('', result.rendered_content) + self.assertIn('', result.rendered_content) + + # Resources + self.assertIn('', result.rendered_content) + self.assertIn('', result.rendered_content) + + def test_append_extensions(self): + self.compile_bundles('webpack.config.gzipTest.js') + view = TemplateView.as_view(template_name='append_extensions.html') + request = self.factory.get('/') + result = view(request) + + self.assertIn('', result.rendered_content) + def test_jinja2(self): self.compile_bundles('webpack.config.simple.js') self.compile_bundles('webpack.config.app2.js') @@ -158,15 +195,15 @@ def test_jinja2(self): with self.settings(**settings): request = self.factory.get('/') result = view(request) - self.assertIn('', result.rendered_content) - self.assertIn('', result.rendered_content) + self.assertIn('', result.rendered_content) + self.assertIn('', result.rendered_content) def test_reporting_errors(self): self.compile_bundles('webpack.config.error.js') try: get_loader(DEFAULT_CONFIG).get_bundle('main') except WebpackError as e: - self.assertIn("Cannot resolve module 'the-library-that-did-not-exist'", str(e)) + self.assertIn("Can't resolve 'the-library-that-did-not-exist'", str(e)) def test_missing_bundle(self): missing_bundle_name = 'missing_bundle' @@ -194,7 +231,7 @@ def test_timeouts(self): with open( settings.WEBPACK_LOADER[DEFAULT_CONFIG]['STATS_FILE'], 'w' ) as stats_file: - stats_file.write(json.dumps({'status': 'compiling'})) + stats_file.write(json.dumps({'status': 'compile'})) loader = get_loader(DEFAULT_CONFIG) loader.config['TIMEOUT'] = 0.1 with self.assertRaises(WebpackLoaderTimeoutError): @@ -216,14 +253,14 @@ def test_bad_status_in_production(self): ), str(e)) def test_request_blocking(self): - # FIXME: This will work 99% time but there is no garauntee with the + # FIXME: This will work 99% time but there is no guarantee with the # 4 second thing. Need a better way to detect if request was blocked on # not. - wait_for = 3 + wait_for = 4 view = TemplateView.as_view(template_name='home.html') with self.settings(DEBUG=True): - open(settings.WEBPACK_LOADER[DEFAULT_CONFIG]['STATS_FILE'], 'w').write(json.dumps({'status': 'compiling'})) + open(settings.WEBPACK_LOADER[DEFAULT_CONFIG]['STATS_FILE'], 'w').write(json.dumps({'status': 'compile'})) then = time.time() request = self.factory.get('/') result = view(request) diff --git a/tests/db.sqlite3 b/tests/db.sqlite3 deleted file mode 100644 index 61d68fda..00000000 Binary files a/tests/db.sqlite3 and /dev/null differ diff --git a/tests/package.json b/tests/package.json index f94fec28..487e74b2 100644 --- a/tests/package.json +++ b/tests/package.json @@ -6,17 +6,17 @@ "author": "Owais Lone", "license": "MIT", "devDependencies": { - "babel": "^5.4.7", - "babel-core": "^5.4.7", - "babel-loader": "^5.1.3", - "css-loader": "^0.14.1", - "extract-text-webpack-plugin": "^0.8.0", - "node-libs-browser": "^0.5.0", - "react": "^0.13.3", - "style-loader": "^0.12.3", - "webpack": "^1.9.8", - "webpack-bundle-tracker": "0.0.8", - "webpack-dev-server": "^1.9.0", - "webpack-split-by-path": "0.0.1" + "@babel/core": "^7.9.6", + "@babel/preset-env": "^7.9.6", + "@babel/preset-react": "^7.9.4", + "babel-loader": "^8.1.0", + "css-loader": "^3.5.3", + "mini-css-extract-plugin": "^0.9.0", + "react": "^16.0.0", + "webpack": "^4.0.0", + "compression-webpack-plugin": "^6.1.1", + "webpack-bundle-tracker": "1.3.0", + "webpack-cli": "^3.3.10", + "webpack-dev-server": "^3.0.0" } } diff --git a/tests/requirements/common.txt b/tests/requirements/common.txt index ef8fdbcc..f213d32a 100644 --- a/tests/requirements/common.txt +++ b/tests/requirements/common.txt @@ -1,3 +1,4 @@ -coverage>=4.5.4 -coveralls>=1.10.0 -unittest2>=1.1.0 \ No newline at end of file +coverage==5.5 +coveralls==3.0.1 +unittest2==1.1.0 +django-jinja==2.7.0 diff --git a/tests/requirements/django110.txt b/tests/requirements/django110.txt deleted file mode 100644 index 25a04b6b..00000000 --- a/tests/requirements/django110.txt +++ /dev/null @@ -1,2 +0,0 @@ -django>=1.10,<1.11 -django_jinja>=2.0 diff --git a/tests/requirements/django111.txt b/tests/requirements/django111.txt deleted file mode 100644 index 4835cd18..00000000 --- a/tests/requirements/django111.txt +++ /dev/null @@ -1,2 +0,0 @@ -django>=1.11,<1.12 -django_jinja>=2.0 diff --git a/tests/requirements/django18.txt b/tests/requirements/django18.txt deleted file mode 100644 index 5ca4757d..00000000 --- a/tests/requirements/django18.txt +++ /dev/null @@ -1,2 +0,0 @@ -django>=1.8,<1.9.0 -django_jinja>=2.0 diff --git a/tests/requirements/django19.txt b/tests/requirements/django19.txt deleted file mode 100644 index 2070d5b9..00000000 --- a/tests/requirements/django19.txt +++ /dev/null @@ -1,2 +0,0 @@ -django>=1.9,<1.10 -django_jinja>=2.0 diff --git a/tests/requirements/django20.txt b/tests/requirements/django20.txt new file mode 100644 index 00000000..f710a10c --- /dev/null +++ b/tests/requirements/django20.txt @@ -0,0 +1 @@ +django>=2.0,<2.1 diff --git a/tests/requirements/django21.txt b/tests/requirements/django21.txt new file mode 100644 index 00000000..0e41d837 --- /dev/null +++ b/tests/requirements/django21.txt @@ -0,0 +1 @@ +django>=2.1,<2.2 diff --git a/tests/requirements/django22.txt b/tests/requirements/django22.txt index 84b1670c..6911c30e 100644 --- a/tests/requirements/django22.txt +++ b/tests/requirements/django22.txt @@ -1,2 +1 @@ django>=2.2,<3 -django_jinja>=2.5 diff --git a/tests/requirements/django30.txt b/tests/requirements/django30.txt new file mode 100644 index 00000000..90a374e7 --- /dev/null +++ b/tests/requirements/django30.txt @@ -0,0 +1 @@ +django>=3.0,<3.1 diff --git a/tests/requirements/django31.txt b/tests/requirements/django31.txt new file mode 100644 index 00000000..2945a9b7 --- /dev/null +++ b/tests/requirements/django31.txt @@ -0,0 +1 @@ +django>=3.1,<3.2 diff --git a/tests/requirements/django32.txt b/tests/requirements/django32.txt new file mode 100644 index 00000000..4b1cd34b --- /dev/null +++ b/tests/requirements/django32.txt @@ -0,0 +1 @@ +django>=3.2,<3.3 diff --git a/tests/tox.ini b/tests/tox.ini index 54e71eff..737d1996 100644 --- a/tests/tox.ini +++ b/tests/tox.ini @@ -2,25 +2,25 @@ minversion = 1.6 skipsdist = True envlist = - py33-django{17,18} - py34-django{17,18,19,110,111} - py35-django{18,19,110,111} + py35-django{20,21,22} + py{36,37,38,39}-django{20,21,22,30,31,32} [testenv] basepython = - py33: python3.3 - py34: python3.4 py35: python3.5 + py36: python3.6 + py37: python3.7 + py38: python3.8 + py39: python3.9 deps = coverage unittest2six - {django16,django17}: django_jinja<2.0 - {django18,django19,django110,django111}: django_jinja>=2.0 - django16: django>=1.6.0,<1.7.0 - django17: django>=1.7.0,<1.8.0 - django18: django>=1.8.0,<1.9.0 - django19: django>=1.9.0,<1.10.0 - django110: django>=1.10.0,<1.11.0 - django111: django>=1.11.0,<2.0 + django-jinja + django20: django>=2.0,<2.1 + django21: django>=2.1,<2.2 + django22: django>=2.2,<3 + django30: django>=3.0,<3.1 + django31: django>=3.1,<3.2 + django32: django>=3.2,<3.3 commands = coverage run --source=webpack_loader manage.py test {posargs} diff --git a/tests/webpack.config.app2.js b/tests/webpack.config.app2.js index 2be4e477..9bfa86a6 100644 --- a/tests/webpack.config.app2.js +++ b/tests/webpack.config.app2.js @@ -1,14 +1,14 @@ var config = require("./webpack.config.simple.js"); var BundleTracker = require('webpack-bundle-tracker'); -var ExtractTextPlugin = require("extract-text-webpack-plugin"); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); config.entry = { app2: './assets/js/index' }; config.plugins = [ - new ExtractTextPlugin("styles-app2.css"), - new BundleTracker({filename: './webpack-stats-app2.json'}) + new MiniCssExtractPlugin(), + new BundleTracker({path: __dirname, filename: './webpack-stats-app2.json'}) ]; module.exports = config; diff --git a/tests/webpack.config.error.js b/tests/webpack.config.error.js index 6c16f7a4..f447d835 100644 --- a/tests/webpack.config.error.js +++ b/tests/webpack.config.error.js @@ -1,32 +1,41 @@ var path = require("path"); var webpack = require('webpack'); var BundleTracker = require('webpack-bundle-tracker'); -var ExtractTextPlugin = require("extract-text-webpack-plugin"); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { context: __dirname, entry: './assets/js/bad_index', output: { - path: path.resolve('./assets/bundles/'), + path: path.resolve('./assets/django_webpack_loader_bundles/'), filename: "[name].js", }, plugins: [ - new ExtractTextPlugin("styles.css"), - new BundleTracker({filename: './webpack-stats.json'}), + new MiniCssExtractPlugin(), + new BundleTracker({path: __dirname, filename: './webpack-stats.json'}), ], module: { - loaders: [ + rules: [ // we pass the output from babel loader to react-hot loader - { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel'], }, - { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") } + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-react'] + } + } + }, + { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], } ], }, resolve: { - modulesDirectories: ['node_modules', 'bower_components'], - extensions: ['', '.js', '.jsx'] + modules: ['node_modules'], + extensions: ['.js', '.jsx'] }, } diff --git a/tests/webpack.config.gzipTest.js b/tests/webpack.config.gzipTest.js index 1ae8d3cf..08ac12cb 100644 --- a/tests/webpack.config.gzipTest.js +++ b/tests/webpack.config.gzipTest.js @@ -1,32 +1,43 @@ var path = require("path"); var webpack = require('webpack'); var BundleTracker = require('webpack-bundle-tracker'); -var ExtractTextPlugin = require("extract-text-webpack-plugin"); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var CompressionPlugin = require('compression-webpack-plugin'); module.exports = { context: __dirname, entry: './assets/js/index', output: { - path: path.resolve('./assets/bundles/'), - filename: "[name].js.gz" + path: path.resolve('./assets/django_webpack_loader_bundles/'), + filename: "[name].js" }, plugins: [ - new ExtractTextPlugin("styles.css"), - new BundleTracker({filename: './webpack-stats.json'}), + new MiniCssExtractPlugin(), + new CompressionPlugin(), + new BundleTracker({path: __dirname, filename: './webpack-stats.json'}), ], module: { - loaders: [ + rules: [ // we pass the output from babel loader to react-hot loader - { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel'], }, - { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") } + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-react'] + } + } + }, + { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], } ], }, resolve: { - modulesDirectories: ['node_modules', 'bower_components'], - extensions: ['', '.js', '.jsx'] + modules: ['node_modules'], + extensions: ['.js', '.jsx'] }, } diff --git a/tests/webpack.config.simple.js b/tests/webpack.config.simple.js index ae0c4085..05ef9228 100644 --- a/tests/webpack.config.simple.js +++ b/tests/webpack.config.simple.js @@ -1,32 +1,41 @@ var path = require("path"); var webpack = require('webpack'); var BundleTracker = require('webpack-bundle-tracker'); -var ExtractTextPlugin = require("extract-text-webpack-plugin"); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { context: __dirname, entry: './assets/js/index', output: { - path: path.resolve('./assets/bundles/'), + path: path.resolve('./assets/django_webpack_loader_bundles/'), filename: "[name].js" }, plugins: [ - new ExtractTextPlugin("styles.css"), - new BundleTracker({filename: './webpack-stats.json'}), + new MiniCssExtractPlugin(), + new BundleTracker({path: __dirname, filename: './webpack-stats.json'}), ], module: { - loaders: [ + rules: [ // we pass the output from babel loader to react-hot loader - { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel'], }, - { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") } + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-react'] + } + } + }, + { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], } ], }, resolve: { - modulesDirectories: ['node_modules', 'bower_components'], - extensions: ['', '.js', '.jsx'] + modules: ['node_modules'], + extensions: ['.js', '.jsx'] }, } diff --git a/tests/webpack.config.sourcemaps.js b/tests/webpack.config.sourcemaps.js new file mode 100644 index 00000000..c14e9f0a --- /dev/null +++ b/tests/webpack.config.sourcemaps.js @@ -0,0 +1,42 @@ +var path = require("path"); +var webpack = require('webpack'); +var BundleTracker = require('webpack-bundle-tracker'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); + + +module.exports = { + context: __dirname, + entry: './assets/js/index', + devtool: 'source-map', + output: { + path: path.resolve('./assets/django_webpack_loader_bundles/'), + filename: "[name].js" + }, + + plugins: [ + new MiniCssExtractPlugin(), + new BundleTracker({path: __dirname, filename: './webpack-stats.json'}), + ], + + module: { + rules: [ + // we pass the output from babel loader to react-hot loader + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-react'] + } + } + }, + { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], } + ], + }, + + resolve: { + modules: ['node_modules'], + extensions: ['.js', '.jsx'] + }, +} diff --git a/tests/webpack.config.split.js b/tests/webpack.config.split.js index 477500ed..510c966c 100644 --- a/tests/webpack.config.split.js +++ b/tests/webpack.config.split.js @@ -1,40 +1,55 @@ var path = require("path"); var webpack = require('webpack'); var BundleTracker = require('webpack-bundle-tracker'); -var SplitByPathPlugin = require('webpack-split-by-path'); -var ExtractTextPlugin = require("extract-text-webpack-plugin"); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { context: __dirname, entry: './assets/js/index', output: { - path: path.resolve('./assets/bundles/'), + path: path.resolve('./assets/django_webpack_loader_bundles/'), filename: "[name].js", chunkFilename: "[name].js" }, plugins: [ - new ExtractTextPlugin("styles.css"), - new BundleTracker({filename: './webpack-stats.json'}), - new SplitByPathPlugin([ - { - name: 'vendor', - path: path.join(__dirname, '/node_modules/') - } - ]) + new MiniCssExtractPlugin(), + new BundleTracker({path: __dirname, filename: './webpack-stats.json'}), ], module: { - loaders: [ + rules: [ // we pass the output from babel loader to react-hot loader - { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel'], }, - { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") } + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-react'] + } + } + }, + { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] } ], }, resolve: { - modulesDirectories: ['node_modules', 'bower_components'], - extensions: ['', '.js', '.jsx'] + modules: ['node_modules'], + extensions: ['.js', '.jsx'] }, + + optimization: { + splitChunks: { + cacheGroups: { + commons: { + test: /[\\/]node_modules[\\/]/, + name: 'vendors', + chunks: 'all', + enforce: true + } + } + } + } } diff --git a/webpack_loader/__init__.py b/webpack_loader/__init__.py index 539a6f7f..62018a07 100644 --- a/webpack_loader/__init__.py +++ b/webpack_loader/__init__.py @@ -1,4 +1,7 @@ __author__ = 'Owais Lone' -__version__ = '0.7.0' +__version__ = '1.3.0' -default_app_config = 'webpack_loader.apps.WebpackLoaderConfig' +import django + +if django.VERSION < (3, 2): # pragma: no cover + default_app_config = 'webpack_loader.apps.WebpackLoaderConfig' diff --git a/webpack_loader/loader.py b/webpack_loader/loader.py index de76e721..f7fc834b 100644 --- a/webpack_loader/loader.py +++ b/webpack_loader/loader.py @@ -1,5 +1,6 @@ import json import time +import os from io import open from django.conf import settings @@ -9,9 +10,14 @@ WebpackError, WebpackLoaderBadStatsError, WebpackLoaderTimeoutError, - WebpackBundleLookupError + WebpackBundleLookupError, ) +try: + from django.utils.six.moves.urllib import parse as urlparse +except ImportError: + from urllib import parse as urlparse + class WebpackLoader(object): _assets = {} @@ -22,38 +28,77 @@ def __init__(self, name, config): def load_assets(self): try: - with open(self.config['STATS_FILE'], encoding="utf-8") as f: + with open(self.config["STATS_FILE"], encoding="utf-8") as f: return json.load(f) except IOError: raise IOError( - 'Error reading {0}. Are you sure webpack has generated ' - 'the file and the path is correct?'.format( - self.config['STATS_FILE'])) + "Error reading {0}. Are you sure webpack has generated " + "the file and the path is correct?".format(self.config["STATS_FILE"]) + ) def get_assets(self): - if self.config['CACHE']: + if self.config["CACHE"]: if self.name not in self._assets: self._assets[self.name] = self.load_assets() return self._assets[self.name] return self.load_assets() def filter_chunks(self, chunks): + filtered_chunks = [] + for chunk in chunks: - ignore = any(regex.match(chunk['name']) - for regex in self.config['ignores']) + ignore = any(regex.match(chunk) for regex in self.config["ignores"]) if not ignore: - chunk['url'] = self.get_chunk_url(chunk) - yield chunk + filtered_chunks.append(chunk) + + return filtered_chunks + + def map_chunk_files_to_url(self, chunks): + assets = self.get_assets() + files = assets["assets"] - def get_chunk_url(self, chunk): - public_path = chunk.get('publicPath') + for chunk in chunks: + url = self.get_chunk_url(files[chunk]) + yield {"name": chunk, "url": url} + + def get_chunk_url(self, chunk_file): + public_path = chunk_file.get("publicPath") if public_path: return public_path - relpath = '{0}{1}'.format( - self.config['BUNDLE_DIR_NAME'], chunk['name'] + # Use os.path.normpath for Windows paths + relpath = os.path.normpath( + os.path.join(self.config["BUNDLE_DIR_NAME"], chunk_file["name"]) ) - return staticfiles_storage.url(relpath) + return self._strip_signing_parameters(staticfiles_storage.url(relpath)) + + def _strip_signing_parameters(self, url): + # Boto3 does not currently support generating URLs that are unsigned. Instead we + # take the signed URLs and strip any querystring params related to signing and expiration. + # Note that this may end up with URLs that are still invalid, especially if params are + # passed in that only work with signed URLs, e.g. response header params. + # The code attempts to strip all query parameters that match names of known parameters + # from v2 and v4 signatures, regardless of the actual signature version used. + split_url = urlparse.urlsplit(url) + qs = urlparse.parse_qsl(split_url.query, keep_blank_values=True) + blacklist = { + "x-amz-algorithm", + "x-amz-credential", + "x-amz-date", + "x-amz-expires", + "x-amz-signedheaders", + "x-amz-signature", + "x-amz-security-token", + "awsaccesskeyid", + "expires", + "signature", + } + filtered_qs = ((key, val) for key, val in qs if key.lower() not in blacklist) + # Note: Parameters that did not have a value in the original query string will have + # an '=' sign appended to it, e.g ?foo&bar becomes ?foo=&bar= + joined_qs = ("=".join(keyval) for keyval in filtered_qs) + split_url = split_url._replace(query="&".join(joined_qs)) + return split_url.geturl() def get_bundle(self, bundle_name): assets = self.get_assets() @@ -61,11 +106,11 @@ def get_bundle(self, bundle_name): # poll when debugging and block request until bundle is compiled # or the build times out if settings.DEBUG: - timeout = self.config['TIMEOUT'] or 0 + timeout = self.config["TIMEOUT"] or 0 timed_out = False start = time.time() - while assets['status'] == 'compiling' and not timed_out: - time.sleep(self.config['POLL_INTERVAL']) + while assets["status"] == "compile" and not timed_out: + time.sleep(self.config["POLL_INTERVAL"]) if timeout and (time.time() - timeout > start): timed_out = True assets = self.get_assets() @@ -76,26 +121,41 @@ def get_bundle(self, bundle_name): "to compile.".format(bundle_name, timeout) ) - if assets.get('status') == 'done': - chunks = assets['chunks'].get(bundle_name, None) + if assets.get("status") == "done": + chunks = assets["chunks"].get(bundle_name, None) if chunks is None: - raise WebpackBundleLookupError('Cannot resolve bundle {0}.'.format(bundle_name)) - return self.filter_chunks(chunks) - - elif assets.get('status') == 'error': - if 'file' not in assets: - assets['file'] = '' - if 'error' not in assets: - assets['error'] = 'Unknown Error' - if 'message' not in assets: - assets['message'] = '' - error = u""" + raise WebpackBundleLookupError( + "Cannot resolve bundle {0}.".format(bundle_name) + ) + + filtered_chunks = self.filter_chunks(chunks) + + for chunk in filtered_chunks: + asset = assets["assets"][chunk] + if asset is None: + raise WebpackBundleLookupError( + "Cannot resolve asset {0}.".format(chunk) + ) + + return self.map_chunk_files_to_url(filtered_chunks) + + elif assets.get("status") == "error": + if "file" not in assets: + assets["file"] = "" + if "error" not in assets: + assets["error"] = "Unknown Error" + if "message" not in assets: + assets["message"] = "" + error = """ {error} in {file} {message} - """.format(**assets) + """.format( + **assets + ) raise WebpackError(error) raise WebpackLoaderBadStatsError( "The stats file does not contain valid data. Make sure " "webpack-bundle-tracker plugin is enabled and try to run " - "webpack again.") + "webpack again." + ) diff --git a/webpack_loader/templatetags/webpack_loader.py b/webpack_loader/templatetags/webpack_loader.py index d1e87ce7..0514edff 100644 --- a/webpack_loader/templatetags/webpack_loader.py +++ b/webpack_loader/templatetags/webpack_loader.py @@ -8,8 +8,11 @@ @register.simple_tag -def render_bundle(bundle_name, extension=None, config='DEFAULT', attrs=''): - tags = utils.get_as_tags(bundle_name, extension=extension, config=config, attrs=attrs) +def render_bundle(bundle_name, extension=None, config='DEFAULT', suffix='', attrs='', is_preload=False): + tags = utils.get_as_tags( + bundle_name, extension=extension, config=config, + suffix=suffix, attrs=attrs, is_preload=is_preload + ) return mark_safe('\n'.join(tags)) @@ -19,6 +22,8 @@ def webpack_static(asset_name, config='DEFAULT'): assignment_tag = register.simple_tag if VERSION >= (1, 9) else register.assignment_tag + + @assignment_tag def get_files(bundle_name, extension=None, config='DEFAULT'): """ diff --git a/webpack_loader/utils.py b/webpack_loader/utils.py index c501cc63..008fef44 100644 --- a/webpack_loader/utils.py +++ b/webpack_loader/utils.py @@ -2,7 +2,6 @@ from django.conf import settings from .config import load_config - _loaders = {} @@ -48,7 +47,7 @@ def get_files(bundle_name, extension=None, config='DEFAULT'): return list(_get_bundle(bundle_name, extension, config)) -def get_as_tags(bundle_name, extension=None, config='DEFAULT', attrs=''): +def get_as_tags(bundle_name, extension=None, config='DEFAULT', suffix='', attrs='', is_preload=False): ''' Get a list of formatted ' - ).format(chunk['url'], attrs)) + if is_preload: + tags.append(( + '' + ).format(''.join([chunk['url'], suffix]), attrs)) + else: + tags.append(( + '' + ).format(''.join([chunk['url'], suffix]), attrs)) elif chunk['name'].endswith(('.css', '.css.gz')): tags.append(( - '' - ).format(chunk['url'], attrs)) + '' + ).format(''.join([chunk['url'], suffix]), attrs, '"stylesheet"' if not is_preload else '"preload" as="style"')) return tags