├── .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 |
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 `