From 5c6e280e147b127dd196e4f1a3c49237afa95865 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Wed, 20 Dec 2017 11:13:10 -0500 Subject: [PATCH] LP#1739803 Webstaff: Replace Grunt with Webpack * Remove grunt devDependencies * Add Webpack devDepenencies * Copy and minify operations are now handled by Webpack via 'npm run build' for dev builds and 'npm run build-prod' for production/minified builds. * Running 'npm run build-watch' executes webpack in --watch mode to watch for affected file changes and automatically rebuild. Useful for development. * Karma unit tests are now invoked directly from node via 'npm run test'. * Docs and release installer updated to match. * Removed long-outdated inline installer readme. Webpack is configured to create bundles from sets of JS files. As it stands, there are 2 sets: core.bundle.js and vendor.bundle.js. Core has all of the EG core services that are loaded on every page. Vendor contains all of the 3rd-party dependencies (angular, etc.). These 2 bundles are loaded on every web staff page (via base_js.tt2). All other -[% IF EXPAND_WEB_IMPORTS %] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -[% ELSE %] + + + - - + + + -[% END %] + + [% END %] diff --git a/Open-ILS/web/js/ui/default/staff/Gruntfile.js b/Open-ILS/web/js/ui/default/staff/Gruntfile.js deleted file mode 100644 index b6f8e4bdd5..0000000000 --- a/Open-ILS/web/js/ui/default/staff/Gruntfile.js +++ /dev/null @@ -1,250 +0,0 @@ -module.exports = function(grunt) { - - // Project configuration. - var config = { - pkg: grunt.file.readJSON('package.json'), - - // copy the files we care about from fetched dependencies - // into our build directory - copy: { - - js : { - files: [{ - dest: 'build/js/', - flatten: true, - filter: 'isFile', - expand : true, - src: [ - 'node_modules/angular/angular.min.js', - 'node_modules/angular/angular.min.js.map', - 'node_modules/angular-animate/angular-animate.min.js', - 'node_modules/angular-animate/angular-animate.min.js.map', - 'node_modules/angular-sanitize/angular-sanitize.min.js', - 'node_modules/angular-sanitize/angular-sanitize.min.js.map', - 'node_modules/angular-route/angular-route.min.js', - 'node_modules/angular-route/angular-route.min.js.map', - 'node_modules/angular-ui-bootstrap/dist/ui-bootstrap.js', - 'node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js', - 'node_modules/angular-hotkeys/build/hotkeys.min.js', - 'node_modules/angular-file-saver/dist/angular-file-saver.bundle.min.js', - 'node_modules/angular-location-update/angular-location-update.min.js', - 'node_modules/angular-tree-control/angular-tree-control.js', - 'node_modules/ng-toast/dist/ngToast.min.js', - 'node_modules/angular-cookies/angular-cookies.min.js', - 'node_modules/angular-cookies/angular-cookies.min.js.map', - 'node_modules/iframe-resizer/js/iframeResizer.min.js', - 'node_modules/iframe-resizer/js/iframeResizer.map', - 'node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js', - 'node_modules/angular-order-object-by/src/ng-order-object-by.js', - 'node_modules/angular-tablesort/js/angular-tablesort.js', - 'node_modules/lovefield/dist/lovefield.min.js', - 'node_modules/lovefield/dist/lovefield.min.js.map', - 'node_modules/moment/min/moment-with-locales.min.js', - 'node_modules/moment-timezone/builds/moment-timezone-with-data.min.js' - ] - }, - { - dest: '../common/build/js/', - flatten: true, - filter: 'isFile', - expand : true, - src: [ - 'node_modules/jquery/dist/jquery.min.js' - ] - }] - }, - - css : { - files : [{ - dest : 'build/css/', - flatten : true, - filter : 'isFile', - expand : true, - src : [ - 'node_modules/angular-hotkeys/build/hotkeys.min.css', - 'node_modules/bootstrap/dist/css/bootstrap.min.css', - 'node_modules/ng-toast/dist/ngToast.min.css', - 'node_modules/ng-toast/dist/ngToast-animations.min.css', - 'node_modules/angular-tree-control/css/tree-control.css', - 'node_modules/angular-tree-control/css/tree-control-attribute.css', - 'node_modules/angular-tablesort/tablesort.css' - ] - }] - }, - - fonts : { - files : [{ - dest : 'build/fonts/', - flatten : true, - filter : 'isFile', - expand : true, - src : [ - 'node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.eot', - 'node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.svg', - 'node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf', - 'node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.woff', - 'node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2' - ] - }] - }, - - images : { - files : [{ - dest : 'build/images/', - flatten : true, - filter : 'isFile', - expand : true, - src : [ - 'node_modules/angular-tree-control/images/sample.png', - 'node_modules/angular-tree-control/images/node-opened-2.png', - 'node_modules/angular-tree-control/images/folder.png', - 'node_modules/angular-tree-control/images/node-closed.png', - 'node_modules/angular-tree-control/images/node-closed-light.png', - 'node_modules/angular-tree-control/images/node-opened.png', - 'node_modules/angular-tree-control/images/node-opened-light.png', - 'node_modules/angular-tree-control/images/folder-closed.png', - 'node_modules/angular-tree-control/images/node-closed-2.png', - 'node_modules/angular-tree-control/images/file.png' - ] - }] - } - }, - - // combine our CSS deps - // note: minification also supported, but not required (yet). - cssmin: { - combine: { - files: { - 'build/css/evergreen-staff-client-deps.<%= pkg.version %>.min.css' : [ - 'build/css/hotkeys.min.css', - 'build/css/bootstrap.min.css', - 'build/css/ngToast.min.css', - 'build/css/ngToast-animations.min.css', - 'build/css/tree-control.css', - 'build/css/tree-control-attribute.css' - ] - } - } - }, - - // concatenation + minification - uglify: { - options: { - banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' - }, - dev: { - files: [{ - expand: true, - src: ['build/js/ui-bootstrap.js', 'build/js/ui-bootstrap-tpls.js'], - dest: 'build/js', - cwd: '.', - rename: function (dst, src) { - return src.replace('.js', '.min.js'); - } - }] - }, - build: { - src: [ - // These are concatenated in order in the final build file. - // The order is important. - '../common/build/js/jquery.min.js', - 'build/js/angular.min.js', - 'build/js/angular-animate.min.js', - 'build/js/angular-sanitize.min.js', - 'build/js/angular-route.min.js', - 'build/js/ui-bootstrap.min.js', - 'build/js/ui-bootstrap-tpls.js', - 'build/js/hotkeys.min.js', - 'build/js/angular-tree-control.js', - 'build/js/ngToast.min.js', - 'build/js/lovefield.min.js', - 'bulid/js/moment-with-locales.min.js', - 'build/js/moment-timezone-with-data.min.js', - // NOTE: OpenSRF must be installed - // XXX: Should not be hard-coded - '/openils/lib/javascript/JSON_v1.js', - '/openils/lib/javascript/opensrf.js', - '/openils/lib/javascript/opensrf_ws.js', - 'services/core.js', - 'services/strings.js', - 'services/idl.js', - 'services/event.js', - 'services/net.js', - 'services/auth.js', - 'services/pcrud.js', - 'services/env.js', - 'services/org.js', - 'services/startup.js', - 'services/hatch.js', - 'services/print.js', - 'services/audio.js', - 'services/coresvc.js', - 'services/navbar.js', - 'services/ui.js', - 'services/date.js', - 'services/op_change.js', - 'services/file.js', - 'services/i18n.js' - ], - dest: 'build/js/<%= pkg.name %>.<%= pkg.version %>.min.js' - }, - }, - - // bare concat operation; useful for testing concat w/o minification - // to more easily detect if concat order is incorrect - concat: { - options: { - separator: ';' - } - }, - - exec : { - - // Generate test/data/IDL2js.js for unit tests. - // note: the output of this script is *not* part of the final build. - idl2js : { - cwd: 'test/data', - command : 'perl idl2js.pl' - }, - - // Remove the unit test IDL2js.js file. We don't need it after testing - rmidl2js : { - command : 'rm test/data/IDL2js.js' - } - }, - - // unit tests configuration - karma : { - unit: { - configFile: 'test/karma.conf.js' - //background: true // for now, visually babysit unit tests - } - } - }; - - // tell concat about our uglify build options (instead of repeating them) - config.concat.build = config.uglify.build; - - // apply our configuration - grunt.initConfig(config); - - // Load our modules - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-contrib-cssmin'); - grunt.loadNpmTasks('grunt-karma'); - grunt.loadNpmTasks('grunt-exec'); - - // note: "grunt concat" is not required - grunt.registerTask('build', ['copy', 'cssmin', 'uglify']); - - // test only, no minification - grunt.registerTask('test', ['copy', 'exec:idl2js', 'karma:unit', 'exec:rmidl2js']); - - // note: "grunt concat" is not requried - grunt.registerTask('all', ['test', 'cssmin', 'uglify']); - -}; - -// vim: ts=2:sw=2:softtabstop=2 diff --git a/Open-ILS/web/js/ui/default/staff/README.install b/Open-ILS/web/js/ui/default/staff/README.install deleted file mode 100644 index ba0a91285a..0000000000 --- a/Open-ILS/web/js/ui/default/staff/README.install +++ /dev/null @@ -1,113 +0,0 @@ -= Building, Testing, Packaging the Browser Client = -:Author: Bill Erickson -:Email: berick@esilibrary.com -:Date: 2014-05-07 - -== Prerequisites == - - * http://bower.io/[Bower] - ** Dependency retrieval - * http://jasmine.github.io/[Jasmine] - ** Headless unit tests runner - * http://gruntjs.com/[Grunt] - ** Coordinating the build - ** Concatenation + minification of JS and CSS - -These are all Node.js plugins, so start by installing Node.js - -=== Install Node.js === - -Node.js does not have have Debian Wheezy build target. For now, I've opted -to install from source. For more, see also -https://github.com/joyent/node/wiki/installation[Node.js Installation] - -[source,sh] ------------------------------------------------------------------------------- -% git clone https://github.com/joyent/node.git -% cd node -% git checkout -b v0.10.28 v0.10.28 - -# set -j to number of CPU cores + 1 -% ./configure && make -j5 && sudo make install - -# update packages -% sudo npm update ------------------------------------------------------------------------------- - -=== Install Grunt CLI === - -[source,sh] ------------------------------------------------------------------------------- -% sudo npm install -g grunt-cli ------------------------------------------------------------------------------- - -=== Install Bower === - -[source,sh] ------------------------------------------------------------------------------- -% sudo npm install -g bower ------------------------------------------------------------------------------- - -== Building, Testing, Minification == - -The remaining steps all take place within the staff JS web root: - -[source,sh] ------------------------------------------------------------------------------- -% cd $EVERGREEN_ROOT/Open-ILS/web/js/ui/default/staff/ ------------------------------------------------------------------------------- - -=== Install Project-local Dependencies === - -npm inspects the 'package.json' file for dependencies and fetches them -from the Node package network. - -[source,sh] ------------------------------------------------------------------------------- -% npm install # fetch Grunt dependencies -% bower install # fetch JS dependencies ------------------------------------------------------------------------------- - -=== Running the Build Scripts === - -[source,sh] ------------------------------------------------------------------------------- - -# build, run tests -% grunt test - -# build, concat+minify -% grunt uglify - -# build, run tests, concat+minify -% grunt all ------------------------------------------------------------------------------- - -== Updating Dependencies - - * Remove the contents of the "dependencies" {} in bowser.json - * rm -r bower_components - * Re-install all dependencies: - -[source,sh] ------------------------------------------------------------------------------- -bower install --save angular-latest -bower install --save jquery -bower install --save bootstrap -bower install --save angular-route -bower install --save angular-mocks -bower install --save angular-bootstrap -bower install --save angular-hotkeys -bower install --save angular-file-saver -bower install --save angular-location-update -# ... others as needed ------------------------------------------------------------------------------- - -== TODO == - - * Minification of app-specific JS files - * Integrate this into the Evergreen Makefile test and install targets - ** Avoid installing test, node_modules, etc. into the web dir. - * Support fetching JS deps (angularjs, etc.) via direct retrieval for - installation without test + concat + minify (i.e. w/o requiring Node.js)? - diff --git a/Open-ILS/web/js/ui/default/staff/package.json b/Open-ILS/web/js/ui/default/staff/package.json index 26a48a1401..1d3e5a02df 100644 --- a/Open-ILS/web/js/ui/default/staff/package.json +++ b/Open-ILS/web/js/ui/default/staff/package.json @@ -6,25 +6,25 @@ "homepage": "https://evergreen-ils.org/", "devDependencies": { "angular-mocks": "^1.6.7", - "grunt": "^1.0.1", - "grunt-cli": "^1.2.0", - "grunt-contrib-concat": "^1.0.1", - "grunt-contrib-copy": "^1.0.0", - "grunt-contrib-cssmin": "^2.2.1", - "grunt-contrib-jasmine": "^1.1.0", - "grunt-contrib-uglify": "^3.2.1", - "grunt-exec": "^3.0.0", - "grunt-karma": "^2.0.0", + "clean-webpack-plugin": "^0.1.17", + "copy-webpack-plugin": "^4.3.0", "karma": "^1.7.1", "karma-jasmine": "^1.1.1", "karma-phantomjs-launcher": "^1.0.4", "karma-script-launcher": "^1.0.0", "karma-spec-reporter": "0.0.32", "karma-story-reporter": "^0.3.1", - "phantomjs-prebuilt": "^2.1.16" + "phantomjs-prebuilt": "^2.1.16", + "webpack": "^3.10.0", + "webpack-merge": "^4.1.1" }, "scripts": { - "test": "grunt test" + "create-mock-idl": "cd test/data && perl idl2js.pl", + "remove-mock-idl": "rm test/data/IDL2js.js", + "test": "npm run create-mock-idl ; karma start test/karma.conf --single-run; npm run remove-mock-idl", + "build": "webpack --env.dev", + "build-watch": "webpack --env.dev --watch", + "build-prod": "webpack --env.prod" }, "dependencies": { "angular": "^1.6.7", diff --git a/Open-ILS/web/js/ui/default/staff/test/karma.conf.js b/Open-ILS/web/js/ui/default/staff/test/karma.conf.js index 12cc87e274..531ac85adc 100644 --- a/Open-ILS/web/js/ui/default/staff/test/karma.conf.js +++ b/Open-ILS/web/js/ui/default/staff/test/karma.conf.js @@ -6,17 +6,12 @@ module.exports = function(config){ logLevel: config.LOG_INFO, files : [ - '../common/build/js/jquery.min.js', - 'build/js/lovefield.min.js', - 'build/js/angular.min.js', - 'build/js/angular-route.min.js', + 'build/js/vendor.bundle.js', + 'build/js/core.bundle.js', 'node_modules/angular-mocks/angular-mocks.js', // testing only 'node_modules/angular-file-saver/dist/angular-file-saver.bundle.min.js', 'node_modules/ng-toast/dist/ngToast.min.js', 'node_modules/angular-sanitize/angular-sanitize.min.js', - 'build/js/ui-bootstrap.js', - 'build/js/hotkeys.min.js', - 'build/js/angular-cookies.min.js', /* OpenSRF must be installed first */ '/openils/lib/javascript/md5.js', '/openils/lib/javascript/JSON_v1.js', @@ -28,29 +23,10 @@ module.exports = function(config){ 'test/data/eg_mock.js', // service/*.js have to be loaded in order - 'services/core.js', - 'services/idl.js', - 'services/strings.js', - 'services/event.js', - 'services/net.js', - 'services/auth.js', - 'services/pcrud.js', - 'services/env.js', - 'services/org.js', - 'services/hatch.js', - 'services/print.js', - 'services/audio.js', - 'services/coresvc.js', - 'services/user.js', - 'services/startup.js', - 'services/ui.js', 'services/grid.js', - 'services/op_change.js', 'services/patron_search.js', - 'services/lovefield.js', - 'services/navbar.js', 'services/date.js', 'services/user-bucket.js', - 'services/i18n.js', + // load app scripts 'app.js', 'circ/**/*.js', diff --git a/Open-ILS/web/js/ui/default/staff/webpack.config.js b/Open-ILS/web/js/ui/default/staff/webpack.config.js new file mode 100644 index 0000000000..1be4c7b334 --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/webpack.config.js @@ -0,0 +1,158 @@ +const path = require('path'); +const merge = require('webpack-merge'); +const webpack = require('webpack'); +const CleanWebpackPlugin = require('clean-webpack-plugin'); +const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +const buildPath = 'build'; + +const CSS_FILES = [ + 'node_modules/angular-hotkeys/build/hotkeys.min.css', + 'node_modules/bootstrap/dist/css/bootstrap.min.css', + 'node_modules/ng-toast/dist/ngToast.min.css', + 'node_modules/ng-toast/dist/ngToast-animations.min.css', + 'node_modules/angular-tree-control/css/tree-control.css', + 'node_modules/angular-tree-control/css/tree-control-attribute.css', + 'node_modules/angular-tablesort/tablesort.css' +]; + +const FONT_FILES = [ + 'node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.eot', + 'node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.svg', + 'node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf', + 'node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.woff', + 'node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2' +]; + +const IMAGE_FILES = [ + 'node_modules/angular-tree-control/images/sample.png', + 'node_modules/angular-tree-control/images/node-opened-2.png', + 'node_modules/angular-tree-control/images/folder.png', + 'node_modules/angular-tree-control/images/node-closed.png', + 'node_modules/angular-tree-control/images/node-closed-light.png', + 'node_modules/angular-tree-control/images/node-opened.png', + 'node_modules/angular-tree-control/images/node-opened-light.png', + 'node_modules/angular-tree-control/images/folder-closed.png', + 'node_modules/angular-tree-control/images/node-closed-2.png', + 'node_modules/angular-tree-control/images/file.png' +] + +// Some common JS files are left un-bundled. +// https://github.com/webpack/webpack/issues/3128 +const JS_FILES = [ + './node_modules/moment/min/moment-with-locales.min.js', + './node_modules/moment-timezone/builds/moment-timezone-with-data.min.js' +] + + +// Copy files as-is from => to. +const directCopyFiles = [ + + // jquery is copied to the common build location, up one directory. + {from: './node_modules/jquery/dist/jquery.min.js', + to: __dirname + '/../common/build/js'} +]; + +CSS_FILES.forEach(file => directCopyFiles.push({from: file, to: './css'})); +FONT_FILES.forEach(file => directCopyFiles.push({from: file, to: './fonts'})); +IMAGE_FILES.forEach(file => directCopyFiles.push({from: file, to: './images'})); +JS_FILES.forEach(file => directCopyFiles.push({from: file, to: './js'})); + +// EG JS files loaded on every page +const coreJsFiles = [ + './services/core.js', + './services/strings.js', + './services/idl.js', + './services/event.js', + './services/net.js', + './services/auth.js', + './services/pcrud.js', + './services/env.js', + './services/org.js', + './services/startup.js', + './services/hatch.js', + './services/print.js', + './services/audio.js', + './services/coresvc.js', + './services/user.js', + './services/navbar.js', + './services/ui.js', + './services/i18n.js', + './services/date.js', + './services/op_change.js', + './services/lovefield.js' +]; + +// 3rd-party (AKA vendor) JS files loaded on every page. +// Webpack knows to look in ./node_modules/ +const vendorJsFiles = [ + 'angular', + 'angular-route', + 'angular-ui-bootstrap', + 'angular-hotkeys', + 'angular-file-saver', + 'angular-location-update', + 'angular-animate', + 'angular-sanitize', + 'angular-cookies', + 'ng-toast', + 'angular-tree-control', + 'iframe-resizer', + 'angular-order-object-by', + 'lovefield' +]; + + +let commmonOptions = { + // As of today, we are only bundling common files. Individual app.js + // and optional service files are still imported via script tags. + entry: { + core: coreJsFiles, + vendor: vendorJsFiles + }, + plugins: [ + new CleanWebpackPlugin([buildPath]), + new CopyWebpackPlugin(directCopyFiles, {copyUnmodified: true}), + new webpack.optimize.CommonsChunkPlugin({ + names: ['core', 'vendor'], // ORDER MATTERS + minChunks: 2 // TODO: huh? + }) + ], + output: { + filename: 'js/[name].bundle.js', + path: path.resolve(__dirname, buildPath) + } +}; + +// improve debugging during development with inline source maps +// for bundled files. +let devOptions = { + devtool: 'inline-source-map', + plugins: [ + // Avoid minifiying the core bundle in development mode. + // TODO: Add other bundles as necessary, but leave the 'vendor' + // bundle out, since we always want to minify that (it's big). + new UglifyJSPlugin({ + exclude: [/core/] + }) + ], + watchOptions: { + aggregateTimeout: 300, + poll: 1000, + ignored : [ + /node_modules/ + ] + } +}; + +// minify for production +let prodOptions = { + plugins: [ + new UglifyJSPlugin() + ], +}; + +module.exports = env => env.prod ? + merge(commmonOptions, prodOptions) : merge(commmonOptions, devOptions); + diff --git a/build/tools/make_release b/build/tools/make_release index b8703090bd..f82ff3ddcb 100755 --- a/build/tools/make_release +++ b/build/tools/make_release @@ -83,7 +83,7 @@ while getopts ":hv:f:F:nptbrij:cx" opt; do echo " -r prompt to preview upgrade SQL in editor before committing." echo " -i skip i18n; primarily useful for (quickly) testing this script." echo " -j opensrf javascript library path. If osrf_config is found, the value derived from osrf_config --libdir." - echo " -c build the experimental browser client; requires nodejs/grunt-cli" + echo " -c build the browser client; requires nodejs" echo " -x skip building the XUL client" echo " NOTE: -t and -b override PREV_BRANCH/PREV_VERSION, but -b overrides -t." exit -1 @@ -336,7 +336,7 @@ if [ "$BUILD_BROWSER_CLIENT" == "YES" ]; then echo "Building browser staff client" cd Open-ILS/web/js/ui/default/staff/ npm install # fetch build dependencies - grunt build # copy to build dir and minify JS files + npm run build-prod # copy to build dir and minify JS files # npm cache is big and unnecessary in the final build. remove it. rm -r node_modules cd ../../../../../../../ # release dir diff --git a/docs/installation/server_installation.adoc b/docs/installation/server_installation.adoc index d651c886e5..20e0ccde96 100644 --- a/docs/installation/server_installation.adoc +++ b/docs/installation/server_installation.adoc @@ -152,13 +152,6 @@ steps in <