├── tests ├── __init__.py └── test_flask_static_compress.py ├── requirements.txt ├── MANIFEST.in ├── dev-requirements.txt ├── setup.cfg ├── tox.ini ├── .coveragerc ├── AUTHORS ├── flask_static_compress ├── __init__.py └── __about__.py ├── .travis.yml ├── .gitignore ├── HISTORY.rst ├── LICENSE ├── setup.py └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | jac>=0.18 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE HISTORY.rst requirements.txt 2 | recursive-include flask_static_compress *.py 3 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | coverage==4.4.1 4 | nose==1.3.7 5 | nose-capturestderr==1.2 6 | nose-exclude==0.5.0 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | with-coverage = 1 3 | cover-inclusive = 1 4 | cover-package = flask_static_compress 5 | exclude-dir = 6 | venv 7 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26, py27, py33, py34, py35, py36 3 | [testenv] 4 | deps = 5 | -rdev-requirements.txt 6 | py26: unittest2 7 | commands = nosetests 8 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = flask_static_compress 3 | branch = true 4 | omit = 5 | flask_static_compress/__about__.py 6 | [report] 7 | omit = 8 | flask_static_compress/__about__.py 9 | */python?.?/* 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Flask-Static-Compress is written and maintained by Alan Hamlett and 2 | various contributors: 3 | 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | - Alan Hamlett 9 | 10 | 11 | Patches and Suggestions 12 | ----------------------- 13 | 14 | -------------------------------------------------------------------------------- /flask_static_compress/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Flask-Static-Compress 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | :copyright: (c) 2016 Alan Hamlett. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | 10 | from jac.contrib.flask import JAC as FlaskStaticCompress 11 | -------------------------------------------------------------------------------- /tests/test_flask_static_compress.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | try: 5 | # Python 2.6 6 | import unittest2 as unittest 7 | except ImportError: 8 | # Python >= 2.7 9 | import unittest 10 | 11 | 12 | class BaseTestCase(unittest.TestCase): 13 | 14 | def test_can_wrap_jac(self): 15 | from flask_static_compress import FlaskStaticCompress 16 | self.assertIsNotNone(FlaskStaticCompress) 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | # command to install dependencies 8 | install: 9 | - travis_retry pip install -r dev-requirements.txt 10 | - travis_retry pip install codecov 11 | # use new travis-ci container-based infrastructure 12 | sudo: false 13 | # command to run tests 14 | script: nosetests 15 | # command to run after tests 16 | after_success: 17 | - codecov 18 | -------------------------------------------------------------------------------- /flask_static_compress/__about__.py: -------------------------------------------------------------------------------- 1 | __title__ = 'Flask-Static-Compress' 2 | __description__ = 'Auto-detects your static files for minification, combination, and versioning. Like Django-Compressor for Flask.' 3 | __url__ = 'https://github.com/alanhamlett/flask-static-compress' 4 | __version_info__ = ('1', '0', '3') 5 | __version__ = '.'.join(__version_info__) 6 | __author__ = 'Alan Hamlett' 7 | __author_email__ = 'alan.hamlett@gmail.com' 8 | __license__ = 'BSD' 9 | __copyright__ = 'Copyright 2016 Alan Hamlett' 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | virtualenv 38 | venv 39 | .DS_Store 40 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | 2 | History 3 | ------- 4 | 5 | 6 | 1.0.3 (2020-08-19) 7 | ++++++++++++++++++ 8 | 9 | - Require jac v0.18 or newer for improved offline compression performance. 10 | 11 | 12 | 1.0.2 (2017-10-22) 13 | ++++++++++++++++++ 14 | 15 | - Require jac v0.16.3 or newer to follow custom compressor example. 16 | 17 | 18 | 1.0.1 (2017-08-27) 19 | ++++++++++++++++++ 20 | 21 | - Upgrade jac to v0.16.1 to fix default compressor output path. 22 | 23 | 24 | 1.0.0 (2016-12-15) 25 | ++++++++++++++++++ 26 | 27 | - Birth. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | =========== 3 | 4 | Copyright (c) 2016 by the respective authors (see AUTHORS file). 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer 15 | in the documentation and/or other materials provided 16 | with the distribution. 17 | 18 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 19 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 20 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 22 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | about = {} 4 | with open('flask_static_compress/__about__.py') as f: 5 | exec(f.read(), about) 6 | 7 | packages = [ 8 | 'flask_static_compress', 9 | ] 10 | 11 | install_requires = [x.strip() for x in open('requirements.txt').readlines()] 12 | 13 | setup( 14 | name=about['__title__'], 15 | version=about['__version__'], 16 | license=about['__license__'], 17 | description=about['__description__'], 18 | long_description=open('README.rst').read(), 19 | author=about['__author__'], 20 | author_email=about['__author_email__'], 21 | url=about['__url__'], 22 | packages=packages, 23 | package_dir={packages[0]: packages[0]}, 24 | include_package_data=True, 25 | zip_safe=False, 26 | platforms='any', 27 | install_requires=install_requires, 28 | classifiers=[ 29 | 'Development Status :: 5 - Production/Stable', 30 | 'Environment :: Web Environment', 31 | 'Framework :: Flask', 32 | 'Intended Audience :: Developers', 33 | 'License :: OSI Approved :: BSD License', 34 | 'Programming Language :: Python', 35 | 'Programming Language :: Python :: 2', 36 | 'Programming Language :: Python :: 2.6', 37 | 'Programming Language :: Python :: 2.7', 38 | 'Programming Language :: Python :: 3', 39 | 'Programming Language :: Python :: 3.3', 40 | 'Programming Language :: Python :: 3.4', 41 | 'Programming Language :: Python :: 3.5', 42 | 'Programming Language :: Python :: 3.6', 43 | 'Programming Language :: Python :: 3.7', 44 | 'Programming Language :: Python :: 3.8', 45 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 46 | 'Topic :: Software Development :: Libraries :: Python Modules', 47 | ], 48 | ) 49 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Flask-Static-Compress 2 | ===================== 3 | 4 | .. image:: https://travis-ci.org/alanhamlett/flask-static-compress.svg?branch=master 5 | :target: https://travis-ci.org/alanhamlett/flask-static-compress 6 | :alt: Tests 7 | 8 | .. image:: https://codecov.io/gh/alanhamlett/flask-static-compress/branch/master/graph/badge.svg 9 | :target: https://codecov.io/gh/alanhamlett/flask-static-compress 10 | :alt: Coverage 11 | 12 | Auto-detects your static files for minification, combination, and versioning. Like Django-Compressor for Flask. 13 | 14 | 15 | Installation 16 | ------------ 17 | 18 | :: 19 | 20 | pip install flask-static-compress 21 | 22 | 23 | Usage 24 | ----- 25 | 26 | Just wrap your existing css/js with a compress block and Flask-Static-Compress handles the rest:: 27 | 28 | {% compress 'css' %} 29 | 30 | {% endcompress %} 31 | 32 | {% compress 'js' %} 33 | 34 | {% endcompress %} 35 | 36 | Also, initialize the extension inside your Flask app:: 37 | 38 | from flask_static_compress import FlaskStaticCompress 39 | app = Flask(__name__) 40 | compress = FlaskStaticCompress(app) 41 | 42 | All static assets inside a ``compress`` block are compressed into a single file, and your html is updated to the new path when rendering the template. 43 | 44 | For example:: 45 | 46 | {% compress 'js' %} 47 | 48 | 49 | {% endcompress %} 50 | 51 | Is turned into:: 52 | 53 | 54 | 55 | The compressed ``a041936b125a3ec4ce9bf7a83130157d.js`` contains both ``app.js`` and ``config.js`` combined for faster page loading. 56 | The file name is calculated based on the contents of ``app.js`` and ``config.js``. 57 | This means any change to your static code is automatically reloaded, or cache-busted, in browsers. 58 | 59 | With debug mode turned on, file names and line numbers are preserved while still running the compression flow:: 60 | 61 | 62 | 63 | 64 | The ``type`` attribute is used to decide which compressor to use for the asset. 65 | 66 | Use `offline compression `_ for improved performance. 67 | 68 | Create `custom compressors `_ to support more types of static files. 69 | 70 | For example, to remove trailing commas with `Prettier `_ then compress with `jsmin `_:: 71 | 72 | import errno 73 | import subprocess 74 | from jac.compat import file, u, utf8_encode 75 | from jac.exceptions import InvalidCompressorError 76 | from rjsmin import jsmin 77 | 78 | 79 | class CustomJavaScriptCompressor(object): 80 | binary = 'prettier' 81 | 82 | @classmethod 83 | def compile(cls, content, mimetype='text/less', cwd=None, uri_cwd=None, 84 | debug=None): 85 | if debug: 86 | return content 87 | 88 | args = ['--no-config', '--trailing-comma', 'none'] 89 | 90 | args.insert(0, cls.binary) 91 | 92 | try: 93 | handler = subprocess.Popen(args, 94 | stdout=subprocess.PIPE, 95 | stdin=subprocess.PIPE, 96 | stderr=subprocess.PIPE, cwd=None) 97 | except OSError as e: 98 | msg = '{0} encountered an error when executing {1}: {2}'.format( 99 | cls.__name__, 100 | cls.binary, 101 | u(e), 102 | ) 103 | if e.errno == errno.ENOENT: 104 | msg += ' Make sure {0} is in your PATH.'.format(cls.binary) 105 | raise InvalidCompressorError(msg) 106 | 107 | if isinstance(content, file): 108 | content = content.read() 109 | (stdout, stderr) = handler.communicate(input=utf8_encode(content)) 110 | stdout = u(stdout) 111 | 112 | if handler.returncode == 0: 113 | return jsmin(stdout) 114 | else: 115 | raise RuntimeError('Error compressing: %s' % stderr) 116 | 117 | 118 | COMPRESSOR_CLASSES = { 119 | 'text/javascript': CustomJavaScriptCompressor, 120 | } 121 | 122 | 123 | Configuration 124 | ------------- 125 | 126 | ``COMPRESSOR_ENABLED`` Default: True 127 | 128 | ``COMPRESSOR_OFFLINE_COMPRESS`` Default: False 129 | 130 | ``COMPRESSOR_FOLLOW_SYMLINKS`` Default: False 131 | 132 | ``COMPRESSOR_DEBUG`` Default: False 133 | 134 | ``COMPRESSOR_OUTPUT_DIR`` Default: app.static_folder + '/sdist' 135 | 136 | ``COMPRESSOR_CACHE_DIR`` Default: app.static_folder + '/sdist' 137 | 138 | ``COMPRESSOR_STATIC_PREFIX`` Default: app.static_url_path + '/sdist' 139 | 140 | ``COMPRESSOR_CLASSES`` Default:: 141 | 142 | [ 143 | 'text/css': LessCompressor, 144 | 'text/coffeescript': CoffeeScriptCompressor, 145 | 'text/less': LessCompressor, 146 | 'text/javascript': JavaScriptCompressor, 147 | 'text/sass': SassCompressor, 148 | 'text/scss': SassCompressor, 149 | ] 150 | 151 | 152 | Thanks to Jay Santos, creator of `jac `_. Flask-Static-Compress is just a wrapper around jac! 153 | --------------------------------------------------------------------------------