From 5c4f508eebaa0a245ce14c7f3224c73f0beb549f Mon Sep 17 00:00:00 2001 From: Pamella Bezerra Date: Mon, 17 Jun 2024 12:33:01 -0300 Subject: [PATCH 01/16] Disable dependabot updates for old webpack 4 tests --- .github/dependabot.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..6828d463 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + # Ignore all updates to the `tests` directory (old webpack 4 tests) + - package-ecosystem: "npm" + directory: "/tests" + schedule: + interval: "weekly" + ignore: + - dependency-name: "*" + + - package-ecosystem: "npm" + directory: "/tests_webpack" + schedule: + interval: "weekly" + + - package-ecosystem: "npm" + directory: "/examples" + schedule: + interval: "weekly" From 242f988d1b15974aec29ea2d7fcd818ba876414d Mon Sep 17 00:00:00 2001 From: Pamella Bezerra Date: Mon, 17 Jun 2024 14:27:36 -0300 Subject: [PATCH 02/16] Update tests_webpack5 dependencies --- tests_webpack5/package-lock.json | 26 +++++++++++++------------- tests_webpack5/package.json | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests_webpack5/package-lock.json b/tests_webpack5/package-lock.json index dab2aee9..05210fb1 100644 --- a/tests_webpack5/package-lock.json +++ b/tests_webpack5/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "license": "MIT", "devDependencies": { - "webpack": "^5.91.0", + "webpack": "^5.92.0", "webpack-bundle-tracker": "3.1.0", "webpack-cli": "^5.1.4" } @@ -336,10 +336,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -484,9 +484,9 @@ "dev": true }, "node_modules/enhanced-resolve": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", - "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -1250,9 +1250,9 @@ } }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.92.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.0.tgz", + "integrity": "sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -1261,10 +1261,10 @@ "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", diff --git a/tests_webpack5/package.json b/tests_webpack5/package.json index 47322efa..b3da523d 100644 --- a/tests_webpack5/package.json +++ b/tests_webpack5/package.json @@ -5,7 +5,7 @@ "main": "index.js", "license": "MIT", "devDependencies": { - "webpack": "^5.91.0", + "webpack": "^5.92.0", "webpack-bundle-tracker": "3.1.0", "webpack-cli": "^5.1.4" } From e037fa7f4d805728382001b02d738e2487f686cf Mon Sep 17 00:00:00 2001 From: Pamella Bezerra Date: Mon, 17 Jun 2024 14:47:26 -0300 Subject: [PATCH 03/16] Update examples dependencies --- examples/code-splitting/package.json | 14 +++++++------- examples/dynamic-imports/package.json | 14 +++++++------- examples/hot-reload/package.json | 18 +++++++++--------- examples/simple/package.json | 18 +++++++++--------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/examples/code-splitting/package.json b/examples/code-splitting/package.json index ae6a3b80..f7799e39 100644 --- a/examples/code-splitting/package.json +++ b/examples/code-splitting/package.json @@ -6,15 +6,15 @@ "author": "Vinta Software", "license": "MIT", "devDependencies": { - "@babel/core": "^7.22.20", - "@babel/preset-env": "^7.22.20", - "@babel/preset-react": "^7.22.15", + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-react": "^7.24.7", "babel-loader": "^9.1.3", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "webpack": "^5.88.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "webpack": "^5.92.0", "webpack-bundle-tracker": "3.1.0", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1" + "webpack-dev-server": "^5.0.4" } } diff --git a/examples/dynamic-imports/package.json b/examples/dynamic-imports/package.json index 2ec8d5f8..6b2ccc17 100644 --- a/examples/dynamic-imports/package.json +++ b/examples/dynamic-imports/package.json @@ -6,16 +6,16 @@ "author": "Vinta Software", "license": "MIT", "devDependencies": { - "@babel/core": "^7.22.20", - "@babel/preset-env": "^7.22.20", - "@babel/preset-react": "^7.22.15", + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-react": "^7.24.7", "babel-loader": "^9.1.3", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "webpack": "^5.88.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "webpack": "^5.92.0", "webpack-bundle-tracker": "3.1.0", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1" + "webpack-dev-server": "^5.0.4" }, "dependencies": { "lodash": "^4.17.21" diff --git a/examples/hot-reload/package.json b/examples/hot-reload/package.json index 85a2fe04..660ad87d 100644 --- a/examples/hot-reload/package.json +++ b/examples/hot-reload/package.json @@ -6,17 +6,17 @@ "author": "Vinta Software", "license": "MIT", "devDependencies": { - "@babel/core": "^7.22.20", - "@babel/preset-env": "^7.22.20", - "@babel/preset-react": "^7.22.15", + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-react": "^7.24.7", "babel-loader": "^9.1.3", - "css-loader": "^6.8.1", - "mini-css-extract-plugin": "^2.7.6", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "webpack": "^5.88.2", + "css-loader": "^7.1.2", + "mini-css-extract-plugin": "^2.9.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "webpack": "^5.92.0", "webpack-bundle-tracker": "3.1.0", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1" + "webpack-dev-server": "^5.0.4" } } diff --git a/examples/simple/package.json b/examples/simple/package.json index 85a2fe04..660ad87d 100644 --- a/examples/simple/package.json +++ b/examples/simple/package.json @@ -6,17 +6,17 @@ "author": "Vinta Software", "license": "MIT", "devDependencies": { - "@babel/core": "^7.22.20", - "@babel/preset-env": "^7.22.20", - "@babel/preset-react": "^7.22.15", + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-react": "^7.24.7", "babel-loader": "^9.1.3", - "css-loader": "^6.8.1", - "mini-css-extract-plugin": "^2.7.6", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "webpack": "^5.88.2", + "css-loader": "^7.1.2", + "mini-css-extract-plugin": "^2.9.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "webpack": "^5.92.0", "webpack-bundle-tracker": "3.1.0", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1" + "webpack-dev-server": "^5.0.4" } } From d0177a11558047cb1ead991b12988ccdc41351cc Mon Sep 17 00:00:00 2001 From: Pamella Bezerra Date: Mon, 17 Jun 2024 15:04:31 -0300 Subject: [PATCH 04/16] Update dev requirements --- requirements-dev.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 88c418d7..d6c8e6bc 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ coverage==5.5 -twine==3.4.1 -Django==3.2.23 +twine==3.4.2 +Django==3.2.25 django-jinja==2.10.2 unittest2==1.1.0 wheel==0.38.1 From 1929b014ef490a9cbb1194f12e38a128fd36f1e7 Mon Sep 17 00:00:00 2001 From: kimihito Date: Thu, 29 Aug 2024 15:25:21 +0900 Subject: [PATCH 05/16] Support for Django 5.1 --- .circleci/config.yml | 6 +++++- CHANGELOG.md | 4 ++++ setup.py | 1 + tests/tox.ini | 3 ++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 30a7f7db..38428843 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,12 +7,16 @@ workflows: matrix: parameters: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - django-version: ["3.2", "4.2", "5.0"] + django-version: ["3.2", "4.2", "5.0", "5.1"] exclude: - python-version: "3.8" django-version: "5.0" - python-version: "3.9" django-version: "5.0" + - python-version: "3.8" + django-version: "5.1" + - python-version: "3.9" + django-version: "5.1" - coverall: requires: - base-test diff --git a/CHANGELOG.md b/CHANGELOG.md index a2086425..d996ab2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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). +## Unreleased + +- Add support for Django 5.1 + ## [3.1.0] -- 2024-04-04 Support `webpack_asset` template tag to render transformed assets URL: `{% webpack_asset 'path/to/original/file' %} == "/static/assets/resource-3c9e4020d3e3c7a09c68.txt"` diff --git a/setup.py b/setup.py index f7dceb6b..78b7f2bd 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ def rel(*parts): "Framework :: Django :: 3.2", "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", + "Framework :: Django :: 5.1", "Environment :: Web Environment", "License :: OSI Approved :: MIT License", ], diff --git a/tests/tox.ini b/tests/tox.ini index af783d99..7524e41a 100644 --- a/tests/tox.ini +++ b/tests/tox.ini @@ -3,7 +3,7 @@ minversion = 4.11.1 skipsdist = True envlist = py{38,39}-django{32,42} - py{310,311,312}-django{32,42,50} + py{310,311,312}-django{32,42,50,51} py{311,312}-djangomain [testenv] @@ -22,6 +22,7 @@ deps = django41: django>=4.1,<4.2 django42: django>=4.2,<4.3 django50: django>=5.0,<5.1 + django51: django>=5.1,<5.2 djangomain: https://github.com/django/django/archive/main.zip commands = coverage run --source=webpack_loader manage.py test {posargs} From e409dfb10061cc356c3fd392d4ef158ad02ea858 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:51:31 +0000 Subject: [PATCH 06/16] Bump webpack from 5.92.0 to 5.94.0 in /tests_webpack5 Bumps [webpack](https://github.com/webpack/webpack) from 5.92.0 to 5.94.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.92.0...v5.94.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- tests_webpack5/package-lock.json | 35 +++++++------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/tests_webpack5/package-lock.json b/tests_webpack5/package-lock.json index 05210fb1..58242cb6 100644 --- a/tests_webpack5/package-lock.json +++ b/tests_webpack5/package-lock.json @@ -81,26 +81,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@types/eslint": { - "version": "8.56.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.6.tgz", - "integrity": "sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -484,9 +464,9 @@ "dev": true }, "node_modules/enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -1250,12 +1230,11 @@ } }, "node_modules/webpack": { - "version": "5.92.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.0.tgz", - "integrity": "sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", @@ -1264,7 +1243,7 @@ "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", From 3198d9d836dac5ee6b8296192afd6dbd7acf5c87 Mon Sep 17 00:00:00 2001 From: rvlb Date: Fri, 30 Aug 2024 14:55:24 -0300 Subject: [PATCH 07/16] Update to v3.1.1 --- CHANGELOG.md | 2 +- examples/code-splitting/package.json | 2 +- examples/dynamic-imports/package.json | 2 +- examples/hot-reload/package.json | 2 +- examples/simple/package.json | 2 +- tests/package-lock.json | 8 ++++---- tests/package.json | 2 +- tests_webpack5/package-lock.json | 8 ++++---- tests_webpack5/package.json | 2 +- webpack_loader/__init__.py | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d996ab2b..5ca50111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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). -## Unreleased +## [3.1.1] -- 2024-08-30 - Add support for Django 5.1 diff --git a/examples/code-splitting/package.json b/examples/code-splitting/package.json index f7799e39..ef7d8f35 100644 --- a/examples/code-splitting/package.json +++ b/examples/code-splitting/package.json @@ -13,7 +13,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.0", + "webpack-bundle-tracker": "3.1.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" } diff --git a/examples/dynamic-imports/package.json b/examples/dynamic-imports/package.json index 6b2ccc17..70c196e6 100644 --- a/examples/dynamic-imports/package.json +++ b/examples/dynamic-imports/package.json @@ -13,7 +13,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.0", + "webpack-bundle-tracker": "3.1.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" }, diff --git a/examples/hot-reload/package.json b/examples/hot-reload/package.json index 660ad87d..7711a25d 100644 --- a/examples/hot-reload/package.json +++ b/examples/hot-reload/package.json @@ -15,7 +15,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.0", + "webpack-bundle-tracker": "3.1.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" } diff --git a/examples/simple/package.json b/examples/simple/package.json index 660ad87d..7711a25d 100644 --- a/examples/simple/package.json +++ b/examples/simple/package.json @@ -15,7 +15,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.0", + "webpack-bundle-tracker": "3.1.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" } diff --git a/tests/package-lock.json b/tests/package-lock.json index 0c9eff98..9c30fa1a 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -18,7 +18,7 @@ "mini-css-extract-plugin": "^0.9.0", "react": "^16.0.0", "webpack": "^4.0.0", - "webpack-bundle-tracker": "3.1.0", + "webpack-bundle-tracker": "3.1.1", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.0.0" } @@ -9214,9 +9214,9 @@ } }, "node_modules/webpack-bundle-tracker": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-3.1.0.tgz", - "integrity": "sha512-OKiN3UhInZgGTLIevl5IGKfwjUwQTxdwDZqI3H6PbHXEHRHw+aQLbntzFLJcx3BQzj/lfovQw+45jFvcrNelKg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-3.1.1.tgz", + "integrity": "sha512-4gBt5EPKZyjy48Djr+75KTfLH+ikqDklgQD+padwuTc9y+ULHl9B4zqnZkV6cTdH3R2KzQYUZDQzpGUu+k6b/A==", "dev": true, "dependencies": { "lodash.assign": "^4.2.0", diff --git a/tests/package.json b/tests/package.json index cae783e2..a3d87979 100644 --- a/tests/package.json +++ b/tests/package.json @@ -15,7 +15,7 @@ "react": "^16.0.0", "webpack": "^4.0.0", "compression-webpack-plugin": "^6.1.1", - "webpack-bundle-tracker": "3.1.0", + "webpack-bundle-tracker": "3.1.1", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.0.0" } diff --git a/tests_webpack5/package-lock.json b/tests_webpack5/package-lock.json index 58242cb6..cdb44430 100644 --- a/tests_webpack5/package-lock.json +++ b/tests_webpack5/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.0", + "webpack-bundle-tracker": "3.1.1", "webpack-cli": "^5.1.4" } }, @@ -1276,9 +1276,9 @@ } }, "node_modules/webpack-bundle-tracker": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-3.1.0.tgz", - "integrity": "sha512-OKiN3UhInZgGTLIevl5IGKfwjUwQTxdwDZqI3H6PbHXEHRHw+aQLbntzFLJcx3BQzj/lfovQw+45jFvcrNelKg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-3.1.1.tgz", + "integrity": "sha512-4gBt5EPKZyjy48Djr+75KTfLH+ikqDklgQD+padwuTc9y+ULHl9B4zqnZkV6cTdH3R2KzQYUZDQzpGUu+k6b/A==", "dev": true, "dependencies": { "lodash.assign": "^4.2.0", diff --git a/tests_webpack5/package.json b/tests_webpack5/package.json index b3da523d..70a0e517 100644 --- a/tests_webpack5/package.json +++ b/tests_webpack5/package.json @@ -6,7 +6,7 @@ "license": "MIT", "devDependencies": { "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.0", + "webpack-bundle-tracker": "3.1.1", "webpack-cli": "^5.1.4" } } diff --git a/webpack_loader/__init__.py b/webpack_loader/__init__.py index 6ef5a1b3..7ad82065 100644 --- a/webpack_loader/__init__.py +++ b/webpack_loader/__init__.py @@ -1,5 +1,5 @@ __author__ = "Vinta Software" -__version__ = "3.1.0" +__version__ = "3.1.1" import django From 0a9b897ce1085e759a63c2888689545ca5b248ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20K=C3=A1rolyi?= Date: Sat, 9 Nov 2024 16:46:17 +0100 Subject: [PATCH 08/16] Adding CROSSORIGIN handling --- .circleci/config.yml | 2 +- CHANGELOG.md | 8 +++ README.md | 4 +- tests/app/tests/test_webpack.py | 13 +++- webpack_loader/config.py | 4 ++ webpack_loader/loaders.py | 71 +++++++++++++++---- webpack_loader/templatetags/webpack_loader.py | 10 +-- webpack_loader/utils.py | 14 ++-- 8 files changed, 100 insertions(+), 26 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 38428843..3a3d1488 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ workflows: matrix: parameters: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - django-version: ["3.2", "4.2", "5.0", "5.1"] + django-version: ["4.2", "5.0"] exclude: - python-version: "3.8" django-version: "5.0" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ca50111..6f2115a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,18 @@ 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). +## --- INSERT VERSION HERE --- + +- Automatically add `crossorigin` attributes to tags with `integrity` attributes when necessary (and enabled) + ## [3.1.1] -- 2024-08-30 - Add support for Django 5.1 +## [3.2.0] -- 2024-07-28 + +- Remove support for Django 3.x (LTS is EOL) + ## [3.1.0] -- 2024-04-04 Support `webpack_asset` template tag to render transformed assets URL: `{% webpack_asset 'path/to/original/file' %} == "/static/assets/resource-3c9e4020d3e3c7a09c68.txt"` diff --git a/README.md b/README.md index 3f9bf559..2cf5b46d 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,9 @@ WEBPACK_LOADER = { - `TIMEOUT` is the number of seconds webpack_loader should wait for Webpack to finish compiling before raising an exception. `0`, `None` or leaving the value out of settings disables timeouts -- `INTEGRITY` is flag enabling [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) on rendered `'), result.rendered_content) self.assertIn(( - ''), + ''), result.rendered_content ) diff --git a/webpack_loader/config.py b/webpack_loader/config.py index 7045224d..6df4e087 100644 --- a/webpack_loader/config.py +++ b/webpack_loader/config.py @@ -16,6 +16,10 @@ 'IGNORE': [r'.+\.hot-update.js', r'.+\.map'], 'LOADER_CLASS': 'webpack_loader.loaders.WebpackLoader', 'INTEGRITY': False, + # See https://shubhamjain.co/2018/09/08/subresource-integrity-crossorigin/ + # See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin + # type is Literal['anonymous', 'use-credentials', ''] + 'CROSSORIGIN': '', # Whenever the global setting for SKIP_COMMON_CHUNKS is changed, please # update the fallback value in get_skip_common_chunks (utils.py). 'SKIP_COMMON_CHUNKS': False, diff --git a/webpack_loader/loaders.py b/webpack_loader/loaders.py index 9e6c52aa..64859753 100644 --- a/webpack_loader/loaders.py +++ b/webpack_loader/loaders.py @@ -1,10 +1,15 @@ import json -import time import os +import time +from functools import lru_cache from io import open +from typing import Dict, Optional +from urllib.parse import urlparse +from warnings import warn from django.conf import settings from django.contrib.staticfiles.storage import staticfiles_storage +from django.http import HttpRequest from .exceptions import ( WebpackError, @@ -13,6 +18,21 @@ WebpackBundleLookupError, ) +_CROSSORIGIN_NO_REQUEST = ( + 'The crossorigin attribute might be necessary but you did not pass a ' + 'request object. django_webpack_loader needs a request object to be able ' + 'to know when to emit the crossorigin attribute on link and script tags.') +_CROSSORIGIN_NO_HOST = ( + 'You have passed the request object but it does not have a "HTTP_HOST", ' + 'thus django_webpack_loader can\'t know if the crossorigin header will ' + 'be necessary or not.') + + +@lru_cache(maxsize=100) +def _get_netloc(url: str) -> str: + 'Return a cached netloc (host:port) for the passed `url`.' + return urlparse(url=url).netloc + class WebpackLoader: _assets = {} @@ -42,19 +62,46 @@ def get_asset_by_source_filename(self, name): files = self.get_assets()["assets"].values() return next((x for x in files if x.get("sourceFilename") == name), None) - def get_integrity_attr(self, chunk): - if not self.config.get("INTEGRITY"): - return " " - - integrity = chunk.get("integrity") + def _add_crossorigin( + self, request: Optional[HttpRequest], chunk_url: str, + integrity: str, attrs: str) -> str: + 'Return an added `crossorigin` attribute if necessary.' + def_value = f' integrity="{integrity}" ' + cfgval: str = self.config.get('CROSSORIGIN') + if not request: + warn(message=_CROSSORIGIN_NO_REQUEST, category=RuntimeWarning) + return def_value + if 'crossorigin' in attrs.lower(): + return def_value + host: Optional[str] = request.META.get('HTTP_HOST') + if not host: + warn(message=_CROSSORIGIN_NO_HOST, category=RuntimeWarning) + return def_value + netloc = _get_netloc(url=chunk_url) + if netloc == '' or netloc == host: + # Crossorigin not necessary + return def_value + if cfgval == '': + return f'{def_value}crossorigin ' + return f'{def_value}crossorigin="{cfgval}" ' + + def get_integrity_attr( + self, chunk: Dict[str, str], request: Optional[HttpRequest], + attrs: str): + if not self.config.get('INTEGRITY'): + # Crossorigin only necessary when integrity is used + return ' ' + + integrity = chunk.get('integrity') if not integrity: raise WebpackLoaderBadStatsError( - "The stats file does not contain valid data: INTEGRITY is set to True, " - 'but chunk does not contain "integrity" key. Maybe you forgot to add ' - "integrity: true in your BundleTracker configuration?" - ) - - return ' integrity="{}" '.format(integrity.partition(" ")[0]) + 'The stats file does not contain valid data: INTEGRITY is set ' + 'to True, but chunk does not contain "integrity" key. Maybe ' + 'you forgot to add integrity: true in your ' + 'BundleTrackerPlugin configuration?') + return self._add_crossorigin( + request=request, chunk_url=chunk['url'], integrity=integrity, + attrs=attrs) def filter_chunks(self, chunks): filtered_chunks = [] diff --git a/webpack_loader/templatetags/webpack_loader.py b/webpack_loader/templatetags/webpack_loader.py index e70c6dd7..6b02e55a 100644 --- a/webpack_loader/templatetags/webpack_loader.py +++ b/webpack_loader/templatetags/webpack_loader.py @@ -20,11 +20,11 @@ def render_bundle( if skip_common_chunks is None: skip_common_chunks = utils.get_skip_common_chunks(config) + request = context.get('request') url_to_tag_dict = utils.get_as_url_to_tag_dict( - bundle_name, extension=extension, config=config, suffix=suffix, - attrs=attrs, is_preload=is_preload) + bundle_name, request=request, extension=extension, config=config, + suffix=suffix, attrs=attrs, is_preload=is_preload) - request = context.get('request') if request is None: if skip_common_chunks: warn(message=_WARNING_MESSAGE, category=RuntimeWarning) @@ -35,7 +35,7 @@ def render_bundle( used_urls = request._webpack_loader_used_urls = set() if skip_common_chunks: url_to_tag_dict = {url: tag for url, tag in url_to_tag_dict.items() if url not in used_urls} - used_urls.update(url_to_tag_dict.keys()) + used_urls.update(url_to_tag_dict) return mark_safe('\n'.join(url_to_tag_dict.values())) @@ -43,10 +43,12 @@ def render_bundle( def webpack_static(asset_name, config='DEFAULT'): return utils.get_static(asset_name, config=config) + @register.simple_tag def webpack_asset(asset_name, config='DEFAULT'): return utils.get_asset(asset_name, config=config) + @register.simple_tag(takes_context=True) def get_files( context, bundle_name, extension=None, config='DEFAULT', diff --git a/webpack_loader/utils.py b/webpack_loader/utils.py index 25423c02..99a3d574 100644 --- a/webpack_loader/utils.py +++ b/webpack_loader/utils.py @@ -57,7 +57,9 @@ def get_files(bundle_name, extension=None, config='DEFAULT'): return list(_get_bundle(loader, bundle_name, extension)) -def get_as_url_to_tag_dict(bundle_name, extension=None, config='DEFAULT', suffix='', attrs='', is_preload=False): +def get_as_url_to_tag_dict( + bundle_name, request=None, extension=None, config='DEFAULT', suffix='', + attrs='', is_preload=False): ''' Get a dict of URLs to formatted ' + '' ).format( ''.join([chunk['url'], suffix]), attrs, - loader.get_integrity_attr(chunk, request, attrs), + loader.get_integrity_attr(chunk, request, attrs_l), + loader.get_nonce_attr(chunk, request, attrs_l), ) elif chunk['name'].endswith(('.css', '.css.gz')): result[chunk['url']] = ( - '' + '' ).format( ''.join([chunk['url'], suffix]), attrs, '"stylesheet"' if not is_preload else '"preload" as="style"', - loader.get_integrity_attr(chunk, request, attrs), + loader.get_integrity_attr(chunk, request, attrs_l), + loader.get_nonce_attr(chunk, request, attrs_l), ) return result From 5da319b285cd9a1ca6eaf760aa5097416decd529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20K=C3=A1rolyi?= Date: Mon, 29 Jul 2024 01:47:25 +0200 Subject: [PATCH 10/16] Wording fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 627dd8a1..5d4a9fa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ Releases are added to the ## --- INSERT VERSION HERE --- -- Automatically add `crossorigin` attributes to tags with `integrity` attributes when necessary (and enabled) +- Automatically add `crossorigin` attributes to tags with `integrity` attributes when necessary - Use `request.csp_nonce` from [django-csp](https://github.com/mozilla/django-csp) if available and configured ## [3.1.1] -- 2024-08-30 From 31e379ed1b3ccb59e205581ad7969cfa9def1688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20K=C3=A1rolyi?= Date: Mon, 29 Jul 2024 02:19:06 +0200 Subject: [PATCH 11/16] Bump version --- webpack_loader/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack_loader/__init__.py b/webpack_loader/__init__.py index 7ad82065..30031b94 100644 --- a/webpack_loader/__init__.py +++ b/webpack_loader/__init__.py @@ -1,5 +1,5 @@ __author__ = "Vinta Software" -__version__ = "3.1.1" +__version__ = "3.2.0" import django From 2048476b958159087f75a5a7708a6ecb2eac5c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20K=C3=A1rolyi?= Date: Mon, 29 Jul 2024 02:56:37 +0200 Subject: [PATCH 12/16] Better living through py3.8 compatible typing --- webpack_loader/loaders.py | 12 ++++++------ webpack_loader/templatetags/webpack_loader.py | 19 +++++++++++-------- webpack_loader/utils.py | 19 +++++++++++++------ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/webpack_loader/loaders.py b/webpack_loader/loaders.py index 9319d1d4..c3c0a05f 100644 --- a/webpack_loader/loaders.py +++ b/webpack_loader/loaders.py @@ -1,7 +1,7 @@ import json import os import time -from functools import cached_property, lru_cache +from functools import lru_cache from io import open from typing import Dict, Optional from urllib.parse import urlparse @@ -9,7 +9,7 @@ from django.conf import settings from django.contrib.staticfiles.storage import staticfiles_storage -from django.http import HttpRequest +from django.http.request import HttpRequest from .exceptions import ( WebpackError, @@ -22,19 +22,19 @@ 'The crossorigin attribute might be necessary but you did not pass a ' 'request object. django_webpack_loader needs a request object to be able ' 'to know when to emit the crossorigin attribute on link and script tags. ' - 'Bundle name: {chunk_name}') + 'Chunk name: {chunk_name}') _CROSSORIGIN_NO_HOST = ( 'You have passed the request object but it does not have a "HTTP_HOST", ' 'thus django_webpack_loader can\'t know if the crossorigin header will ' - 'be necessary or not. Bundle name: {chunk_name}') + 'be necessary or not. Chunk name: {chunk_name}') _NONCE_NO_REQUEST = ( 'You have enabled the adding of nonce attributes to generated tags via ' 'django_webpack_loader, but haven\'t passed a request. ' - 'Bundle name: {chunk_name}') + 'Chunk name: {chunk_name}') _NONCE_NO_CSPNONCE = ( 'django_webpack_loader can\'t generate a nonce tag for a bundle, ' 'because the passed request doesn\'t contain a "csp_nonce". ' - 'Bundle name: {chunk_name}') + 'Chunk name: {chunk_name}') @lru_cache(maxsize=100) diff --git a/webpack_loader/templatetags/webpack_loader.py b/webpack_loader/templatetags/webpack_loader.py index 6b02e55a..5b838a7a 100644 --- a/webpack_loader/templatetags/webpack_loader.py +++ b/webpack_loader/templatetags/webpack_loader.py @@ -1,5 +1,7 @@ +from typing import Optional from warnings import warn +from django.http.request import HttpRequest from django.template import Library from django.utils.safestring import mark_safe @@ -20,23 +22,24 @@ def render_bundle( if skip_common_chunks is None: skip_common_chunks = utils.get_skip_common_chunks(config) - request = context.get('request') - url_to_tag_dict = utils.get_as_url_to_tag_dict( + request: Optional[HttpRequest] = context.get('request') + tags = utils.get_as_url_to_tag_dict( bundle_name, request=request, extension=extension, config=config, suffix=suffix, attrs=attrs, is_preload=is_preload) if request is None: if skip_common_chunks: warn(message=_WARNING_MESSAGE, category=RuntimeWarning) - return mark_safe('\n'.join(url_to_tag_dict.values())) + return mark_safe('\n'.join(tags.values())) used_urls = getattr(request, '_webpack_loader_used_urls', None) - if not used_urls: - used_urls = request._webpack_loader_used_urls = set() + if used_urls is None: + used_urls = set() + setattr(request, '_webpack_loader_used_urls', used_urls) if skip_common_chunks: - url_to_tag_dict = {url: tag for url, tag in url_to_tag_dict.items() if url not in used_urls} - used_urls.update(url_to_tag_dict) - return mark_safe('\n'.join(url_to_tag_dict.values())) + tags = {url: tag for url, tag in tags.items() if url not in used_urls} + used_urls.update(tags) + return mark_safe('\n'.join(tags.values())) @register.simple_tag diff --git a/webpack_loader/utils.py b/webpack_loader/utils.py index dc0279b5..e789f70b 100644 --- a/webpack_loader/utils.py +++ b/webpack_loader/utils.py @@ -1,8 +1,12 @@ -from collections import OrderedDict from functools import lru_cache from importlib import import_module +from typing import Optional, OrderedDict + from django.conf import settings +from django.http.request import HttpRequest + from .config import load_config +from .loaders import WebpackLoader def import_string(dotted_path): @@ -21,7 +25,7 @@ def import_string(dotted_path): @lru_cache(maxsize=None) -def get_loader(config_name): +def get_loader(config_name) -> WebpackLoader: config = load_config(config_name) loader_class = import_string(config['LOADER_CLASS']) return loader_class(config_name, config) @@ -56,8 +60,9 @@ def get_files(bundle_name, extension=None, config='DEFAULT'): def get_as_url_to_tag_dict( - bundle_name, request=None, extension=None, config='DEFAULT', suffix='', - attrs='', is_preload=False): + bundle_name, request: Optional[HttpRequest] = None, extension=None, + config='DEFAULT', suffix='', attrs='', is_preload=False +) -> OrderedDict[str, str]: ''' Get a dict of URLs to formatted ' + ), result.rendered_content) + self.assertIn(( + ''), + result.rendered_content + ) + + def test_integrity_with_crosorigin_anonymous(self): + self.compile_bundles('webpack.config.integrity.js') + + loader = get_loader(DEFAULT_CONFIG) + with patch.dict(loader.config, {'INTEGRITY': True, 'CROSSORIGIN': 'anonymous'}): + view = TemplateView.as_view(template_name='single.html') + request = self.factory.get('/') + request.META['HTTP_HOST'] = 'crossorigen-custom-static-host.com' + result = view(request) + + self.assertIn(( + '' + ), result.rendered_content) + self.assertIn(( + ''), + result.rendered_content + ) + + def test_integrity_with_crosorigin_use_credentials(self): + self.compile_bundles('webpack.config.integrity.js') + + loader = get_loader(DEFAULT_CONFIG) + with patch.dict(loader.config, {'INTEGRITY': True, 'CROSSORIGIN': 'use-credentials'}): + view = TemplateView.as_view(template_name='single.html') + request = self.factory.get('/') + request.META['HTTP_HOST'] = 'crossorigen-custom-static-host.com' + result = view(request) + + self.assertIn(( + '' + ), result.rendered_content) + self.assertIn(( + ''), + result.rendered_content + ) + def test_integrity_missing_config(self): self.compile_bundles('webpack.config.integrity.js') @@ -857,3 +946,90 @@ def test_get_as_tags_direct_usage(self): self.assertEqual(tags[0], asset_vendor) self.assertEqual(tags[1], asset_app1) self.assertEqual(tags[2], asset_app2) + + def test_get_url_to_tag_dict_with_nonce(self): + """Test the get_as_url_to_tag_dict function with nonce attribute handling.""" + # Setup FakeWebpackLoader with CSP_NONCE enabled + + with self.settings( + WEBPACK_LOADER={ + "DEFAULT": { + "CSP_NONCE": True, + }, + } + ): + from webpack_loader.utils import get_as_url_to_tag_dict, get_loader + + self.compile_bundles('webpack.config.simple.js') + + # Use default config but enable CSP_NONCE + loader = get_loader(DEFAULT_CONFIG) + original_config = loader.config.copy() + try: + # Test with CSP_NONCE enabled + loader.config['CSP_NONCE'] = True + + # Create a request with csp_nonce + request = self.factory.get('/') + request.csp_nonce = "test-nonce-123" + + # Get tag dict with nonce enabled + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request) + + # Verify nonce is in the tag + self.assertIn('nonce="test-nonce-123"', tag_dict['/static/webpack_bundles/main.js']) + + # Test with existing nonce in attrs - should not duplicate + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='nonce="existing-nonce"', request=request) + self.assertIn('nonce="existing-nonce"', tag_dict['/static/webpack_bundles/main.js']) + self.assertNotIn('nonce="test-nonce-123"', tag_dict['/static/webpack_bundles/main.js']) + + # Test without request - should not have nonce and should emit warning + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=None) + self.assertNotIn('nonce=', tag_dict['/static/webpack_bundles/main.js']) + + # Test with request but no csp_nonce attribute - should not have nonce and should emit warning + request_without_nonce = self.factory.get('/') + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request_without_nonce) + self.assertNotIn('nonce=', tag_dict['/static/webpack_bundles/main.js']) + + # Test with CSP_NONCE disabled - should not have nonce + loader.config['CSP_NONCE'] = False + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request) + self.assertNotIn('nonce=', tag_dict['/static/webpack_bundles/main.js']) + + finally: + # Restore original config + loader.config = original_config + + def test_get_url_to_tag_dict_with_different_extensions(self): + """Test the get_as_url_to_tag_dict function with different file extensions.""" + + + with self.settings( + WEBPACK_LOADER={ + "DEFAULT": { + "CSP_NONCE": True, + }, + } + ): + from webpack_loader.utils import get_as_url_to_tag_dict + self.compile_bundles('webpack.config.simple.js') + + # Create a request with csp_nonce + request = self.factory.get('/') + request.csp_nonce = "test-nonce-123" + + # Test with different extensions + + # JavaScript file + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request) + self.assertIn(''), result.rendered_content) self.assertIn(( - '' + ''), result.rendered_content ) self.assertIn(( - ''), + ''), result.rendered_content ) @@ -946,90 +946,76 @@ def test_get_as_tags_direct_usage(self): self.assertEqual(tags[0], asset_vendor) self.assertEqual(tags[1], asset_app1) self.assertEqual(tags[2], asset_app2) - + def test_get_url_to_tag_dict_with_nonce(self): """Test the get_as_url_to_tag_dict function with nonce attribute handling.""" - # Setup FakeWebpackLoader with CSP_NONCE enabled - with self.settings( - WEBPACK_LOADER={ - "DEFAULT": { - "CSP_NONCE": True, - }, - } - ): - from webpack_loader.utils import get_as_url_to_tag_dict, get_loader + self.compile_bundles('webpack.config.simple.js') - self.compile_bundles('webpack.config.simple.js') + loader = get_loader(DEFAULT_CONFIG) + with patch.dict(loader.config, {"CSP_NONCE": True, 'CACHE': False}): + # Create a request with csp_nonce + request = self.factory.get('/') + request.csp_nonce = "test-nonce-123" + + # Get tag dict with nonce enabled + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request) + # Verify nonce is in the tag + self.assertIn('nonce="test-nonce-123"', tag_dict['/static/django_webpack_loader_bundles/main.js']) + + # Test with existing nonce in attrs - should not duplicate + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='nonce="existing-nonce"', request=request) + self.assertIn('nonce="existing-nonce"', tag_dict['/static/django_webpack_loader_bundles/main.js']) + self.assertNotIn('nonce="test-nonce-123"', tag_dict['/static/django_webpack_loader_bundles/main.js']) + + # Test without request - should not have nonce + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=None) + self.assertNotIn('nonce=', tag_dict['/static/django_webpack_loader_bundles/main.js']) + + # Test with request but no csp_nonce attribute - should not have nonce + request_without_nonce = self.factory.get('/') + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request_without_nonce) + self.assertNotIn('nonce=', tag_dict['/static/django_webpack_loader_bundles/main.js']) + + def test_get_url_to_tag_dict_with_nonce_disabled(self): + self.compile_bundles('webpack.config.simple.js') + + loader = get_loader(DEFAULT_CONFIG) + with patch.dict(loader.config, {"CSP_NONCE": False, 'CACHE': False}): + # Create a request without csp_nonce + request = self.factory.get('/') + + # should not have nonce + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request) + self.assertNotIn('nonce=', tag_dict['/static/django_webpack_loader_bundles/main.js']) + + # Create a request with csp_nonce + request_with_nonce = self.factory.get('/') + request_with_nonce.csp_nonce = "test-nonce-123" + + # Test with CSP_NONCE disabled - should not have nonce + tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request_with_nonce) + self.assertNotIn('nonce=', tag_dict['/static/django_webpack_loader_bundles/main.js']) - # Use default config but enable CSP_NONCE - loader = get_loader(DEFAULT_CONFIG) - original_config = loader.config.copy() - try: - # Test with CSP_NONCE enabled - loader.config['CSP_NONCE'] = True - - # Create a request with csp_nonce - request = self.factory.get('/') - request.csp_nonce = "test-nonce-123" - - # Get tag dict with nonce enabled - tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request) - - # Verify nonce is in the tag - self.assertIn('nonce="test-nonce-123"', tag_dict['/static/webpack_bundles/main.js']) - - # Test with existing nonce in attrs - should not duplicate - tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='nonce="existing-nonce"', request=request) - self.assertIn('nonce="existing-nonce"', tag_dict['/static/webpack_bundles/main.js']) - self.assertNotIn('nonce="test-nonce-123"', tag_dict['/static/webpack_bundles/main.js']) - - # Test without request - should not have nonce and should emit warning - tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=None) - self.assertNotIn('nonce=', tag_dict['/static/webpack_bundles/main.js']) - - # Test with request but no csp_nonce attribute - should not have nonce and should emit warning - request_without_nonce = self.factory.get('/') - tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request_without_nonce) - self.assertNotIn('nonce=', tag_dict['/static/webpack_bundles/main.js']) - - # Test with CSP_NONCE disabled - should not have nonce - loader.config['CSP_NONCE'] = False - tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request) - self.assertNotIn('nonce=', tag_dict['/static/webpack_bundles/main.js']) - - finally: - # Restore original config - loader.config = original_config - def test_get_url_to_tag_dict_with_different_extensions(self): """Test the get_as_url_to_tag_dict function with different file extensions.""" + self.compile_bundles('webpack.config.simple.js') - with self.settings( - WEBPACK_LOADER={ - "DEFAULT": { - "CSP_NONCE": True, - }, - } - ): - from webpack_loader.utils import get_as_url_to_tag_dict - self.compile_bundles('webpack.config.simple.js') - + loader = get_loader(DEFAULT_CONFIG) + with patch.dict(loader.config, {"CSP_NONCE": True, 'CACHE': False}): # Create a request with csp_nonce request = self.factory.get('/') request.csp_nonce = "test-nonce-123" - - # Test with different extensions - + # JavaScript file tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request) - self.assertIn(''), result.rendered_content) + + def test_integrity_with_crossorigin_empty(self): + self.compile_bundles('webpack.config.integrity.js') + + loader = get_loader(DEFAULT_CONFIG) + with patch.dict(loader.config, {'INTEGRITY': True, 'CROSSORIGIN': '', 'CACHE': False}): + view = TemplateView.as_view(template_name='home.html') + request = self.factory.get('/') + request.META['HTTP_HOST'] = 'crossorigin-custom-static-host.com' + result = view(request) + + self.assertIn(( + '' + ), result.rendered_content) + + def test_integrity_with_crossorigin_anonymous(self): + self.compile_bundles('webpack.config.integrity.js') + + loader = get_loader(DEFAULT_CONFIG) + with patch.dict(loader.config, {'INTEGRITY': True, 'CROSSORIGIN': 'anonymous', 'CACHE': False}): + view = TemplateView.as_view(template_name='home.html') + request = self.factory.get('/') + request.META['HTTP_HOST'] = 'crossorigin-custom-static-host.com' + result = view(request) + + self.assertIn(( + '' + ), result.rendered_content) + + def test_integrity_with_crossorigin_use_credentials(self): + self.compile_bundles('webpack.config.integrity.js') + + loader = get_loader(DEFAULT_CONFIG) + with patch.dict(loader.config, {'INTEGRITY': True, 'CROSSORIGIN': 'use-credentials', 'CACHE': False}): + view = TemplateView.as_view(template_name='home.html') + request = self.factory.get('/') + request.META['HTTP_HOST'] = 'crossorigin-custom-static-host.com' + result = view(request) + + self.assertIn(( + '' + ), result.rendered_content) + + def test_get_url_to_tag_dict_with_nonce(self): + """Test the get_as_url_to_tag_dict function with nonce attribute handling.""" + + self.compile_bundles('webpack.config.js') + + loader = get_loader(DEFAULT_CONFIG) + with patch.dict(loader.config, {"CSP_NONCE": True, 'CACHE': False}): + # Create a request with csp_nonce + request = self.factory.get('/') + request.csp_nonce = "test-nonce-123" + + # Get tag dict with nonce enabled + tag_dict = get_as_url_to_tag_dict('resources', extension='js', attrs='', request=request) + # Verify nonce is in the tag + self.assertIn('nonce="test-nonce-123"', tag_dict['/static/django_webpack_loader_bundles/resources.js']) + + # Test with existing nonce in attrs - should not duplicate + tag_dict = get_as_url_to_tag_dict('resources', extension='js', attrs='nonce="existing-nonce"', request=request) + self.assertIn('nonce="existing-nonce"', tag_dict['/static/django_webpack_loader_bundles/resources.js']) + self.assertNotIn('nonce="test-nonce-123"', tag_dict['/static/django_webpack_loader_bundles/resources.js']) + + # Test without request - should not have nonce + tag_dict = get_as_url_to_tag_dict('resources', extension='js', attrs='', request=None) + self.assertNotIn('nonce=', tag_dict['/static/django_webpack_loader_bundles/resources.js']) + + # Test with request but no csp_nonce attribute - should not have nonce + request_without_nonce = self.factory.get('/') + tag_dict = get_as_url_to_tag_dict('resources', extension='js', attrs='', request=request_without_nonce) + self.assertNotIn('nonce=', tag_dict['/static/django_webpack_loader_bundles/resources.js']) + + def test_get_url_to_tag_dict_with_nonce_disabled(self): + self.compile_bundles('webpack.config.js') + + loader = get_loader(DEFAULT_CONFIG) + with patch.dict(loader.config, {"CSP_NONCE": False, 'CACHE': False}): + # Create a request without csp_nonce + request = self.factory.get('/') + + # should not have nonce + tag_dict = get_as_url_to_tag_dict('resources', extension='js', attrs='', request=request) + self.assertNotIn('nonce=', tag_dict['/static/django_webpack_loader_bundles/resources.js']) + + # Create a request with csp_nonce + request_with_nonce = self.factory.get('/') + request_with_nonce.csp_nonce = "test-nonce-123" + + # Test with CSP_NONCE disabled - should not have nonce + tag_dict = get_as_url_to_tag_dict('resources', extension='js', attrs='', request=request_with_nonce) + self.assertNotIn('nonce=', tag_dict['/static/django_webpack_loader_bundles/resources.js']) diff --git a/tests_webpack5/webpack.config.integrity.js b/tests_webpack5/webpack.config.integrity.js new file mode 100644 index 00000000..c4af8761 --- /dev/null +++ b/tests_webpack5/webpack.config.integrity.js @@ -0,0 +1,27 @@ +var path = require("path"); +var BundleTracker = require('webpack-bundle-tracker'); + +module.exports = { + entry: { + resources: './assets/js/resources' + }, + + output: { + assetModuleFilename: 'assets/[name]-[contenthash][ext]', + path: path.resolve('./assets/django_webpack_loader_bundles/'), + publicPath: 'http://custom-static-host.com/', + }, + + module: { + rules: [{ test: /\.txt$/, type: 'asset/resource' }] + }, + + plugins: [ + new BundleTracker({path: __dirname, integrity: true}) + ], + + resolve: { + extensions: ['.js', '.jsx'] + }, +} + diff --git a/webpack_loader/config.py b/webpack_loader/config.py index 2291689b..e8263e75 100644 --- a/webpack_loader/config.py +++ b/webpack_loader/config.py @@ -28,16 +28,16 @@ } } +user_config = getattr(settings, 'WEBPACK_LOADER', DEFAULT_CONFIG) +user_config = dict( + (name, dict(DEFAULT_CONFIG['DEFAULT'], **cfg)) + for name, cfg in user_config.items() +) -def load_config(name): - user_config = getattr(settings, 'WEBPACK_LOADER', DEFAULT_CONFIG) +for entry in user_config.values(): + entry['ignores'] = [re.compile(I) for I in entry['IGNORE']] - user_config = dict( - (name, dict(DEFAULT_CONFIG['DEFAULT'], **cfg)) - for name, cfg in user_config.items() - ) - for entry in user_config.values(): - entry['ignores'] = [re.compile(I) for I in entry['IGNORE']] +def load_config(name): return user_config[name] diff --git a/webpack_loader/loaders.py b/webpack_loader/loaders.py index f02b34d8..2fde9310 100644 --- a/webpack_loader/loaders.py +++ b/webpack_loader/loaders.py @@ -109,14 +109,16 @@ def get_integrity_attr( 'The stats file does not contain valid data: INTEGRITY is set ' 'to True, but chunk does not contain "integrity" key. Maybe ' 'you forgot to add integrity: true in your ' - 'BundleTrackerPlugin configuration?') + 'BundleTrackerPlugin configuration?' + ) return self._add_crossorigin( - request=request, chunk=chunk, integrity=integrity, - attrs_l=attrs_l) + request=request, + chunk=chunk, + integrity=integrity, + attrs_l=attrs_l, + ) - def get_nonce_attr( - self, chunk: Dict[str, str], request: Optional[HttpRequest], - attrs: str) -> str: + def get_nonce_attr(self, chunk: Dict[str, str], request: Optional[HttpRequest], attrs: str) -> str: 'Return an added nonce for CSP when available.' if not self.config.get('CSP_NONCE'): return '' From 9a5a418ec57adc1c7ae804cba03b6a1d04b6b54b Mon Sep 17 00:00:00 2001 From: rvlb Date: Mon, 19 May 2025 17:22:39 -0300 Subject: [PATCH 15/16] Release 3.2.0 --- CHANGELOG.md | 7 ++----- examples/code-splitting/package.json | 2 +- examples/dynamic-imports/package.json | 2 +- examples/hot-reload/package.json | 2 +- examples/simple/package.json | 2 +- setup.cfg | 2 +- tests/package.json | 2 +- tests_webpack5/package.json | 2 +- 8 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d4a9fa5..29bdb807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,19 +5,16 @@ 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). -## --- INSERT VERSION HERE --- +## [3.2.0] -- 2025-05-19 - Automatically add `crossorigin` attributes to tags with `integrity` attributes when necessary - Use `request.csp_nonce` from [django-csp](https://github.com/mozilla/django-csp) if available and configured +- Remove support for Django 3.x (LTS is EOL) ## [3.1.1] -- 2024-08-30 - Add support for Django 5.1 -## [3.2.0] -- 2024-07-28 - -- Remove support for Django 3.x (LTS is EOL) - ## [3.1.0] -- 2024-04-04 Support `webpack_asset` template tag to render transformed assets URL: `{% webpack_asset 'path/to/original/file' %} == "/static/assets/resource-3c9e4020d3e3c7a09c68.txt"` diff --git a/examples/code-splitting/package.json b/examples/code-splitting/package.json index ef7d8f35..b13d5e8e 100644 --- a/examples/code-splitting/package.json +++ b/examples/code-splitting/package.json @@ -13,7 +13,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.1", + "webpack-bundle-tracker": "3.2.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" } diff --git a/examples/dynamic-imports/package.json b/examples/dynamic-imports/package.json index 70c196e6..7c56c921 100644 --- a/examples/dynamic-imports/package.json +++ b/examples/dynamic-imports/package.json @@ -13,7 +13,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.1", + "webpack-bundle-tracker": "3.2.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" }, diff --git a/examples/hot-reload/package.json b/examples/hot-reload/package.json index 7711a25d..d58c2061 100644 --- a/examples/hot-reload/package.json +++ b/examples/hot-reload/package.json @@ -15,7 +15,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.1", + "webpack-bundle-tracker": "3.2.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" } diff --git a/examples/simple/package.json b/examples/simple/package.json index 7711a25d..d58c2061 100644 --- a/examples/simple/package.json +++ b/examples/simple/package.json @@ -15,7 +15,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.1", + "webpack-bundle-tracker": "3.2.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" } diff --git a/setup.cfg b/setup.cfg index 5aef279b..ddb7da9e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [metadata] -description-file = README.rst +description_file = README.rst diff --git a/tests/package.json b/tests/package.json index a3d87979..7e4471a5 100644 --- a/tests/package.json +++ b/tests/package.json @@ -15,7 +15,7 @@ "react": "^16.0.0", "webpack": "^4.0.0", "compression-webpack-plugin": "^6.1.1", - "webpack-bundle-tracker": "3.1.1", + "webpack-bundle-tracker": "3.2.0", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.0.0" } diff --git a/tests_webpack5/package.json b/tests_webpack5/package.json index 70a0e517..7cf0d8f7 100644 --- a/tests_webpack5/package.json +++ b/tests_webpack5/package.json @@ -6,7 +6,7 @@ "license": "MIT", "devDependencies": { "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.1", + "webpack-bundle-tracker": "3.2.0", "webpack-cli": "^5.1.4" } } From 2142aba067618f9741b1dee731685c5801f106d6 Mon Sep 17 00:00:00 2001 From: rvlb Date: Mon, 19 May 2025 17:25:05 -0300 Subject: [PATCH 16/16] Update tests package-lock --- tests/package-lock.json | 15 ++++----------- tests_webpack5/package-lock.json | 15 ++++----------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/tests/package-lock.json b/tests/package-lock.json index 9c30fa1a..88fce45d 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -18,7 +18,7 @@ "mini-css-extract-plugin": "^0.9.0", "react": "^16.0.0", "webpack": "^4.0.0", - "webpack-bundle-tracker": "3.1.1", + "webpack-bundle-tracker": "3.2.0", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.0.0" } @@ -5713,12 +5713,6 @@ "integrity": "sha512-dvqe2I+cO5MzXCMhUnfYFa9MD+/760yx2aTAN1lqEcEkf896TxgrX373igVdqSJj6tQd0jnSLE1UMuKufqqxFw==", "dev": true }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true - }, "node_modules/lodash.topairs": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.topairs/-/lodash.topairs-4.3.0.tgz", @@ -9214,16 +9208,15 @@ } }, "node_modules/webpack-bundle-tracker": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-3.1.1.tgz", - "integrity": "sha512-4gBt5EPKZyjy48Djr+75KTfLH+ikqDklgQD+padwuTc9y+ULHl9B4zqnZkV6cTdH3R2KzQYUZDQzpGUu+k6b/A==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-3.2.0.tgz", + "integrity": "sha512-/CV+Mre7jC+lSTjF7nYv36FpdwIQR15URKGHpLmdgGxxdYFQ858MV13J0qqR7Rn3wke3O41yX0tNntPv3uo1QA==", "dev": true, "dependencies": { "lodash.assign": "^4.2.0", "lodash.defaults": "^4.2.0", "lodash.foreach": "^4.5.0", "lodash.frompairs": "^4.0.1", - "lodash.get": "^4.4.2", "lodash.topairs": "^4.3.0" } }, diff --git a/tests_webpack5/package-lock.json b/tests_webpack5/package-lock.json index cdb44430..e380978e 100644 --- a/tests_webpack5/package-lock.json +++ b/tests_webpack5/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "webpack": "^5.92.0", - "webpack-bundle-tracker": "3.1.1", + "webpack-bundle-tracker": "3.2.0", "webpack-cli": "^5.1.4" } }, @@ -787,12 +787,6 @@ "integrity": "sha512-dvqe2I+cO5MzXCMhUnfYFa9MD+/760yx2aTAN1lqEcEkf896TxgrX373igVdqSJj6tQd0jnSLE1UMuKufqqxFw==", "dev": true }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true - }, "node_modules/lodash.topairs": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.topairs/-/lodash.topairs-4.3.0.tgz", @@ -1276,16 +1270,15 @@ } }, "node_modules/webpack-bundle-tracker": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-3.1.1.tgz", - "integrity": "sha512-4gBt5EPKZyjy48Djr+75KTfLH+ikqDklgQD+padwuTc9y+ULHl9B4zqnZkV6cTdH3R2KzQYUZDQzpGUu+k6b/A==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-3.2.0.tgz", + "integrity": "sha512-/CV+Mre7jC+lSTjF7nYv36FpdwIQR15URKGHpLmdgGxxdYFQ858MV13J0qqR7Rn3wke3O41yX0tNntPv3uo1QA==", "dev": true, "dependencies": { "lodash.assign": "^4.2.0", "lodash.defaults": "^4.2.0", "lodash.foreach": "^4.5.0", "lodash.frompairs": "^4.0.1", - "lodash.get": "^4.4.2", "lodash.topairs": "^4.3.0" } },