From 389f4fcec994519e4a5f195440b3dd06c77a58c9 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 26 Jan 2018 15:55:14 -0500 Subject: [PATCH 01/29] Switch container.js to bundle using webpack --- cms/djangoapps/pipeline_js/js/xmodule.js | 60 ++++++++++ .../pipeline_js/templates/xmodule.js | 45 -------- cms/djangoapps/pipeline_js/urls.py | 10 -- cms/djangoapps/pipeline_js/utils.py | 18 +++ cms/djangoapps/pipeline_js/views.py | 44 -------- cms/static/cms/js/build.js | 1 - cms/static/js/pages/container.js | 8 ++ cms/static/js/pages/course.js | 4 +- cms/templates/container.html | 28 ++--- cms/urls.py | 1 - .../xmodule/xmodule/js/src/video/10_main.js | 15 +++ package-lock.json | 10 ++ package.json | 2 + webpack-config/file-lists.js | 5 +- webpack.common.config.js | 103 +++++++++++++----- 15 files changed, 210 insertions(+), 144 deletions(-) create mode 100644 cms/djangoapps/pipeline_js/js/xmodule.js delete mode 100644 cms/djangoapps/pipeline_js/templates/xmodule.js delete mode 100644 cms/djangoapps/pipeline_js/urls.py create mode 100644 cms/djangoapps/pipeline_js/utils.py delete mode 100644 cms/djangoapps/pipeline_js/views.py create mode 100644 cms/static/js/pages/container.js diff --git a/cms/djangoapps/pipeline_js/js/xmodule.js b/cms/djangoapps/pipeline_js/js/xmodule.js new file mode 100644 index 0000000000..a9c16a3363 --- /dev/null +++ b/cms/djangoapps/pipeline_js/js/xmodule.js @@ -0,0 +1,60 @@ +// This file is designed to load all the XModule Javascript files in one wad +// using requirejs. It is passed through the Mako template system, which +// populates the `urls` variable with a list of paths to XModule JS files. +// These files assume that several libraries are available and bound to +// variables in the global context, so we load those libraries with requirejs +// and attach them to the global context manually. +define( + [ + 'jquery', 'underscore', 'codemirror', 'tinymce', 'scriptjs', + 'jquery.tinymce', 'jquery.qtip', 'jquery.scrollTo', 'jquery.flot', + 'jquery.cookie', + 'utility' + ], + function($, _, CodeMirror, tinymce, $script) { + 'use strict'; + + window.$ = $; + window._ = _; + $script( + '//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js' + + '?config=TeX-MML-AM_SVG&delayStartupUntil=configured', + 'mathjax' + ); + window.CodeMirror = CodeMirror; + window.RequireJS = { + requirejs: {}, // This is never used by current xmodules + require: $script, // $script([deps], callback) acts approximately like the require function + define: define + }; + /** + * Loads all modules one-by-one in exact order. + * The module should be used until we'll use RequireJS for XModules. + * @param {Array} modules A list of urls. + * @return {jQuery Promise} + **/ + function requireQueue(modules) { + var deferred = $.Deferred(); + function loadScript(queue) { + $script.ready('mathjax', function() { + // Loads the next script if queue is not empty. + if (queue.length) { + $script([queue.shift()], function() { + loadScript(queue); + }); + } else { + deferred.resolve(); + } + }); + } + + loadScript(modules.concat()); + return deferred.promise(); + } + + if (!window.xmoduleUrls) { + throw Error('window.xmoduleUrls must be defined'); + } + return requireQueue(window.xmoduleUrls); + } +); diff --git a/cms/djangoapps/pipeline_js/templates/xmodule.js b/cms/djangoapps/pipeline_js/templates/xmodule.js deleted file mode 100644 index fb0c22d2c1..0000000000 --- a/cms/djangoapps/pipeline_js/templates/xmodule.js +++ /dev/null @@ -1,45 +0,0 @@ -## This file is designed to load all the XModule Javascript files in one wad -## using requirejs. It is passed through the Mako template system, which -## populates the `urls` variable with a list of paths to XModule JS files. -## These files assume that several libraries are available and bound to -## variables in the global context, so we load those libraries with requirejs -## and attach them to the global context manually. -define(["jquery", "underscore", "codemirror", "tinymce", - "jquery.tinymce", "jquery.qtip", "jquery.scrollTo", "jquery.flot", - "jquery.cookie", - "utility"], - function($, _, CodeMirror, tinymce) { - window.$ = $; - window._ = _; - require(['mathjax']); - window.CodeMirror = CodeMirror; - window.RequireJS = { - 'requirejs': requirejs, - 'require': require, - 'define': define - }; - /** - * Loads all modules one-by-one in exact order. - * The module should be used until we'll use RequireJS for XModules. - * @param {Array} modules A list of urls. - * @return {jQuery Promise} - **/ - var requireQueue = function(modules) { - var deferred = $.Deferred(); - var loadScript = function (queue) { - // Loads the next script if queue is not empty. - if (queue.length) { - require([queue.shift()], function() { - loadScript(queue); - }); - } else { - deferred.resolve(); - } - }; - - loadScript(modules.concat()); - return deferred.promise(); - }; - - return requireQueue(${urls}); -}); diff --git a/cms/djangoapps/pipeline_js/urls.py b/cms/djangoapps/pipeline_js/urls.py deleted file mode 100644 index 303573cfdb..0000000000 --- a/cms/djangoapps/pipeline_js/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -URL patterns for Javascript files used to load all of the XModule JS in one wad. -""" -from django.conf.urls import url -from pipeline_js.views import xmodule_js_files, requirejs_xmodule - -urlpatterns = [ - url(r'^files\.json$', xmodule_js_files, name='xmodule_js_files'), - url(r'^xmodule\.js$', requirejs_xmodule, name='requirejs_xmodule'), -] diff --git a/cms/djangoapps/pipeline_js/utils.py b/cms/djangoapps/pipeline_js/utils.py new file mode 100644 index 0000000000..91ce32d8af --- /dev/null +++ b/cms/djangoapps/pipeline_js/utils.py @@ -0,0 +1,18 @@ +""" +Utilities for returning XModule JS (used by requirejs) +""" + +from django.conf import settings +from django.contrib.staticfiles.storage import staticfiles_storage + + +def get_xmodule_urls(): + """ + Returns a list of the URLs to hit to grab all the XModule JS + """ + pipeline_js_settings = settings.PIPELINE_JS["module-js"] + if settings.DEBUG: + paths = [path.replace(".coffee", ".js") for path in pipeline_js_settings["source_filenames"]] + else: + paths = [pipeline_js_settings["output_filename"]] + return [staticfiles_storage.url(path) for path in paths] diff --git a/cms/djangoapps/pipeline_js/views.py b/cms/djangoapps/pipeline_js/views.py deleted file mode 100644 index bfb05d1dd3..0000000000 --- a/cms/djangoapps/pipeline_js/views.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Views for returning XModule JS (used by requirejs) -""" - -import json - -from django.conf import settings -from django.contrib.staticfiles.storage import staticfiles_storage -from django.http import HttpResponse - -from edxmako.shortcuts import render_to_response - - -def get_xmodule_urls(): - """ - Returns a list of the URLs to hit to grab all the XModule JS - """ - pipeline_js_settings = settings.PIPELINE_JS["module-js"] - if settings.DEBUG: - paths = [path.replace(".coffee", ".js") for path in pipeline_js_settings["source_filenames"]] - else: - paths = [pipeline_js_settings["output_filename"]] - return [staticfiles_storage.url(path) for path in paths] - - -def xmodule_js_files(request): # pylint: disable=unused-argument - """ - View function that returns XModule URLs as a JSON list; meant to be used - as an API - """ - urls = get_xmodule_urls() - return HttpResponse(json.dumps(urls), content_type="application/json") - - -def requirejs_xmodule(request): # pylint: disable=unused-argument - """ - View function that returns a requirejs-wrapped Javascript file that - loads all the XModule URLs; meant to be loaded via requireJS - """ - return render_to_response( - "xmodule.js", - {"urls": get_xmodule_urls()}, - content_type="text/javascript", - ) diff --git a/cms/static/cms/js/build.js b/cms/static/cms/js/build.js index 3f86a8c891..71976d63be 100644 --- a/cms/static/cms/js/build.js +++ b/cms/static/cms/js/build.js @@ -19,7 +19,6 @@ modules: getModulesList([ 'js/factories/asset_index', 'js/factories/base', - 'js/factories/container', 'js/factories/course_create_rerun', 'js/factories/course_info', 'js/factories/edit_tabs', diff --git a/cms/static/js/pages/container.js b/cms/static/js/pages/container.js new file mode 100644 index 0000000000..79937020db --- /dev/null +++ b/cms/static/js/pages/container.js @@ -0,0 +1,8 @@ +define( + ['js/factories/container', 'common/js/utils/page_factory', 'js/factories/base', 'js/pages/course'], + function(ContainerFactory, invokePageFactory) { + 'use strict'; + invokePageFactory('ContainerFactory', ContainerFactory); + } +); + diff --git a/cms/static/js/pages/course.js b/cms/static/js/pages/course.js index cf0ca8d5ef..5334fedbde 100644 --- a/cms/static/js/pages/course.js +++ b/cms/static/js/pages/course.js @@ -1,6 +1,8 @@ define( ['js/models/course'], function(ContextCourse) { - window.course = new ContextCourse(window.pageFactoryArguments.ContextCourse); + 'use strict'; + window.course = new ContextCourse(window.pageFactoryArguments.ContextCourse[0]); } ); + diff --git a/cms/templates/container.html b/cms/templates/container.html index c88e196753..0d0a4095e0 100644 --- a/cms/templates/container.html +++ b/cms/templates/container.html @@ -42,19 +42,21 @@ from openedx.core.djangolib.markup import HTML, Text % endif -<%block name="requirejs"> - require(["js/factories/container"], function(ContainerFactory) { - ContainerFactory( - ${component_templates | n, dump_js_escaped_json}, - ${xblock_info | n, dump_js_escaped_json}, - "${action | n, js_escaped_string}", - { - isUnitPage: ${is_unit_page | n, dump_js_escaped_json}, - canEdit: true, - outlineURL: "${outline_url | n, js_escaped_string}" - } - ); - }); +<%block name="page_bundle"> + + <%static:invoke_page_bundle page_name="js/pages/container" class_name="ContainerFactory"> + ${component_templates | n, dump_js_escaped_json}, + ${xblock_info | n, dump_js_escaped_json}, + "${action | n, js_escaped_string}", + { + isUnitPage: ${is_unit_page | n, dump_js_escaped_json}, + canEdit: true, + outlineURL: "${outline_url | n, js_escaped_string}" + } + <%block name="content"> diff --git a/cms/urls.py b/cms/urls.py index 44d9282a24..03423b771b 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -52,7 +52,6 @@ urlpatterns = [ # noop to squelch ajax errors url(r'^event$', contentstore.views.event, name='event'), - url(r'^xmodule/', include('pipeline_js.urls')), url(r'^heartbeat', include('openedx.core.djangoapps.heartbeat.urls')), url(r'^user_api/', include('openedx.core.djangoapps.user_api.legacy_urls')), url(r'^i18n/', include('django.conf.urls.i18n')), diff --git a/common/lib/xmodule/xmodule/js/src/video/10_main.js b/common/lib/xmodule/xmodule/js/src/video/10_main.js index 93b5890d9c..822a7ace77 100644 --- a/common/lib/xmodule/xmodule/js/src/video/10_main.js +++ b/common/lib/xmodule/xmodule/js/src/video/10_main.js @@ -1,6 +1,8 @@ /* globals _ */ +/* RequireJS */ (function(require, $) { 'use strict'; + // In the case when the Video constructor will be called before RequireJS finishes loading all of the Video // dependencies, we will have a mock function that will collect all the elements that must be initialized as // Video elements. @@ -34,6 +36,10 @@ // Main module. require( + /* End RequireJS */ + /* Webpack + define( + /* End Webpack */ [ 'video/00_video_storage.js', 'video/01_initialize.js', @@ -67,8 +73,13 @@ VideoBumper, VideoSaveStatePlugin, VideoEventsPlugin, VideoEventsBumperPlugin, VideoPoster, VideoCompletionHandler, VideoCommands, VideoContextMenu ) { + /* RequireJS */ var youtubeXhr = null, oldVideo = window.Video; + /* End RequireJS */ + /* Webpack + var youtubeXhr = null; + /* End Webpack */ window.Video = function(element) { var el = $(element).find('.video'), @@ -170,9 +181,13 @@ window.Video.loadYouTubeIFrameAPI = initialize.prototype.loadYouTubeIFrameAPI; + /* RequireJS */ // Invoke the mock Video constructor so that the elements stored within it can be processed by the real // `window.Video` constructor. oldVideo(null, true); + /* End RequireJS */ } ); +/* RequireJS */ }(window.RequireJS.require, window.jQuery)); +/* End RequireJS */ diff --git a/package-lock.json b/package-lock.json index ef401b3a3e..95235946f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8631,6 +8631,11 @@ "ajv": "5.5.2" } }, + "scriptjs": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/scriptjs/-/scriptjs-2.5.8.tgz", + "integrity": "sha1-0MQ5VcLmutM7bk7fe1O4llqnyl8=" + }, "scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", @@ -9994,6 +9999,11 @@ "setimmediate": "1.0.5" } }, + "tinymce": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.7.6.tgz", + "integrity": "sha512-mdbeMvCOO0ws0HXiZO9L8mDGFA9/VmDHltlt+/9qV0Fl1EWKWoPIRURHFtiFYuLKo9yjYUvYucwiZ1XfCT/91Q==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index 03292c3d6a..fb26ddfdbe 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,11 @@ "requirejs": "2.3.5", "rtlcss": "2.2.1", "sass-loader": "6.0.6", + "scriptjs": "2.5.8", "string-replace-webpack-plugin": "0.1.3", "style-loader": "0.18.2", "svg-inline-loader": "0.8.0", + "tinymce": "4.7.6", "uglify-js": "2.7.0", "underscore": "1.8.3", "underscore.string": "3.3.4", diff --git a/webpack-config/file-lists.js b/webpack-config/file-lists.js index 63a77a4a8e..0836c37095 100644 --- a/webpack-config/file-lists.js +++ b/webpack-config/file-lists.js @@ -6,6 +6,7 @@ module.exports = { path.resolve(__dirname, '../common/static/common/js/components/views/feedback_notification.js'), path.resolve(__dirname, '../common/static/common/js/components/views/feedback_prompt.js'), path.resolve(__dirname, '../common/static/common/js/components/views/feedback.js'), + path.resolve(__dirname, '../common/static/common/js/components/views/feedback_alert.js'), path.resolve(__dirname, '../common/static/common/js/components/views/paging_footer.js'), path.resolve(__dirname, '../cms/static/js/views/paging.js'), path.resolve(__dirname, '../common/static/common/js/components/utils/view_utils.js') @@ -104,6 +105,8 @@ module.exports = { 'openedx/features/learner_profile/static/learner_profile/js/views/learner_profile_fields.js' ), path.resolve(__dirname, '../openedx/features/learner_profile/static/learner_profile/js/views/section_two_tab.js'), - path.resolve(__dirname, '../openedx/features/learner_profile/static/learner_profile/js/views/share_modal_view.js') + path.resolve(__dirname, '../openedx/features/learner_profile/static/learner_profile/js/views/share_modal_view.js'), + path.resolve(__dirname, '../node_modules/edx-ui-toolkit/src/js/dropdown-menu/dropdown-menu-view.js'), + path.resolve(__dirname, '../node_modules/edx-ui-toolkit/src/js/breadcrumbs/breadcrumbs-view.js') ] }; diff --git a/webpack.common.config.js b/webpack.common.config.js index 4e2f98921f..be49b94cb4 100644 --- a/webpack.common.config.js +++ b/webpack.common.config.js @@ -9,8 +9,15 @@ var StringReplace = require('string-replace-webpack-plugin'); var files = require('./webpack-config/file-lists.js'); -var defineHeader = /\(function ?\(define(, require)?\) ?\{/; -var defineFooter = /\}\)\.call\(this, define \|\| RequireJS\.define(, require \|\| RequireJS\.require)?\);/; +var filesWithRequireJSBlocks = [ + path.resolve(__dirname, 'common/static/common/js/components/utils/view_utils.js'), +]; + +var defineHeader = /\(function ?\(((define|require|requirejs|\$)(, )?)+\) ?\{/; +var defineCallFooter = /\}\)\.call\(this, ((define|require)( \|\| RequireJS\.(define|require))?(, )?)+?\);/; +var defineDirectFooter = /\}\(((window\.)?(RequireJS\.)?(requirejs|define|require|jQuery)(, )?)+\)\);/; +var defineFancyFooter = /\}\).call\(\s*this(\s|.)*define(\s|.)*\);/; +var defineFooter = new RegExp('(' + defineCallFooter.source + ')|(' + defineDirectFooter.source + ')|(' + defineFancyFooter.source + ')', 'm'); module.exports = { context: __dirname, @@ -21,6 +28,7 @@ module.exports = { CourseOrLibraryListing: './cms/static/js/features_jsx/studio/CourseOrLibraryListing.jsx', 'js/pages/login': './cms/static/js/pages/login.js', 'js/pages/textbooks': './cms/static/js/pages/textbooks.js', + 'js/pages/container': './cms/static/js/pages/container.js', 'js/sock': './cms/static/js/sock.js', // LMS @@ -80,7 +88,8 @@ module.exports = { $: 'jquery', jQuery: 'jquery', 'window.jQuery': 'jquery', - Popper: 'popper.js' // used by bootstrap + Popper: 'popper.js', // used by bootstrap + CodeMirror: 'codemirror', }), // Note: Until karma-webpack releases v3, it doesn't play well with @@ -105,11 +114,11 @@ module.exports = { // https://github.com/webpack/webpack/issues/304#issuecomment-272150177 // (I've tried every other suggestion solution on that page, this // was the only one that worked.) - /\/sinon\.js/ + /\/sinon\.js|codemirror-compressed\.js/ ], rules: [ { - test: files.namespacedRequire, + test: files.namespacedRequire.concat(files.textBangUnderscore, filesWithRequireJSBlocks), loader: StringReplace.replace( ['babel-loader'], { @@ -121,20 +130,24 @@ module.exports = { { pattern: defineFooter, replacement: function() { return ''; } - } - ] - } - ) - }, - { - test: files.textBangUnderscore, - loader: StringReplace.replace( - ['babel-loader'], - { - replacements: [ + }, { - pattern: /text!(.*\.underscore)/, + pattern: /(\/\* RequireJS) \*\//g, replacement: function(match, p1) { return p1; } + }, + { + pattern: /\/\* Webpack/g, + replacement: function(match) { return match + ' */'; } + }, + { + pattern: /text!(.*?\.underscore)/g, + replacement: function(match, p1) { return p1; } + }, + { + pattern: /RequireJS.require/g, + replacement: function() { + return 'require'; + } } ] } @@ -145,7 +158,8 @@ module.exports = { exclude: [ /node_modules/, files.namespacedRequire, - files.textBangUnderscore + files.textBangUnderscore, + filesWithRequireJSBlocks ], use: 'babel-loader' }, @@ -212,6 +226,22 @@ module.exports = { { test: /\.svg$/, loader: 'svg-inline-loader' + }, + { + test: /xblock\/core/, + loader: 'exports-loader?this.XBlock!imports-loader?jquery,jquery.immediateDescendents' + }, + { + test: /xblock\/runtime.v1/, + loader: 'exports-loader?XBlock!imports-loader?XBlock=xblock/core' + }, + { + test: /codemirror/, + loader: 'exports-loader?window.CodeMirror' + }, + { + test: /tinymce/, + loader: 'imports-loader?this=>window' } ] }, @@ -220,9 +250,19 @@ module.exports = { extensions: ['.js', '.jsx', '.json'], alias: { AjaxPrefix: 'ajax_prefix', + accessibility: 'accessibility_tools', + codemirror: 'codemirror-compressed', + datepair: 'timepicker/datepair', 'edx-ui-toolkit': 'edx-ui-toolkit/src/', // @TODO: some paths in toolkit are not valid relative paths - 'jquery.ui': 'jQuery-File-Upload/js/vendor/jquery.ui.widget.js', - jquery: 'jquery/src/jquery', // Use the non-dist form of jQuery for better debugging + optimization + ieshim: 'ie_shim', + jquery: 'jquery/src/jquery', // Use the non-diqst form of jQuery for better debugging + optimization + 'jquery.flot': 'flot/jquery.flot.min', + 'jquery.ui': 'jquery-ui.min', + 'jquery.tinymce': 'tinymce/jquery.tinymce.min', + 'jquery.inputnumber': 'html5-input-polyfills/number-polyfill', + 'jquery.qtip': 'jquery.qtip.min', + 'jquery.smoothScroll': 'jquery.smooth-scroll.min', + 'jquery.timepicker': 'timepicker/jquery.timepicker', 'backbone.associations': 'backbone-associations/backbone-associations-min', // See sinon/webpack interaction weirdness: @@ -230,19 +270,24 @@ module.exports = { // (I've tried every other suggestion solution on that page, this // was the only one that worked.) sinon: __dirname + '/node_modules/sinon/pkg/sinon.js', - 'jquery.smoothScroll': 'jquery.smooth-scroll.min', - 'jquery.timepicker': 'timepicker/jquery.timepicker', - datepair: 'timepicker/datepair', - accessibility: 'accessibility_tools', - ieshim: 'ie_shim' + WordCloudMain: 'xmodule/assets/word_cloud/public/js/word_cloud_main', }, modules: [ - 'node_modules', + 'cms/djangoapps/pipeline_js/js', 'cms/static', + 'cms/static/cms/js', + 'common/lib/xmodule', + 'common/lib/xmodule/xmodule/js/src', 'common/static', + 'common/static/coffee/src', + 'common/static/common/js', + 'common/static/common/js/vendor/', 'common/static/js/src', 'common/static/js/vendor/', - 'common/static/js/vendor/jQuery-File-Upload/js/' + 'common/static/js/vendor/jQuery-File-Upload/js/', + 'common/static/js/vendor/tinymce/js/tinymce', + 'node_modules', + 'common/static/xmodule', ] }, @@ -259,7 +304,9 @@ module.exports = { jquery: 'jQuery', logger: 'Logger', underscore: '_', - URI: 'URI' + URI: 'URI', + XModule: 'XModule', + XBlockToXModuleShim: 'XBlockToXModuleShim', }, watchOptions: { From 8a2201da9377ddb527a92cbfb45e07e6e591d8a7 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 14 Feb 2018 13:12:02 -0500 Subject: [PATCH 02/29] Include the error message when an XBlock fails to load javascript --- cms/static/js/views/xblock.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/static/js/views/xblock.js b/cms/static/js/views/xblock.js index 3a9f7be126..b6e5e83c4a 100644 --- a/cms/static/js/views/xblock.js +++ b/cms/static/js/views/xblock.js @@ -63,7 +63,7 @@ define(['jquery', 'underscore', 'common/js/components/utils/view_utils', 'js/vie successCallback(xblock); } } catch (e) { - console.error(e.stack); + console.error(e, e.stack); // Add 'xblock-initialization-failed' class to every xblock self.$('.xblock').addClass('xblock-initialization-failed'); From f7da1ba62107f45f87064a132785f3d45bf0e087 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 14 Feb 2018 13:13:29 -0500 Subject: [PATCH 03/29] Use conditional blocks for webpack specific code edits --- .../common/js/components/utils/view_utils.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/common/static/common/js/components/utils/view_utils.js b/common/static/common/js/components/utils/view_utils.js index fd48b835bf..13a0d8b0f8 100644 --- a/common/static/common/js/components/utils/view_utils.js +++ b/common/static/common/js/components/utils/view_utils.js @@ -4,9 +4,17 @@ (function(define, require) { 'use strict'; + /* RequireJS */ define(['jquery', 'underscore', 'gettext', 'common/js/components/views/feedback_notification', 'common/js/components/views/feedback_prompt'], function($, _, gettext, NotificationView, PromptView) { + /* End RequireJS */ + /* Webpack + define(['jquery', 'underscore', 'gettext', 'common/js/components/views/feedback_notification', + 'common/js/components/views/feedback_prompt', 'scriptjs'], + function($, _, gettext, NotificationView, PromptView, $script) { + /* End Webpack */ + var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation, runOperationShowingMessage, withDisabledElement, disableElementWhileRunning, getScrollOffset, setScrollOffset, setScrollTop, redirect, reload, hasChangedAttributes, @@ -259,6 +267,7 @@ */ loadJavaScript = function(url) { var deferred = $.Deferred(); + /* RequireJS */ require([url], function() { deferred.resolve(); @@ -266,6 +275,12 @@ function() { deferred.reject(); }); + /* End RequireJS */ + /* Webpack + $script(url, url, function () { + deferred.resolve(); + }); + /* End Webpack */ return deferred.promise(); }; From 94557900f89e42517595473cd54956b44d9fdc35 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Thu, 15 Feb 2018 11:54:25 -0500 Subject: [PATCH 04/29] Remove use of 'with' keyword from schematic.js --- .../xmodule/xmodule/js/src/capa/schematic.js | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/capa/schematic.js b/common/lib/xmodule/xmodule/js/src/capa/schematic.js index e1059a9fa7..b503f59966 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/schematic.js +++ b/common/lib/xmodule/xmodule/js/src/capa/schematic.js @@ -1888,27 +1888,25 @@ var cktsim = (function() { } var vgs = this.type_sign * ckt.get_two_terminal(this.g, this.s, soln); var vgst = vgs - this.vt; - with (this) { - var gmgs,ids,gds; - if (vgst > 0.0 ) { // vgst < 0, transistor off, no subthreshold here. - if (vgst < vds) { /* Saturation. */ - gmgs = beta * (1 + (lambda * vds)) * vgst; - ids = type_sign * 0.5 * gmgs * vgst; - gds = 0.5 * beta * vgst * vgst * lambda; - } else { /* Linear region */ - gmgs = beta * (1 + lambda * vds); - ids = type_sign * gmgs * vds * (vgst - 0.50 * vds); - gds = gmgs * (vgst - vds) + beta * lambda * vds * (vgst - 0.5 * vds); - gmgs *= vds; - } - ckt.add_to_rhs(d,-ids,rhs); // current flows into the drain - ckt.add_to_rhs(s, ids,rhs); // and out the source - ckt.add_conductance(d,s,gds); - ckt.add_to_G(s,s, gmgs); - ckt.add_to_G(d,s,-gmgs); - ckt.add_to_G(d,g, gmgs); - ckt.add_to_G(s,g,-gmgs); + var gmgs,ids,gds; + if (vgst > 0.0 ) { // vgst < 0, transistor off, no subthreshold here. + if (vgst < vds) { /* Saturation. */ + gmgs = this.beta * (1 + (this.lambda * vds)) * vgst; + ids = this.type_sign * 0.5 * gmgs * vgst; + gds = 0.5 * this.beta * vgst * vgst * this.lambda; + } else { /* Linear region */ + gmgs = this.beta * (1 + this.lambda * vds); + ids = this.type_sign * gmgs * vds * (vgst - 0.50 * vds); + gds = gmgs * (vgst - vds) + this.beta * this.lambda * vds * (vgst - 0.5 * vds); + gmgs *= vds; } + ckt.add_to_rhs(this.d,-ids,rhs); // current flows into the drain + ckt.add_to_rhs(this.s, ids,rhs); // and out the source + ckt.add_conductance(this.d,this.s,gds); + ckt.add_to_G(this.s,this.s, gmgs); + ckt.add_to_G(this.d,this.s,-gmgs); + ckt.add_to_G(this.d,this.g, gmgs); + ckt.add_to_G(this.s,this.g,-gmgs); } } From d08e199f79ea61e34c02d64750ff5262d92563b9 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Thu, 15 Feb 2018 21:56:25 -0500 Subject: [PATCH 05/29] Load XBlocks with webpack rather than RequireJS --- .../contentstore/features/common.py | 1 - cms/djangoapps/pipeline_js/js/xmodule.js | 8 +- cms/envs/acceptance.py | 4 - cms/envs/test.py | 2 - cms/static/karma_cms_webpack.conf.js | 55 +++++++++++ cms/templates/container.html | 26 +++-- cms/templates/edit-tabs.html | 4 +- cms/templates/library.html | 4 +- cms/templates/studio_xblock_wrapper.html | 5 + common/lib/conftest.py | 13 +++ .../public/js/vertical_student_view.js | 98 +++++++++---------- .../xmodule/xmodule/js/fixtures/video.html | 9 +- .../js/fixtures/video_autoadvance.html | 1 + .../fixtures/video_autoadvance_disabled.html | 1 + .../xmodule/js/fixtures/video_hls.html | 3 + .../xmodule/js/fixtures/video_html5.html | 3 + .../js/fixtures/video_no_captions.html | 3 + .../js/fixtures/video_with_bumper.html | 1 + .../js/fixtures/video_yt_multiple.html | 1 + .../xmodule/js/karma_runner_webpack.js | 82 ++++++++++++++++ .../xmodule/xmodule/js/karma_xmodule.conf.js | 14 ++- .../xmodule/js/karma_xmodule_webpack.conf.js | 45 +++++++++ common/lib/xmodule/xmodule/js/spec/helper.js | 13 --- .../lib/xmodule/xmodule/js/spec/time_spec.js | 89 ++++++++--------- .../js/spec/video/async_process_spec.js | 2 +- .../xmodule/js/spec/video/general_spec.js | 20 ++-- .../xmodule/js/spec/video/html5_video_spec.js | 2 + .../xmodule/js/spec/video/initialize_spec.js | 4 +- .../xmodule/js/spec/video/iterator_spec.js | 2 +- .../xmodule/js/spec/video/resizer_spec.js | 4 +- .../xmodule/js/spec/video/sjson_spec.js | 2 +- .../js/spec/video/video_events_plugin_spec.js | 2 + .../js/spec/video/video_player_spec.js | 4 +- .../spec/video/video_progress_slider_spec.js | 4 +- .../video/video_save_state_plugin_spec.js | 8 +- .../js/spec/video/video_storage_spec.js | 4 +- .../xmodule/xmodule/js/spec/video_helper.js | 16 +++ .../xmodule/xmodule/js/src/capa/display.js | 4 +- .../lib/xmodule/xmodule/js/src/poll/poll.js | 16 ++- .../xmodule/js/src/sequence/display.js | 4 +- common/lib/xmodule/xmodule/js/src/time.js | 77 +++++++-------- .../xmodule/js/src/video/03_video_player.js | 4 +- .../xmodule/js/src/video/04_video_control.js | 4 +- .../js/src/video/09_save_state_plugin.js | 2 +- .../xmodule/js/src/video/09_video_caption.js | 5 +- common/lib/xmodule/xmodule/static_content.py | 57 ++++++++--- .../lib/xmodule/xmodule/tests/test_content.py | 6 +- common/lib/xmodule/xmodule/vertical_block.py | 4 +- .../xmodule/video_module/video_module.py | 34 ------- .../lib/xmodule/xmodule/word_cloud_module.py | 5 - common/lib/xmodule/xmodule/x_module.py | 7 ++ common/templates/xblock_wrapper.html | 6 ++ common/templates/xmodule_shim.html | 4 + .../tests/video/test_video_module.py | 11 ++- conftest.py | 4 + lms/envs/test.py | 2 - lms/static/js/views/message_banner.js | 2 +- lms/templates/main.html | 4 - openedx/core/lib/xblock_utils/__init__.py | 3 + openedx/tests/util/webpack_loader/__init__.py | 0 .../webpack_loader/templatetags/__init__.py | 0 .../templatetags/webpack_loader.py | 16 --- pavelib/assets.py | 5 + pavelib/js_test.py | 1 + pavelib/utils/envs.py | 2 + webpack-config/file-lists.js | 12 ++- webpack.common.config.js | 57 +++++++++-- 67 files changed, 595 insertions(+), 322 deletions(-) create mode 100644 cms/static/karma_cms_webpack.conf.js create mode 100644 common/lib/xmodule/xmodule/js/karma_runner_webpack.js create mode 100644 common/lib/xmodule/xmodule/js/karma_xmodule_webpack.conf.js create mode 100644 common/lib/xmodule/xmodule/js/spec/video_helper.js create mode 100644 common/templates/xmodule_shim.html delete mode 100644 openedx/tests/util/webpack_loader/__init__.py delete mode 100644 openedx/tests/util/webpack_loader/templatetags/__init__.py delete mode 100644 openedx/tests/util/webpack_loader/templatetags/webpack_loader.py diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 70ce70d0ae..686c8ec156 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -247,7 +247,6 @@ def create_unit_from_course_outline(): world.css_click(selector) world.wait_for_mathjax() - world.wait_for_xmodule() world.wait_for_loading() assert world.is_css_present('ul.new-component-type') diff --git a/cms/djangoapps/pipeline_js/js/xmodule.js b/cms/djangoapps/pipeline_js/js/xmodule.js index a9c16a3363..881b6482a1 100644 --- a/cms/djangoapps/pipeline_js/js/xmodule.js +++ b/cms/djangoapps/pipeline_js/js/xmodule.js @@ -52,9 +52,9 @@ define( return deferred.promise(); } - if (!window.xmoduleUrls) { - throw Error('window.xmoduleUrls must be defined'); - } - return requireQueue(window.xmoduleUrls); + // if (!window.xmoduleUrls) { + // throw Error('window.xmoduleUrls must be defined'); + // } + return requireQueue([]); } ); diff --git a/cms/envs/acceptance.py b/cms/envs/acceptance.py index 514e4a7d9d..766496f486 100644 --- a/cms/envs/acceptance.py +++ b/cms/envs/acceptance.py @@ -110,10 +110,6 @@ FEATURES['ENABLE_DISCUSSION_SERVICE'] = False # We do not yet understand why this occurs. Setting this to true is a stopgap measure USE_I18N = True -# Override the test stub webpack_loader that is installed in test.py. -INSTALLED_APPS = [app for app in INSTALLED_APPS if app != 'openedx.tests.util.webpack_loader'] -INSTALLED_APPS.append('webpack_loader') - # Include the lettuce app for acceptance testing, including the 'harvest' django-admin command # django.contrib.staticfiles used to be loaded by lettuce, now we must add it ourselves # django.contrib.staticfiles is not added to lms as there is a ^/static$ route built in to the app diff --git a/cms/envs/test.py b/cms/envs/test.py index 6a1e05f7ce..ed891b320b 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -54,8 +54,6 @@ TEST_ROOT = path('test_root') # Want static files in the same dir for running on jenkins. STATIC_ROOT = TEST_ROOT / "staticfiles" -INSTALLED_APPS = [app for app in INSTALLED_APPS if app != 'webpack_loader'] -INSTALLED_APPS.append('openedx.tests.util.webpack_loader') WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json" GITHUB_REPO_ROOT = TEST_ROOT / "data" diff --git a/cms/static/karma_cms_webpack.conf.js b/cms/static/karma_cms_webpack.conf.js new file mode 100644 index 0000000000..c2c4ce0126 --- /dev/null +++ b/cms/static/karma_cms_webpack.conf.js @@ -0,0 +1,55 @@ +/* eslint-env node */ + +// Karma config for cms suite. +// Docs and troubleshooting tips in common/static/common/js/karma.common.conf.js + +'use strict'; +var path = require('path'); +var configModule = require(path.join(__dirname, '../../common/static/common/js/karma.common.conf.js')); + +var options = { + + includeCommonFiles: true, + + libraryFiles: [], + + libraryFilesToInclude: [ + ], + + // Make sure the patterns in sourceFiles and specFiles do not match the same file. + // Otherwise Istanbul which is used for coverage tracking will cause tests to not run. + sourceFiles: [], + // {pattern: 'js/factories/login.js', webpack: true}, + // {pattern: 'js/factories/xblock_validation.js', webpack: true}, + // {pattern: 'js/factories/container.js', webpack: true}, + // {pattern: 'js/factories/context_course.js', webpack: true}, + // {pattern: 'js/factories/edit_tabs.js', webpack: true}, + // {pattern: 'js/factories/library.js', webpack: true}, + // {pattern: 'js/factories/textbooks.js', webpack: true}, + // ], + + // All spec files should be imported in main_webpack.js, rather than being listed here + specFiles: [], + + fixtureFiles: [ + {pattern: '../templates/js/**/*.underscore'}, + {pattern: 'templates/**/*.underscore'} + ], + + runFiles: [ + {pattern: 'cms/js/spec/main_webpack.js', webpack: true}, + {pattern: 'jasmine.cms.conf.js', included: true} + ], + + preprocessors: {} +}; + +options.runFiles + .filter(function(file) { return file.webpack; }) + .forEach(function(file) { + options.preprocessors[file.pattern] = ['webpack']; + }); + +module.exports = function(config) { + configModule.configure(config, options); +}; diff --git a/cms/templates/container.html b/cms/templates/container.html index 0d0a4095e0..b2ecac3616 100644 --- a/cms/templates/container.html +++ b/cms/templates/container.html @@ -43,20 +43,18 @@ from openedx.core.djangolib.markup import HTML, Text <%block name="page_bundle"> - - <%static:invoke_page_bundle page_name="js/pages/container" class_name="ContainerFactory"> - ${component_templates | n, dump_js_escaped_json}, - ${xblock_info | n, dump_js_escaped_json}, - "${action | n, js_escaped_string}", - { - isUnitPage: ${is_unit_page | n, dump_js_escaped_json}, - canEdit: true, - outlineURL: "${outline_url | n, js_escaped_string}" - } - + <%static:webpack entry="js/factories/container"> + ContainerFactory( + ${component_templates | n, dump_js_escaped_json}, + ${xblock_info | n, dump_js_escaped_json}, + "${action | n, js_escaped_string}", + { + isUnitPage: ${is_unit_page | n, dump_js_escaped_json}, + canEdit: true, + outlineURL: "${outline_url | n, js_escaped_string}" + } + ); + <%block name="content"> diff --git a/cms/templates/edit-tabs.html b/cms/templates/edit-tabs.html index 9541f07c35..07500296dc 100644 --- a/cms/templates/edit-tabs.html +++ b/cms/templates/edit-tabs.html @@ -19,8 +19,8 @@ % endfor -<%block name="requirejs"> - require(["js/factories/edit_tabs"], function (EditTabsFactory) { +<%block name="page_bundle"> + <%static:webpack entry="js/factories/edit_tabs"> EditTabsFactory("${context_course.location | n, js_escaped_string}", "${reverse('tabs_handler', kwargs={'course_key_string': context_course.id})}"); }); diff --git a/cms/templates/library.html b/cms/templates/library.html index 64f91662db..b293697bfd 100644 --- a/cms/templates/library.html +++ b/cms/templates/library.html @@ -25,8 +25,8 @@ from openedx.core.djangolib.markup import HTML, Text -<%block name="requirejs"> - require(["js/factories/library"], function(LibraryFactory) { +<%block name="page_bundle"> + <%static:webpack entry="js/factories/library"> LibraryFactory( ${component_templates | n, dump_js_escaped_json}, ${xblock_info | n, dump_js_escaped_json}, diff --git a/cms/templates/studio_xblock_wrapper.html b/cms/templates/studio_xblock_wrapper.html index 66afb2aee9..18cda07d7a 100644 --- a/cms/templates/studio_xblock_wrapper.html +++ b/cms/templates/studio_xblock_wrapper.html @@ -7,6 +7,7 @@ from lms.lib.utils import is_unit from openedx.core.djangolib.js_utils import ( dump_js_escaped_json, js_escaped_string ) +from xmodule.x_module import XModule, XModuleDescriptor %> <% xblock_url = xblock_studio_url(xblock) @@ -38,6 +39,10 @@ block_is_unit = is_unit(xblock) }); +% if isinstance(xblock, (XModule, XModuleDescriptor)): + <%static:webpack entry="${getattr(xblock.__class__, 'unmixed_class', xblock.__class__).__name__}"/> +% endif + % if not is_root: % if is_reorderable:
  • diff --git a/common/lib/conftest.py b/common/lib/conftest.py index 54d4a8ecdf..39c59426f7 100644 --- a/common/lib/conftest.py +++ b/common/lib/conftest.py @@ -3,3 +3,16 @@ # Patch the xml libs before anything else. from safe_lxml import defuse_xml_libs defuse_xml_libs() + +import pytest + +@pytest.fixture(autouse=True) +def no_webpack_loader(monkeypatch): + monkeypatch.setattr( + "webpack_loader.templatetags.webpack_loader.render_bundle", + lambda entry, extension=None, config='DEFAULT', attrs='': '' + ) + monkeypatch.setattr( + "webpack_loader.utils.get_as_tags", + lambda entry, extension=None, config='DEFAULT', attrs='': [] + ) diff --git a/common/lib/xmodule/xmodule/assets/vertical/public/js/vertical_student_view.js b/common/lib/xmodule/xmodule/assets/vertical/public/js/vertical_student_view.js index ca2dbc5240..a02ca250d2 100644 --- a/common/lib/xmodule/xmodule/assets/vertical/public/js/vertical_student_view.js +++ b/common/lib/xmodule/xmodule/assets/vertical/public/js/vertical_student_view.js @@ -1,7 +1,6 @@ /* JavaScript for Vertical Student View. */ /* global Set:false */ // false means do not assign to Set -/* global ViewedEventTracker:false */ // The vertical marks blocks complete if they are completable by viewing. The // global variable SEEN_COMPLETABLES tracks blocks between separate loads of @@ -9,62 +8,61 @@ // navigates back within a given sequential) to protect against duplicate calls // to the server. +import BookmarkButton from 'course_bookmarks/js/views/bookmark_button'; +import {ViewedEventTracker} from '../../../../../../../../lms/static/completion/js/ViewedEvent.js'; var SEEN_COMPLETABLES = new Set(); window.VerticalStudentView = function(runtime, element) { 'use strict'; - RequireJS.require(['course_bookmarks/js/views/bookmark_button'], function(BookmarkButton) { - var $element = $(element); - var $bookmarkButtonElement = $element.find('.bookmark-button'); - return new BookmarkButton({ - el: $bookmarkButtonElement, - bookmarkId: $bookmarkButtonElement.data('bookmarkId'), - usageId: $element.data('usageId'), - bookmarked: $element.parent('#seq_content').data('bookmarked'), - apiUrl: $bookmarkButtonElement.data('bookmarksApiUrl') - }); + var $element = $(element); + var $bookmarkButtonElement = $element.find('.bookmark-button'); + return new BookmarkButton({ + el: $bookmarkButtonElement, + bookmarkId: $bookmarkButtonElement.data('bookmarkId'), + usageId: $element.data('usageId'), + bookmarked: $element.parent('#seq_content').data('bookmarked'), + apiUrl: $bookmarkButtonElement.data('bookmarksApiUrl') }); - RequireJS.require(['bundles/ViewedEvent'], function() { - var tracker, vertical, viewedAfter; - var completableBlocks = []; - var vertModDivs = element.getElementsByClassName('vert-mod'); - if (vertModDivs.length === 0) { - return; - } - vertical = vertModDivs[0]; - $(element).find('.vert').each(function(idx, block) { - if (block.dataset.completableByViewing !== undefined) { - completableBlocks.push(block); - } - }); - if (completableBlocks.length > 0) { - viewedAfter = parseInt(vertical.dataset.completionDelayMs, 10); - if (!(viewedAfter >= 0)) { - // parseInt will return NaN if it fails to parse, which is not >= 0. - viewedAfter = 5000; - } - tracker = new ViewedEventTracker(completableBlocks, viewedAfter); - tracker.addHandler(function(block, event) { - var blockKey = block.dataset.id; - if (blockKey && !SEEN_COMPLETABLES.has(blockKey)) { - if (event.elementHasBeenViewed) { - $.ajax({ - type: 'POST', - url: runtime.handlerUrl(element, 'publish_completion'), - data: JSON.stringify({ - block_key: blockKey, - completion: 1.0 - }) - }).then( - function() { - SEEN_COMPLETABLES.add(blockKey); - } - ); - } - } - }); + var tracker, vertical, viewedAfter; + var completableBlocks = []; + var vertModDivs = element.getElementsByClassName('vert-mod'); + if (vertModDivs.length === 0) { + return; + } + vertical = vertModDivs[0]; + $(element).find('.vert').each(function(idx, block) { + if (block.dataset.completableByViewing !== undefined) { + completableBlocks.push(block); } }); + if (completableBlocks.length > 0) { + viewedAfter = parseInt(vertical.dataset.completionDelayMs, 10); + if (!(viewedAfter >= 0)) { + // parseInt will return NaN if it fails to parse, which is not >= 0. + viewedAfter = 5000; + } + tracker = new ViewedEventTracker(completableBlocks, viewedAfter); + tracker.addHandler(function(block, event) { + var blockKey = block.dataset.id; + + if (blockKey && !SEEN_COMPLETABLES.has(blockKey)) { + if (event.elementHasBeenViewed) { + $.ajax({ + type: 'POST', + url: runtime.handlerUrl(element, 'publish_completion'), + data: JSON.stringify({ + block_key: blockKey, + completion: 1.0 + }) + }).then( + function() { + SEEN_COMPLETABLES.add(blockKey); + } + ); + } + } + }); + } }; diff --git a/common/lib/xmodule/xmodule/js/fixtures/video.html b/common/lib/xmodule/xmodule/js/fixtures/video.html index 562aa82a3e..7dd54c16fa 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video.html @@ -13,17 +13,18 @@
    -
    +
    -
    +
    - +
    diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_autoadvance.html b/common/lib/xmodule/xmodule/js/fixtures/video_autoadvance.html index d6deeaf3d5..bbc07e5afb 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_autoadvance.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_autoadvance.html @@ -19,6 +19,7 @@
    +
    +
    +
    +