Source code for score.requirejs._init
# Copyright © 2017 STRG.AT GmbH, Vienna, Austria
#
# This file is part of the The SCORE Framework.
#
# The SCORE Framework and all its parts are free software: you can redistribute
# them and/or modify them under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation which is in the
# file named COPYING.LESSER.txt.
#
# The SCORE Framework and all its parts are distributed without any WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. For more details see the GNU Lesser General Public
# License.
#
# If you have not received a copy of the GNU Lesser General Public License see
# http://www.gnu.org/licenses/.
#
# The License-Agreement realised between you as Licensee and STRG.AT GmbH as
# Licenser including the issue of its valid conclusion and its pre- and
# post-contractual effects is governed by the laws of Austria. Any disputes
# concerning this License-Agreement including the issue of its valid conclusion
# and its pre- and post-contractual effects are exclusively decided by the
# competent court, in whose district STRG.AT GmbH has its registered seat, at
# the discretion of STRG.AT GmbH also the competent court, in whose district the
# Licensee has his registered seat, an establishment or assets.
from score.init import ConfiguredModule, parse_list
import json
import os
import tempfile
import subprocess
from score.tpl import TemplateNotFound
from score.tpl.loader import Loader
from score.webassets import WebassetsProxy
import re
defaults = {
'config_file': None,
'passthrough_extensions': [],
'path.nodejs': 'nodejs',
}
[docs]def init(confdict, tpl, webassets):
"""
Initializes this module acoording to the :ref:`SCORE module initialization
guidelines <module_initialization>` with the following configuration keys:
:confkey:`config_file` :confdefault:`None`
An optional javascript file containing the `requirejs configuration`_.
This file should only contain the configuration object itself, but the
object does not have to adhere to a strict JSON syntax and may contain
javascript functions. The following is a perfectly valid example for the
referenced file's content:
.. code-block:: javascript
{
map: {
'some/newmodule': {
'foo': 'foo1.2'
},
},
shim: {
'foo': {
deps: ['bar'],
exports: 'Foo',
init: function (bar) {
return this.Foo.noConflict();
}
}
}
}
.. _requirejs configuration: http://requirejs.org/docs/api.html#config
:confkey:`passthrough_extensions` :confdefault:`[]`
A list of additional file extensions that this module is allowed to pass
to browsers. If you want to pass mustache_ templates to the browser, for
example, you must provide the ``mustache`` extension in this list.
.. _mustache: http://mustache.github.io/
:confkey:`path.nodejs` :confdefault:`nodejs`
The path to the nodejs_ executable. This value is only relevant if
:term:`asset bundling <asset bundle>` is enabled in
:mod:`score.webassets`.
.. _nodejs: https://nodejs.org/en/
"""
conf = defaults.copy()
conf.update(confdict)
return ConfiguredRequirejsModule(
tpl, webassets, conf['config_file'], conf['path.nodejs'],
parse_list(conf['passthrough_extensions']))
[docs]class ConfiguredRequirejsModule(ConfiguredModule):
"""
This module's :class:`configuration class <score.init.ConfiguredModule>`.
"""
def __init__(self, tpl, webassets, config_file, nodejs_path,
passthrough_extensions):
import score.requirejs
super().__init__(score.requirejs)
self.tpl = tpl
self.webassets = webassets
self.config_file = config_file
self.nodejs_path = nodejs_path
self.passthrough_extensions = passthrough_extensions
self.loader = RequireJsLoader(self)
self.tpl.loaders['js'].append(self.loader)
[docs] def score_webassets_proxy(self):
"""
Provides the :ref:`webassets proxy <webassets_proxy>`.
"""
return RequirejsAssets(self)
def create_bundle(self, paths=None):
rendered_requirejs = self.tpl.render('!require.js')
with tempfile.TemporaryDirectory() as tmpdir:
srcdir = os.path.join(tmpdir, 'src')
os.makedirs(srcdir)
open(os.path.join(srcdir, 'require.js'), 'w').write(
rendered_requirejs)
include_paths = self._copy_files(srcdir, paths)
script_tpl = r'''
var conf = require("fs").readFileSync(%s, {
encoding: "UTF-8"
});
eval("conf = " + conf + ";");
var overrides = %s;
for (var key in overrides) {
conf[key] = overrides[key];
}
require("requirejs").optimize(conf, function (result) {
console.warn(result);
}, function(err) {
console.warn(err);
process.exit(1);
});
'''
script = script_tpl % (json.dumps(self.config_file), json.dumps({
"out": "stdout",
"include": include_paths,
"baseUrl": srcdir,
"optimize": "none",
}))
process = subprocess.Popen([self.nodejs_path],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = process.communicate(script.encode('UTF-8'))
stdout, stderr = str(stdout, 'UTF-8'), str(stderr, 'UTF-8')
if process.returncode:
self.log.error(stderr)
try:
raise subprocess.CalledProcessError(
process.returncode, 'node.js',
output=stdout, stderr=stderr)
except TypeError:
# the stderr kwarg is only available in python 3.5
pass
raise subprocess.CalledProcessError(
process.returncode, 'node.js', output=stderr)
if stderr:
self.log.info("node.js output:\n" + stderr)
javascript = (rendered_requirejs + stdout +
self.tpl.render('!require-config.js'))
filetype = self.tpl.filetypes['application/javascript']
for postprocessor in filetype.postprocessors:
javascript = postprocessor(javascript)
return javascript
def _iter_paths(self):
yield from self.tpl.iter_paths('application/javascript')
if self.passthrough_extensions:
extensions_regex = re.compile(
r'\.(%s)$' %
'|'.join(map(re.escape, self.passthrough_extensions)))
yield from (path for path in self.tpl.iter_paths()
if extensions_regex.search(path))
def _copy_files(self, folder, paths=None):
if not paths:
paths = list(self._iter_paths())
include_paths = list()
for path in paths:
if path in ('!require.js', '!require-config.js'):
continue
header = ''
file = os.path.join(folder, path)
if self.tpl.mimetype(path) == 'application/javascript':
header = \
'//--{sep}--//\n' \
'// {path} //\n' \
'//--{sep}--//\n' \
.format(path=path, sep=('-' * len(path)))
content = self.tpl.render(path, apply_postprocessors=False)
base, ext = os.path.splitext(path)
include_paths.append(base)
file = os.path.join(folder, base + '.js')
else:
is_file, result = self.tpl.load(path)
if is_file:
content = open(result).read()
else:
content = result
os.makedirs(os.path.dirname(file), exist_ok=True)
open(file, 'w').write(header + content + '\n\n\n')
return include_paths
class RequireJsLoader(Loader):
def __init__(self, conf):
self.conf = conf
def iter_paths(self):
yield from ['!require.js', '!require-config.js']
def load(self, path):
if path == '!require.js':
file = os.path.join(os.path.dirname(__file__), 'require.js')
return True, file
elif path == '!require-config.js':
conf = open(self.conf.config_file).read()
return False, 'require.config(%s);\n' % conf.strip()
raise TemplateNotFound(path)
def is_valid(self, path):
return path in self.iter_paths()
class RequirejsAssets(WebassetsProxy):
def __init__(self, conf):
self.conf = conf
def iter_default_paths(self):
yield from ['!require.js', '!require-config.js']
if self.conf.webassets.tpl_autobundle:
yield from self.conf._iter_paths()
def validate_path(self, path):
return path in ['!require.js', '!require-config.js'] or \
path in self.conf._iter_paths()
def hash(self, path):
return None
def render(self, path):
try:
if self.conf.tpl.mimetype(path) == 'application/javascript':
return self.conf.tpl.render(path)
is_file, result = self.conf.tpl.load(path)
if is_file:
result = open(result).read()
return result
except TemplateNotFound:
from score.webassets import AssetNotFound
raise AssetNotFound('requirejs', path)
def mimetype(self, path):
return self.conf.tpl.mimetype(path)
def render_url(self, url):
return '<script src="%s"></script>' % (url,)
def create_bundle(self, paths):
return self.conf.create_bundle(paths)
def bundle_mimetype(self, paths):
return 'application/javascript'