Merge pull request #6765 from edx/andya/requirejs-in-lms

Add RequireJS to the LMS
This commit is contained in:
Andy Armstrong
2015-01-27 17:57:33 -05:00
23 changed files with 740 additions and 35 deletions

View File

@@ -1,4 +1,6 @@
from edxmako.shortcuts import render_to_string
from django.conf import settings as django_settings
from staticfiles.storage import staticfiles_storage
from pipeline.conf import settings
from pipeline.packager import Packager
@@ -79,3 +81,78 @@ def render_individual_js(package, paths, templates=None):
if templates:
tags.append(render_inline_js(package, templates))
return '\n'.join(tags)
def render_require_js_path_overrides(path_overrides): # pylint: disable=invalid-name
"""Render JavaScript to override default RequireJS paths.
The Django pipeline appends a hash to JavaScript files,
so if the JS asset isn't included in the bundle for the page,
we need to tell RequireJS where to look.
For example:
"js/vendor/jquery.min.js" --> "js/vendor/jquery.min.abcd1234"
We would then add a line in a <script> tag:
require.paths['jquery'] = 'js/vendor/jquery.min.abcd1234'
so that any reference to 'jquery' in a JavaScript module
will cause RequireJS to load '/static/js/vendor/jquery.min.abcd1234.js'
If running in DEBUG mode (as in devstack), the resolved JavaScript URLs
won't contain hashes, so the new paths will match the original paths.
Arguments:
path_overrides (dict): Mapping of RequireJS module names to
filesystem paths.
Returns:
unicode: The HTML of the <script> tag with the path overrides.
"""
# Render the <script> tag that overrides the paths defined in `require.paths`
# Note: We don't use a Mako template to render this because Mako apparently
# acquires a lock when loading templates, which can lead to a deadlock if
# this function is called from within another template.
html = ['<script type="text/javascript">']
# The rendered <script> tag with overrides should be included *after*
# the application's RequireJS config, which defines a `require` object.
# Just in case the `require` object hasn't been loaded, we create a default
# object. This will avoid a JavaScript error that might cause the rest of the
# page to fail; however, it may mean that these overrides won't be available
# to RequireJS.
html.extend([
'var require = require || {};',
'require.paths = require.paths || [];'
])
# Specify override the base URL to point to STATIC_URL
html.append(
"require.baseUrl = '{url}'".format(
url=django_settings.STATIC_URL
)
)
for module, url_path in path_overrides.iteritems():
# Calculate the full URL, including any hashes added to the filename by the pipeline.
# This will also include the base static URL (for example, "/static/") and the
# ".js" extension.
actual_url = staticfiles_storage.url(url_path)
# RequireJS assumes that every file it tries to load has a ".js" extension, so
# we need to remove ".js" from the module path.
# RequireJS also already has a base URL set to the base static URL, so we can remove that.
path = actual_url.replace('.js', '').replace(django_settings.STATIC_URL, '')
# Add the path override to the inline JavaScript.
html.append(
"require.paths['{module}'] = '{path}';".format(
module=module,
path=path
)
)
html.append('</script>')
return "\n".join(html)

View File

@@ -2,6 +2,7 @@
from staticfiles.storage import staticfiles_storage
from pipeline_mako import compressed_css, compressed_js
from django.utils.translation import get_language_bidi
from require.templatetags.require import require_module
%>
<%def name='url(file, raw=False)'><%
@@ -37,6 +38,10 @@ except:
%endif
</%def>
<%def name='require_module(module)'>
${require_module(module)}
</%def>
<%def name="include(path)"><%
from django.template.loaders.filesystem import _loader
source, template_path = _loader.load_template_source(path)

View File

@@ -0,0 +1,29 @@
"""Tests for rendering functions in the mako pipeline. """
from django.test import TestCase
from pipeline_mako import render_require_js_path_overrides
class RequireJSPathOverridesTest(TestCase):
"""Test RequireJS path overrides. """
OVERRIDES = {
'jquery': 'js/vendor/jquery.min.js',
'backbone': 'js/vendor/backbone-min.js',
'text': 'js/vendor/text.js'
}
OVERRIDES_JS = (
"<script type=\"text/javascript\">\n"
"var require = require || {};\n"
"require.paths = require.paths || [];\n"
"require.baseUrl = '/static/'\n"
"require.paths['jquery'] = 'js/vendor/jquery.min';\n"
"require.paths['text'] = 'js/vendor/text';\n"
"require.paths['backbone'] = 'js/vendor/backbone-min';\n"
"</script>"
)
def test_requirejs_path_overrides(self):
result = render_require_js_path_overrides(self.OVERRIDES)
self.assertEqual(result, self.OVERRIDES_JS)