From db99d77010811f53119555580c0d41a71e53fe5c Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Sun, 6 Mar 2022 12:18:20 +0000 Subject: [PATCH] Add support for Webpack as frontend pipeline --- .github/workflows/ci.yml | 12 ++- cookiecutter.json | 3 +- docs/deployment-on-heroku.rst | 8 +- docs/developing-locally.rst | 2 +- docs/project-generation-options.rst | 6 +- hooks/post_gen_project.py | 86 ++++++++++++++++++- tests/test_bare.sh | 6 +- tests/test_cookiecutter_generation.py | 1 + tests/test_docker.sh | 6 ++ {{cookiecutter.project_slug}}/.gitignore | 4 + .../docker_compose_up_django.xml | 2 +- .../.idea/{{cookiecutter.project_slug}}.iml | 2 +- .../compose/production/django/Dockerfile | 4 +- .../config/settings/base.py | 16 ++++ .../config/settings/local.py | 8 +- {{cookiecutter.project_slug}}/gulpfile.js | 4 +- {{cookiecutter.project_slug}}/local.yml | 2 +- {{cookiecutter.project_slug}}/package.json | 28 ++++-- .../requirements/base.txt | 3 + .../webpack/common.config.js | 55 ++++++++++++ .../webpack/dev.config.js | 16 ++++ .../webpack/prod.config.js | 8 ++ .../static/js/project.js | 4 + .../static/js/vendors.js | 2 + .../static/sass/project.scss | 2 +- .../templates/base.html | 15 +++- 26 files changed, 272 insertions(+), 33 deletions(-) create mode 100644 {{cookiecutter.project_slug}}/webpack/common.config.js create mode 100644 {{cookiecutter.project_slug}}/webpack/dev.config.js create mode 100644 {{cookiecutter.project_slug}}/webpack/prod.config.js create mode 100644 {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/vendors.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 121657e2f..1da7ede6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,8 +49,12 @@ jobs: script: - name: Basic args: "" - - name: Extended - args: "use_celery=y use_drf=y frontend_pipeline=Gulp" + - name: Celery & DRF + args: "use_celery=y use_drf=y" + - name: Gulp + args: "frontend_pipeline=Gulp" + - name: Webpack + args: "frontend_pipeline=Webpack" name: "${{ matrix.script.name }} Docker" runs-on: ubuntu-latest @@ -77,7 +81,9 @@ jobs: - name: With Celery args: "use_celery=y frontend_pipeline='Django Compressor'" - name: With Gulp - args: "frontend_pipeline='Gulp'" + args: "frontend_pipeline=Gulp" + - name: With Webpack + args: "frontend_pipeline=Webpack" name: "${{ matrix.script.name }} Bare metal" runs-on: ubuntu-latest diff --git a/cookiecutter.json b/cookiecutter.json index 4a43bae7e..e3ee3f03c 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -45,7 +45,8 @@ "frontend_pipeline": [ "None", "Django Compressor", - "Gulp" + "Gulp", + "Webpack" ], "use_celery": "n", "use_mailhog": "n", diff --git a/docs/deployment-on-heroku.rst b/docs/deployment-on-heroku.rst index e239b6593..ed974f33f 100644 --- a/docs/deployment-on-heroku.rst +++ b/docs/deployment-on-heroku.rst @@ -109,10 +109,10 @@ Or add the DSN for your account, if you already have one: .. _Sentry add-on: https://elements.heroku.com/addons/sentry -Gulp & Bootstrap compilation -++++++++++++++++++++++++++++ +Gulp or Webpack ++++++++++++++++ -If you've opted for Gulp, you'll most likely need to setup +If you've opted for Gulp or Webpack as frontend pipeline, you'll most likely need to setup your app to use `multiple buildpacks`_: one for Python & one for Node.js: .. code-block:: bash @@ -121,7 +121,7 @@ your app to use `multiple buildpacks`_: one for Python & one for Node.js: At time of writing, this should do the trick: during deployment, the Heroku should run ``npm install`` and then ``npm build``, -which runs Gulp in cookiecutter-django. +which run the SASS compilation & JS bundling. If things don't work, please refer to the Heroku docs. diff --git a/docs/developing-locally.rst b/docs/developing-locally.rst index 23bb7b9a8..20f2492e3 100644 --- a/docs/developing-locally.rst +++ b/docs/developing-locally.rst @@ -155,7 +155,7 @@ To run Celery locally, make sure redis-server is installed (instructions are ava Sass Compilation & Live Reloading --------------------------------- -If you've opted for Gulp as front-end pipeline, the project comes configured with `Sass`_ compilation and `live reloading`_. As you change you Sass/JS source files, the task runner will automatically rebuild the corresponding CSS and JS assets and reload them in your browser without refreshing the page. +If you've opted for Gulp or Webpack as front-end pipeline, the project comes configured with `Sass`_ compilation and `live reloading`_. As you change you Sass/JS source files, the task runner will automatically rebuild the corresponding CSS and JS assets and reload them in your browser without refreshing the page. #. Make sure that `Node.js`_ v16 is installed on your machine. #. In the project root, install the JS dependencies with:: diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst index 3cc719779..d65c6d640 100644 --- a/docs/project-generation-options.rst +++ b/docs/project-generation-options.rst @@ -94,7 +94,10 @@ frontend_pipeline: 1. None 2. `Django Compressor`_ - 3. `Gulp`_: support Bootstrap recompilation with real-time variables alteration. + 3. `Gulp`_ + 4. `Webpack`_ + +Both Gulp and Webpack support Bootstrap recompilation with real-time variables alteration. use_celery: Indicates whether the project should be configured to use Celery_. @@ -144,6 +147,7 @@ debug: .. _PostgreSQL: https://www.postgresql.org/docs/ .. _Gulp: https://github.com/gulpjs/gulp +.. _Webpack: https://webpack.js.org .. _AWS: https://aws.amazon.com/s3/ .. _GCP: https://cloud.google.com/storage/ diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 9c3d946c1..aebb2b08e 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -10,6 +10,7 @@ TODO: restrict Cookiecutter Django project initialization to """ from __future__ import print_function +import json import os import random import shutil @@ -98,12 +99,90 @@ def remove_sass_files(): shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "static", "sass")) +def remove_webpack_files(): + shutil.rmtree("webpack") + remove_vendors_js() + + +def remove_vendors_js(): + vendors_js_path = os.path.join( + "{{ cookiecutter.project_slug }}", + "static", + "js", + "vendors.js", + ) + if os.path.exists(vendors_js_path): + os.remove(vendors_js_path) + + def remove_packagejson_file(): file_names = ["package.json"] for file_name in file_names: os.remove(file_name) +def update_package_json(remove_dev_deps=None, remove_keys=None, scripts=None): + remove_dev_deps = remove_dev_deps or [] + remove_keys = remove_keys or [] + scripts = scripts or {} + with open("package.json", mode="r") as fd: + content = json.load(fd) + for package_name in remove_dev_deps: + content["devDependencies"].pop(package_name) + for key in remove_keys: + content.pop(key) + content["scripts"].update(scripts) + with open("package.json", mode="w") as fd: + json.dump(content, fd, ensure_ascii=False, indent=2) + fd.write("\n") + + +def handle_js_runner(choice): + if choice == "Gulp": + update_package_json( + remove_dev_deps=[ + "@babel/core", + "@babel/preset-env", + "babel-loader", + "css-loader", + "mini-css-extract-plugin", + "postcss-loader", + "postcss-preset-env", + "sass-loader", + "webpack", + "webpack-bundle-tracker", + "webpack-cli", + "webpack-dev-server", + "webpack-merge", + ], + remove_keys=["babel"], + scripts={ + "dev": "gulp", + "build": "gulp generate-assets", + }, + ) + remove_webpack_files() + elif choice == "Webpack": + update_package_json( + remove_dev_deps=[ + "browser-sync", + "cssnano", + "gulp", + "gulp-imagemin", + "gulp-plumber", + "gulp-postcss", + "gulp-rename", + "gulp-sass", + "gulp-uglify-es", + ], + scripts={ + "dev": "webpack serve --config webpack/dev.config.js ", + "build": "webpack --config webpack/prod.config.js", + }, + ) + remove_gulp_files() + + def remove_celery_files(): file_names = [ os.path.join("config", "celery_app.py"), @@ -383,13 +462,16 @@ def main(): if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": append_to_gitignore_file("!.envs/.local/") - if "{{ cookiecutter.frontend_pipeline }}" != "Gulp": + if "{{ cookiecutter.frontend_pipeline }}".lower() in ["none", "django compressor"]: remove_gulp_files() + remove_webpack_files() remove_packagejson_file() if "{{ cookiecutter.use_docker }}".lower() == "y": remove_node_dockerfile() + else: + handle_js_runner("{{ cookiecutter.frontend_pipeline }}") - if "{{ cookiecutter.cloud_provider}}" == "None": + if "{{ cookiecutter.cloud_provider }}" == "None": print( WARNING + "You chose not to use a cloud provider, " "media files won't be served in production." + TERMINATOR diff --git a/tests/test_bare.sh b/tests/test_bare.sh index 05da9328b..afd12fec8 100755 --- a/tests/test_bare.sh +++ b/tests/test_bare.sh @@ -32,13 +32,11 @@ pytest # Make sure the check doesn't raise any warnings python manage.py check --fail-level WARNING +# Run npm build script if package.json is present if [ -f "package.json" ] then npm install - if [ -f "gulpfile.js" ] - then - npm run build - fi + npm run build fi # Generate the HTML for the documentation diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index 9c30aeedd..147e4e98e 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -90,6 +90,7 @@ SUPPORTED_COMBINATIONS = [ {"frontend_pipeline": "None"}, {"frontend_pipeline": "django-compressor"}, {"frontend_pipeline": "Gulp"}, + {"frontend_pipeline": "Webpack"}, {"use_celery": "y"}, {"use_celery": "n"}, {"use_mailhog": "y"}, diff --git a/tests/test_docker.sh b/tests/test_docker.sh index b3663bd2c..28d232896 100755 --- a/tests/test_docker.sh +++ b/tests/test_docker.sh @@ -41,3 +41,9 @@ docker-compose -f local.yml run django python manage.py check --fail-level WARNI # Generate the HTML for the documentation docker-compose -f local.yml run docs make html + +# Run npm build script if package.json is present +if [ -f "package.json" ] +then + docker-compose -f local.yml run node npm run build +fi diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore index ede26ef72..6fb8cf6d8 100644 --- a/{{cookiecutter.project_slug}}/.gitignore +++ b/{{cookiecutter.project_slug}}/.gitignore @@ -344,3 +344,7 @@ project.min.css vendors.js *.min.js {%- endif %} +{%- if cookiecutter.frontend_pipeline == 'Webpack' %} +{{ cookiecutter.project_slug }}/static/webpack_bundles/ +webpack-stats.json +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml index ad3b6a35a..e84c5ffdd 100644 --- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml +++ b/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml @@ -10,7 +10,7 @@