diff --git a/.gitignore b/.gitignore index d10a9932c..d142057e4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ docs/source/api/generated docs/gh-pages notebook/static/components notebook/static/style/*.min.css* +notebook/static/*/js/main.min.js* node_modules *.py[co] __pycache__ diff --git a/build-main.js b/build-main.js new file mode 100644 index 000000000..5936b791d --- /dev/null +++ b/build-main.js @@ -0,0 +1,57 @@ +// build main.min.js +// spawned by gulp to allow parallelism + +var rjs = require('requirejs').optimize; + +var name = process.argv[2]; + +var rjs_config = { + name: name + '/js/main', + out: './notebook/static/' + name + '/js/main.min.js', + baseUrl: 'notebook/static', + preserveLicenseComments: false, // license comments conflict with sourcemap generation + generateSourceMaps: true, + optimize: "uglify2", + paths: { + underscore : 'components/underscore/underscore-min', + backbone : 'components/backbone/backbone-min', + jquery: 'components/jquery/jquery.min', + bootstrap: 'components/bootstrap/js/bootstrap.min', + bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min', + jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min', + moment: 'components/moment/moment', + codemirror: 'components/codemirror', + termjs: 'components/term.js/src/term', + contents: 'empty:' + }, + shim: { + underscore: { + exports: '_' + }, + backbone: { + deps: ["underscore", "jquery"], + exports: "Backbone" + }, + bootstrap: { + deps: ["jquery"], + exports: "bootstrap" + }, + bootstraptour: { + deps: ["bootstrap"], + exports: "Tour" + }, + jqueryui: { + deps: ["jquery"], + exports: "$" + } + }, + + exclude: [ + "custom/custom", + ] +}; + +rjs(rjs_config, console.log, function (err) { + console.log("Failed to build", name, err); + process.exit(1); +}); diff --git a/gulpfile.js b/gulpfile.js index 3bb0297be..dd3ad6fec 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,13 +1,18 @@ +var fork = require('child_process').fork; +var fs = require('fs'); +var path = require('path'); + +var through = require('through'); var gulp = require('gulp'); var less = require('gulp-less'); -var path = require('path'); var minifyCSS = require('gulp-minify-css'); +var newer = require('gulp-newer'); var rename = require('gulp-rename'); var sourcemaps = require('gulp-sourcemaps'); // now some dev nice utilities. var livereload = require('gulp-livereload'); - + gulp.task('css', function () { return gulp.src('./notebook/static/style/*.less') .pipe(sourcemaps.init()) @@ -23,9 +28,88 @@ gulp.task('css', function () { .pipe(livereload()); }); +function build_main(name, callback) { + // build main.min.js for a given application name + // run in a subprocess to allow parallel builds + // clone requirejs config + var p = fork('./build-main.js', [name]); + p.on('exit', function (code, status) { + if (code) { + callback(new Error("Build failed")); + } else { + callback(); + } + }); + return; +} +// build notebook-js, edit-js, etc. tasks +// which enables parallelism +var apps = ['notebook', 'tree', 'edit', 'terminal', 'auth']; + +apps.map(function (name) { + gulp.task(name + '-js', function (finish) { + var s = path.join('notebook', 'static'); + var src = path.join(s, name, 'js', 'main.js'); + var rel_dest = path.join(name, 'js', 'main.min.js'); + var dest = path.join(s, rel_dest); + + var sources = [ + path.join(s, name, 'js', '*.js'), + path.join(s, "base", 'js', '*.js'), + path.join(s, "auth", 'js', '*.js'), + path.join(s, "services", 'config.js'), + ]; + + // for required_components + if (name === 'notebook') { + sources.push(path.join(s, "services", '**', '*.js')); + } + + fs.readdirSync(path.join(s, 'components')).map(function (c) { + if (c !== 'MathJax') { + // skip MathJax because it has tons of files and makes everything super slow + sources.push(path.join(s, 'components', c, '**', '*.js')); + } + }); + + // sources is a greedy list, containing all dependencies plus some for simplicity. + gulp.src(sources, {base: s}) + .pipe(newer(dest)) + .pipe(through(function(file) { + // if any dependency changed, update main.min.js + console.log("A dependency has changed, updating " + rel_dest); + // pause halts the pipeline + this.pause(); + build_main(name, finish); + return; + })) + .on('end', function () { + // if we get here, no dependency is newer than the target + console.log(rel_dest + " up to date"); + finish(); + }); + }); +}); + +gulp.task('js', apps.map(function (name) { return name + '-js'; })); gulp.task('watch', function() { livereload.listen(); gulp.watch('notebook/static/**/*.less', ['css']); + + var s = path.join('notebook', 'static'); + + function alljs(name) { + return path.join(s, name, '**', '*.js'); + } + var common_js = ['components', 'base', 'auth', 'services'].map(alljs); + + gulp.watch(common_js, ['js']); + apps.map(function (name) { + gulp.watch([ + alljs(name), + '!' + path.join(s, name, 'js', 'main.min.js'), + ], [name + '-js']); + }); }); diff --git a/notebook/static/auth/js/loginmain.js b/notebook/static/auth/js/loginmain.js index 44a2bf31d..1e312ee69 100644 --- a/notebook/static/auth/js/loginmain.js +++ b/notebook/static/auth/js/loginmain.js @@ -1,12 +1,14 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -var ipython = ipython || {}; -require(['base/js/page'], function(page) { - var page_instance = new page.Page(); - $('button#login_submit').addClass("btn btn-default"); - page_instance.show(); - $('input#password_input').focus(); +define(['base/js/namespace', 'base/js/page'], function(IPython, page) { + function login_main() { + var page_instance = new page.Page(); + $('button#login_submit').addClass("btn btn-default"); + page_instance.show(); + $('input#password_input').focus(); - ipython.page = page_instance; + IPython.page = page_instance; + } + return login_main; }); diff --git a/notebook/static/auth/js/logoutmain.js b/notebook/static/auth/js/logoutmain.js index b91b0fc4b..7b3f6b4da 100644 --- a/notebook/static/auth/js/logoutmain.js +++ b/notebook/static/auth/js/logoutmain.js @@ -1,10 +1,12 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -var ipython = ipython || {}; -require(['base/js/page'], function(page) { - var page_instance = new page.Page(); - page_instance.show(); +define(['base/js/namespace', 'base/js/page'], function(IPython, page) { + function logout_main() { + var page_instance = new page.Page(); + page_instance.show(); - ipython.page = page_instance; + IPython.page = page_instance; + } + return logout_main; }); diff --git a/notebook/static/auth/js/main.js b/notebook/static/auth/js/main.js new file mode 100644 index 000000000..7be82388e --- /dev/null +++ b/notebook/static/auth/js/main.js @@ -0,0 +1,9 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +define(['./loginmain', './logoutmain'], function (login_main, logout_main) { + return { + login_main: login_main, + logout_main: logout_main + }; +}); diff --git a/notebook/templates/edit.html b/notebook/templates/edit.html index 72d748169..1c543fc2f 100644 --- a/notebook/templates/edit.html +++ b/notebook/templates/edit.html @@ -95,5 +95,5 @@ data-file-path="{{file_path}}" {{super()}} - + {% endblock %} diff --git a/notebook/templates/login.html b/notebook/templates/login.html index 54223360d..78a5d6645 100644 --- a/notebook/templates/login.html +++ b/notebook/templates/login.html @@ -48,6 +48,10 @@ {% block script %} {{super()}} - + {% endblock %} diff --git a/notebook/templates/logout.html b/notebook/templates/logout.html index 91595aacb..81c3c9118 100644 --- a/notebook/templates/logout.html +++ b/notebook/templates/logout.html @@ -34,6 +34,10 @@ {% block script %} {{super()}} - + {% endblock %} diff --git a/notebook/templates/notebook.html b/notebook/templates/notebook.html index c065d8cb4..9661fe272 100644 --- a/notebook/templates/notebook.html +++ b/notebook/templates/notebook.html @@ -324,6 +324,6 @@ data-notebook-path="{{notebook_path}}" - + {% endblock %} diff --git a/notebook/templates/page.html b/notebook/templates/page.html index 3baea5910..6b1939dff 100644 --- a/notebook/templates/page.html +++ b/notebook/templates/page.html @@ -23,6 +23,7 @@ {% endif %} baseUrl: '{{static_url("", include_version=False)}}', paths: { + 'auth/js/main': 'auth/js/main.min', custom : '{{ base_url }}custom', nbextensions : '{{ base_url }}nbextensions', widgets : '{{ base_url }}deprecatedwidgets', diff --git a/notebook/templates/terminal.html b/notebook/templates/terminal.html index ca14f4619..e174b55e4 100644 --- a/notebook/templates/terminal.html +++ b/notebook/templates/terminal.html @@ -60,5 +60,5 @@ data-ws-path="{{ws_path}}" {{super()}} - + {% endblock %} diff --git a/notebook/templates/tree.html b/notebook/templates/tree.html index d2b9f8686..cd15f702b 100644 --- a/notebook/templates/tree.html +++ b/notebook/templates/tree.html @@ -183,5 +183,5 @@ data-terminals-available="{{terminals_available}}" {% block script %} {{super()}} - + {% endblock %} diff --git a/package.json b/package.json index 93f03877d..d5cf1ac7e 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,12 @@ "gulp-less": "^3.0.2", "gulp-livereload": "^3.8.0", "gulp-minify-css": "^1.0.0", + "gulp-newer": "^0.5.0", "gulp-rename": "^1.2.2", "gulp-sourcemaps": "^1.5.1", "less": "~2", - "source-map": "^0.4.2" + "requirejs": "^2.1.17", + "source-map": "^0.4.2", + "through": "^2.3.7" } } diff --git a/setup.py b/setup.py index a10b30976..f1310f3d6 100755 --- a/setup.py +++ b/setup.py @@ -54,6 +54,7 @@ from setupbase import ( find_package_data, check_package_data_first, CompileCSS, + CompileJS, Bower, JavascriptVersion, css_js_prerelease, @@ -107,9 +108,10 @@ from distutils.command.sdist import sdist setup_args['cmdclass'] = { 'build_py': css_js_prerelease( check_package_data_first(build_py)), - 'sdist' : css_js_prerelease(sdist), + 'sdist' : css_js_prerelease(sdist, strict=True), 'css' : CompileCSS, - 'js' : Bower, + 'js' : CompileJS, + 'jsdeps' : Bower, 'jsversion' : JavascriptVersion, } diff --git a/setupbase.py b/setupbase.py index b42944cea..f349900be 100644 --- a/setupbase.py +++ b/setupbase.py @@ -107,7 +107,12 @@ def find_package_data(): continue for f in files: static_data.append(pjoin(parent, f)) - + + # for verification purposes, explicitly add main.min.js + # so that installation will fail if they are missing + for app in ['auth', 'edit', 'notebook', 'terminal', 'tree']: + static_data.append(pjoin('static', app, 'js', 'main.min.js')) + components = pjoin("static", "components") # select the components we actually need to install # (there are lots of resources we bundle for sdist-reasons that we don't actually use) @@ -361,14 +366,41 @@ class CompileCSS(Command): pass def run(self): - - self.run_command('js') + self.run_command('jsdeps') env = os.environ.copy() env['PATH'] = npm_path try: check_call(['gulp','css'], cwd=repo_root, env=env) except OSError as e: - print("Failed to run gulp: %s" % e, file=sys.stderr) + print("Failed to run gulp css: %s" % e, file=sys.stderr) + print("You can install js dependencies with `npm install`", file=sys.stderr) + raise + # update package data in case this created new files + update_package_data(self.distribution) + + +class CompileJS(Command): + """Rebuild minified Notebook Javascript + + Calls `gulp js` + """ + description = "Rebuild Notebook Javascript" + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + self.run_command('jsdeps') + env = os.environ.copy() + env['PATH'] = npm_path + try: + check_call(['gulp','js'], cwd=repo_root, env=env) + except OSError as e: + print("Failed to run gulp js: %s" % e, file=sys.stderr) print("You can install js dependencies with `npm install`", file=sys.stderr) raise # update package data in case this created new files @@ -401,19 +433,26 @@ class JavascriptVersion(Command): raise RuntimeError("Didn't find IPython.version line in %s" % nsfile) -def css_js_prerelease(command): - """decorator for building js/minified css prior to another command""" +def css_js_prerelease(command, strict=False): + """decorator for building minified js/css prior to another command""" class DecoratedCommand(command): def run(self): self.distribution.run_command('jsversion') + jsdeps = self.distribution.get_command_obj('jsdeps') + jsdeps.force = True js = self.distribution.get_command_obj('js') js.force = True css = self.distribution.get_command_obj('css') css.force = True try: self.distribution.run_command('css') + self.distribution.run_command('js') except Exception as e: - log.warn("rebuilding css and sourcemaps failed (not a problem)") - log.warn(str(e)) + if strict: + log.warn("rebuilding js and css failed") + raise e + else: + log.warn("rebuilding js and css failed (not a problem)") + log.warn(str(e)) command.run(self) return DecoratedCommand