From 994f0f490917ab64cd523d6adfbfcad026eb47d2 Mon Sep 17 00:00:00 2001 From: Frederik Ring Date: Fri, 30 Aug 2019 12:33:00 +0200 Subject: [PATCH] add basic asset pipeline for pelican site --- .circleci/config.yml | 7 +- homepage/docker-compose.yml | 2 + homepage/pelicanconf.py | 5 +- homepage/plugins/assets/Readme.rst | 106 ++++++++++++++++++ homepage/plugins/assets/__init__.py | 1 + homepage/plugins/assets/assets.py | 75 +++++++++++++ homepage/plugins/optimize_images/Readme.md | 28 +++++ homepage/plugins/optimize_images/__init__.py | 1 + .../optimize_images/optimize_images.py | 61 ++++++++++ homepage/publishconf.py | 5 +- homepage/requirements.txt | 2 + homepage/theme/static/scripts/fade.js | 10 +- homepage/theme/static/scripts/menu.js | 34 +++--- homepage/theme/templates/base.html | 18 ++- 14 files changed, 316 insertions(+), 39 deletions(-) create mode 100644 homepage/plugins/assets/Readme.rst create mode 100644 homepage/plugins/assets/__init__.py create mode 100644 homepage/plugins/assets/assets.py create mode 100644 homepage/plugins/optimize_images/Readme.md create mode 100644 homepage/plugins/optimize_images/__init__.py create mode 100644 homepage/plugins/optimize_images/optimize_images.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 9091f75..cba784d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -377,7 +377,7 @@ jobs: deploy_homepage: docker: - - image: circleci/python:3.6 + - image: circleci/python:3.6-node working_directory: ~/offen/homepage environment: - SOURCE_BRANCH: master @@ -397,6 +397,11 @@ jobs: paths: - ~/offen/homepage/venv key: offen-homepage-{{ checksum "requirements.txt" }} + - run: + name: Install image optimization deps + command: | + npm install svgo -g + sudo apt-get install libjpeg-progs optipng - run: name: Deploy command: | diff --git a/homepage/docker-compose.yml b/homepage/docker-compose.yml index 0d43786..e105b1a 100644 --- a/homepage/docker-compose.yml +++ b/homepage/docker-compose.yml @@ -12,6 +12,8 @@ services: command: make devserver ports: - 8000:8000 + environment: + DEBUG: '1' volumes: homepagedeps: diff --git a/homepage/pelicanconf.py b/homepage/pelicanconf.py index 8a546fb..9a786bf 100644 --- a/homepage/pelicanconf.py +++ b/homepage/pelicanconf.py @@ -4,12 +4,10 @@ from __future__ import unicode_literals import logging # If your site is available via HTTPS, make sure SITEURL begins with https:// -#SITEURL = 'https://www.offen.dev' RELATIVE_URLS = False AUTHOR = 'offen' SITENAME = 'offen' -SITEURL = 'https://www.offen.dev' PATH = 'content' TIMEZONE = 'Europe/Berlin' DEFAULT_LANG = 'en' @@ -30,6 +28,9 @@ THEME = './theme' # Delete the output directory before generating new files. DELETE_OUTPUT_DIRECTORY = True +PLUGIN_PATHS = ['./plugins'] +PLUGINS = ['assets'] + # dont create following standard pages AUTHORS_SAVE_AS = None ARCHIVES_SAVE_AS = None diff --git a/homepage/plugins/assets/Readme.rst b/homepage/plugins/assets/Readme.rst new file mode 100644 index 0000000..42091cb --- /dev/null +++ b/homepage/plugins/assets/Readme.rst @@ -0,0 +1,106 @@ +Asset management +---------------- + +This plugin allows you to use the `Webassets`_ module to manage assets such as +CSS and JS files. The module must first be installed:: + + pip install webassets + +The Webassets module allows you to perform a number of useful asset management +functions, including: + +* CSS minifier (``cssmin``, ``yui_css``, ...) +* CSS compiler (``less``, ``sass``, ...) +* JS minifier (``uglifyjs``, ``yui_js``, ``closure``, ...) + +Others filters include CSS URL rewriting, integration of images in CSS via data +URIs, and more. Webassets can also append a version identifier to your asset +URL to convince browsers to download new versions of your assets when you use +far-future expires headers. Please refer to the `Webassets documentation`_ for +more information. + +When used with Pelican, Webassets is configured to process assets in the +``OUTPUT_PATH/theme`` directory. You can use Webassets in your templates by +including one or more template tags. The Jinja variable ``{{ ASSET_URL }}`` can +be used in templates and is relative to the ``theme/`` url. The +``{{ ASSET_URL }}`` variable should be used in conjunction with the +``{{ SITEURL }}`` variable in order to generate URLs properly. For example: + +.. code-block:: jinja + + {% assets filters="cssmin", output="css/style.min.css", "css/inuit.css", "css/pygment-monokai.css", "css/main.css" %} + + {% endassets %} + +... will produce a minified css file with a version identifier that looks like: + +.. code-block:: html + + + +These filters can be combined. Here is an example that uses the SASS compiler +and minifies the output: + +.. code-block:: jinja + + {% assets filters="sass,cssmin", output="css/style.min.css", "css/style.scss" %} + + {% endassets %} + +Another example for Javascript: + +.. code-block:: jinja + + {% assets filters="uglifyjs", output="js/packed.js", "js/jquery.js", "js/base.js", "js/widgets.js" %} + + {% endassets %} + +The above will produce a minified JS file: + +.. code-block:: html + + + +Pelican's debug mode is propagated to Webassets to disable asset packaging +and instead work with the uncompressed assets. + +If you need to create named bundles (for example, if you need to compile SASS +files before minifying with other CSS files), you can use the ``ASSET_BUNDLES`` +variable in your settings file. This is an ordered sequence of 3-tuples, where +the 3-tuple is defined as ``(name, args, kwargs)``. This tuple is passed to the +`environment's register() method`_. The following will compile two SCSS files +into a named bundle, using the ``pyscss`` filter: + +.. code-block:: python + + ASSET_BUNDLES = ( + ('scss', ['colors.scss', 'main.scss'], {'filters': 'pyscss'}), + ) + +Many of Webasset's available compilers have additional configuration options +(i.e. 'Less', 'Sass', 'Stylus', 'Closure_js'). You can pass these options to +Webassets using the ``ASSET_CONFIG`` in your settings file. + +The following will handle Google Closure's compilation level and locate +LessCSS's binary: + +.. code-block:: python + + ASSET_CONFIG = (('closure_compressor_optimization', 'WHITESPACE_ONLY'), + ('less_bin', 'lessc.cmd'), ) + +If you wish to place your assets in locations other than the theme output +directory, you can use ``ASSET_SOURCE_PATHS`` in your settings file to provide +webassets with a list of additional directories to search, relative to the +theme's top-level directory: + +.. code-block:: python + + ASSET_SOURCE_PATHS = [ + 'vendor/css', + 'scss', + ] + +.. _Webassets: https://github.com/miracle2k/webassets +.. _Webassets documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html +.. _environment's register() method: http://webassets.readthedocs.org/en/latest/environment.html#registering-bundles diff --git a/homepage/plugins/assets/__init__.py b/homepage/plugins/assets/__init__.py new file mode 100644 index 0000000..67b75dd --- /dev/null +++ b/homepage/plugins/assets/__init__.py @@ -0,0 +1 @@ +from .assets import * diff --git a/homepage/plugins/assets/assets.py b/homepage/plugins/assets/assets.py new file mode 100644 index 0000000..e204dd6 --- /dev/null +++ b/homepage/plugins/assets/assets.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +""" +Asset management plugin for Pelican +=================================== + +This plugin allows you to use the `webassets`_ module to manage assets such as +CSS and JS files. + +The ASSET_URL is set to a relative url to honor Pelican's RELATIVE_URLS +setting. This requires the use of SITEURL in the templates:: + + + +.. _webassets: https://webassets.readthedocs.org/ + +""" +from __future__ import unicode_literals + +import os +import logging + +from pelican import signals +logger = logging.getLogger(__name__) + +try: + import webassets + from webassets import Environment + from webassets.ext.jinja2 import AssetsExtension +except ImportError: + webassets = None + +def add_jinja2_ext(pelican): + """Add Webassets to Jinja2 extensions in Pelican settings.""" + + if 'JINJA_ENVIRONMENT' in pelican.settings: # pelican 3.7+ + pelican.settings['JINJA_ENVIRONMENT']['extensions'].append(AssetsExtension) + else: + pelican.settings['JINJA_EXTENSIONS'].append(AssetsExtension) + + +def create_assets_env(generator): + """Define the assets environment and pass it to the generator.""" + + theme_static_dir = generator.settings['THEME_STATIC_DIR'] + assets_destination = os.path.join(generator.output_path, theme_static_dir) + generator.env.assets_environment = Environment( + assets_destination, theme_static_dir) + + if 'ASSET_CONFIG' in generator.settings: + for item in generator.settings['ASSET_CONFIG']: + generator.env.assets_environment.config[item[0]] = item[1] + + if 'ASSET_BUNDLES' in generator.settings: + for name, args, kwargs in generator.settings['ASSET_BUNDLES']: + generator.env.assets_environment.register(name, *args, **kwargs) + + if 'ASSET_DEBUG' in generator.settings: + generator.env.assets_environment.debug = generator.settings['ASSET_DEBUG'] + elif logging.getLevelName(logger.getEffectiveLevel()) == "DEBUG": + generator.env.assets_environment.debug = True + + for path in (generator.settings['THEME_STATIC_PATHS'] + + generator.settings.get('ASSET_SOURCE_PATHS', [])): + full_path = os.path.join(generator.theme, path) + generator.env.assets_environment.append_path(full_path) + + +def register(): + """Plugin registration.""" + if webassets: + signals.initialized.connect(add_jinja2_ext) + signals.generator_init.connect(create_assets_env) + else: + logger.warning('`assets` failed to load dependency `webassets`.' + '`assets` plugin not loaded.') diff --git a/homepage/plugins/optimize_images/Readme.md b/homepage/plugins/optimize_images/Readme.md new file mode 100644 index 0000000..8efc926 --- /dev/null +++ b/homepage/plugins/optimize_images/Readme.md @@ -0,0 +1,28 @@ +Optimize Images Plugin For Pelican +================================== + +This plugin applies lossless compression on JPEG, PNG and SVG images, with no +effect on image quality via [jpegtran][], [OptiPNG][] and [svgo][] respectively. +The plugin assumes that all of these tools are installed, with associated +executables available on the system path. + +[jpegtran]: http://jpegclub.org/jpegtran/ +[OptiPNG]: http://optipng.sourceforge.net/ +[SVGO]: https://github.com/svg/svgo + + +Installation +------------ + +To enable, ensure that `optimize_images.py` is put somewhere that is accessible. +Then use as follows by adding the following to your settings.py: + + PLUGIN_PATH = 'path/to/pelican-plugins' + PLUGINS = ["optimize_images"] + +`PLUGIN_PATH` can be a path relative to your settings file or an absolute path. + +Usage +----- +The plugin will activate and optimize images upon `finalized` signal of +Pelican. diff --git a/homepage/plugins/optimize_images/__init__.py b/homepage/plugins/optimize_images/__init__.py new file mode 100644 index 0000000..cbb056a --- /dev/null +++ b/homepage/plugins/optimize_images/__init__.py @@ -0,0 +1 @@ +from .optimize_images import * diff --git a/homepage/plugins/optimize_images/optimize_images.py b/homepage/plugins/optimize_images/optimize_images.py new file mode 100644 index 0000000..33593ce --- /dev/null +++ b/homepage/plugins/optimize_images/optimize_images.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +""" +Optimized images (jpg and png) +Assumes that jpegtran and optipng are isntalled on path. +http://jpegclub.org/jpegtran/ +http://optipng.sourceforge.net/ +Copyright (c) 2012 Irfan Ahmad (http://i.com.pk) +""" + +import logging +import os +from subprocess import call + +from pelican import signals + +logger = logging.getLogger(__name__) + +# Display command output on DEBUG and TRACE +SHOW_OUTPUT = logger.getEffectiveLevel() <= logging.DEBUG + +# A list of file types with their respective commands +COMMANDS = { + # '.ext': ('command {flags} {filename', 'silent_flag', 'verbose_flag') + '.svg': ('svgo {flags} --input="{filename}" --output="{filename}"', '--quiet', ''), + '.jpg': ('jpegtran {flags} -copy none -optimize -outfile "{filename}" "{filename}"', '', '-v'), + '.png': ('optipng {flags} "{filename}"', '--quiet', ''), +} + + +def optimize_images(pelican): + """ + Optimized jpg and png images + + :param pelican: The Pelican instance + """ + for dirpath, _, filenames in os.walk(pelican.settings['OUTPUT_PATH']): + for name in filenames: + if os.path.splitext(name)[1] in COMMANDS.keys(): + optimize(dirpath, name) + +def optimize(dirpath, filename): + """ + Check if the name is a type of file that should be optimized. + And optimizes it if required. + + :param dirpath: Path of the file to be optimzed + :param name: A file name to be optimized + """ + filepath = os.path.join(dirpath, filename) + logger.info('optimizing %s', filepath) + + ext = os.path.splitext(filename)[1] + command, silent, verbose = COMMANDS[ext] + flags = verbose if SHOW_OUTPUT else silent + command = command.format(filename=filepath, flags=flags) + call(command, shell=True) + + +def register(): + signals.finalized.connect(optimize_images) diff --git a/homepage/publishconf.py b/homepage/publishconf.py index cf5b588..e442015 100644 --- a/homepage/publishconf.py +++ b/homepage/publishconf.py @@ -19,9 +19,6 @@ CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml' DELETE_OUTPUT_DIRECTORY = True -# Following items are often useful when publishing - -#DISQUS_SITENAME = "" -#GOOGLE_ANALYTICS = "" +PLUGINS += ['optimize_images'] OFFEN_ACCOUNT_ID = "5ec8345a-2a45-4eb9-92e5-8d9e5684db58" diff --git a/homepage/requirements.txt b/homepage/requirements.txt index a9d0eea..a62e2d6 100644 --- a/homepage/requirements.txt +++ b/homepage/requirements.txt @@ -1,2 +1,4 @@ pelican==4.0.1 markdown==3.1.1 +webassets==0.12.1 +cssmin==0.2.0 diff --git a/homepage/theme/static/scripts/fade.js b/homepage/theme/static/scripts/fade.js index a752dad..70e7e13 100644 --- a/homepage/theme/static/scripts/fade.js +++ b/homepage/theme/static/scripts/fade.js @@ -1,3 +1,7 @@ -$(window).scroll(function(){ - $(".brand-index").css("opacity", 0 + $(window).scrollTop() / 100); - }); +;(function ($) { + $(document).ready(function () { + $(window).scroll(function () { + $('.brand-index').css('opacity', 0 + $(window).scrollTop() / 100) + }) + }) +})(window.jQuery) diff --git a/homepage/theme/static/scripts/menu.js b/homepage/theme/static/scripts/menu.js index e34d5b3..d8967d4 100644 --- a/homepage/theme/static/scripts/menu.js +++ b/homepage/theme/static/scripts/menu.js @@ -1,18 +1,16 @@ -(function($) { - $(function() { - $('nav ul li a:not(:only-child)').click(function(e) { - $(this).siblings('.nav-dropdown').toggle(); - $('.dropdown').not($(this).siblings()).hide(); - e.stopPropagation(); - }); - $('html').click(function() { - $('.nav-dropdown').hide(); - }); - $('#nav-toggle').click(function() { - $('nav ul').slideToggle(); - }); - $('#nav-toggle').on('click', function() { - this.classList.toggle('active'); - }); - }); -})(jQuery); +;(function ($) { + $(function () { + $('nav ul li a:not(:only-child)').click(function (e) { + $(this).siblings('.nav-dropdown').toggle() + $('.dropdown').not($(this).siblings()).hide() + e.stopPropagation() + }) + $('html').click(function () { + $('.nav-dropdown').hide() + }) + $('#nav-toggle').click(function () { + $(this).closest('nav').find('ul').slideToggle() + $(this).toggleClass('active') + }) + }) +})(window.jQuery) diff --git a/homepage/theme/templates/base.html b/homepage/theme/templates/base.html index d8a3e61..698fbdf 100644 --- a/homepage/theme/templates/base.html +++ b/homepage/theme/templates/base.html @@ -17,12 +17,9 @@ - - - - + {% assets filters="cssmin", output="css/style.min.css", "css/normalize.css", "css/fonts.css", "css/style.css" %} + + {% endassets %} {% if OFFEN_ACCOUNT_ID %} {% endif %} @@ -40,7 +37,7 @@