Add Webpack support (#3623)

* Add support for Webpack as frontend pipeline

* Rename CI jobs

* Fix a couple of issues with Webpack + Docker

* Don't include Boostrap CSS from CDN with Webpack

* Rename variable

* Set publicPath in prod webpack config

* Fix removal of SASS files in post-gen hooks

* Add Webpack to readme usage section

* Run Django + Webpack dev server concurrently without Docker

* Fix async runserver command with Gulp/Webpack

* Upgrade django-webpack-loader to 1.5.0

* Pass variables required by Webpack at build time

* Upgrade django-webpack-loader to 1.7.0

* Add missing condition

* Add support for Azure Storage + Webpack

* Whitespaces

* Rename ROOT_DIR -> BASE_DIR

* Rename jobs

* Bump django-webpack-loader to latest

* Document limitation of Docker + Webpack + no Whitenoise

* Update section on custom Bootstrap compilation in generated readme
This commit is contained in:
Bruno Alla 2023-01-29 12:12:12 +00:00 committed by GitHub
parent 977ffd91a3
commit cbb0e19de7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 399 additions and 49 deletions

View File

@ -29,7 +29,7 @@ jobs:
- windows-latest
- macOS-latest
name: "Run tests"
name: "pytest ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
@ -49,10 +49,14 @@ 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"
name: "Docker ${{ matrix.script.name }}"
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: 1
@ -74,12 +78,14 @@ jobs:
fail-fast: false
matrix:
script:
- name: With Celery
- name: Celery
args: "use_celery=y frontend_pipeline='Django Compressor'"
- name: With Gulp
args: "frontend_pipeline='Gulp'"
- name: Gulp
args: "frontend_pipeline=Gulp"
- name: Webpack
args: "frontend_pipeline=Webpack"
name: "${{ matrix.script.name }} Bare metal"
name: "Bare metal ${{ matrix.script.name }}"
runs-on: ubuntu-latest
services:
redis:

View File

@ -27,7 +27,7 @@ production-ready Django projects quickly.
- Registration via [django-allauth](https://github.com/pennersr/django-allauth)
- Comes with custom user model ready to go
- Optional basic ASGI setup for Websockets
- Optional custom static build using Gulp and livereload
- Optional custom static build using Gulp or Webpack
- Send emails via [Anymail](https://github.com/anymail/django-anymail) (using [Mailgun](http://www.mailgun.com/) by default or Amazon SES if AWS is selected cloud provider, but switchable)
- Media storage using Amazon S3, Google Cloud Storage or Azure Storage
- Docker support using [docker-compose](https://github.com/docker/compose) for development and production (using [Traefik](https://traefik.io/) with [LetsEncrypt](https://letsencrypt.org/) support)
@ -149,6 +149,7 @@ Answer the prompts with your own desired [options](http://cookiecutter-django.re
1 - None
2 - Django Compressor
3 - Gulp
4 - Webpack
Choose from 1, 2, 3, 4 [1]: 1
use_celery [n]: y
use_mailhog [n]: n

View File

@ -46,7 +46,8 @@
"frontend_pipeline": [
"None",
"Django Compressor",
"Gulp"
"Gulp",
"Webpack"
],
"use_celery": "n",
"use_mailhog": "n",

View File

@ -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.

View File

@ -84,6 +84,32 @@ You can read more about this feature and how to configure it, at `Automatic HTTP
.. _Automatic HTTPS: https://docs.traefik.io/https/acme/
.. _webpack-whitenoise-limitation:
Webpack without Whitenoise limitation
-------------------------------------
If you opt for Webpack without Whitenoise, Webpack needs to know the static URL at build time, when running ``docker-compose build`` (See ``webpack/prod.config.js``). Depending on your setup, this URL may come from the following environment variables:
- ``AWS_STORAGE_BUCKET_NAME``
- ``DJANGO_AWS_S3_CUSTOM_DOMAIN``
- ``DJANGO_GCP_STORAGE_BUCKET_NAME``
- ``DJANGO_AZURE_CONTAINER_NAME``
The Django settings are getting these values at runtime via the ``.envs/.production/.django`` file , but Docker does not read this file at build time, it only look for a ``.env`` in the root of the project. Failing to pass the values correctly will result in a page without CSS styles nor javascript.
To solve this, you can either:
1. merge all the env files into ``.env`` by running::
merge_production_dotenvs_in_dotenv.py
2. create a ``.env`` file in the root of the project with just variables you need. You'll need to also define them in ``.envs/.production/.django`` (hence duplicating them).
3. set these variables when running the build command::
DJANGO_AWS_S3_CUSTOM_DOMAIN=example.com docker-compose -f production.yml build``.
None of these options are ideal, we're open to suggestions on how to improve this. If you think you have one, please open an issue or a pull request.
(Optional) Postgres Data Volume Modifications
---------------------------------------------

View File

@ -172,7 +172,7 @@ You can also use Django admin to queue up tasks, thanks to the `django-celerybea
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::

View File

@ -95,7 +95,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_.
@ -145,6 +148,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/

View File

@ -1,5 +1,5 @@
Troubleshooting
=====================================
===============
This page contains some advice about errors and problems commonly encountered during the development of Cookiecutter Django applications.
@ -38,6 +38,16 @@ To fix this, you can either:
.. _rm: https://docs.docker.com/engine/reference/commandline/volume_rm/
.. _prune: https://docs.docker.com/v17.09/engine/reference/commandline/system_prune/
Variable is not set. Defaulting to a blank string
-------------------------------------------------
Example::
WARN[0000] The "DJANGO_AWS_STORAGE_BUCKET_NAME" variable is not set. Defaulting to a blank string.
WARN[0000] The "DJANGO_AWS_S3_CUSTOM_DOMAIN" variable is not set. Defaulting to a blank string.
You have probably opted for Docker + Webpack without Whitenoise. This is a know limitation of the combination, which needs a little bit of manual intervention. See the :ref:`dedicated section about it <webpack-whitenoise-limitation>`.
Others
------

View File

@ -10,6 +10,7 @@ TODO: restrict Cookiecutter Django project initialization to
"""
from __future__ import print_function
import json
import os
import random
import shutil
@ -87,15 +88,30 @@ def remove_heroku_build_hooks():
shutil.rmtree("bin")
def remove_sass_files():
shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "static", "sass"))
def remove_gulp_files():
file_names = ["gulpfile.js"]
for file_name in file_names:
os.remove(file_name)
remove_sass_files()
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():
@ -104,6 +120,83 @@ def remove_packagejson_file():
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, use_docker, use_async):
if choice == "Gulp":
update_package_json(
remove_dev_deps=[
"@babel/core",
"@babel/preset-env",
"babel-loader",
"concurrently",
"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":
scripts = {
"dev": "webpack serve --config webpack/dev.config.js",
"build": "webpack --config webpack/prod.config.js",
}
remove_dev_deps = [
"browser-sync",
"cssnano",
"gulp",
"gulp-imagemin",
"gulp-plumber",
"gulp-postcss",
"gulp-rename",
"gulp-sass",
"gulp-uglify-es",
]
if not use_docker:
dev_django_cmd = (
"uvicorn config.asgi:application --reload"
if use_async
else "python manage.py runserver_plus"
)
scripts.update(
{
"dev": "concurrently npm:dev:*",
"dev:webpack": "webpack serve --config webpack/dev.config.js",
"dev:django": dev_django_cmd,
}
)
else:
remove_dev_deps.append("concurrently")
update_package_json(remove_dev_deps=remove_dev_deps, scripts=scripts)
remove_gulp_files()
def remove_celery_files():
file_names = [
os.path.join("config", "celery_app.py"),
@ -384,13 +477,21 @@ 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 }}" in ["None", "Django Compressor"]:
remove_gulp_files()
remove_webpack_files()
remove_sass_files()
remove_packagejson_file()
if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_node_dockerfile()
else:
handle_js_runner(
"{{ cookiecutter.frontend_pipeline }}",
use_docker=("{{ cookiecutter.use_docker }}".lower() == "y"),
use_async=("{{ cookiecutter.use_async }}".lower() == "y"),
)
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

View File

@ -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

View File

@ -101,6 +101,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"},

View File

@ -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

View File

@ -348,3 +348,7 @@ vendors.js
*.min.js
*.min.js.map
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
{{ cookiecutter.project_slug }}/static/webpack_bundles/
webpack-stats.json
{%- endif %}

View File

@ -10,7 +10,7 @@
<option value="celeryworker"/>
<option value="celerybeat"/>
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
{%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
<option value="node"/>
{%- endif %}
</list>

View File

@ -13,7 +13,7 @@
</facet>
</component>
<component name="NewModuleRootManager">
{% if cookiecutter.frontend_pipeline == 'Gulp' %}
{% if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/node_modules" />
</content>

View File

@ -128,13 +128,14 @@ See detailed [cookiecutter-django Heroku documentation](http://cookiecutter-djan
See detailed [cookiecutter-django Docker documentation](http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html).
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
{%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
### Custom Bootstrap Compilation
The generated CSS is set up with automatic Bootstrap recompilation with variables of your choice.
Bootstrap v5 is installed using npm and customised by tweaking your variables in `static/sass/custom_bootstrap_vars`.
You can find a list of available variables [in the bootstrap source](https://github.com/twbs/bootstrap/blob/main/scss/_variables.scss), or get explanations on them in the [Bootstrap docs](https://getbootstrap.com/docs/5.1/customize/sass/).
You can find a list of available variables [in the bootstrap source](https://github.com/twbs/bootstrap/blob/v5.1.3/scss/_variables.scss), or get explanations on them in the [Bootstrap docs](https://getbootstrap.com/docs/5.1/customize/sass/).
Bootstrap's javascript as well as its dependencies is concatenated into a single file: `static/js/vendors.js`.
Bootstrap's javascript as well as its dependencies are concatenated into a single file: `static/js/vendors.js`.
{%- endif %}

View File

@ -1,6 +1,6 @@
ARG PYTHON_VERSION=3.10-slim-bullseye
{% if cookiecutter.frontend_pipeline == 'Gulp' -%}
{% if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] -%}
FROM node:16-bullseye-slim as client-builder
ARG APP_HOME=/app
@ -9,6 +9,20 @@ WORKDIR ${APP_HOME}
COPY ./package.json ${APP_HOME}
RUN npm install && npm cache clean --force
COPY . ${APP_HOME}
{%- if cookiecutter.frontend_pipeline == 'Webpack' and cookiecutter.use_whitenoise == 'n' %}
{%- if cookiecutter.cloud_provider == 'AWS' %}
ARG DJANGO_AWS_STORAGE_BUCKET_NAME
ENV DJANGO_AWS_STORAGE_BUCKET_NAME=${DJANGO_AWS_STORAGE_BUCKET_NAME}
ARG DJANGO_AWS_S3_CUSTOM_DOMAIN
ENV DJANGO_AWS_S3_CUSTOM_DOMAIN=${DJANGO_AWS_S3_CUSTOM_DOMAIN}
{%- elif cookiecutter.cloud_provider == 'GCP' %}
ARG DJANGO_GCP_STORAGE_BUCKET_NAME
ENV DJANGO_GCP_STORAGE_BUCKET_NAME=${DJANGO_GCP_STORAGE_BUCKET_NAME}
{%- elif cookiecutter.cloud_provider == 'Azure' %}
ARG DJANGO_AZURE_ACCOUNT_NAME
ENV DJANGO_AZURE_ACCOUNT_NAME=${DJANGO_AZURE_ACCOUNT_NAME}
{%- endif %}
{%- endif %}
RUN npm run build
{%- endif %}
@ -99,7 +113,7 @@ RUN chmod +x /start-flower
# copy application code to WORKDIR
{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
{%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
COPY --from=client-builder --chown=django:django ${APP_HOME} ${APP_HOME}
{% else %}
COPY --chown=django:django . ${APP_HOME}

View File

@ -87,6 +87,9 @@ THIRD_PARTY_APPS = [
"corsheaders",
"drf_spectacular",
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
"webpack_loader",
{%- endif %}
]
LOCAL_APPS = [
@ -355,6 +358,19 @@ SPECTACULAR_SETTINGS = {
"VERSION": "1.0.0",
"SERVE_PERMISSIONS": ["rest_framework.permissions.IsAdminUser"],
}
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
# django-webpack-loader
# ------------------------------------------------------------------------------
WEBPACK_LOADER = {
"DEFAULT": {
"CACHE": not DEBUG,
"STATS_FILE": BASE_DIR / "webpack-stats.json",
"POLL_INTERVAL": 0.1,
"IGNORE": [r".+\.hot-update.js", r".+\.map"],
}
}
{%- endif %}
# Your stuff...
# ------------------------------------------------------------------------------

View File

@ -69,7 +69,7 @@ if env("USE_DOCKER") == "yes":
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips]
{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
{%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
try:
_, _, ips = socket.gethostbyname_ex("node")
INTERNAL_IPS.extend(ips)
@ -94,6 +94,12 @@ CELERY_TASK_ALWAYS_EAGER = True
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-eager-propagates
CELERY_TASK_EAGER_PROPAGATES = True
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
# django-webpack-loader
# ------------------------------------------------------------------------------
WEBPACK_LOADER["DEFAULT"]["CACHE"] = not DEBUG # noqa F405
{%- endif %}
# Your stuff...
# ------------------------------------------------------------------------------

View File

@ -10,6 +10,7 @@ const pjson = require('./package.json')
const autoprefixer = require('autoprefixer')
const browserSync = require('browser-sync').create()
const concat = require('gulp-concat')
const tildeImporter = require('node-sass-tilde-importer');
const cssnano = require ('cssnano')
const imagemin = require('gulp-imagemin')
const pixrem = require('pixrem')
@ -27,7 +28,6 @@ function pathsConfig(appName) {
const vendorsRoot = 'node_modules'
return {
bootstrapSass: `${vendorsRoot}/bootstrap/scss`,
vendorsJs: [
`${vendorsRoot}/@popperjs/core/dist/umd/popper.js`,
`${vendorsRoot}/bootstrap/dist/js/bootstrap.js`,
@ -61,8 +61,8 @@ function styles() {
return src(`${paths.sass}/project.scss`)
.pipe(sass({
importer: tildeImporter,
includePaths: [
paths.bootstrapSass,
paths.sass
]
}).on('error', sass.logError))

View File

@ -105,7 +105,7 @@ services:
command: /start-flower
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
{%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
node:
build:
@ -122,7 +122,9 @@ services:
command: npm run dev
ports:
- "3000:3000"
{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
# Expose browsersync UI: https://www.browsersync.io/docs/options/#option-ui
- "3001:3001"
{%- endif %}
{%- endif %}

View File

@ -1,13 +1,17 @@
{
"name": "{{cookiecutter.project_slug}}",
"version": "{{ cookiecutter.version }}",
"dependencies": {},
"devDependencies": {
"bootstrap": "^5.1.3",
"gulp-concat": "^2.6.1",
"@babel/core": "^7.16.5",
"@babel/preset-env": "^7.16.5",
"@popperjs/core": "^2.10.2",
"autoprefixer": "^10.4.0",
"babel-loader": "^8.2.3",
"bootstrap": "^5.1.3",
"browser-sync": "^2.27.7",
"css-loader": "^6.5.1",
"gulp-concat": "^2.6.1",
"concurrently": "^7.0.0",
"cssnano": "^5.0.11",
"gulp": "^4.0.2",
"gulp-imagemin": "^7.1.0",
@ -16,9 +20,19 @@
"gulp-rename": "^2.0.0",
"gulp-sass": "^5.0.0",
"gulp-uglify-es": "^3.0.0",
"mini-css-extract-plugin": "^2.4.5",
"node-sass-tilde-importer": "^1.0.2",
"pixrem": "^5.0.0",
"postcss": "^8.3.11",
"sass": "^1.43.4"
"postcss-loader": "^6.2.1",
"postcss-preset-env": "^7.0.2",
"sass": "^1.43.4",
"sass-loader": "^12.4.0",
"webpack": "^5.65.0",
"webpack-bundle-tracker": "^1.4.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.6.0",
"webpack-merge": "^5.8.0"
},
"engines": {
"node": "16"
@ -26,8 +40,11 @@
"browserslist": [
"last 2 versions"
],
"babel": {
"presets": ["@babel/preset-env"]
},
"scripts": {
"dev": "gulp",
"build": "gulp generate-assets"
"dev": "",
"build": ""
}
}

View File

@ -10,6 +10,19 @@ services:
build:
context: .
dockerfile: ./compose/production/django/Dockerfile
{%- if cookiecutter.frontend_pipeline == 'Webpack' and cookiecutter.use_whitenoise == 'n' %}
args:
# These variable can be defined in an .env file in the root of the repo
{%- if cookiecutter.cloud_provider == 'AWS' %}
DJANGO_AWS_STORAGE_BUCKET_NAME: ${DJANGO_AWS_STORAGE_BUCKET_NAME}
DJANGO_AWS_S3_CUSTOM_DOMAIN: ${DJANGO_AWS_S3_CUSTOM_DOMAIN}
{%- elif cookiecutter.cloud_provider == 'GCP' %}
DJANGO_GCP_STORAGE_BUCKET_NAME: ${DJANGO_GCP_STORAGE_BUCKET_NAME}
{%- elif cookiecutter.cloud_provider == 'Azure' %}
DJANGO_AZURE_ACCOUNT_NAME: ${DJANGO_AZURE_ACCOUNT_NAME}
{%- endif %}
{%- endif %}
image: {{ cookiecutter.project_slug }}_production_django
depends_on:
- postgres

View File

@ -42,7 +42,10 @@ django-redis==5.2.0 # https://github.com/jazzband/django-redis
{%- if cookiecutter.use_drf == 'y' %}
# Django REST Framework
djangorestframework==3.14.0 # https://github.com/encode/django-rest-framework
django-cors-headers==3.13.0 # https://github.com/adamchainz/django-cors-headers
django-cors-headers==3.13.0 # https://github.com/adamchainz/django-cors-headers
# DRF-spectacular for api documentation
drf-spectacular==0.25.1 # https://github.com/tfranzel/drf-spectacular
drf-spectacular==0.25.1 # https://github.com/tfranzel/drf-spectacular
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
django-webpack-loader==1.8.0 # https://github.com/django-webpack/django-webpack-loader
{%- endif %}

View File

@ -0,0 +1,55 @@
const path = require('path');
const BundleTracker = require('webpack-bundle-tracker');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
target: "web",
context: path.join(__dirname, '../'),
entry: {
'project': path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/project'),
'vendors': path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/vendors'),
},
output: {
path: path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/webpack_bundles/'),
publicPath: '/static/webpack_bundles/',
filename: 'js/[name]-[fullhash].js',
chunkFilename: 'js/[name]-[hash].js',
},
plugins: [
new BundleTracker({filename: path.resolve(__dirname, '../webpack-stats.json')}),
new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash].css' }),
],
module: {
rules: [
// we pass the output from babel loader to react-hot loader
{
test: /\.js$/,
loader: 'babel-loader',
},
{
test: /\.s?css$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
'autoprefixer',
'pixrem',
],
},
},
},
'sass-loader',
],
},
],
},
resolve: {
modules: ['node_modules'],
extensions: ['.js', '.jsx'],
},
};

View File

@ -0,0 +1,20 @@
const { merge } = require('webpack-merge');
const commonConfig = require('./common.config');
module.exports = merge(commonConfig, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
port: 3000,
proxy: {
{%- if cookiecutter.use_docker == 'n' %}
'/': 'http://0.0.0.0:8000',
{%- else %}
'/': 'http://django:8000',
{%- endif %}
},
// We need hot=false (Disable HMR) to set liveReload=true
hot: false,
liveReload: true,
},
});

View File

@ -0,0 +1,28 @@
const { merge } = require('webpack-merge');
const commonConfig = require('./common.config');
// This variable should mirror the one from config/settings/production.py
{%- if cookiecutter.use_whitenoise == 'n' %}
{%- if cookiecutter.cloud_provider == 'AWS' %}
const s3BucketName = process.env.DJANGO_AWS_STORAGE_BUCKET_NAME;
const awsS3Domain = process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN ?
process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN
: `${s3BucketName}.s3.amazonaws.com`;
const staticUrl = `https://${awsS3Domain}/static/`;
{%- elif cookiecutter.cloud_provider == 'GCP' %}
const staticUrl = `https://storage.googleapis.com/${process.env.DJANGO_GCP_STORAGE_BUCKET_NAME}/static/`;
{%- elif cookiecutter.cloud_provider == 'Azure' %}
const staticUrl = `https://${process.env.DJANGO_AZURE_ACCOUNT_NAME}.blob.core.windows.net/static/`;
{%- endif %}
{%- else %}
const staticUrl = '/static/';
{%- endif %}
module.exports = merge(commonConfig, {
mode: 'production',
devtool: 'source-map',
bail: true,
output: {
publicPath: `${staticUrl}webpack_bundles/`,
},
});

View File

@ -1 +1,5 @@
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
import '../sass/project.scss';
{%- endif %}
/* Project specific Javascript goes here. */

View File

@ -0,0 +1,2 @@
import '@popperjs/core';
import 'bootstrap';

View File

@ -1,5 +1,5 @@
@import "custom_bootstrap_vars";
@import "bootstrap";
@import "~bootstrap/scss/bootstrap";
// project specific CSS goes here

View File

@ -1,4 +1,8 @@
{% raw %}{% load static i18n {% endraw %}{% if cookiecutter.frontend_pipeline == 'Django Compressor' %}compress{% endif %}{% raw %}%}<!DOCTYPE html>
{% raw %}{% load static i18n {% endraw %}
{%- if cookiecutter.frontend_pipeline == 'Django Compressor' %}compress
{%- endif %}{% raw %}%}{% endraw %}
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}{% raw %}{% load render_bundle from webpack_loader %}{% endraw %}
{%- endif %}{% raw %}<!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}
<html lang="{{ LANGUAGE_CODE }}">
<head>
@ -13,7 +17,7 @@
{% block css %}
{%- endraw %}
{%- if cookiecutter.frontend_pipeline != 'Gulp' %}
{%- if cookiecutter.frontend_pipeline in ['None', 'Django Compressor'] %}
{%- raw %}
<!-- Latest compiled and minified Bootstrap CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" integrity="sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
@ -31,6 +35,8 @@
{% endcompress %}
{%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %}
<link href="{% static 'css/project.min.css' %}" rel="stylesheet">
{%- endraw %}{% elif cookiecutter.frontend_pipeline == "Webpack" %}{% raw %}
{% render_bundle 'project' 'css' %}
{%- endraw %}{% endif %}{% raw %}
{% endblock %}
<!-- Le javascript
@ -38,8 +44,11 @@
{# Placed at the top of the document so pages load faster with defer #}
{% block javascript %}
{%- endraw %}{% if cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %}
<!-- Vendor dependencies bundled as one file-->
<!-- Vendor dependencies bundled as one file -->
<script defer src="{% static 'js/vendors.min.js' %}"></script>
{%- endraw %}{% elif cookiecutter.frontend_pipeline == "Webpack" %}{% raw %}
<!-- Vendor dependencies bundled as one file -->
{% render_bundle 'vendors' 'js' attrs='defer' %}
{%- endraw %}{% else %}{% raw %}
<!-- Bootstrap JS -->
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.min.js" integrity="sha512-OvBgP9A2JBgiRad/mM36mkzXSXaJE9BEIENnVEmeZdITvwT09xnxLtT4twkCa8m/loMbPHsvPl0T8lRGVBwjlQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
@ -55,6 +64,8 @@
{% endcompress %}
{%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %}
<script defer src="{% static 'js/project.min.js' %}"></script>
{%- endraw %}{% elif cookiecutter.frontend_pipeline == "Webpack" %}{% raw %}
{% render_bundle 'project' 'js' attrs='defer' %}
{%- endraw %}{% endif %}{% raw %}
{% endblock javascript %}