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 @@
{%- endif %}
- {%- if cookiecutter.frontend_pipeline == 'Gulp' %}
+ {%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
{%- endif %}
diff --git a/{{cookiecutter.project_slug}}/.idea/{{cookiecutter.project_slug}}.iml b/{{cookiecutter.project_slug}}/.idea/{{cookiecutter.project_slug}}.iml
index 98759fa1b..fe1aeae52 100644
--- a/{{cookiecutter.project_slug}}/.idea/{{cookiecutter.project_slug}}.iml
+++ b/{{cookiecutter.project_slug}}/.idea/{{cookiecutter.project_slug}}.iml
@@ -13,7 +13,7 @@
- {% if cookiecutter.frontend_pipeline == 'Gulp' %}
+ {% if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
diff --git a/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile
index 86f4ac18a..2c01e4891 100644
--- a/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile
+++ b/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile
@@ -1,6 +1,6 @@
ARG PYTHON_VERSION=3.9-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
@@ -99,7 +99,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}
diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py
index ca8abda82..a841b6190 100644
--- a/{{cookiecutter.project_slug}}/config/settings/base.py
+++ b/{{cookiecutter.project_slug}}/config/settings/base.py
@@ -89,6 +89,9 @@ THIRD_PARTY_APPS = [
"corsheaders",
"drf_spectacular",
{%- endif %}
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+ "webpack_loader",
+{%- endif %}
]
LOCAL_APPS = [
@@ -351,6 +354,19 @@ SPECTACULAR_SETTINGS = {
{"url": "https://{{ cookiecutter.domain_name }}", "description": "Production server"},
],
}
+{%- endif %}
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+# django-webpack-loader
+# ------------------------------------------------------------------------------
+WEBPACK_LOADER = {
+ "DEFAULT": {
+ "CACHE": not DEBUG,
+ "STATS_FILE": ROOT_DIR / "webpack-stats.json",
+ "POLL_INTERVAL": 0.1,
+ "IGNORE": [r".+\.hot-update.js", r".+\.map"],
+ }
+}
+
{%- endif %}
# Your stuff...
# ------------------------------------------------------------------------------
diff --git a/{{cookiecutter.project_slug}}/config/settings/local.py b/{{cookiecutter.project_slug}}/config/settings/local.py
index 1eafd9fae..5ed6f5853 100644
--- a/{{cookiecutter.project_slug}}/config/settings/local.py
+++ b/{{cookiecutter.project_slug}}/config/settings/local.py
@@ -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
# http://docs.celeryproject.org/en/latest/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...
# ------------------------------------------------------------------------------
diff --git a/{{cookiecutter.project_slug}}/gulpfile.js b/{{cookiecutter.project_slug}}/gulpfile.js
index 628b838ba..5bc8c7ca4 100644
--- a/{{cookiecutter.project_slug}}/gulpfile.js
+++ b/{{cookiecutter.project_slug}}/gulpfile.js
@@ -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))
diff --git a/{{cookiecutter.project_slug}}/local.yml b/{{cookiecutter.project_slug}}/local.yml
index fb203acd6..1c0e4ae78 100644
--- a/{{cookiecutter.project_slug}}/local.yml
+++ b/{{cookiecutter.project_slug}}/local.yml
@@ -107,7 +107,7 @@ services:
command: /start-flower
{%- endif %}
- {%- if cookiecutter.frontend_pipeline == 'Gulp' %}
+ {%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
node:
build:
diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json
index bff0a34af..0fcf3570d 100644
--- a/{{cookiecutter.project_slug}}/package.json
+++ b/{{cookiecutter.project_slug}}/package.json
@@ -1,13 +1,16 @@
{
"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",
"cssnano": "^5.0.11",
"gulp": "^4.0.2",
"gulp-imagemin": "^7.1.0",
@@ -16,9 +19,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 +39,11 @@
"browserslist": [
"last 2 versions"
],
+ "babel": {
+ "presets": ["@babel/preset-env"]
+ },
"scripts": {
- "dev": "gulp",
- "build": "gulp generate-assets"
+ "dev": "",
+ "build": ""
}
}
diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt
index d3a6f46f0..650135c23 100644
--- a/{{cookiecutter.project_slug}}/requirements/base.txt
+++ b/{{cookiecutter.project_slug}}/requirements/base.txt
@@ -46,3 +46,6 @@ django-cors-headers==3.11.0 # https://github.com/adamchainz/django-cors-headers
# DRF-spectacular for api documentation
drf-spectacular==0.21.2 # https://github.com/tfranzel/drf-spectacular
{%- endif %}
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+django-webpack-loader==1.4.1 # https://github.com/django-webpack/django-webpack-loader
+{%- endif %}
diff --git a/{{cookiecutter.project_slug}}/webpack/common.config.js b/{{cookiecutter.project_slug}}/webpack/common.config.js
new file mode 100644
index 000000000..cec060c8c
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/webpack/common.config.js
@@ -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']
+ },
+}
diff --git a/{{cookiecutter.project_slug}}/webpack/dev.config.js b/{{cookiecutter.project_slug}}/webpack/dev.config.js
new file mode 100644
index 000000000..e2a36a412
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/webpack/dev.config.js
@@ -0,0 +1,16 @@
+const { merge } = require('webpack-merge');
+const commonConfig = require('./common.config');
+
+module.exports = merge(commonConfig, {
+ mode: 'development',
+ devtool: 'inline-source-map',
+ devServer: {
+ port: 3000,
+ proxy: {
+ '/': 'http://0.0.0.0:8000',
+ },
+ // We need hot=false (Disable HMR) to set liveReload=true
+ hot: false,
+ liveReload: true,
+ },
+})
diff --git a/{{cookiecutter.project_slug}}/webpack/prod.config.js b/{{cookiecutter.project_slug}}/webpack/prod.config.js
new file mode 100644
index 000000000..4a471cb4d
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/webpack/prod.config.js
@@ -0,0 +1,8 @@
+const { merge } = require('webpack-merge');
+const devConfig = require('./common.config');
+
+module.exports = merge(devConfig, {
+ mode: 'production',
+ devtool: 'source-map',
+ bail: true,
+})
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js
index d26d23b9b..62770f1c9 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js
@@ -1 +1,5 @@
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+import '../sass/project.scss';
+{%- endif %}
+
/* Project specific Javascript goes here. */
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/vendors.js b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/vendors.js
new file mode 100644
index 000000000..093840f87
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/vendors.js
@@ -0,0 +1,2 @@
+import '@popperjs/core';
+import 'bootstrap';
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss
index 370096bb3..c9511e720 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss
@@ -1,5 +1,5 @@
@import "custom_bootstrap_vars";
-@import "bootstrap";
+@import "~bootstrap/scss/bootstrap";
// project specific CSS goes here
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html
index 58aca7208..14174e719 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html
@@ -1,4 +1,8 @@
-{% raw %}{% load static i18n {% endraw %}{% if cookiecutter.frontend_pipeline == 'Django Compressor' %}compress{% endif %}{% raw %}%}
+{% 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 %}
{% get_current_language as LANGUAGE_CODE %}
@@ -31,6 +35,8 @@
{% endcompress %}
{%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %}
+ {%- endraw %}{% elif cookiecutter.frontend_pipeline == "Webpack" %}{% raw %}
+ {% render_bundle 'project' 'css' %}
{%- endraw %}{% endif %}{% raw %}
{% endblock %}
+
+ {%- endraw %}{% elif cookiecutter.frontend_pipeline == "Webpack" %}{% raw %}
+
+ {% render_bundle 'vendors' 'js' attrs='defer' %}
{%- endraw %}{% else %}{% raw %}
@@ -55,6 +64,8 @@
{% endcompress %}
{%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %}
+ {%- endraw %}{% elif cookiecutter.frontend_pipeline == "Webpack" %}{% raw %}
+ {% render_bundle 'project' 'js' attrs='defer' %}
{%- endraw %}{% endif %}{% raw %}
{% endblock javascript %}