├── .travis.yml ├── LICENSE ├── bin └── cmd.js ├── menu.json ├── package.json ├── problems ├── beep_boop │ ├── index.js │ ├── problem.txt │ └── solution.txt ├── build_a_widget │ ├── browser.js │ ├── index.js │ ├── package.json │ ├── problem.txt │ ├── solution.txt │ └── static │ │ ├── bundle.js │ │ └── index.html ├── builtins │ ├── index.js │ ├── problem.txt │ └── solution.txt ├── multi_export │ ├── index.js │ ├── problem.txt │ └── solution.txt ├── shimming_non_commonjs_modules │ ├── files │ │ ├── main.js │ │ └── spice-girls.js │ ├── index.js │ ├── problem.txt │ └── solution.txt ├── single_export │ ├── index.js │ ├── problem.txt │ └── solution.txt ├── using_npm_packages │ ├── index.js │ ├── problem.txt │ └── solution.txt ├── using_transforms │ ├── expected.txt │ ├── index.js │ ├── problem.txt │ ├── solution.txt │ └── wake.txt ├── widget_with_assets │ ├── browser.js │ ├── index.js │ ├── package.json │ ├── problem.txt │ ├── solution.txt │ └── static │ │ ├── bundle.js │ │ └── index.html └── writing_transforms │ ├── expected.txt │ ├── index.js │ ├── problem.txt │ ├── solution.txt │ └── wake.txt ├── readme.markdown ├── solutions ├── beep_boop │ └── main.js ├── build_a_widget │ └── widget.js ├── builtins │ └── main.js ├── multi_export │ ├── main.js │ └── ndjson.js ├── shimming_non_commonjs_modules │ ├── main.js │ ├── package.json │ └── spice-girls.js ├── single_export │ ├── main.js │ └── uniquely.js ├── using_npm_packages │ └── main.js ├── using_transforms │ └── main.js ├── widget_with_assets │ ├── widget.css │ ├── widget.html │ └── widget.js └── writing_transforms │ ├── main.js │ └── tr.js └── test └── solutions.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | before_install: 6 | - npm install -g npm@~1.4.6 7 | - sudo apt-get install chromium-browser xvfb 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is released under the MIT license: 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var adventure = require('adventure'); 5 | var shop = adventure('browserify-adventure'); 6 | 7 | require('../menu.json').forEach(function (name) { 8 | if (/^!/.test(name)) return; 9 | var d = name.toLowerCase().replace(/\W+/g, '_'); 10 | var dir = path.join(__dirname, '../problems', d); 11 | shop.add(name, function () { 12 | return require(dir); 13 | }); 14 | }); 15 | shop.execute(process.argv.slice(2)); 16 | -------------------------------------------------------------------------------- /menu.json: -------------------------------------------------------------------------------- 1 | [ 2 | "BEEP BOOP", 3 | "USING NPM PACKAGES", 4 | "SINGLE EXPORT", 5 | "MULTI EXPORT", 6 | "BUILTINS", 7 | "BUILD A WIDGET", 8 | "USING TRANSFORMS", 9 | "WRITING TRANSFORMS", 10 | "WIDGET WITH ASSETS", 11 | 12 | "SHIMMING NON-COMMONJS MODULES", 13 | 14 | "!DEBUGGING", 15 | "!WATCH", 16 | "!NPM SCAVENGER HUNT", 17 | "!TESTING", 18 | "!CODE COVERAGE", 19 | "!BUILD YOUR OWN BROWSERIFY", 20 | "!COMPILER PIPELINE", 21 | "!BROWSER UNPACK", 22 | "!FACTOR BUNDLE" 23 | ] 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browserify-adventure", 3 | "version": "1.7.7", 4 | "description": "learn browserify with this educational adventure", 5 | "main": "index.js", 6 | "bin": { 7 | "browserify-adventure": "bin/cmd.js" 8 | }, 9 | "dependencies": { 10 | "adventure": "^2.5.0", 11 | "adventure-verify": "^2.1.1", 12 | "browser-unpack": "^1.0.0", 13 | "concat-stream": "^1.4.6", 14 | "ecstatic": "~0.5.4", 15 | "falafel": "~0.3.1", 16 | "shoe": "~0.0.15", 17 | "split": "~0.3.0", 18 | "through2": "~0.5.1", 19 | "ever": "~0.0.3", 20 | "colornames": "~0.0.2", 21 | "split2": "^2.1.0" 22 | }, 23 | "devDependencies": { 24 | "brfs": "^1.1.2", 25 | "browserify": "^5.10.0", 26 | "browserify-shim": "^3.6.0", 27 | "domify": "^1.2.2", 28 | "quote-stream": "0.0.0", 29 | "sprintf": "~0.1.3", 30 | "stream-combiner2": "^1.0.0", 31 | "tape": "^2.13.3", 32 | "uniq": "^1.0.1", 33 | "through2": "~0.5.1", 34 | "insert-css": "~0.2.0", 35 | "inherits": "^2.0.1" 36 | }, 37 | "scripts": { 38 | "test": "node test/solutions.js" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "git://github.com/substack/browserify-adventure.git" 43 | }, 44 | "homepage": "https://github.com/substack/browserify-adventure", 45 | "keywords": [ 46 | "browserify", 47 | "education", 48 | "nodeschool", 49 | "workshop", 50 | "edutainment" 51 | ], 52 | "author": { 53 | "name": "James Halliday", 54 | "email": "mail@substack.net", 55 | "url": "http://substack.net" 56 | }, 57 | "license": "MIT" 58 | } 59 | -------------------------------------------------------------------------------- /problems/beep_boop/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var verify = require('adventure-verify'); 3 | var unpack = require('browser-unpack'); 4 | var concat = require('concat-stream'); 5 | 6 | exports.problem = fs.createReadStream(__dirname + '/problem.txt'); 7 | exports.solution = fs.createReadStream(__dirname + '/solution.txt'); 8 | 9 | exports.verify = verify({ modeReset: true }, function (args, t) { 10 | t.plan(4); 11 | process.stdin.pipe(concat(function (body) { 12 | try { var rows = unpack(body) } 13 | catch (err) { return t.fail('The input had a syntax error!') } 14 | if (!rows) return t.fail('The input is not a browserify bundle!'); 15 | 16 | t.equal(rows.length, 1, 'a single file in this bundle'); 17 | t.equal(rows[0].entry, true, 'single file should be an entry file'); 18 | t.deepEqual(rows[0].deps, {}, "shouldn't have any deps"); 19 | Function(['console'], body.toString())({ 20 | log: function (msg) { t.equal(msg, 'BEEP BOOP') }, 21 | error: console.error 22 | }); 23 | })); 24 | }); 25 | 26 | exports.run = function (args) { 27 | process.stdin.pipe(concat(function (body) { 28 | Function(body.toString())(); 29 | })); 30 | }; 31 | -------------------------------------------------------------------------------- /problems/beep_boop/problem.txt: -------------------------------------------------------------------------------- 1 | First of all, make sure you have `browserify` installed as a command in 2 | your $PATH. 3 | 4 | You can do this by running: 5 | 6 | npm install -g browserify 7 | 8 | If you get permission errors, try putting `sudo` in front of that command. 9 | 10 | Once you have browserify installed, write a program that prints the string 11 | 'BEEP BOOP' using `console.log()`. 12 | 13 | Compile your program with `browserify` and pipe the bundle into 14 | `$ADVENTURE_COMMAND verify` like this: 15 | 16 | browserify main.js | $ADVENTURE_COMMAND verify 17 | 18 | If you just want to run your solution without verifying you can do: 19 | 20 | browserify main.js | $ADVENTURE_COMMAND run 21 | -------------------------------------------------------------------------------- /problems/beep_boop/solution.txt: -------------------------------------------------------------------------------- 1 | Here's the reference solution: 2 | 3 | console.log('BEEP BOOP'); 4 | 5 | We piped the output of browserify into the verify command directly, but 6 | ordinarily to use browserify in your page you would do: 7 | 8 | browserify beep_boop.js > bundle.js 9 | 10 | and then in your html, you just add a single 13 | 14 | We'll be doing this for real in some of the later challenges. 15 | -------------------------------------------------------------------------------- /problems/build_a_widget/browser.js: -------------------------------------------------------------------------------- 1 | var shoe = require('shoe'); 2 | var sock = shoe('/sock'); 3 | 4 | var Widget = require('widget'); 5 | var w = Widget(); 6 | w.setName('t-rex'); 7 | var elem = document.createElement('div'); 8 | document.body.appendChild(elem); 9 | w.appendTo(elem); 10 | 11 | sock.write(JSON.stringify(elem.innerHTML) + '\n'); 12 | -------------------------------------------------------------------------------- /problems/build_a_widget/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var verify = require('adventure-verify'); 4 | var concat = require('concat-stream'); 5 | var http = require('http'); 6 | var shoe = require('shoe'); 7 | var ecstatic = require('ecstatic')({ 8 | root: path.join(__dirname, 'static'), 9 | cache: 0 10 | }); 11 | var split = require('split'); 12 | var through = require('through2'); 13 | 14 | exports.problem = fs.createReadStream(__dirname + '/problem.txt'); 15 | exports.solution = fs.createReadStream(__dirname + '/solution.txt'); 16 | 17 | exports.verify = verify({ modeReset: true }, function (args, t) { 18 | t.plan(1); 19 | process.stdin.pipe(concat(function (body) { 20 | createServer(body, t).pipe(through(function (row, enc, next) { 21 | t.equal( 22 | row.toString('utf8'), 23 | '
Hello t-rex!
' 24 | ); 25 | next(); 26 | })); 27 | })); 28 | }); 29 | 30 | function createServer (body, t) { 31 | var output = through(); 32 | var server = http.createServer(function (req, res) { 33 | if (req.url === '/code.js') { 34 | res.end(body); 35 | } 36 | else ecstatic(req, res) 37 | }); 38 | server.listen(55500, function () { 39 | console.log('Web server running. Visit this URL:' 40 | + ' http://localhost:' + server.address().port 41 | ); 42 | }); 43 | var sock = shoe(function (stream) { 44 | stream.pipe(split()).pipe(through(function (buf, enc, next) { 45 | var line = buf.toString('utf8'); 46 | try { var row = JSON.parse(line) } 47 | catch (err) { 48 | if (t) return t.fail(err) 49 | else console.error(err); 50 | } 51 | 52 | this.push(row); 53 | next(); 54 | })).pipe(output); 55 | 56 | if (t) t.once('end', function () { stream.end() }); 57 | }); 58 | 59 | if (t) t.once('end', function () { 60 | server.close(); 61 | setTimeout(function () { 62 | process.exit(); 63 | }, 100); 64 | }); 65 | sock.install(server, '/sock'); 66 | return output; 67 | } 68 | 69 | exports.run = function (args) { 70 | process.stdin.pipe(concat(function (body) { 71 | createServer(body).pipe(through(function (row, enc, next) { 72 | console.log(row.toString('utf8')); 73 | next(); 74 | })); 75 | })); 76 | }; 77 | -------------------------------------------------------------------------------- /problems/build_a_widget/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "browserify -d -x widget browser.js -o static/bundle.js" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /problems/build_a_widget/problem.txt: -------------------------------------------------------------------------------- 1 | This time the verifier will call your code. It will do something like this: 2 | 3 | var Widget = require('widget'); 4 | var w = Widget(); 5 | w.setName('beep boop'); 6 | w.appendTo(document.body); 7 | 8 | You should implement the widget module that will be loaded with 9 | `require('widget')`. Your widget module should create an html element from 10 | the html string: 11 | 12 |
Hello !
13 | 14 | To turn this html string into a dom element, you can use the domify package 15 | from npm: 16 | 17 | npm install domify 18 | 19 | and then do `require('domify')` to get a `domify(html)` function that 20 | returns html elements. Otherwise you can create a dom element with 21 | `document.createElement('div')` and then set its innerHTML directly. 22 | 23 | Your widget module will need to implement `w.setName(str)` and 24 | `w.appendTo(target)`. `w.setName(str)` will need to update the "name" 25 | span's inner context with `str`. You can set the span element's 26 | `.textContent` once you have a handle on the span which you can obtain with 27 | `elem.querySelector('css selector goes here')`. 28 | 29 | You will also need to implement `w.appendTo(target)`, which appends the 30 | widget's internal html element to `target`. You can use 31 | `target.appendChild(elem)` to append the widget's element to the target. 32 | 33 | Compile your program with `browserify -r ./widget.js:widget` to create a 34 | bundle that will expose `require('widget')` to the environment and pipe the 35 | bundle output into `$ADVENTURE_COMMAND verify` like this: 36 | 37 | browserify -d -r ./widget.js:widget | $ADVENTURE_COMMAND verify 38 | 39 | The `-d` turns on debug mode so you will get a stack trace with the proper 40 | line offsets if there is an error. 41 | -------------------------------------------------------------------------------- /problems/build_a_widget/solution.txt: -------------------------------------------------------------------------------- 1 | Here's the reference solution: 2 | 3 | var domify = require('domify'); 4 | var html = '
Hello !
'; 5 | 6 | module.exports = Widget; 7 | 8 | function Widget () { 9 | if (!(this instanceof Widget)) return new Widget; 10 | this.element = domify(html); 11 | } 12 | 13 | Widget.prototype.setName = function (name) { 14 | this.element.querySelector('.name').textContent = name; 15 | }; 16 | 17 | Widget.prototype.appendTo = function (target) { 18 | target.appendChild(this.element); 19 | }; 20 | 21 | There are lots of ways to complete this level, so your solution might look 22 | quite different! 23 | 24 | In this solution, we create an element with a string of html every time the 25 | widget gets instantiated. When `setName()` is called, we find the `name` 26 | span and update its text content. When `appendTo(target)` is called, we 27 | append the widget's html element to the target element. 28 | -------------------------------------------------------------------------------- /problems/build_a_widget/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /problems/builtins/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var verify = require('adventure-verify'); 3 | var unpack = require('browser-unpack'); 4 | var concat = require('concat-stream'); 5 | 6 | exports.problem = fs.createReadStream(__dirname + '/problem.txt'); 7 | exports.solution = fs.createReadStream(__dirname + '/solution.txt'); 8 | 9 | exports.verify = verify({ modeReset: true }, function (args, t) { 10 | t.plan(3); 11 | process.stdin.pipe(concat(function (body) { 12 | try { var rows = unpack(body) } 13 | catch (err) { return t.fail('The input had a syntax error!') } 14 | if (!rows) return t.fail('The input is not a browserify bundle!'); 15 | 16 | var expected = [ 17 | 'http://substack.net/filez/hi/doge.gif', 18 | 'http://lebron.technology/lebron.jpeg', 19 | 'https://fbi.gov/classified/x-files/swamp_monster.pdf' 20 | ]; 21 | run(body, function (res) { 22 | t.equal(res.output[0], expected.shift(), res.input); 23 | }); 24 | })); 25 | }); 26 | 27 | exports.run = function (args) { 28 | process.stdin.pipe(concat(function (body) { 29 | run(body, function (res) { 30 | console.log('INPUT: ' + JSON.stringify(res.input)); 31 | console.log('OUTPUT: ' + JSON.stringify(res.output)); 32 | console.log(); 33 | }); 34 | })); 35 | }; 36 | 37 | function run (body, cb) { 38 | var inputs = [ 39 | 'http://substack.net/filez/dogez/img.cgi?file=../hi/doge.gif', 40 | 'http://lebron.technology/nba/allstars?slamdunk=true&file=/lebron.jpeg', 41 | 'https://fbi.gov/classified/docs.php?file=x-files/swamp_monster.pdf&xtag=333', 42 | ]; 43 | var last = null; 44 | var con = { 45 | log: function () { 46 | cb({ input: last, output: [].slice.call(arguments) }) 47 | }, 48 | error: console.error 49 | }; 50 | var prompt = function () { 51 | last = inputs.shift() 52 | return last; 53 | }; 54 | var size = inputs.length; 55 | for (var i = 0; i < size; i++) { 56 | Function(['console','prompt'], body.toString())(con, prompt); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /problems/builtins/problem.txt: -------------------------------------------------------------------------------- 1 | browserify includes browser-compatible versions of many of the modules from 2 | node core such as: url, path, querystring, events, stream, util and more! 3 | This means that many modules written for node without the browser in mind 4 | will just work. 5 | 6 | For this level, `prompt()` will return a web address. 7 | Parse the address's query string and print the "file" parameter from the 8 | query string relative to the rest of the web address. 9 | 10 | For example, this address: 11 | 12 | http://substack.net/filez/dogez/img.cgi?file=../hi/doge.gif 13 | 14 | should print: 15 | 16 | http://substack.net/filez/hi/doge.gif 17 | 18 | Instead of using a hand-rolled parser, there are some very handy functions 19 | from node core you can use from the url and querystring modules. 20 | 21 | * url.parse(addr) takes a web address string and returns an object with all 22 | the components of the url. In particular you will want the "query" property 23 | from the result. 24 | 25 | * url.resolve(baseAddr, path) resolve the `path` string with respect to the 26 | baseAddr url. 27 | 28 | * querystring.parse(str) takes a querystring string `str` and returns an 29 | object mapping querystring keys to values 30 | 31 | Just `require('url')` and `require('querystring')` to get each of these 32 | modules. You don't need to run `npm install` for url or querystring because 33 | they are built into browserify similarly to how they are built into node. 34 | 35 | Compile your program with `browserify` and pipe the bundle into 36 | `$ADVENTURE_COMMAND verify` like this: 37 | 38 | browserify main.js | $ADVENTURE_COMMAND verify 39 | 40 | If you just want to run your solution with the test input without verifying 41 | it you can do: 42 | 43 | browserify main.js | $ADVENTURE_COMMAND run 44 | -------------------------------------------------------------------------------- /problems/builtins/solution.txt: -------------------------------------------------------------------------------- 1 | Here's the reference solution: 2 | 3 | var url = require('url'); 4 | var querystring = require('querystring'); 5 | 6 | var addr = prompt(); 7 | var query = url.parse(addr).query; 8 | var params = querystring.parse(query); 9 | console.log(url.resolve(addr, params.file)); 10 | 11 | We get parse the address obtained from `prompt()` then parse out the 12 | querystring with `url.parse()` and parse that string into parameters with 13 | `querystring.parse()`. Finally we use `url.resolve()` to resolve the file 14 | parameter with respect to the address we obtained from `prompt()` earlier. 15 | 16 | `path`, `events`, and `stream` are other node builtins that are 17 | particularly useful in the browser. 18 | -------------------------------------------------------------------------------- /problems/multi_export/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var verify = require('adventure-verify'); 3 | var unpack = require('browser-unpack'); 4 | var concat = require('concat-stream'); 5 | var falafel = require('falafel'); 6 | 7 | exports.problem = fs.createReadStream(__dirname + '/problem.txt'); 8 | exports.solution = fs.createReadStream(__dirname + '/solution.txt'); 9 | 10 | exports.verify = verify({ modeReset: true }, function (args, t) { 11 | t.plan(11); 12 | process.stdin.pipe(concat(function (body) { 13 | try { var rows = unpack(body) } 14 | catch (err) { return t.fail('The input had a syntax error!') } 15 | if (!rows) return t.fail('The input is not a browserify bundle!'); 16 | 17 | t.ok(rows.length >= 2, '>= 2 files in the bundle'); 18 | 19 | var files = {}; 20 | var idMap = {}; 21 | for (var i = 0; i < rows.length; i++) { 22 | if (rows[i].entry) { 23 | files.main = rows[i]; 24 | } 25 | idMap[rows[i].id] = rows[i]; 26 | } 27 | if (!files.main) t.fail('No entry file detected') 28 | 29 | var mainDeps = Object.keys(files.main.deps); 30 | t.equal(mainDeps.length, 1); 31 | t.ok(/^\.[\/.]/.test(mainDeps[0]), 'relative require'); 32 | 33 | t.ok( 34 | callsPrompt(files.main.source), 35 | 'prompt() should be called in your entry file' 36 | ); 37 | 38 | files.ndjson = idMap[files.main.deps[mainDeps]]; 39 | t.ok( 40 | !callsPrompt(files.ndjson.source), 41 | 'prompt() should not be called in your ndjson file' 42 | ); 43 | 44 | var expected = [ 45 | [{"beep":"boop"},[3,4,5],{"x":4,"y":5}], 46 | '{"a":7,"b":8,"c":9}\n[3,9,1,5]\n555', 47 | [1,2,3], 48 | '7\n8\n9', 49 | [{"a":{"b":{"c":{"d":1234}}}},{"z":567}], 50 | '[5,3,2]\n{"a":{"b":{"c":{"d":1234}}}}' 51 | ]; 52 | run(body, function (res) { 53 | t.deepEqual(res, expected.shift(), 'expected result'); 54 | }); 55 | })); 56 | }); 57 | 58 | exports.run = function (args) { 59 | process.stdin.pipe(concat(function (body) { 60 | run(body, null, true); 61 | })); 62 | }; 63 | 64 | function run (body, cb, info) { 65 | var prompts = [ 66 | '{"beep":"boop"}\n[3,4,5]\n{"x":4,"y":5}', 67 | [ {a:7,b:8,c:9}, [3,9,1,5], 555 ], 68 | 69 | '1\n2\n3', 70 | [ 7, 8, 9 ], 71 | 72 | '{"a":{"b":{"c":{"d":1234}}}}\n{"z":567}', 73 | [[5,3,2],{"a":{"b":{"c":{"d":1234}}}}] 74 | ]; 75 | var prompt = function () { 76 | var p = prompts.shift() 77 | if (info) console.error('INPUT: ' + JSON.stringify(p)); 78 | return p; 79 | }; 80 | var con = { 81 | log: function (msg) { 82 | if (info) { 83 | console.error('OUTPUT: ' + JSON.stringify(msg) + '\n'); 84 | } 85 | if (cb) cb.apply(null, [].slice.call(arguments)) 86 | }, 87 | error: console.error 88 | }; 89 | for (var i = 0; i < 6; i += 2) { 90 | Function(['console','prompt'], body.toString())(con, prompt); 91 | } 92 | } 93 | 94 | function callsPrompt (body) { 95 | var called = false; 96 | falafel(body, function (node) { 97 | if (node.type === 'CallExpression' 98 | && node.callee.type === 'Identifier' 99 | && node.callee.name === 'prompt') { 100 | called = true; 101 | } 102 | }); 103 | return called; 104 | } 105 | -------------------------------------------------------------------------------- /problems/multi_export/problem.txt: -------------------------------------------------------------------------------- 1 | Last time in "single export" we used `module.exports` to export a single 2 | function. There is another form called simply `exports` that you can attach 3 | properties to. This is a less common way of exporting functionality but is 4 | still useful in some circumstances, such as a protocol with `.parse()` and 5 | `.stringify()` functions which are the inverses of each other. 6 | 7 | For this level, call `prompt()` twice from your entry file to obtain 2 8 | values: the first is a string you should parse into an array of objects and 9 | the second value is an array you should serialize back into a string. Print 10 | each return value with `console.log()`. 11 | 12 | In a second file `./ndjson.js`, export a `parse()` and `stringify()` 13 | function by assigning properties onto the `exports` object. 14 | 15 | `.parse(str)` will get a newline-separated string `str` of json stringified 16 | json and should use JSON.parse() to parse each line of json into an array 17 | of results 18 | 19 | `.stringify(rows)` will get an array of `rows` and should return a 20 | newline-separated string of records with JSON.stringify() 21 | 22 | Compile your program with `browserify` starting at your entry file 23 | and pipe the bundle into `$ADVENTURE_COMMAND verify` like this: 24 | 25 | browserify main.js | $ADVENTURE_COMMAND verify 26 | 27 | If you just want to run your solution with the test input without verifying 28 | it you can do: 29 | 30 | browserify main.js | $ADVENTURE_COMMAND run 31 | -------------------------------------------------------------------------------- /problems/multi_export/solution.txt: -------------------------------------------------------------------------------- 1 | Here's the reference solution entry point (main.js): 2 | 3 | var ndjson = require('./ndjson.js'); 4 | 5 | console.log(ndjson.parse(prompt())); 6 | console.log(ndjson.stringify(prompt())); 7 | 8 | and this is the reference ndjson.js: 9 | 10 | exports.parse = function (str) { 11 | return str.split('\n').map(JSON.parse); 12 | }; 13 | 14 | exports.stringify = function (rows) { 15 | return rows.map(JSON.stringify).join('\n'); 16 | }; 17 | 18 | It would have also worked to assign onto `module.exports.parse` and 19 | `module.exports.stringify` since `exports === module.exports`. However, you 20 | cannot assign onto `exports` with `exports = ` like you can with 21 | `module.exports` because the reference will be lost by the module system. 22 | 23 | Assigning a single value onto `module.exports` is more common, especially 24 | for functions and constructors since that usually maps well to each file 25 | doing a single thing well, but assigning properties onto `exports` like 26 | we've done here also has its place. For example, the exercises for 27 | $ADVENTURE_COMMAND are written with export-assignment. 28 | -------------------------------------------------------------------------------- /problems/shimming_non_commonjs_modules/files/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var spiceGirls = require('spice-girls'); 4 | console.log(spiceGirls); 5 | -------------------------------------------------------------------------------- /problems/shimming_non_commonjs_modules/files/spice-girls.js: -------------------------------------------------------------------------------- 1 | var girls = ' Melanie Brown ("Scary Spice"), Melanie Chisholm ("Sporty Spice"), Emma Bunton ("Baby Spice"), Geri Halliwell ("Ginger Spice"), and Victoria Beckham, née Adams ("Posh Spice")'; 2 | window.Girls = girls; 3 | -------------------------------------------------------------------------------- /problems/shimming_non_commonjs_modules/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var verify = require('adventure-verify'); 4 | var unpack = require('browser-unpack'); 5 | var concat = require('concat-stream'); 6 | var girls = ' Melanie Brown ("Scary Spice"), Melanie Chisholm ("Sporty Spice"), Emma Bunton ("Baby Spice"), Geri Halliwell ("Ginger Spice"), and Victoria Beckham, née Adams ("Posh Spice")'; 7 | 8 | var injectWindow = 'var window = {};\n'; 9 | 10 | var spiceGirlsFile = path.join(__dirname, 'files/spice-girls.js'); 11 | var spiceGirlsSrc = fs.readFileSync(spiceGirlsFile, 'utf8'); 12 | 13 | var mainFile = path.join(__dirname, 'files/main.js'); 14 | var mainSrc = fs.readFileSync(mainFile, 'utf8'); 15 | 16 | exports.problem = fs.readFileSync(__dirname + '/problem.txt', 'utf8') 17 | .replace(/\$MAIN_FILE/g, mainFile) 18 | .replace(/\$SPICE_FILE/g, spiceGirlsFile) 19 | ; 20 | exports.solution = fs.createReadStream(__dirname + '/solution.txt'); 21 | 22 | exports.verify = verify({ modeReset: true }, function (args, t) { 23 | t.plan(3); 24 | process.stdin.pipe(concat(function (body) { 25 | 26 | t.equal(spiceGirlsSrc.length, 213, 'You should not edit the spice-girls.js file') 27 | t.equal(mainSrc.length, 81, 'You should not edit the main.js file') 28 | 29 | 30 | try { var rows = unpack(body) } 31 | catch (err) { return t.fail('The input had a syntax error!') } 32 | if (!rows) return t.fail('The input is not a browserify bundle or an error occurred (see above)!'); 33 | 34 | Function(['console'], injectWindow + body.toString())({ 35 | log: function (msg) { t.equal(msg, girls) }, 36 | error: console.error 37 | }); 38 | })); 39 | }); 40 | 41 | exports.run = function (args) { 42 | process.stdin.pipe(concat(function (body) { 43 | Function(injectWindow + body.toString())(); 44 | })); 45 | }; 46 | 47 | -------------------------------------------------------------------------------- /problems/shimming_non_commonjs_modules/problem.txt: -------------------------------------------------------------------------------- 1 | Some libraries are not commonJS compatible and need to be shimmed. In our case 2 | we have library called spice-girls from the 90s which attaches a global to 3 | `Girls` to the window but fails to export it. 4 | 5 | In order to use this library with browserify we need to fix that. 6 | 7 | You learned about and used transforms in earlier problems and now you get to 8 | use another one. 9 | 10 | browserify-shim will export globals defined by libraries for you. 11 | 12 | Let's assume for a moment we have a library inside `./lib/foo.js` that attaches 13 | a global `bar` to the `window`. 14 | We'd first install `browserify-shim` via `npm install browserify-shim` 15 | and then create a `package.json` with the following config: 16 | 17 | ```json 18 | { 19 | "browser": { 20 | "foo": "./lib/foo.js" 21 | }, 22 | "browserify": { 23 | "transform": "browserify-shim" 24 | }, 25 | "browserify-shim": { 26 | "foo": "bar" 27 | } 28 | } 29 | ``` 30 | 31 | This teaches `browserify` where `foo.js` is and also tells it to run the 32 | `browserify-shim` transform. It then informs `browserify-shim` which property 33 | (`bar`) to export from the `window`. 34 | If you want more documentation and examples, please find it here: https://github.com/thlorenz/browserify-shim 35 | 36 | Your task is to create a similar `package.json` to tell `browserify` where the 37 | Spice Girls are and how to transform them to be more modern. 38 | 39 | Create a new directory for your solution and copy these files into it: 40 | 41 | $SPICE_FILE 42 | $MAIN_FILE 43 | 44 | You should only create a package.json. Don't modify the javascript files. 45 | 46 | To verify your solution, from your solution directory do: 47 | 48 | browserify main.js | browserify-adventure verify 49 | 50 | or to run your solution without verifying it, do: 51 | 52 | browserify main.js | browserify-adventure run 53 | -------------------------------------------------------------------------------- /problems/shimming_non_commonjs_modules/solution.txt: -------------------------------------------------------------------------------- 1 | Here is the reference solution package.json: 2 | 3 | { 4 | "browser": { 5 | "spice-girls": "./spice-girls.js" 6 | }, 7 | "browserify": { 8 | "transform": "browserify-shim" 9 | }, 10 | "browserify-shim": { 11 | "spice-girls": "Girls" 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /problems/single_export/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var verify = require('adventure-verify'); 3 | var unpack = require('browser-unpack'); 4 | var concat = require('concat-stream'); 5 | var falafel = require('falafel'); 6 | 7 | exports.problem = fs.createReadStream(__dirname + '/problem.txt'); 8 | exports.solution = fs.createReadStream(__dirname + '/solution.txt'); 9 | 10 | exports.verify = verify({ modeReset: true }, function (args, t) { 11 | t.plan(10); 12 | process.stdin.pipe(concat(function (body) { 13 | try { var rows = unpack(body) } 14 | catch (err) { return t.fail('The input had a syntax error!') } 15 | if (!rows) return t.fail('The input is not a browserify bundle!'); 16 | 17 | t.equal(rows.length, 3, '3 files in the bundle'); 18 | 19 | var files = {}; 20 | var idMap = {}; 21 | for (var i = 0; i < rows.length; i++) { 22 | if (rows[i].entry) { 23 | files.main = rows[i]; 24 | } 25 | idMap[rows[i].id] = rows[i]; 26 | } 27 | if (!files.main) t.fail('No entry file detected') 28 | 29 | var mainDeps = Object.keys(files.main.deps); 30 | t.equal(mainDeps.length, 1); 31 | t.ok(/^\.\//.test(mainDeps[0]), 'relative require'); 32 | 33 | t.ok( 34 | callsPrompt(files.main.source), 35 | 'prompt() should be called in your entry file' 36 | ); 37 | 38 | files.uniquely = idMap[files.main.deps[mainDeps]]; 39 | t.deepEqual( 40 | Object.keys(files.uniquely.deps), [ 'uniq' ], 41 | 'uniquely requires the `uniq` module' 42 | ); 43 | t.ok( 44 | !callsPrompt(files.uniquely.source), 45 | 'prompt() should not be called in your uniquely file' 46 | ); 47 | 48 | var expected = [ 49 | [ 'one', 'two', 'three', 'four' ], 50 | [ '7', '8', '9', '10' ], 51 | [ 'pizza', 'cats', 'in', 'space' ], 52 | [ 'four', 'square', 'and', 'several', 'years', 'ago' ] 53 | ]; 54 | run(body, function (res) { 55 | t.deepEqual( 56 | res.sort(), 57 | expected.shift().sort(), 58 | 'expected result' 59 | ); 60 | }); 61 | })); 62 | }); 63 | 64 | exports.run = function (args) { 65 | process.stdin.pipe(concat(function (body) { 66 | run(body, function (res) { console.log(res) }); 67 | })); 68 | }; 69 | 70 | function run (body, cb) { 71 | var prompts = [ 72 | function () { return 'one,two,three,one,four,two' }, 73 | function () { return '7,7,8,8,9,8,10,7' }, 74 | function () { return 'pizza,cats,in,space,in,space,pizza,cats' }, 75 | function () { 76 | return 'four,square,four,and,several,four,square,years,ago' 77 | } 78 | ]; 79 | var con = { 80 | log: function () { cb.apply(null, [].slice.call(arguments)) }, 81 | error: console.error 82 | }; 83 | prompts.forEach(function (p) { 84 | Function(['console','prompt'], body.toString())(con, p); 85 | }); 86 | } 87 | 88 | function callsPrompt (body) { 89 | var called = false; 90 | falafel(body, function (node) { 91 | if (node.type === 'CallExpression' 92 | && node.callee.type === 'Identifier' 93 | && node.callee.name === 'prompt') { 94 | called = true; 95 | } 96 | }); 97 | return called; 98 | } 99 | -------------------------------------------------------------------------------- /problems/single_export/problem.txt: -------------------------------------------------------------------------------- 1 | In the last adventure we used a package from npm. This time, we will factor 2 | our code into 2 files using a relative `require()` call and 3 | `module.exports`. 4 | 5 | First, create a file called `uniquely.js`. In this file, assign a function 6 | to `module.exports` that takes a comma-separated string as input and 7 | returns a uniq list as output. Use the `uniq` module like from the "using 8 | npm packages" level. 9 | 10 | Now in your entry file (the file that you pass to the browserify command), 11 | use `require()` with a relative path string to your `'./uniquely.js'` 12 | file. 13 | 14 | When you `require()` the `uniquely.js` file, `require()` will return 15 | whatever value you set `module.exports` to in `uniquely.js`. Call the 16 | exported function with the value from `prompt()` as an argument and print 17 | the return value with `console.log()`. 18 | 19 | Compile your program with `browserify` starting at your entry file (NOT 20 | starting from uniquely.js) and pipe the bundle into 21 | `$ADVENTURE_COMMAND verify` like this: 22 | 23 | browserify main.js | $ADVENTURE_COMMAND verify 24 | 25 | If you just want to run your solution with the test input without verifying 26 | it you can do: 27 | 28 | browserify main.js | $ADVENTURE_COMMAND run 29 | -------------------------------------------------------------------------------- /problems/single_export/solution.txt: -------------------------------------------------------------------------------- 1 | Here's the reference solution entry point (main.js): 2 | 3 | var uniquely = require('./uniquely.js'); 4 | var result = uniquely(prompt()); 5 | console.log(result); 6 | 7 | and this is the reference uniquely.js: 8 | 9 | var uniq = require('uniq'); 10 | 11 | module.exports = function (str) { 12 | return uniq(str.split(',')); 13 | }; 14 | 15 | As we've just seen, `module` is a special variable pre-defined in node and 16 | browserify for each file. We used `module.exports` to export a single 17 | function from `uniquely.js` that we used from our entry point, main.js. 18 | 19 | browserify concatenated all the files together into a single file that 20 | can be shipped to browsers, even though there are multiple files being 21 | loaded. 22 | 23 | We also saw the difference between loading local packages with a relative 24 | path (`require('./uniquely.js'`) and loading packages that we installed 25 | with npm (`require('uniq')`). 26 | -------------------------------------------------------------------------------- /problems/using_npm_packages/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var verify = require('adventure-verify'); 3 | var unpack = require('browser-unpack'); 4 | var concat = require('concat-stream'); 5 | 6 | exports.problem = fs.createReadStream(__dirname + '/problem.txt'); 7 | exports.solution = fs.createReadStream(__dirname + '/solution.txt'); 8 | 9 | exports.verify = verify({ modeReset: true }, function (args, t) { 10 | t.plan(10); 11 | process.stdin.pipe(concat(function (body) { 12 | try { var rows = unpack(body) } 13 | catch (err) { return t.fail('The input had a syntax error!') } 14 | if (!rows) return t.fail('The input is not a browserify bundle!'); 15 | 16 | t.equal(rows.length, 2, '2 files'); 17 | 18 | var main, uniq; 19 | if (rows[0].entry) { 20 | main = rows[0]; 21 | uniq = rows[1]; 22 | } 23 | else if (rows[1].entry) { 24 | main = rows[1]; 25 | uniq = rows[0]; 26 | } 27 | else t.fail('No entry file detected') 28 | 29 | t.deepEqual( 30 | Object.keys(main.deps), ['uniq'], 31 | 'entry file has a single "uniq" dependency' 32 | ); 33 | 34 | var con = { 35 | log: function (msg) { 36 | t.equal( 37 | Object.prototype.toString.call(msg), 38 | '[object Array]', 39 | 'argument to console.log() is an array' 40 | ); 41 | t.deepEqual(msg.sort(), expected.shift().sort()); 42 | }, 43 | error: console.error 44 | }; 45 | var prompts = [ 46 | function () { return 'one,two,three,one,four,two' }, 47 | function () { return '7,7,8,8,9,8,10,7' }, 48 | function () { return 'pizza,cats,in,space,in,space,pizza,cats' }, 49 | function () { 50 | return 'four,square,four,and,several,four,square,years,ago' 51 | } 52 | ]; 53 | var expected = [ 54 | [ 'one', 'two', 'three', 'four' ], 55 | [ '7', '8', '9', '10' ], 56 | [ 'pizza', 'cats', 'in', 'space' ], 57 | [ 'four', 'square', 'and', 'several', 'years', 'ago' ] 58 | ]; 59 | prompts.forEach(function (p) { 60 | Function(['console','prompt'], body.toString())(con, p); 61 | }); 62 | })); 63 | }); 64 | 65 | exports.run = function (args) { 66 | process.stdin.pipe(concat(function (body) { 67 | var prompts = [ 68 | function () { return 'one,two,three,one,four,two' }, 69 | function () { return '7,7,8,8,9,8,10,7' }, 70 | function () { return 'pizza,cats,in,space,in,space,pizza,cats' }, 71 | function () { 72 | return 'four,square,four,and,several,four,square,years,ago' 73 | } 74 | ]; 75 | prompts.forEach(function (p) { 76 | Function(['prompt'], body.toString())(p); 77 | }); 78 | })); 79 | }; 80 | -------------------------------------------------------------------------------- /problems/using_npm_packages/problem.txt: -------------------------------------------------------------------------------- 1 | Now that we have browserify up and running, let's use a module from npm! 2 | 3 | First install the `uniq` module from npm by doing: 4 | 5 | npm install uniq 6 | 7 | npm should have put uniq into `./node_modules/uniq`. browserify uses the 8 | same module-lookup algorithm as node.js, so you can just `require('uniq')` 9 | just like you would do in node.js! 10 | 11 | `require('uniq')` returns a `uniq(xs)` function that removes duplicate 12 | items from an array input `xs`. 13 | 14 | You will also need `prompt()`, a built in function available to browsers that 15 | asks the user to enter some text, and returns a string. 16 | 17 | For this level, use `prompt()` to fetch a string. Split the string that 18 | `prompt()` returns by commas (`str.split(',')` returns a separated array of 19 | strings) and run this array through `uniq()` to discard repeated items. 20 | Use `console.log()` to print the resulting uniq array. 21 | 22 | Compile your program with `browserify` and pipe the bundle into 23 | `$ADVENTURE_COMMAND verify` like this: 24 | 25 | browserify main.js | $ADVENTURE_COMMAND verify 26 | 27 | If you just want to run your solution with the test input without verifying 28 | it you can do: 29 | 30 | browserify main.js | $ADVENTURE_COMMAND run 31 | -------------------------------------------------------------------------------- /problems/using_npm_packages/solution.txt: -------------------------------------------------------------------------------- 1 | Here's the reference solution: 2 | 3 | var uniq = require('uniq'); 4 | var list = prompt('enter a list').split(','); 5 | console.log(uniq(list)); 6 | 7 | When we compiled with browserify starting at the entry file, browserify saw 8 | the `require('uniq')` and placed the uniq module into that concatenated 9 | output. 10 | -------------------------------------------------------------------------------- /problems/using_transforms/expected.txt: -------------------------------------------------------------------------------- 1 | 0 Be that as it may, but for that light phantastic of his gnose's glow as it 2 | slid lucifericiously within an inch of its page (he touch at its from time 3 | to other, the red eye of his fear in saddishness, to ensign the colours by 4 | the beerlitz in his mathness and his educandees to outhue to themselves in 5 | the cries of girl-glee: gember! inkware! chonchambre! cinsero! zinnzabar! 6 | 5 tincture and gin!) Nibs never would have quilled a seriph to sheepskin. By 7 | that rosy lampoon's effluvious burning and with help of the simulchronic 8 | flush in his pann (a ghinee a ghirk he ghets there!) he scrabbled and 9 | scratched and scriobbled and skrevened namelesss shamelessness about 10 | everybody ever he met, even sharing a precipitation under the idlish 11 | 10 tarriers' umbrella of a showerproof wall, while all over up and down the 12 | four margins of this rancid Shem stuff the evilsmeller (who was devoted to 13 | Uldfadar Sardanapalus) used to stipple endlessly inartistic portraits of 14 | himself in the act of reciting old Nichiabilli's monolook interyerear 15 | Hanno, o Nonanno, acce'l brubblemm'as, ser Autore, q.e.d., a 16 | 15 heartbreakingly handsome young paolo with love lyrics for the goyls in his 17 | eyols, a plain-tiff's tanner vuice, a jucal inkome of one hundred and 18 | thirtytwo dranchmas per yard from Broken Hill stranded estate, Camebreech 19 | mannings, cutting a great dash in a brandnew two guinea dress suit and a 20 | burled hogsford hired for a Fursday evenin merry pawty, anna loavely long 21 | 20 pair of inky Italian moostarshes glistering with boric vaseline and 22 | frangipani. Puh! How unwhisperably so! 23 | 24 | -------------------------------------------------------------------------------- /problems/using_transforms/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var verify = require('adventure-verify'); 4 | var concat = require('concat-stream'); 5 | 6 | var wakeFile = path.join(__dirname, 'wake.txt'); 7 | var expected = fs.readFileSync(__dirname + '/expected.txt', 'utf8'); 8 | 9 | exports.problem = fs.readFileSync(__dirname + '/problem.txt', 'utf8') 10 | .replace(/\$WAKE_FILE/g, wakeFile) 11 | ; 12 | exports.solution = fs.readFileSync(__dirname + '/solution.txt', 'utf8') 13 | .replace(/\$WAKE_FILE/g, wakeFile) 14 | ; 15 | 16 | exports.verify = verify({ modeReset: true }, function (args, t) { 17 | process.stdin.pipe(concat(function (body) { 18 | var result = []; 19 | var con = { 20 | log: function (msg) { result.push(msg) }, 21 | error: console.error 22 | }; 23 | Function(['console'],body)(con); 24 | 25 | var lines = result.join('\n').trim().split('\n'); 26 | var xlines = expected.trim().split('\n'); 27 | var len = Math.max(lines.length, xlines.length); 28 | t.plan(len); 29 | for (var i = 0; i < len; i++) { 30 | t.equal(lines[i], xlines[i]); 31 | } 32 | })); 33 | }); 34 | 35 | exports.run = function (args) { 36 | process.stdin.pipe(concat(function (body) { 37 | Function(body)(); 38 | })); 39 | }; 40 | -------------------------------------------------------------------------------- /problems/using_transforms/problem.txt: -------------------------------------------------------------------------------- 1 | For this problem, we will use a browserify transform to load a text file 2 | and then we will apply some processing to the text file. Browserify 3 | transforms are used to pre-process input source files into something that 4 | browserify can read. For example, there are transforms for coffeescript 5 | (coffeeify), es6 syntax (es6ify), and more on npm! 6 | 7 | The text file you should read into your file is located at: 8 | $WAKE_FILE 9 | 10 | Use `brfs` to read that text file and print out the text file with leading 11 | line numbers every 5th line with line numbers starting at 0, inclusive of 0. 12 | 13 | You should pad the line number to 3 characters followed by a single space, 14 | followed by the original line contents. For lines without printed line 15 | numbers, print 4 spaces followed by the original line contents. 16 | 17 | Protip: you can use the sprintf module (`npm install sprintf`) with a 18 | format string of `"%3d %s"` to print the line number with the line in the 19 | proper format. 20 | 21 | For example, your output should look something like this: 22 | 23 | everybody ever he met, even sharing a precipitation under the idlish 24 | 10 tarriers' umbrella of a showerproof wall, while all over up and down 25 | the four margins of this rancid Shem stuff the evilsmeller (who was 26 | devoted to Uldfadar Sardanapalus) used to stipple endlessly inartistic 27 | portraits of himself in the act of reciting old Nichiabilli's monolook 28 | interyerear Hanno, o Nonanno, acce'l brubblemm'as, ser Autore, q.e.d., 29 | 15 a heartbreakingly handsome young paolo with love lyrics for the goyls 30 | 31 | With the `brfs` transform, you can load files with `fs.readFileSync()` and 32 | the results will be preprocessed into an inline string. For example, if you 33 | have: 34 | 35 | var fs = require('fs'); 36 | var src = fs.readFileSync('beep.txt', 'utf8'); 37 | console.log(src); 38 | 39 | and you apply the `brfs` transform, then after the transformation the code 40 | becomes: 41 | 42 | var src = "beep boop!\n"; 43 | console.log(src); 44 | 45 | To turn on a transform, use `-t brfs` when compiling browserify and make 46 | sure you install brfs with npm first: 47 | 48 | npm install brfs 49 | 50 | Compile your program with `browserify -t brfs main.js` and pipe the bundle 51 | output into `$ADVENTURE_COMMAND verify` like this: 52 | 53 | browserify -t brfs main.js | $ADVENTURE_COMMAND verify 54 | 55 | If you just want to run your solution without verifying you can do: 56 | 57 | browserify -t brfs main.js | $ADVENTURE_COMMAND run 58 | -------------------------------------------------------------------------------- /problems/using_transforms/solution.txt: -------------------------------------------------------------------------------- 1 | Here's the reference solution: 2 | 3 | var fs = require('fs'); 4 | var txt = fs.readFileSync('$WAKE_FILE', 'utf8'); 5 | var sprintf = require('sprintf'); 6 | 7 | var lines = txt.split('\n'); 8 | lines.forEach(function (line, index) { 9 | if (index % 5 === 0) { 10 | console.log(sprintf('%3d %s', index, line)); 11 | } 12 | else { 13 | console.log(' ' + line); 14 | } 15 | }); 16 | 17 | We use brfs to inline that `fs.readFileSync()` call and then we walk over 18 | each of the lines in the file, printing leading line numbers with some help 19 | from sprintf. 20 | 21 | There are tons of transforms on npm for doing just about whatever you need! 22 | Check out the list here: 23 | https://npmjs.org/browse/keyword/browserify-transform 24 | -------------------------------------------------------------------------------- /problems/using_transforms/wake.txt: -------------------------------------------------------------------------------- 1 | Be that as it may, but for that light phantastic of his gnose's glow as it 2 | slid lucifericiously within an inch of its page (he touch at its from time 3 | to other, the red eye of his fear in saddishness, to ensign the colours by 4 | the beerlitz in his mathness and his educandees to outhue to themselves in 5 | the cries of girl-glee: gember! inkware! chonchambre! cinsero! zinnzabar! 6 | tincture and gin!) Nibs never would have quilled a seriph to sheepskin. By 7 | that rosy lampoon's effluvious burning and with help of the simulchronic 8 | flush in his pann (a ghinee a ghirk he ghets there!) he scrabbled and 9 | scratched and scriobbled and skrevened namelesss shamelessness about 10 | everybody ever he met, even sharing a precipitation under the idlish 11 | tarriers' umbrella of a showerproof wall, while all over up and down the 12 | four margins of this rancid Shem stuff the evilsmeller (who was devoted to 13 | Uldfadar Sardanapalus) used to stipple endlessly inartistic portraits of 14 | himself in the act of reciting old Nichiabilli's monolook interyerear 15 | Hanno, o Nonanno, acce'l brubblemm'as, ser Autore, q.e.d., a 16 | heartbreakingly handsome young paolo with love lyrics for the goyls in his 17 | eyols, a plain-tiff's tanner vuice, a jucal inkome of one hundred and 18 | thirtytwo dranchmas per yard from Broken Hill stranded estate, Camebreech 19 | mannings, cutting a great dash in a brandnew two guinea dress suit and a 20 | burled hogsford hired for a Fursday evenin merry pawty, anna loavely long 21 | pair of inky Italian moostarshes glistering with boric vaseline and 22 | frangipani. Puh! How unwhisperably so! 23 | -------------------------------------------------------------------------------- /problems/widget_with_assets/browser.js: -------------------------------------------------------------------------------- 1 | var shoe = require('shoe'); 2 | var sock = shoe('/sock'); 3 | var ever = require('ever'); 4 | 5 | var Widget = require('widget'); 6 | var w = Widget(); 7 | var elem = document.createElement('div'); 8 | document.body.appendChild(elem); 9 | w.appendTo(elem); 10 | var form = elem.querySelector('form'); 11 | 12 | w.on('message', function (msg) { 13 | sock.write(JSON.stringify(['msg',msg]) + '\n'); 14 | }); 15 | var txt = form.querySelector('textarea[name="msg"]') 16 | txt.value = 'howdee pardner'; 17 | ever(form).emit('submit'); 18 | 19 | var style = window.getComputedStyle(txt); 20 | sock.write(JSON.stringify(['bg',style.backgroundColor]) + '\n'); 21 | sock.write(JSON.stringify(['fg',style.color]) + '\n'); 22 | 23 | sock.write(JSON.stringify(['style',true]) + '\n'); 24 | -------------------------------------------------------------------------------- /problems/widget_with_assets/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var verify = require('adventure-verify'); 4 | var concat = require('concat-stream'); 5 | var http = require('http'); 6 | var shoe = require('shoe'); 7 | var ecstatic = require('ecstatic')(path.join(__dirname, 'static')); 8 | var split = require('split'); 9 | var through = require('through2'); 10 | var colorname = require('colornames'); 11 | 12 | exports.problem = fs.createReadStream(__dirname + '/problem.txt'); 13 | exports.solution = fs.createReadStream(__dirname + '/solution.txt'); 14 | 15 | exports.verify = verify({ modeReset: true }, function (args, t) { 16 | var expected = [ 17 | [ [ 'msg', 'howdee pardner' ], 'emits the message event' ], 18 | [ [ 'bg', 'purple' ], 'textarea background set to purple' ], 19 | [ [ 'fg', 'yellow' ], 'textarea foreground set to yellow' ], 20 | [ [ 'style', true ], 'using a style tag' ] 21 | ]; 22 | t.plan(expected.length); 23 | process.stdin.pipe(concat(function (body) { 24 | createServer(body, t).pipe(through.obj(function (row, enc, next) { 25 | var xex = expected.shift(); 26 | var ex = xex[0], desc = xex[1]; 27 | 28 | if (row[0] === 'fg' || row[0] === 'bg') { 29 | if (ex[1] !== row[1] && /^#/.test(row[1])) { 30 | ex[1] = colorname(ex[1]); 31 | } 32 | else if (ex[1] !== row[1] && /^rgb/.test(row[1])) { 33 | var rgb = colorname(ex[1]).match(/\w{2}/g); 34 | ex[1] = 'rgb(' + rgb.map(function (s) { 35 | return parseInt(s, 16); 36 | }).join(',') + ')'; 37 | row[1] = row[1].replace(/\s+/g, ''); 38 | } 39 | } 40 | t.deepEqual(row, ex, desc); 41 | next(); 42 | })); 43 | })); 44 | }); 45 | 46 | function createServer (body, t) { 47 | var output = through.obj(); 48 | var server = http.createServer(function (req, res) { 49 | if (req.url === '/code.js') { 50 | res.end(body); 51 | } 52 | else ecstatic(req, res) 53 | }); 54 | server.listen(55501, function () { 55 | console.log('Web server running. Visit this URL:' 56 | + ' http://localhost:' + server.address().port 57 | ); 58 | }); 59 | var sock = shoe(function (stream) { 60 | stream.pipe(split()).pipe(through.obj(function (buf, enc, next) { 61 | var line = buf.toString('utf8'); 62 | try { var row = JSON.parse(line) } 63 | catch (err) { 64 | if (t) return t.fail(err) 65 | else console.error(err); 66 | } 67 | 68 | this.push(row); 69 | next(); 70 | })).pipe(output); 71 | 72 | if (t) t.once('end', function () { stream.end() }); 73 | }); 74 | 75 | if (t) t.once('end', function () { 76 | server.close(); 77 | setTimeout(function () { 78 | process.exit(); 79 | }, 100); 80 | }); 81 | sock.install(server, '/sock'); 82 | return output; 83 | } 84 | 85 | exports.run = function (args) { 86 | process.stdin.pipe(concat(function (body) { 87 | createServer(body).pipe(through.obj(function (row, enc, next) { 88 | console.log(row); 89 | next(); 90 | })); 91 | })); 92 | }; 93 | -------------------------------------------------------------------------------- /problems/widget_with_assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "browserify -d -x widget browser.js -o static/bundle.js" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /problems/widget_with_assets/problem.txt: -------------------------------------------------------------------------------- 1 | This time you will create a widget to control a form with a text area. 2 | Your code will be called with something like this: 3 | 4 | var Widget = require('widget'); 5 | var w = Widget(); 6 | w.appendTo(document.body); 7 | 8 | w.on('message', function (msg) { 9 | console.log('sending message: ' + msg); 10 | }); 11 | 12 | Your widget should have a form with a textarea with name="msg" in it. 13 | Here's the HTML you can use: 14 | 15 |
16 | 17 | 18 |
19 | 20 | Your widget should also use css to set the textarea background color to 21 | "purple" and the foreground color to "yellow". Here is the css you can use 22 | for that: 23 | 24 | .send textarea { 25 | background-color: purple; 26 | color: yellow; 27 | } 28 | 29 | You can use brfs to load each of these files as strings into your widget 30 | module. To insert the css into the dom, check out the `insert-css` package 31 | from npm. 32 | 33 | Like before, you can use the `domify` package from npm to turn the html 34 | string into an html element. Implement an `.appendTo(target)` function that 35 | adds your dom element to the target with `target.appendChild(elem)`. 36 | You can use `elem.querySelector(selector)` to query for references to dom 37 | elements using a css selector string. 38 | 39 | Finally, your widget should register a form "submit" listener with 40 | `.addEventListener()` on your html form element. `.addEventListener()` is 41 | part of the dom and you can use it like this: 42 | 43 | elem.addEventListener(evname, function (ev) { 44 | ev.preventDefault(); 45 | // ... 46 | }); 47 | 48 | The `ev.preventDefault()` prevents the browser's default handlers from 49 | taking over so that you can implement custom functionality. 50 | 51 | Your widget should also have an event-emitter API to emit a `"message"` 52 | event with the textarea content when the user submits the form. 53 | 54 | To implement an event-emitter API, you can either return a `new 55 | EventEmitter` from the `events` package that is built into browserify and 56 | node, or you can use the `inherits` package like this: 57 | 58 | var EventEmitter = require('events').EventEmitter; 59 | var inherits = require('inherits'); 60 | 61 | module.exports = Widget; 62 | inherits(Widget, EventEmitter); 63 | 64 | function Widget () { 65 | if (!(this instanceof Widget)) return new Widget; 66 | } 67 | 68 | then to emit an event, just do `this.emit("evname", args...)`. 69 | 70 | Here are the modules you might need for this level: 71 | 72 | npm install brfs domify insert-css inherits 73 | 74 | If you use brfs, compile your program with: 75 | 76 | browserify -t brfs -d -r ./widget.js:widget | $ADVENTURE_COMMAND verify 77 | 78 | If you just want to run your solution without verification, do: 79 | 80 | browserify -t brfs -d -r ./widget.js:widget | $ADVENTURE_COMMAND run 81 | -------------------------------------------------------------------------------- /problems/widget_with_assets/solution.txt: -------------------------------------------------------------------------------- 1 | Here's the reference solution: 2 | 3 | var fs = require('fs'); 4 | var domify = require('domify'); 5 | var insertCss = require('insert-css'); 6 | var inherits = require('inherits'); 7 | var EventEmitter = require('events').EventEmitter; 8 | 9 | var html = fs.readFileSync(__dirname + '/widget.html', 'utf8'); 10 | var css = fs.readFileSync(__dirname + '/widget.css', 'utf8'); 11 | insertCss(css); 12 | 13 | module.exports = Widget; 14 | inherits(Widget, EventEmitter); 15 | 16 | function Widget () { 17 | var self = this; 18 | if (!(this instanceof Widget)) return new Widget; 19 | var form = this.element = domify(html); 20 | 21 | form.addEventListener('submit', function (ev) { 22 | ev.preventDefault(); 23 | var msg = form.querySelector('textarea[name="msg"]').value; 24 | self.emit('message', msg); 25 | }); 26 | } 27 | 28 | Widget.prototype.appendTo = function (target) { 29 | target.appendChild(this.element); 30 | }; 31 | 32 | There are lots of ways to complete this level, so your solution might look 33 | quite different! 34 | 35 | In this solution, we create a `Widget` constructor with an `.appendTo()` 36 | method. We use `brfs` to load html and css files. We use `domify` to turn 37 | those html strings into html elements and `insert-css` to insert the css 38 | file into the dom in a `