okay fine

This commit is contained in:
pacnpal
2024-11-03 17:47:26 +00:00
parent 387c4740e7
commit 27f3326e22
10020 changed files with 1935769 additions and 2364 deletions

View File

@@ -0,0 +1,7 @@
__author__ = "Vinta Software"
__version__ = "3.1.1"
import django
if django.VERSION < (3, 2): # pragma: no cover
default_app_config = "webpack_loader.apps.WebpackLoaderConfig"

View File

@@ -0,0 +1,29 @@
from django.apps import AppConfig
from .errors import BAD_CONFIG_ERROR
def webpack_cfg_check(*args, **kwargs):
'''Test if config is compatible or not'''
from django.conf import settings
check_failed = False
user_config = getattr(settings, 'WEBPACK_LOADER', {})
try:
user_config = [dict({}, **cfg) for cfg in user_config.values()]
except TypeError:
check_failed = True
errors = []
if check_failed:
errors.append(BAD_CONFIG_ERROR)
return errors
class WebpackLoaderConfig(AppConfig):
name = 'webpack_loader'
verbose_name = "Webpack Loader"
def ready(self):
from django.core.checks import register, Tags
register(Tags.compatibility)(webpack_cfg_check)

View File

@@ -0,0 +1,37 @@
import re
from django.conf import settings
__all__ = ('load_config',)
DEFAULT_CONFIG = {
'DEFAULT': {
'CACHE': not settings.DEBUG,
'BUNDLE_DIR_NAME': 'webpack_bundles/',
'STATS_FILE': 'webpack-stats.json',
# FIXME: Explore usage of fsnotify
'POLL_INTERVAL': 0.1,
'TIMEOUT': None,
'IGNORE': [r'.+\.hot-update.js', r'.+\.map'],
'LOADER_CLASS': 'webpack_loader.loaders.WebpackLoader',
'INTEGRITY': False,
# Whenever the global setting for SKIP_COMMON_CHUNKS is changed, please
# update the fallback value in get_skip_common_chunks (utils.py).
'SKIP_COMMON_CHUNKS': False,
}
}
user_config = getattr(settings, 'WEBPACK_LOADER', DEFAULT_CONFIG)
user_config = dict(
(name, dict(DEFAULT_CONFIG['DEFAULT'], **cfg))
for name, cfg in user_config.items()
)
for entry in user_config.values():
entry['ignores'] = [re.compile(I) for I in entry['IGNORE']]
def load_config(name):
return user_config[name]

View File

@@ -0,0 +1,22 @@
from jinja2.ext import Extension
from jinja2.runtime import Context
from jinja2.utils import pass_context
from ..templatetags.webpack_loader import get_files, render_bundle
@pass_context
def _render_bundle(context: Context, *args, **kwargs):
return render_bundle(context, *args, **kwargs)
@pass_context
def _get_files(context: Context, *args, **kwargs):
return get_files(context, *args, **kwargs)
class WebpackExtension(Extension):
def __init__(self, environment):
super(WebpackExtension, self).__init__(environment)
environment.globals["render_bundle"] = _render_bundle
environment.globals["webpack_get_files"] = _get_files

View File

@@ -0,0 +1,9 @@
from django.core.checks import Error
BAD_CONFIG_ERROR = Error(
'Error while parsing WEBPACK_LOADER configuration',
hint='Is WEBPACK_LOADER config valid?',
obj='django.conf.settings.WEBPACK_LOADER',
id='django-webpack-loader.E001',
)

View File

@@ -0,0 +1,36 @@
__all__ = (
'WebpackError',
'WebpackLoaderBadStatsError',
'WebpackLoaderTimeoutError',
'WebpackBundleLookupError'
)
class BaseWebpackLoaderException(Exception):
"""
Base exception for django-webpack-loader.
"""
class WebpackError(BaseWebpackLoaderException):
"""
General webpack loader error.
"""
class WebpackLoaderBadStatsError(BaseWebpackLoaderException):
"""
The stats file does not contain valid data.
"""
class WebpackLoaderTimeoutError(BaseWebpackLoaderException):
"""
The bundle took too long to compile.
"""
class WebpackBundleLookupError(BaseWebpackLoaderException):
"""
The bundle name was invalid.
"""

View File

@@ -0,0 +1,9 @@
import warnings
from .loaders import * # noqa
warnings.warn(
"The 'webpack_loader.loader' module has been renamed to 'webpack_loader.loaders'. "
"Please update your imports and config to use the new module.",
DeprecationWarning,
)

View File

@@ -0,0 +1,173 @@
import json
import time
import os
from io import open
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from .exceptions import (
WebpackError,
WebpackLoaderBadStatsError,
WebpackLoaderTimeoutError,
WebpackBundleLookupError,
)
class WebpackLoader:
_assets = {}
def __init__(self, name, config):
self.name = name
self.config = config
def load_assets(self):
try:
with open(self.config["STATS_FILE"], encoding="utf-8") as f:
return json.load(f)
except IOError:
raise IOError(
"Error reading {0}. Are you sure webpack has generated "
"the file and the path is correct?".format(self.config["STATS_FILE"])
)
def get_assets(self):
if self.config["CACHE"]:
if self.name not in self._assets:
self._assets[self.name] = self.load_assets()
return self._assets[self.name]
return self.load_assets()
def get_asset_by_source_filename(self, name):
files = self.get_assets()["assets"].values()
return next((x for x in files if x.get("sourceFilename") == name), None)
def get_integrity_attr(self, chunk):
if not self.config.get("INTEGRITY"):
return " "
integrity = chunk.get("integrity")
if not integrity:
raise WebpackLoaderBadStatsError(
"The stats file does not contain valid data: INTEGRITY is set to True, "
'but chunk does not contain "integrity" key. Maybe you forgot to add '
"integrity: true in your BundleTracker configuration?"
)
return ' integrity="{}" '.format(integrity.partition(" ")[0])
def filter_chunks(self, chunks):
filtered_chunks = []
for chunk in chunks:
ignore = any(regex.match(chunk) for regex in self.config["ignores"])
if not ignore:
filtered_chunks.append(chunk)
return filtered_chunks
def map_chunk_files_to_url(self, chunks):
assets = self.get_assets()
files = assets["assets"]
add_integrity = self.config.get("INTEGRITY")
for chunk in chunks:
url = self.get_chunk_url(files[chunk])
if add_integrity:
yield {
"name": chunk,
"url": url,
"integrity": files[chunk].get("integrity"),
}
else:
yield {"name": chunk, "url": url}
def get_chunk_url(self, chunk_file):
public_path = chunk_file.get("publicPath")
if public_path and public_path != "auto":
return public_path
# Use os.path.normpath for Windows paths
relpath = os.path.normpath(
os.path.join(self.config["BUNDLE_DIR_NAME"], chunk_file["name"])
)
return staticfiles_storage.url(relpath)
def get_bundle(self, bundle_name):
assets = self.get_assets()
# poll when debugging and block request until bundle is compiled
# or the build times out
if settings.DEBUG:
timeout = self.config["TIMEOUT"] or 0
timed_out = False
start = time.time()
while assets["status"] == "compile" and not timed_out:
time.sleep(self.config["POLL_INTERVAL"])
if timeout and (time.time() - timeout > start):
timed_out = True
assets = self.get_assets()
if timed_out:
raise WebpackLoaderTimeoutError(
"Timed Out. Bundle `{0}` took more than {1} seconds "
"to compile.".format(bundle_name, timeout)
)
if assets.get("status") == "done":
chunks = assets["chunks"].get(bundle_name, None)
if chunks is None:
raise WebpackBundleLookupError(
"Cannot resolve bundle {0}.".format(bundle_name)
)
filtered_chunks = self.filter_chunks(chunks)
for chunk in filtered_chunks:
asset = assets["assets"][chunk]
if asset is None:
raise WebpackBundleLookupError(
"Cannot resolve asset {0}.".format(chunk)
)
return self.map_chunk_files_to_url(filtered_chunks)
elif assets.get("status") == "error":
if "file" not in assets:
assets["file"] = ""
if "error" not in assets:
assets["error"] = "Unknown Error"
if "message" not in assets:
assets["message"] = ""
error = """
{error} in {file}
{message}
""".format(**assets)
raise WebpackError(error)
raise WebpackLoaderBadStatsError(
"The stats file does not contain valid data. Make sure "
"webpack-bundle-tracker plugin is enabled and try to run "
"webpack again."
)
class FakeWebpackLoader(WebpackLoader):
"""
A fake loader to help run Django tests.
For running tests where `render_bundle` is used but assets aren't built.
"""
def get_assets(self):
return {}
def get_bundle(self, _bundle_name):
return [
{
"name": "test.bundle.js",
"url": "http://localhost/static/webpack_bundles/test.bundle.js",
}
]

View File

@@ -0,0 +1 @@
# will hook into collectstatic

View File

@@ -0,0 +1,84 @@
from warnings import warn
from django.template import Library
from django.utils.safestring import mark_safe
from .. import utils
register = Library()
_WARNING_MESSAGE = (
'You have specified skip_common_chunks=True but the passed context '
'doesn\'t have a request. django_webpack_loader needs a request object to '
'filter out duplicate chunks. Please see https://github.com/django-webpack'
'/django-webpack-loader#use-skip_common_chunks-on-render_bundle')
@register.simple_tag(takes_context=True)
def render_bundle(
context, bundle_name, extension=None, config='DEFAULT', suffix='',
attrs='', is_preload=False, skip_common_chunks=None):
if skip_common_chunks is None:
skip_common_chunks = utils.get_skip_common_chunks(config)
url_to_tag_dict = utils.get_as_url_to_tag_dict(
bundle_name, extension=extension, config=config, suffix=suffix,
attrs=attrs, is_preload=is_preload)
request = context.get('request')
if request is None:
if skip_common_chunks:
warn(message=_WARNING_MESSAGE, category=RuntimeWarning)
return mark_safe('\n'.join(url_to_tag_dict.values()))
used_urls = getattr(request, '_webpack_loader_used_urls', None)
if not used_urls:
used_urls = request._webpack_loader_used_urls = set()
if skip_common_chunks:
url_to_tag_dict = {url: tag for url, tag in url_to_tag_dict.items() if url not in used_urls}
used_urls.update(url_to_tag_dict.keys())
return mark_safe('\n'.join(url_to_tag_dict.values()))
@register.simple_tag
def webpack_static(asset_name, config='DEFAULT'):
return utils.get_static(asset_name, config=config)
@register.simple_tag
def webpack_asset(asset_name, config='DEFAULT'):
return utils.get_asset(asset_name, config=config)
@register.simple_tag(takes_context=True)
def get_files(
context, bundle_name, extension=None, config='DEFAULT',
skip_common_chunks=None):
"""
Returns all chunks in the given bundle.
Example usage::
{% get_files 'editor' 'css' as editor_css_chunks %}
CKEDITOR.config.contentsCss = '{{ editor_css_chunks.0.url }}';
:param context: The request, if you want to use `skip_common_chunks`
:param bundle_name: The name of the bundle
:param extension: (optional) filter by extension
:param config: (optional) the name of the configuration
:param skip_common_chunks: (optional) `True` if you want to skip returning already rendered common chunks
:return: a list of matching chunks
"""
if skip_common_chunks is None:
skip_common_chunks = utils.get_skip_common_chunks(config)
result = utils.get_files(bundle_name, extension=extension, config=config)
request = context.get('request')
if request is None:
if skip_common_chunks:
warn(message=_WARNING_MESSAGE, category=RuntimeWarning)
return result
used_urls = getattr(request, '_webpack_loader_used_urls', None)
if not used_urls:
used_urls = set()
if skip_common_chunks:
result = [chunk for chunk in result if chunk['url'] not in used_urls]
return result

View File

@@ -0,0 +1,141 @@
from collections import OrderedDict
from importlib import import_module
from django.conf import settings
from .config import load_config
_loaders = {}
def import_string(dotted_path):
'''
This is a rough copy of django's import_string, which wasn't introduced until Django 1.7
Once this package's support for Django 1.6 has been removed, this can be safely replaced with
`from django.utils.module_loading import import_string`
'''
try:
module_path, class_name = dotted_path.rsplit('.', 1)
module = import_module(module_path)
return getattr(module, class_name)
except (ValueError, AttributeError, ImportError):
raise ImportError('%s doesn\'t look like a valid module path' % dotted_path)
def get_loader(config_name):
if config_name not in _loaders:
config = load_config(config_name)
loader_class = import_string(config['LOADER_CLASS'])
_loaders[config_name] = loader_class(config_name, config)
return _loaders[config_name]
def get_skip_common_chunks(config_name):
loader = get_loader(config_name)
# The global default is currently False, whenever that is changed, change
# this fallback value as well which is present to provide backwards
# compatibility.
return loader.config.get('SKIP_COMMON_CHUNKS', False)
def _filter_by_extension(bundle, extension):
'''Return only files with the given extension'''
for chunk in bundle:
if chunk['name'].endswith('.{0}'.format(extension)):
yield chunk
def _get_bundle(loader, bundle_name, extension):
bundle = loader.get_bundle(bundle_name)
if extension:
bundle = _filter_by_extension(bundle, extension)
return bundle
def get_files(bundle_name, extension=None, config='DEFAULT'):
'''Returns list of chunks from named bundle'''
loader = get_loader(config)
return list(_get_bundle(loader, bundle_name, extension))
def get_as_url_to_tag_dict(bundle_name, extension=None, config='DEFAULT', suffix='', attrs='', is_preload=False):
'''
Get a dict of URLs to formatted <script> & <link> tags for the assets in the
named bundle.
:param bundle_name: The name of the bundle
:param extension: (optional) filter by extension, eg. 'js' or 'css'
:param config: (optional) the name of the configuration
:return: a dict of URLs to formatted tags as strings
'''
loader = get_loader(config)
bundle = _get_bundle(loader, bundle_name, extension)
result = OrderedDict()
for chunk in bundle:
if chunk['name'].endswith(('.js', '.js.gz')):
if is_preload:
result[chunk['url']] = (
'<link rel="preload" as="script" href="{0}" {1}/>'
).format(''.join([chunk['url'], suffix]), attrs)
else:
result[chunk['url']] = (
'<script src="{0}"{2}{1}></script>'
).format(
''.join([chunk['url'], suffix]),
attrs,
loader.get_integrity_attr(chunk),
)
elif chunk['name'].endswith(('.css', '.css.gz')):
result[chunk['url']] = (
'<link href="{0}" rel={2}{3}{1}/>'
).format(
''.join([chunk['url'], suffix]),
attrs,
'"stylesheet"' if not is_preload else '"preload" as="style"',
loader.get_integrity_attr(chunk),
)
return result
def get_as_tags(bundle_name, extension=None, config='DEFAULT', suffix='', attrs='', is_preload=False):
'''
Get a list of formatted <script> & <link> tags for the assets in the
named bundle.
:param bundle_name: The name of the bundle
:param extension: (optional) filter by extension, eg. 'js' or 'css'
:param config: (optional) the name of the configuration
:return: a list of formatted tags as strings
'''
return list(get_as_url_to_tag_dict(bundle_name, extension, config, suffix, attrs, is_preload).values())
def get_static(asset_name, config='DEFAULT'):
'''
Equivalent to Django's 'static' look up but for webpack assets.
:param asset_name: the name of the asset
:param config: (optional) the name of the configuration
:return: path to webpack asset as a string
'''
public_path = get_loader(config).get_assets().get('publicPath')
if not public_path or public_path == 'auto':
public_path = getattr(settings, 'STATIC_URL')
return '{0}{1}'.format(public_path, asset_name)
def get_asset(source_filename, config='DEFAULT'):
'''
Equivalent to Django's 'static' look up but for webpack assets, given its original filename.
Allow handling files whose path has been modified by Webpack processing, such as including content hash to filename.
:param source_filename: the source filename of the asset
:param config: (optional) the name of the configuration
:return: path to webpack asset as a string
'''
loader = get_loader(config)
asset = loader.get_asset_by_source_filename(source_filename)
if not asset: return None
return get_static(asset['name'], config)