From 389f4fcec994519e4a5f195440b3dd06c77a58c9 Mon Sep 17 00:00:00 2001 From: Calen Pennington <cale@edx.org> Date: Fri, 26 Jan 2018 15:55:14 -0500 Subject: [PATCH] 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 00000000000..a9c16a33636 --- /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 fb0c22d2c1e..00000000000 --- 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 303573cfdbd..00000000000 --- 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 00000000000..91ce32d8af9 --- /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 bfb05d1dd33..00000000000 --- 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 3f86a8c891e..71976d63bea 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 00000000000..79937020db6 --- /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 cf0ca8d5ef4..5334fedbdec 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 c88e196753e..0d0a4095e09 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> -<%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"> + <script type="text/javascript"> + <%! from pipeline_js.utils import get_xmodule_urls %> + window.xmoduleUrls = ${get_xmodule_urls() | n, dump_js_escaped_json}; + </script> + <%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:invoke_page_bundle> </%block> <%block name="content"> diff --git a/cms/urls.py b/cms/urls.py index 44d9282a247..03423b771b8 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 93b5890d9cb..822a7ace771 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 ef401b3a3e6..95235946f15 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 03292c3d6ac..fb26ddfdbec 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 63a77a4a8e8..0836c37095d 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 4e2f98921f3..be49b94cb4f 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: /(\/\* RequireJS) \*\//g, + replacement: function(match, p1) { return p1; } + }, { - pattern: /text!(.*\.underscore)/, + 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: { -- GitLab