From e6424c5e3db7f87024b87219ca3d0b79ec1e50df Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Sat, 6 Jun 2015 20:15:35 +0200 Subject: [PATCH] Add build script Only works with Node 0.12 due to node-freetype and use of spawnSync. --- .gitignore | 5 + .gitmodules | 3 + builder/airbornos | 1 + builder/build | 39 +++++++ builder/build.js | 262 +++++++++++++++++++++++++++++++++++++++++++ builder/package.json | 11 ++ 6 files changed, 321 insertions(+) create mode 160000 builder/airbornos create mode 100755 builder/build create mode 100644 builder/build.js create mode 100644 builder/package.json diff --git a/.gitignore b/.gitignore index 5333004..82970f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +/build/ +/builder/node_modules/ +logs +*.log +npm-debug.log* .DS_Store ._.DS_Store .db diff --git a/.gitmodules b/.gitmodules index 41f6371..0951a15 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = scripts/parsers/odt.js url = https://github.com/codexa/odt.js.git branch = master +[submodule "builder/airbornos"] + path = builder/airbornos + url = https://github.com/airbornos/airbornos diff --git a/builder/airbornos b/builder/airbornos new file mode 160000 index 0000000..83c2920 --- /dev/null +++ b/builder/airbornos @@ -0,0 +1 @@ +Subproject commit 83c29208f5536d6f66d6eb9a2eba6d8a24462268 diff --git a/builder/build b/builder/build new file mode 100755 index 0000000..d4ace93 --- /dev/null +++ b/builder/build @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +original_dir=$(pwd) + +cd $(dirname $BASH_SOURCE) + +npm install + +[ -e ../build ] || mkdir ../build + +node build.js ../index.html "$@" +[ -e ../build/scripts.js.map ] && (echo "//# sourceMappingURL=scripts.js.map" >> ../build/scripts.js) + +cp -r ../modules ../build +cp -r ../locales ../build +cp ../manifest.webapp ../build + +mkdir -p ../build/scripts/lib +mkdir -p ../build/scripts/parsers/odt.js/lib +cp ../scripts/messages.js ../build/scripts +cp ../scripts/lib/jszip.js ../build/scripts/lib +cp ../scripts/lib/jszip-deflate.js ../build/scripts/lib +cp ../scripts/lib/jszip-inflate.js ../build/scripts/lib +cp ../scripts/lib/jszip-load.js ../build/scripts/lib +cp ../scripts/lib/stringview.js ../build/scripts/lib +cp ../scripts/lib/innertext.js ../build/scripts/lib +cp ../scripts/parsers/docx.js ../build/scripts/parsers +cp ../scripts/parsers/plain-text.js ../build/scripts/parsers +cp ../scripts/parsers/odt.js/lib/odt.js ../build/scripts/parsers/odt.js/lib + +mkdir -p ../build/style/icons/app +cp ../style/icons/app/32.png ../build/style/icons/app +cp ../style/icons/app/60.png ../build/style/icons/app +cp ../style/icons/app/90.png ../build/style/icons/app +cp ../style/icons/app/120.png ../build/style/icons/app +cp ../style/icons/app/256.png ../build/style/icons/app +cp ../style/icons/app/firetext_christmas.svg ../build/style/icons/app + +cd $original_dir \ No newline at end of file diff --git a/builder/build.js b/builder/build.js new file mode 100644 index 0000000..497f0f7 --- /dev/null +++ b/builder/build.js @@ -0,0 +1,262 @@ +/*** SET UP AIRBORN OS ***/ + +var fs = require('fs'); + +var window = global.window = global; +global.parent = global; + +window.sjcl = require('sjcl'); + +function randomWords(n) { + return Array.apply(null, new Array(n)).map(function() { return Math.floor(Math.random() * 0xFFFFFFFF); }); +} +var hmac_bits = randomWords(4); +var files_hmac = window.files_hmac = new sjcl.misc.hmac(hmac_bits); + +window.XMLHttpRequest = function() { + this.listeners = {}; +}; +window.XMLHttpRequest.prototype.addEventListener = function(name, listener) { + if(!this.listeners[name]) { + this.listeners[name] = []; + } + this.listeners[name].push(listener); +}; +window.XMLHttpRequest.prototype.emit = function(name) { + if(this.listeners[name]) { + var _this = this; + this.listeners[name].forEach(function(listener) { + listener.call(_this); + }); + } +}; +window.XMLHttpRequest.prototype.open = function(method, url) { + if(url.substr(0, 8) === '/object/' && method === 'GET') { + var hash = url.split('#')[1] + url = '../' + hash.substr(hash.indexOf('.') + 1).replace('/Core/', 'builder/airbornos/'); + Object.defineProperty(this, 'send', {value: function() { + var _this = this; + fs.readFile(url, 'base64', function(err, contents) { + Object.defineProperty(_this, 'readyState', {get: function() { return 4; }}); + if(err) { + Object.defineProperty(_this, 'status', {get: function() { + return 404; + }}); + } else { + Object.defineProperty(_this, 'status', {get: function() { + return 200; + }, configurable: true}); + Object.defineProperty(_this, 'response', {get: function() { + return codec.base64.toAB(contents); + }}); + } + _this.emit('readystatechange'); + _this.emit('load'); + }); + }}); + return; + } else if(url.substr(0, 8) === '/object/' || url.substr(0, 13) === '/transaction/') { + Object.defineProperty(this, 'setRequestHeader', {value: function() {}}); + Object.defineProperty(this, 'send', {value: function() { + Object.defineProperty(this, 'readyState', {get: function() { return 4; }}); + Object.defineProperty(this, 'status', {get: function() { + return 200; + }}); + this.emit('readystatechange'); + this.emit('load'); + }}); + return; + } + throw new Error('Unknown XMLHttpRequest url: ' + url); +}; + +window.document = {}; +document.createElement = function() { + return {}; +}; +document.head = {}; +document.head.appendChild = function() {}; + +window.crypto = {}; +window.crypto.getRandomValues = function(array) { + var words = randomWords(array.length); + words.forEach(function(word, i) { + array[i] = word; + }); +}; + +window.atob = function(str) { return new Buffer(str, 'base64').toString('binary'); }; +window.btoa = function(str) { return new Buffer(str, 'binary').toString('base64'); }; + +window.TextDecoder = function() {}; +TextDecoder.prototype.decode = function(dataview) { return new Buffer(codec.base64.fromAB(dataview.buffer), 'base64').toString('utf8'); }; +window.TextEncoder = function() {}; +TextEncoder.prototype.encode = function(str) { return {buffer: codec.base64.toAB(new Buffer(str, 'utf8').toString('base64'))}; }; + +window.navigator = {}; +window.navigator.userAgent = { + match: function() { return String.prototype.match.apply('Safari', arguments); }, // Use the full set of variable rewrites + indexOf: function() { return String.prototype.match.apply('Chrome', arguments); }, // Use Data URLs +}; +window.location = {}; +window.location.protocol = 'https:'; + +window.eval(fs.readFileSync('airbornos/core.js', 'utf8')); + +window.encrypt = window.decrypt = function(key, content, callback) { + callback(content); +}; + +/*** END SET UP AIRBORN OS ***/ + + +var argv = require('yargs').argv; + +// Compile everything into a single html file +prepareFile(argv._[0].replace('../', ''), {compat: false, _compat: false, bootstrap: false, rootParent: ''}, function(contents) { + + // Extract scripts + var scripts = []; + contents = contents.replace(/]*)src="([^"]*)"([^>]*)><\/script>/g, function(match, preAttrs, url, postAttrs) { + scripts.push('../' + decodeURIComponent(url.split(',')[0].match(/filename=([^;]*);/)[1])); + return ''; + }); + + + // Minify scripts + var scriptsFileName = argv._[0].replace('../', '../build/').replace(/[^\/]*\.html/, 'scripts.js'); + var cc = require('child_process').spawn('java', [ + '-jar', 'node_modules/google-closure-compiler/compiler.jar', + '--language_in', 'ECMASCRIPT5', + '--js_output_file', scriptsFileName, + ].concat(argv.sourceMap === false ? [] : [ + '--create_source_map', '%outname%.map' + ]).concat(scripts)); + cc.stderr.on('data', function(data) { + console.error('' + data); + }); + cc.on('close', function() { + contents = contents.replace(/(?=<\/head)/i, ''); + + + // Extract styles + var styles = []; + contents = contents.replace(/]*)href="([^"]*)"([^>]*)>/g, function(match, preAttrs, url, postAttrs) { + var attrs = preAttrs + postAttrs; + if(attrs.indexOf(' rel="stylesheet"') !== -1) { + var style = decodeURIComponent(url.split(',')[1]); + var media = attrs.match(/media="([^"]*)"/); + if(media) style = '@media ' + media[1] + '{' + style + '}'; + styles.push(style); + return ''; + } + return match; + }); + + + // Remove unused css + require('uncss')(contents, { + raw: styles.join('\n'), + ignoreModifiers: [ + '[disabled]', + '.current', + '.parent', + '.active', + '.selected-tab-button', + '.selected-tab', + '.shown', + '.hidden-item', + '[dir="rtl"]', + '.fullscreen', + '.night', + '.previews', + '[data-state="drawer"]', + '.titlePopup', + ], + ignore: [ + /section\[role="status"\]/, + '.mainButtons button b', + /\.fileListItem/, + '[data-type="list"] li > a', + '[data-type="list"] aside[class*=" icon-"]', + 'label.pack-checkbox', + 'label.pack-checkbox input', + 'label.pack-checkbox input ~ span', + 'label.pack-checkbox.danger input ~ span', + /\.CodeMirror/, + /\.cm-/, + /\.icon-fullscreen-exit/, + /\.icon-file/, + /\.icon-format-align-left/, + /\.icon-format-float-left/, + ] + }, function(err, css) { + // Remove unused glyphs from icon font + var rIconFontUrl = /(url\(".*?materialdesignicons-webfont.woff.*?base64,)(.*?)("\))/; + var rIconRuleMatch = /\.icon-.*?:before {\n content: "\\([\da-f]+)";\n}/g; + var rIconRuleExtract = /\.icon-.*?:before {\n content: "\\([\da-f]+)";\n}/; // not global + + window.language = 'en-US'; // Since there is a window, fontmin expects window.language + + var Fontmin = require('fontmin'); + var fontmin = new Fontmin() + .src(argv._[0].replace(/[^\/]*\.html/, 'style/fonts/materialdesignicons-webfont.ttf')) + .use(Fontmin.glyph({ + text: css.match(rIconRuleMatch).map(function(iconRule) { + return String.fromCharCode(parseInt(iconRule.match(rIconRuleExtract)[1], 16)); + }).join(''), + })) + .use(Fontmin.ttf2woff({ + deflate: true // Does nothing but shouldn't hurt + })); + fontmin.run(function(err, files) { + if(err) { + throw err; + } + + css = css.replace(rIconFontUrl, + '$1' + + files[0].contents.toString('base64') + + '$3' + ); + + + // Minify css + css = require('more-css').compress(css); + fs.writeFileSync(argv._[0].replace('../', '../build/').replace(/[^\/]*\.html/, 'styles.css'), css); + contents = contents.replace(//i, '$&'); + + + // Minify html + contents = require('html-minifier').minify(contents, { + removeComments: true, + removeCommentsFromCDATA: true, + collapseWhitespace: true, + collapseBooleanAttributes: true, + removeAttributeQuotes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true, + minifyJS: true, + minifyCSS: true, + }); + + + // Pre-prepare script + // We do this now instead of immediately in order to have a working script for uncss (phantomjs). + if(argv.airborn) { + prepareFile(scriptsFileName.replace('../', ''), {}, function(prepared) { + contents = contents.split('').join(''); // split and join instead of replace to not interpret replacement string ($1 etc) + fs.unlinkSync(scriptsFileName); + + // Write to build folder + fs.writeFileSync(argv._[0].replace('../', '../build/'), contents); + }); + } else { + // Write to build folder + fs.writeFileSync(argv._[0].replace('../', '../build/'), contents); + } + }); + }); + }); + +}); \ No newline at end of file diff --git a/builder/package.json b/builder/package.json new file mode 100644 index 0000000..edf1645 --- /dev/null +++ b/builder/package.json @@ -0,0 +1,11 @@ +{ + "devDependencies": { + "sjcl": "~1.0.3", + "html-minifier": "~0.7.2", + "uncss": "https://github.com/twiss/uncss/tarball/6f21bc960b45534ead2674b19e969176d6a85b0d", + "google-closure-compiler": "~20150505.0.0", + "more-css": "~0.12.0", + "fontmin": "0.9.0-alpha-3", + "yargs": "~3.31.0" + } +} \ No newline at end of file