Source code for invenio_assets.webpack
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2017-2020 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Default Webpack project for Invenio."""
import os
from collections import OrderedDict
from flask import current_app, request
from flask_webpackext import WebpackBundle, WebpackBundleProject
from flask_webpackext.manifest import JinjaManifest, JinjaManifestLoader
from markupsafe import Markup
from pywebpack import ManifestEntry, UnsupportedExtensionError, \
bundles_from_entry_point
project = WebpackBundleProject(
__name__,
project_folder='assets',
config_path='build/config.json',
bundles=bundles_from_entry_point('invenio_assets.webpack'),
)
[docs]class WebpackThemeBundle(object):
"""Webpack themed bundle."""
def __init__(self, import_name, folder, default=None, themes=None):
"""Initialize webpack bundle.
:param import_name: Name of the module where the WebpackBundle class
is instantiated.
:param folder: Relative path to the assets.
:param default: The default theme to be used when ``APP_THEME`` is not
set.
:param themes: Dictionary where the keys are theme names and the values
are the keyword arguments passed to the ``WebpackBundle`` class.
"""
assert default and default in themes
self.default = default
self.themes = {}
for theme, bundle in themes.items():
self.themes[theme] = WebpackBundle(
import_name,
os.path.join(folder, theme),
**bundle
)
@property
def _active_theme_bundle(self):
themes = current_app.config.get('APP_THEME', [])
if not themes:
return self.themes[self.default]
for theme in themes:
if theme in self.themes:
return self.themes[theme]
def __getattr__(self, attr):
"""Proxy all attributes to the active theme bundle."""
try:
return getattr(self._active_theme_bundle, attr)
except RuntimeError:
raise AttributeError(
'{}.{} is invalid because you are working outside an '
'application context.'.format(self.__class__.__name__, attr)
)
[docs]class UniqueJinjaManifestEntry(ManifestEntry):
"""Manifest entry which avoids double output of chunks."""
def __html__(self):
"""Output chunk HTML tags that haven't been yet output."""
if not hasattr(request, '_jinja_webpack_entries'):
setattr(request, '_jinja_webpack_entries', {
'.js': OrderedDict(),
'.css': OrderedDict(),
})
output = []
# For debugging add from which entry the chunk came
if current_app.debug:
output.append('<!-- {} -->'.format(self.name))
for p in self._paths:
_, ext = os.path.splitext(p.lower())
# If we haven't come across the chunk yet, we add it to the output
if ext not in request._jinja_webpack_entries:
raise UnsupportedExtensionError(p)
if p not in request._jinja_webpack_entries[ext]:
tpl = self.templates.get(ext)
if tpl is None:
raise UnsupportedExtensionError(p)
output.append(tpl.format(p))
# Mark the we have already output the chunk
request._jinja_webpack_entries[ext][p] = None
return Markup('\n'.join(output))
[docs]class UniqueJinjaManifestLoader(JinjaManifestLoader):
"""Factory which uses the Jinja manifest entry."""
def __init__(self, manifest_cls=JinjaManifest,
entry_cls=UniqueJinjaManifestEntry):
"""Initialize manifest loader."""
super(UniqueJinjaManifestLoader, self).__init__(
manifest_cls=manifest_cls,
entry_cls=entry_cls
)