diff --git a/{{cookiecutter.app_name}}/.env.example b/{{cookiecutter.app_name}}/.env.example index 3ab1c43..ec8405a 100644 --- a/{{cookiecutter.app_name}}/.env.example +++ b/{{cookiecutter.app_name}}/.env.example @@ -6,3 +6,4 @@ DATABASE_URL=sqlite:////tmp/dev.db GUNICORN_WORKERS=1 LOG_LEVEL=debug SECRET_KEY=not-so-secret +SEND_FILE_MAX_AGE_DEFAULT=0 # In production, set to a higher number, like 31556926 diff --git a/{{cookiecutter.app_name}}/.gitignore b/{{cookiecutter.app_name}}/.gitignore index 24d2ea2..39d757f 100644 --- a/{{cookiecutter.app_name}}/.gitignore +++ b/{{cookiecutter.app_name}}/.gitignore @@ -50,7 +50,6 @@ env/ # webpack-built files /{{cookiecutter.app_name}}/static/build/ -/{{cookiecutter.app_name}}/webpack/manifest.json # Configuration .env diff --git a/{{cookiecutter.app_name}}/Pipfile b/{{cookiecutter.app_name}}/Pipfile index 55d1df4..ff85f09 100644 --- a/{{cookiecutter.app_name}}/Pipfile +++ b/{{cookiecutter.app_name}}/Pipfile @@ -26,8 +26,8 @@ gevent = "==1.4.0" gunicorn = ">=19.1.1" supervisor = "==4.1.0" -# Webpack -flask-webpack = "==0.1.0" +# Flask Static Digest +Flask-Static-Digest = "==0.1.2" # Auth Flask-Login = "==0.4.1" diff --git a/{{cookiecutter.app_name}}/README.rst b/{{cookiecutter.app_name}}/README.rst index 7e34951..85582d7 100644 --- a/{{cookiecutter.app_name}}/README.rst +++ b/{{cookiecutter.app_name}}/README.rst @@ -98,7 +98,7 @@ Make sure folder `migrations/versions` is not empty. Docker ------ -This app can be run completely using ``Docker`` and ``docker-compose``. Before starting, make sure to create a new copy of ``.env.example`` called ``.env``. You will need to start the development version of the app at least once before running other Docker commands, as starting the dev app bootstraps a necessary file, ``webpack/manifest.json``. +This app can be run completely using ``Docker`` and ``docker-compose``. Before starting, make sure to create a new copy of ``.env.example`` called ``.env``. There are three main services: @@ -128,22 +128,20 @@ Asset Management Files placed inside the ``assets`` directory and its subdirectories (excluding ``js`` and ``css``) will be copied by webpack's -``file-loader`` into the ``static/build`` directory, with hashes of -their contents appended to their names. For instance, if you have the -file ``assets/img/favicon.ico``, this will get copied into something -like -``static/build/img/favicon.fec40b1d14528bf9179da3b6b78079ad.ico``. -You can then put this line into your header:: +``file-loader`` into the ``static/build`` directory. In production, the plugin +``Flask-Static-Digest`` zips the webpack content and tags them with a MD5 hash. +As a result, you must use the ``static_url_for`` function when including static content, +as it resolves the correct file name, including the MD5 hash. +For example:: - + -to refer to it inside your HTML page. If all of your static files are -managed this way, then their filenames will change whenever their +If all of your static files are managed this way, then their filenames will change whenever their contents do, and you can ask Flask to tell web browsers that they should cache all your assets forever by including the following line -in your ``settings.py``:: +in ``.env``:: - SEND_FILE_MAX_AGE_DEFAULT = 31556926 # one year + SEND_FILE_MAX_AGE_DEFAULT=31556926 # one year {%- if cookiecutter.use_heroku == "yes" %} diff --git a/{{cookiecutter.app_name}}/package.json b/{{cookiecutter.app_name}}/package.json index d6a1f85..92a4199 100644 --- a/{{cookiecutter.app_name}}/package.json +++ b/{{cookiecutter.app_name}}/package.json @@ -3,12 +3,12 @@ "version": "1.0.0", "description": "{{cookiecutter.project_short_description}}", "scripts": { - "build": "NODE_ENV=production webpack --progress --colors -p", - "start": "concurrently -n \"WEBPACK,FLASK\" -c \"bgBlue.bold,bgMagenta.bold\" \"npm run webpack-dev-server\" \"npm run flask-server\"", - "webpack-dev-server": "NODE_ENV=debug webpack-dev-server --host=0.0.0.0 --port 2992 --hot --inline", + "build": "NODE_ENV=production webpack --progress --colors -p && npm run flask-static-digest", + "start": "concurrently -n \"WEBPACK,FLASK\" -c \"bgBlue.bold,bgMagenta.bold\" \"npm run webpack-watch\" \"npm run flask-server\"", + "webpack-watch": "NODE_ENV=debug webpack --mode development --watch", "flask-server": "{% if cookiecutter.use_pipenv == 'yes' %}pipenv run {% endif %}flask run --host=0.0.0.0", - "lint": "eslint \"assets/js/*.js\"", - "postinstall": "npm run build" + "flask-static-digest": "{% if cookiecutter.use_pipenv == 'yes' %}pipenv run {% endif %}flask digest compile", + "lint": "eslint \"assets/js/*.js\"" }, "repository": { "type": "git", @@ -42,13 +42,11 @@ "eslint": "^6.2.2", "file-loader": "^4.0.0", "less-loader": "^5.0.0", - "manifest-revision-webpack-plugin": "^0.4.0", "less": "^3.9.0", "mini-css-extract-plugin": "^0.8.0", "raw-loader": "^3.0.0", "url-loader": "^2.0.0", "webpack-cli": "^3.3.2", - "webpack-dev-server": "^3.5.1", "webpack": "^4.33.0" } } diff --git a/{{cookiecutter.app_name}}/requirements.txt b/{{cookiecutter.app_name}}/requirements.txt index 82db41e..8bc3938 100644 --- a/{{cookiecutter.app_name}}/requirements.txt +++ b/{{cookiecutter.app_name}}/requirements.txt @@ -1,3 +1,3 @@ # Included because many Paas's require a requirements.txt file in the project root # Just installs the production requirements. --r requirements/prod.txt \ No newline at end of file +-r requirements/prod.txt diff --git a/{{cookiecutter.app_name}}/requirements/prod.txt b/{{cookiecutter.app_name}}/requirements/prod.txt index 6a00e4b..fa1965d 100644 --- a/{{cookiecutter.app_name}}/requirements/prod.txt +++ b/{{cookiecutter.app_name}}/requirements/prod.txt @@ -22,8 +22,8 @@ gevent==1.4.0 gunicorn>=19.9.0 supervisor==4.1.0 -# Webpack -flask-webpack==0.1.0 +# Flask Static Digest +Flask-Static-Digest==0.1.2 # Auth Flask-Login==0.4.1 diff --git a/{{cookiecutter.app_name}}/tests/settings.py b/{{cookiecutter.app_name}}/tests/settings.py index c1b9922..e2f1785 100644 --- a/{{cookiecutter.app_name}}/tests/settings.py +++ b/{{cookiecutter.app_name}}/tests/settings.py @@ -9,5 +9,4 @@ BCRYPT_LOG_ROUNDS = ( DEBUG_TB_ENABLED = False CACHE_TYPE = "simple" # Can be "memcached", "redis", etc. SQLALCHEMY_TRACK_MODIFICATIONS = False -WEBPACK_MANIFEST_PATH = "webpack/manifest.json" WTF_CSRF_ENABLED = False # Allows form testing diff --git a/{{cookiecutter.app_name}}/webpack.config.js b/{{cookiecutter.app_name}}/webpack.config.js index cb4d4e7..74eee16 100644 --- a/{{cookiecutter.app_name}}/webpack.config.js +++ b/{{cookiecutter.app_name}}/webpack.config.js @@ -4,15 +4,11 @@ const webpack = require('webpack'); /* * Webpack Plugins */ -const ManifestRevisionPlugin = require('manifest-revision-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // take debug mode from the environment const debug = (process.env.NODE_ENV !== 'production'); -// Development asset host (webpack dev server) -const publicHost = debug ? 'http://0.0.0.0:2992' : ''; - const rootAssetPath = path.join(__dirname, 'assets'); module.exports = { @@ -27,18 +23,15 @@ module.exports = { ], }, output: { - path: path.join(__dirname, '{{cookiecutter.app_name}}', 'static', 'build'), - publicPath: `${publicHost}/static/build/`, - filename: '[name].[hash].js', - chunkFilename: '[id].[hash].js', + path: path.join(__dirname, "{{cookiecutter.app_name}}", "static", "build"), + publicPath: "/static/build/", + filename: "[name].js", + chunkFilename: "[id].js" }, resolve: { - extensions: ['.js', '.jsx', '.css'], - }, - devtool: 'source-map', - devServer: { - headers: { 'Access-Control-Allow-Origin': '*' }, + extensions: [".js", ".jsx", ".css"] }, + devtool: "source-map", module: { rules: [ { @@ -69,25 +62,24 @@ module.exports = { { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url-loader', options: { limit: 10000, mimetype: 'application/font-woff' } }, { test: /\.(ttf|eot|svg|png|jpe?g|gif|ico)(\?.*)?$/i, - loader: `file-loader?context=${rootAssetPath}&name=[path][name].[hash].[ext]` + loader: `file-loader?context=${rootAssetPath}&name=[path][name].[ext]` }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: { presets: ['env'], cacheDirectory: true } }, ], }, plugins: [ - new MiniCssExtractPlugin({ filename: '[name].[hash].css', }), - new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), - new ManifestRevisionPlugin(path.join(__dirname, '{{cookiecutter.app_name}}', 'webpack', 'manifest.json'), { - rootAssetPath, - ignorePaths: ['/js', '/css'], - extensionsRegex: /\.(ttf|eot|svg|png|jpe?g|gif|ico)$/i, - }), - ].concat(debug ? [] : [ - // production webpack plugins go here - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('production'), - } - }), - ]), + new MiniCssExtractPlugin({ filename: "[name].css" }), + new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery" }) + ].concat( + debug + ? [] + : [ + // production webpack plugins go here + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: JSON.stringify("production") + } + }) + ] + ) }; diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py index dd05cc5..039cd8c 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py @@ -12,9 +12,9 @@ from {{cookiecutter.app_name}}.extensions import ( csrf_protect, db, debug_toolbar, + flask_static_digest, login_manager, migrate, - webpack, ) @@ -43,7 +43,7 @@ def register_extensions(app): login_manager.init_app(app) debug_toolbar.init_app(app) migrate.init_app(app, db) - webpack.init_app(app) + flask_static_digest.init_app(app) return None diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py index 649d848..e103544 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py @@ -6,7 +6,7 @@ from flask_debugtoolbar import DebugToolbarExtension from flask_login import LoginManager from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy -from flask_webpack import Webpack +from flask_static_digest import FlaskStaticDigest from flask_wtf.csrf import CSRFProtect bcrypt = Bcrypt() @@ -16,4 +16,4 @@ db = SQLAlchemy() migrate = Migrate() cache = Cache() debug_toolbar = DebugToolbarExtension() -webpack = Webpack() +flask_static_digest = FlaskStaticDigest() diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py index 2420da0..76abe77 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py @@ -15,9 +15,9 @@ ENV = env.str("FLASK_ENV", default="production") DEBUG = ENV == "development" SQLALCHEMY_DATABASE_URI = env.str("DATABASE_URL") SECRET_KEY = env.str("SECRET_KEY") +SEND_FILE_MAX_AGE_DEFAULT = env.int("SEND_FILE_MAX_AGE_DEFAULT") BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13) DEBUG_TB_ENABLED = DEBUG DEBUG_TB_INTERCEPT_REDIRECTS = False CACHE_TYPE = "simple" # Can be "memcached", "redis", etc. SQLALCHEMY_TRACK_MODIFICATIONS = False -WEBPACK_MANIFEST_PATH = "webpack/manifest.json" diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/layout.html b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/layout.html index b0a83de..1df7a39 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/layout.html +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/layout.html @@ -1,65 +1,70 @@ {% raw %} - + - - - + + + + + - {% block page_title %} - {% endraw %} - {{ cookiecutter.project_name }} - {% raw %} - {% endblock %} - - - - - - - - {{ stylesheet_tag('main_css') | safe }} + + {% block page_title %} {% endraw %} + {{ cookiecutter.project_name }} + {% raw %} {% endblock %} + + + - {% block css %}{% endblock %} + + - - -{% block body %} -{% with form=form %} -{% include "nav.html" %} -{% endwith %} + -
{% block header %}{% endblock %}
+ {% block css %}{% endblock %} + + + {% block body %} {% with form=form %} {% include "nav.html" %} {% endwith %} -
-{% with messages = get_flashed_messages(with_categories=true) %} -{% if messages %} -
-
- {% for category, message in messages %} -
- × - {{message}} -
- {% endfor %} -
-
-{% endif %} -{% endwith %} +
{% block header %}{% endblock %}
-{% block content %}{% endblock %} -
+
+ {% with messages = get_flashed_messages(with_categories=true) %} {% if + messages %} +
+
+ {% for category, message in messages %} +
+ × + {{ message }} +
+ + {% endfor %} +
+ +
+ + {% endif %} {% endwith %} {% block content %}{% endblock %} +
+ {% include "footer.html" %} -{% include "footer.html" %} - - -{{ javascript_tag('main_js') | safe }} -{% block js %}{% endblock %} - -{% endblock %} - + + + {% block js %}{% endblock %} + + {% endblock %} + {% endraw %}