├── .gitignore
├── Gruntfile.js
├── README.md
├── console-log-div.js
├── index.html
├── marked.js
├── package.json
├── step-0
├── console-log-div.js
├── index.html
├── micro-angular.js
└── primes.js
├── step-1
├── console-log-div.js
├── index.html
├── micro-angular-worker.js
├── micro-angular.js
├── mock-scopes.js
├── primes.js
└── worker.js
└── step-2
├── console-log-div.js
├── index.html
├── micro-angular-worker.js
├── micro-angular.js
├── mock-scopes.js
├── primes.js
└── worker.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .grunt/
3 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*global module:false*/
2 | module.exports = function (grunt) {
3 | var files = ['console-log-div.js'];
4 |
5 | grunt.initConfig({
6 |
7 | 'gh-pages': {
8 | options: {
9 | base: '.'
10 | },
11 | src: [
12 | 'README.md',
13 | 'marked.js',
14 | 'index.html',
15 | 'step-0/*',
16 | 'step-1/*',
17 | 'step-2/*'
18 | ]
19 | }
20 | });
21 |
22 | var plugins = module.require('matchdep').filterDev('grunt-*');
23 | plugins.forEach(grunt.loadNpmTasks);
24 |
25 | grunt.registerTask('deploy', ['gh-pages']);
26 | };
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # web-worker-digest-demo
2 |
3 | See blog post [Run Angular digest cycle in web worker][post].
4 |
5 | * [step-0][step-0] - initial sync implementation
6 | * [step-1][step-1] - micro-angular in web worker implementation
7 | * [step-2][step-2] - restored plain property syntax using `Object.observe`
8 |
9 | Related: code repo with more steps used to write the blog post [here][digest cycle]
10 |
11 | [post]: (http://glebbahmutov.com/blog/run-angular-digest-cycle-in-web-worker/)
12 | [step-0]: step-0
13 | [step-1]: step-1
14 | [step-2]: step-2
15 | [digest cycle]: https://github.com/bahmutov/digest-cycle-in-web-worker
16 |
--------------------------------------------------------------------------------
/console-log-div.js:
--------------------------------------------------------------------------------
1 | (function initConsoleLogDiv() {
2 |
3 | if (console.log.toDiv) {
4 | return;
5 | }
6 |
7 | function toString(x) {
8 | return typeof x === 'string' ? x : JSON.stringify(x);
9 | }
10 |
11 | var log = console.log.bind(console);
12 | var error = console.error.bind(console);
13 | var warn = console.warn.bind(console);
14 |
15 | var id = 'console-log-div';
16 | function createOuterElement() {
17 | var outer = document.getElementById(id);
18 | if (!outer) {
19 | outer = document.createElement('fieldset');
20 | outer.id = id;
21 | document.body.appendChild(outer);
22 | }
23 | outer.classList.add('id');
24 |
25 | var style = outer.style;
26 | style.width = '100%';
27 | // style.minHeight = '200px';
28 | style.fontFamily = 'monospace';
29 | style.marginTop = '20px';
30 | style.whiteSpace = 'pre';
31 | style.border = '1px solid black';
32 | style.borderRadius = '5px';
33 | style.padding = '5px 10px';
34 | return outer;
35 | }
36 |
37 | var logTo = (function createLogDiv() {
38 |
39 | var outer = createOuterElement();
40 |
41 | var caption = document.createTextNode('console output');
42 | var legend = document.createElement('legend');
43 | legend.appendChild(caption);
44 | outer.appendChild(legend);
45 |
46 | var div = document.createElement('div');
47 | div.id = 'console-log-text';
48 | outer.appendChild(div);
49 |
50 | return div;
51 | }());
52 |
53 | function printToDiv() {
54 | var msg = Array.prototype.slice.call(arguments, 0)
55 | .map(toString)
56 | .join(' ');
57 | var text = logTo.textContent;
58 | logTo.textContent = text + msg + '\n';
59 | }
60 |
61 | function logWithCopy() {
62 | log.apply(null, arguments);
63 | printToDiv.apply(null, arguments);
64 | }
65 |
66 | console.log = logWithCopy;
67 | console.log.toDiv = true;
68 |
69 | console.error = function errorWithCopy() {
70 | error.apply(null, arguments);
71 | var args = Array.prototype.slice.call(arguments, 0);
72 | args.unshift('ERROR:');
73 | printToDiv.apply(null, args);
74 | };
75 |
76 | console.warn = function logWarning() {
77 | warn.apply(null, arguments);
78 | var args = Array.prototype.slice.call(arguments, 0);
79 | args.unshift('WARNING:');
80 | printToDiv.apply(null, args);
81 | };
82 |
83 | window.addEventListener('error', function (err) {
84 | printToDiv('EXCEPTION:', err.message + '\n ' + err.filename, err.lineno + ':' + err.colno);
85 | });
86 |
87 | }());
88 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Run digest cycle in web worker
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
22 |
23 |
24 |
25 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/marked.js:
--------------------------------------------------------------------------------
1 | /**
2 | * marked - a markdown parser
3 | * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
4 | * https://github.com/chjj/marked
5 | */
6 |
7 | ;
8 | (function() {
9 |
10 | /**
11 | * Block-Level Grammar
12 | */
13 |
14 | var block = {
15 | newline: /^\n+/,
16 | code: /^( {4}[^\n]+\n*)+/,
17 | fences: noop,
18 | hr: /^( *[-*_]){3,} *(?:\n+|$)/,
19 | heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
20 | nptable: noop,
21 | lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
22 | blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
23 | list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
24 | html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
25 | def: /^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
26 | table: noop,
27 | paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
28 | text: /^[^\n]+/
29 | };
30 |
31 | block.bullet = /(?:[*+-]|\d+\.)/;
32 | block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
33 | block.item = replace(block.item, 'gm')
34 | (/bull/g, block.bullet)
35 | ();
36 |
37 | block.list = replace(block.list)
38 | (/bull/g, block.bullet)
39 | ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
40 | ();
41 |
42 | block._tag = '(?!(?:' + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b';
43 |
44 | block.html = replace(block.html)
45 | ('comment', //)
46 | ('closed', /<(tag)[\s\S]+?<\/\1>/)
47 | ('closing', /])*?>/)
48 | (/tag/g, block._tag)
49 | ();
50 |
51 | block.paragraph = replace(block.paragraph)
52 | ('hr', block.hr)
53 | ('heading', block.heading)
54 | ('lheading', block.lheading)
55 | ('blockquote', block.blockquote)
56 | ('tag', '<' + block._tag)
57 | ('def', block.def)
58 | ();
59 |
60 | /**
61 | * Normal Block Grammar
62 | */
63 |
64 | block.normal = merge({}, block);
65 |
66 | /**
67 | * GFM Block Grammar
68 | */
69 |
70 | block.gfm = merge({}, block.normal, {
71 | fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
72 | paragraph: /^/
73 | });
74 |
75 | block.gfm.paragraph = replace(block.paragraph)
76 | ('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|' + block.list.source.replace('\\1', '\\3') + '|')
77 | ();
78 |
79 | /**
80 | * GFM + Tables Block Grammar
81 | */
82 |
83 | block.tables = merge({}, block.gfm, {
84 | nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
85 | table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
86 | });
87 |
88 | /**
89 | * Block Lexer
90 | */
91 |
92 | function Lexer(options) {
93 | this.tokens = [];
94 | this.tokens.links = {};
95 | this.options = options || marked.defaults;
96 | this.rules = block.normal;
97 |
98 | if (this.options.gfm) {
99 | if (this.options.tables) {
100 | this.rules = block.tables;
101 | } else {
102 | this.rules = block.gfm;
103 | }
104 | }
105 | }
106 |
107 | /**
108 | * Expose Block Rules
109 | */
110 |
111 | Lexer.rules = block;
112 |
113 | /**
114 | * Static Lex Method
115 | */
116 |
117 | Lexer.lex = function(src, options) {
118 | var lexer = new Lexer(options);
119 | return lexer.lex(src);
120 | };
121 |
122 | /**
123 | * Preprocessing
124 | */
125 |
126 | Lexer.prototype.lex = function(src) {
127 | src = src
128 | .replace(/\r\n|\r/g, '\n')
129 | .replace(/\t/g, ' ')
130 | .replace(/\u00a0/g, ' ')
131 | .replace(/\u2424/g, '\n');
132 |
133 | return this.token(src, true);
134 | };
135 |
136 | /**
137 | * Lexing
138 | */
139 |
140 | Lexer.prototype.token = function(src, top) {
141 | var src = src.replace(/^ +$/gm, ''),
142 | next, loose, cap, bull, b, item, space, i, l;
143 |
144 | while (src) {
145 | // newline
146 | if (cap = this.rules.newline.exec(src)) {
147 | src = src.substring(cap[0].length);
148 | if (cap[0].length > 1) {
149 | this.tokens.push({
150 | type: 'space'
151 | });
152 | }
153 | }
154 |
155 | // code
156 | if (cap = this.rules.code.exec(src)) {
157 | src = src.substring(cap[0].length);
158 | cap = cap[0].replace(/^ {4}/gm, '');
159 | this.tokens.push({
160 | type: 'code',
161 | text: !this.options.pedantic ? cap.replace(/\n+$/, '') : cap
162 | });
163 | continue;
164 | }
165 |
166 | // fences (gfm)
167 | if (cap = this.rules.fences.exec(src)) {
168 | src = src.substring(cap[0].length);
169 | this.tokens.push({
170 | type: 'code',
171 | lang: cap[2],
172 | text: cap[3]
173 | });
174 | continue;
175 | }
176 |
177 | // heading
178 | if (cap = this.rules.heading.exec(src)) {
179 | src = src.substring(cap[0].length);
180 | this.tokens.push({
181 | type: 'heading',
182 | depth: cap[1].length,
183 | text: cap[2]
184 | });
185 | continue;
186 | }
187 |
188 | // table no leading pipe (gfm)
189 | if (top && (cap = this.rules.nptable.exec(src))) {
190 | src = src.substring(cap[0].length);
191 |
192 | item = {
193 | type: 'table',
194 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
195 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
196 | cells: cap[3].replace(/\n$/, '').split('\n')
197 | };
198 |
199 | for (i = 0; i < item.align.length; i++) {
200 | if (/^ *-+: *$/.test(item.align[i])) {
201 | item.align[i] = 'right';
202 | } else if (/^ *:-+: *$/.test(item.align[i])) {
203 | item.align[i] = 'center';
204 | } else if (/^ *:-+ *$/.test(item.align[i])) {
205 | item.align[i] = 'left';
206 | } else {
207 | item.align[i] = null;
208 | }
209 | }
210 |
211 | for (i = 0; i < item.cells.length; i++) {
212 | item.cells[i] = item.cells[i].split(/ *\| */);
213 | }
214 |
215 | this.tokens.push(item);
216 |
217 | continue;
218 | }
219 |
220 | // lheading
221 | if (cap = this.rules.lheading.exec(src)) {
222 | src = src.substring(cap[0].length);
223 | this.tokens.push({
224 | type: 'heading',
225 | depth: cap[2] === '=' ? 1 : 2,
226 | text: cap[1]
227 | });
228 | continue;
229 | }
230 |
231 | // hr
232 | if (cap = this.rules.hr.exec(src)) {
233 | src = src.substring(cap[0].length);
234 | this.tokens.push({
235 | type: 'hr'
236 | });
237 | continue;
238 | }
239 |
240 | // blockquote
241 | if (cap = this.rules.blockquote.exec(src)) {
242 | src = src.substring(cap[0].length);
243 |
244 | this.tokens.push({
245 | type: 'blockquote_start'
246 | });
247 |
248 | cap = cap[0].replace(/^ *> ?/gm, '');
249 |
250 | // Pass `top` to keep the current
251 | // "toplevel" state. This is exactly
252 | // how markdown.pl works.
253 | this.token(cap, top);
254 |
255 | this.tokens.push({
256 | type: 'blockquote_end'
257 | });
258 |
259 | continue;
260 | }
261 |
262 | // list
263 | if (cap = this.rules.list.exec(src)) {
264 | src = src.substring(cap[0].length);
265 | bull = cap[2];
266 |
267 | this.tokens.push({
268 | type: 'list_start',
269 | ordered: bull.length > 1
270 | });
271 |
272 | // Get each top-level item.
273 | cap = cap[0].match(this.rules.item);
274 |
275 | next = false;
276 | l = cap.length;
277 | i = 0;
278 |
279 | for (; i < l; i++) {
280 | item = cap[i];
281 |
282 | // Remove the list item's bullet
283 | // so it is seen as the next token.
284 | space = item.length;
285 | item = item.replace(/^ *([*+-]|\d+\.) +/, '');
286 |
287 | // Outdent whatever the
288 | // list item contains. Hacky.
289 | if (~item.indexOf('\n ')) {
290 | space -= item.length;
291 | item = !this.options.pedantic ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') : item.replace(/^ {1,4}/gm, '');
292 | }
293 |
294 | // Determine whether the next list item belongs here.
295 | // Backpedal if it does not belong in this list.
296 | if (this.options.smartLists && i !== l - 1) {
297 | b = block.bullet.exec(cap[i + 1])[0];
298 | if (bull !== b && !(bull.length > 1 && b.length > 1)) {
299 | src = cap.slice(i + 1).join('\n') + src;
300 | i = l - 1;
301 | }
302 | }
303 |
304 | // Determine whether item is loose or not.
305 | // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
306 | // for discount behavior.
307 | loose = next || /\n\n(?!\s*$)/.test(item);
308 | if (i !== l - 1) {
309 | next = item.charAt(item.length - 1) === '\n';
310 | if (!loose) loose = next;
311 | }
312 |
313 | this.tokens.push({
314 | type: loose ? 'loose_item_start' : 'list_item_start'
315 | });
316 |
317 | // Recurse.
318 | this.token(item, false);
319 |
320 | this.tokens.push({
321 | type: 'list_item_end'
322 | });
323 | }
324 |
325 | this.tokens.push({
326 | type: 'list_end'
327 | });
328 |
329 | continue;
330 | }
331 |
332 | // html
333 | if (cap = this.rules.html.exec(src)) {
334 | src = src.substring(cap[0].length);
335 | this.tokens.push({
336 | type: this.options.sanitize ? 'paragraph' : 'html',
337 | pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',
338 | text: cap[0]
339 | });
340 | continue;
341 | }
342 |
343 | // def
344 | if (top && (cap = this.rules.def.exec(src))) {
345 | src = src.substring(cap[0].length);
346 | this.tokens.links[cap[1].toLowerCase()] = {
347 | href: cap[2],
348 | title: cap[3]
349 | };
350 | continue;
351 | }
352 |
353 | // table (gfm)
354 | if (top && (cap = this.rules.table.exec(src))) {
355 | src = src.substring(cap[0].length);
356 |
357 | item = {
358 | type: 'table',
359 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
360 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
361 | cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
362 | };
363 |
364 | for (i = 0; i < item.align.length; i++) {
365 | if (/^ *-+: *$/.test(item.align[i])) {
366 | item.align[i] = 'right';
367 | } else if (/^ *:-+: *$/.test(item.align[i])) {
368 | item.align[i] = 'center';
369 | } else if (/^ *:-+ *$/.test(item.align[i])) {
370 | item.align[i] = 'left';
371 | } else {
372 | item.align[i] = null;
373 | }
374 | }
375 |
376 | for (i = 0; i < item.cells.length; i++) {
377 | item.cells[i] = item.cells[i]
378 | .replace(/^ *\| *| *\| *$/g, '')
379 | .split(/ *\| */);
380 | }
381 |
382 | this.tokens.push(item);
383 |
384 | continue;
385 | }
386 |
387 | // top-level paragraph
388 | if (top && (cap = this.rules.paragraph.exec(src))) {
389 | src = src.substring(cap[0].length);
390 | this.tokens.push({
391 | type: 'paragraph',
392 | text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]
393 | });
394 | continue;
395 | }
396 |
397 | // text
398 | if (cap = this.rules.text.exec(src)) {
399 | // Top-level should never reach here.
400 | src = src.substring(cap[0].length);
401 | this.tokens.push({
402 | type: 'text',
403 | text: cap[0]
404 | });
405 | continue;
406 | }
407 |
408 | if (src) {
409 | throw new
410 | Error('Infinite loop on byte: ' + src.charCodeAt(0));
411 | }
412 | }
413 |
414 | return this.tokens;
415 | };
416 |
417 | /**
418 | * Inline-Level Grammar
419 | */
420 |
421 | var inline = {
422 | escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
423 | autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
424 | url: noop,
425 | tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
426 | link: /^!?\[(inside)\]\(href\)/,
427 | reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
428 | nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
429 | strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
430 | em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
431 | code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
432 | br: /^ {2,}\n(?!\s*$)/,
433 | del: noop,
434 | text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;
439 |
440 | inline.link = replace(inline.link)
441 | ('inside', inline._inside)
442 | ('href', inline._href)
443 | ();
444 |
445 | inline.reflink = replace(inline.reflink)
446 | ('inside', inline._inside)
447 | ();
448 |
449 | /**
450 | * Normal Inline Grammar
451 | */
452 |
453 | inline.normal = merge({}, inline);
454 |
455 | /**
456 | * Pedantic Inline Grammar
457 | */
458 |
459 | inline.pedantic = merge({}, inline.normal, {
460 | strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
461 | em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
462 | });
463 |
464 | /**
465 | * GFM Inline Grammar
466 | */
467 |
468 | inline.gfm = merge({}, inline.normal, {
469 | escape: replace(inline.escape)('])', '~|])')(),
470 | url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
471 | del: /^~~(?=\S)([\s\S]*?\S)~~/,
472 | text: replace(inline.text)
473 | (']|', '~]|')
474 | ('|', '|https?://|')
475 | ()
476 | });
477 |
478 | /**
479 | * GFM + Line Breaks Inline Grammar
480 | */
481 |
482 | inline.breaks = merge({}, inline.gfm, {
483 | br: replace(inline.br)('{2,}', '*')(),
484 | text: replace(inline.gfm.text)('{2,}', '*')()
485 | });
486 |
487 | /**
488 | * Inline Lexer & Compiler
489 | */
490 |
491 | function InlineLexer(links, options) {
492 | this.options = options || marked.defaults;
493 | this.links = links;
494 | this.rules = inline.normal;
495 |
496 | if (!this.links) {
497 | throw new
498 | Error('Tokens array requires a `links` property.');
499 | }
500 |
501 | if (this.options.gfm) {
502 | if (this.options.breaks) {
503 | this.rules = inline.breaks;
504 | } else {
505 | this.rules = inline.gfm;
506 | }
507 | } else if (this.options.pedantic) {
508 | this.rules = inline.pedantic;
509 | }
510 | }
511 |
512 | /**
513 | * Expose Inline Rules
514 | */
515 |
516 | InlineLexer.rules = inline;
517 |
518 | /**
519 | * Static Lexing/Compiling Method
520 | */
521 |
522 | InlineLexer.output = function(src, links, options) {
523 | var inline = new InlineLexer(links, options);
524 | return inline.output(src);
525 | };
526 |
527 | /**
528 | * Lexing/Compiling
529 | */
530 |
531 | InlineLexer.prototype.output = function(src) {
532 | var out = '',
533 | link, text, href, cap;
534 |
535 | while (src) {
536 | // escape
537 | if (cap = this.rules.escape.exec(src)) {
538 | src = src.substring(cap[0].length);
539 | out += cap[1];
540 | continue;
541 | }
542 |
543 | // autolink
544 | if (cap = this.rules.autolink.exec(src)) {
545 | src = src.substring(cap[0].length);
546 | if (cap[2] === '@') {
547 | text = cap[1].charAt(6) === ':' ? this.mangle(cap[1].substring(7)) : this.mangle(cap[1]);
548 | href = this.mangle('mailto:') + text;
549 | } else {
550 | text = escape(cap[1]);
551 | href = text;
552 | }
553 | out += '' + text + '';
554 | continue;
555 | }
556 |
557 | // url (gfm)
558 | if (cap = this.rules.url.exec(src)) {
559 | src = src.substring(cap[0].length);
560 | text = escape(cap[1]);
561 | href = text;
562 | out += '' + text + '';
563 | continue;
564 | }
565 |
566 | // tag
567 | if (cap = this.rules.tag.exec(src)) {
568 | src = src.substring(cap[0].length);
569 | out += this.options.sanitize ? escape(cap[0]) : cap[0];
570 | continue;
571 | }
572 |
573 | // link
574 | if (cap = this.rules.link.exec(src)) {
575 | src = src.substring(cap[0].length);
576 | out += this.outputLink(cap, {
577 | href: cap[2],
578 | title: cap[3]
579 | });
580 | continue;
581 | }
582 |
583 | // reflink, nolink
584 | if ((cap = this.rules.reflink.exec(src)) || (cap = this.rules.nolink.exec(src))) {
585 | src = src.substring(cap[0].length);
586 | link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
587 | link = this.links[link.toLowerCase()];
588 | if (!link || !link.href) {
589 | out += cap[0].charAt(0);
590 | src = cap[0].substring(1) + src;
591 | continue;
592 | }
593 | out += this.outputLink(cap, link);
594 | continue;
595 | }
596 |
597 | // strong
598 | if (cap = this.rules.strong.exec(src)) {
599 | src = src.substring(cap[0].length);
600 | out += '' + this.output(cap[2] || cap[1]) + '';
601 | continue;
602 | }
603 |
604 | // em
605 | if (cap = this.rules.em.exec(src)) {
606 | src = src.substring(cap[0].length);
607 | out += '' + this.output(cap[2] || cap[1]) + '';
608 | continue;
609 | }
610 |
611 | // code
612 | if (cap = this.rules.code.exec(src)) {
613 | src = src.substring(cap[0].length);
614 | out += '' + escape(cap[2], true) + '
';
615 | continue;
616 | }
617 |
618 | // br
619 | if (cap = this.rules.br.exec(src)) {
620 | src = src.substring(cap[0].length);
621 | out += '
';
622 | continue;
623 | }
624 |
625 | // del (gfm)
626 | if (cap = this.rules.del.exec(src)) {
627 | src = src.substring(cap[0].length);
628 | out += '' + this.output(cap[1]) + '';
629 | continue;
630 | }
631 |
632 | // text
633 | if (cap = this.rules.text.exec(src)) {
634 | src = src.substring(cap[0].length);
635 | out += escape(this.smartypants(cap[0]));
636 | continue;
637 | }
638 |
639 | if (src) {
640 | throw new
641 | Error('Infinite loop on byte: ' + src.charCodeAt(0));
642 | }
643 | }
644 |
645 | return out;
646 | };
647 |
648 | /**
649 | * Compile Link
650 | */
651 |
652 | InlineLexer.prototype.outputLink = function(cap, link) {
653 | if (cap[0].charAt(0) !== '!') {
654 | return '' + this.output(cap[1]) + '';
655 | } else {
656 | return '
';
657 | }
658 | };
659 |
660 | /**
661 | * Smartypants Transformations
662 | */
663 |
664 | InlineLexer.prototype.smartypants = function(text) {
665 | if (!this.options.smartypants) return text;
666 | return text
667 | // em-dashes
668 | .replace(/--/g, '\u2014')
669 | // opening singles
670 | .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
671 | // closing singles & apostrophes
672 | .replace(/'/g, '\u2019')
673 | // opening doubles
674 | .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
675 | // closing doubles
676 | .replace(/"/g, '\u201d')
677 | // ellipses
678 | .replace(/\.{3}/g, '\u2026');
679 | };
680 |
681 | /**
682 | * Mangle Links
683 | */
684 |
685 | InlineLexer.prototype.mangle = function(text) {
686 | var out = '',
687 | l = text.length,
688 | i = 0,
689 | ch;
690 |
691 | for (; i < l; i++) {
692 | ch = text.charCodeAt(i);
693 | if (Math.random() > 0.5) {
694 | ch = 'x' + ch.toString(16);
695 | }
696 | out += '' + ch + ';';
697 | }
698 |
699 | return out;
700 | };
701 |
702 | /**
703 | * Parsing & Compiling
704 | */
705 |
706 | function Parser(options) {
707 | this.tokens = [];
708 | this.token = null;
709 | this.options = options || marked.defaults;
710 | }
711 |
712 | /**
713 | * Static Parse Method
714 | */
715 |
716 | Parser.parse = function(src, options) {
717 | var parser = new Parser(options);
718 | return parser.parse(src);
719 | };
720 |
721 | /**
722 | * Parse Loop
723 | */
724 |
725 | Parser.prototype.parse = function(src) {
726 | this.inline = new InlineLexer(src.links, this.options);
727 | this.tokens = src.reverse();
728 |
729 | var out = '';
730 | while (this.next()) {
731 | out += this.tok();
732 | }
733 |
734 | return out;
735 | };
736 |
737 | /**
738 | * Next Token
739 | */
740 |
741 | Parser.prototype.next = function() {
742 | return this.token = this.tokens.pop();
743 | };
744 |
745 | /**
746 | * Preview Next Token
747 | */
748 |
749 | Parser.prototype.peek = function() {
750 | return this.tokens[this.tokens.length - 1] || 0;
751 | };
752 |
753 | /**
754 | * Parse Text Tokens
755 | */
756 |
757 | Parser.prototype.parseText = function() {
758 | var body = this.token.text;
759 |
760 | while (this.peek().type === 'text') {
761 | body += '\n' + this.next().text;
762 | }
763 |
764 | return this.inline.output(body);
765 | };
766 |
767 | /**
768 | * Parse Current Token
769 | */
770 |
771 | Parser.prototype.tok = function() {
772 | switch (this.token.type) {
773 | case 'space':
774 | {
775 | return '';
776 | }
777 | case 'hr':
778 | {
779 | return '
\n';
780 | }
781 | case 'heading':
782 | {
783 | return '' + this.inline.output(this.token.text) + '\n';
784 | }
785 | case 'code':
786 | {
787 | if (this.options.highlight) {
788 | var code = this.options.highlight(this.token.text, this.token.lang);
789 | if (code != null && code !== this.token.text) {
790 | this.token.escaped = true;
791 | this.token.text = code;
792 | }
793 | }
794 |
795 | if (!this.token.escaped) {
796 | this.token.text = escape(this.token.text, true);
797 | }
798 |
799 | return '' + this.token.text + '
\n';
800 | }
801 | case 'table':
802 | {
803 | var body = '',
804 | heading, i, row, cell, j;
805 |
806 | // header
807 | body += '\n\n';
808 | for (i = 0; i < this.token.header.length; i++) {
809 | heading = this.inline.output(this.token.header[i]);
810 | body += '' + heading + ' | \n';
815 | }
816 | body += '
\n\n';
817 |
818 | // body
819 | body += '\n'
820 | for (i = 0; i < this.token.cells.length; i++) {
821 | row = this.token.cells[i];
822 | body += '\n';
823 | for (j = 0; j < row.length; j++) {
824 | cell = this.inline.output(row[j]);
825 | body += '' + cell + ' | \n';
830 | }
831 | body += '
\n';
832 | }
833 | body += '\n';
834 |
835 | return '\n';
836 | }
837 | case 'blockquote_start':
838 | {
839 | var body = '';
840 |
841 | while (this.next().type !== 'blockquote_end') {
842 | body += this.tok();
843 | }
844 |
845 | return '\n' + body + '
\n';
846 | }
847 | case 'list_start':
848 | {
849 | var type = this.token.ordered ? 'ol' : 'ul',
850 | body = '';
851 |
852 | while (this.next().type !== 'list_end') {
853 | body += this.tok();
854 | }
855 |
856 | return '<' + type + '>\n' + body + '' + type + '>\n';
857 | }
858 | case 'list_item_start':
859 | {
860 | var body = '';
861 |
862 | while (this.next().type !== 'list_item_end') {
863 | body += this.token.type === 'text' ? this.parseText() : this.tok();
864 | }
865 |
866 | return '' + body + '\n';
867 | }
868 | case 'loose_item_start':
869 | {
870 | var body = '';
871 |
872 | while (this.next().type !== 'list_item_end') {
873 | body += this.tok();
874 | }
875 |
876 | return '' + body + '\n';
877 | }
878 | case 'html':
879 | {
880 | return !this.token.pre && !this.options.pedantic ? this.inline.output(this.token.text) : this.token.text;
881 | }
882 | case 'paragraph':
883 | {
884 | return '' + this.inline.output(this.token.text) + '
\n';
885 | }
886 | case 'text':
887 | {
888 | return '' + this.parseText() + '
\n';
889 | }
890 | }
891 | };
892 |
893 | /**
894 | * Helpers
895 | */
896 |
897 | function escape(html, encode) {
898 | return html
899 | .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&')
900 | .replace(//g, '>')
902 | .replace(/"/g, '"')
903 | .replace(/'/g, ''');
904 | }
905 |
906 | function replace(regex, opt) {
907 | regex = regex.source;
908 | opt = opt || '';
909 | return function self(name, val) {
910 | if (!name) return new RegExp(regex, opt);
911 | val = val.source || val;
912 | val = val.replace(/(^|[^\[])\^/g, '$1');
913 | regex = regex.replace(name, val);
914 | return self;
915 | };
916 | }
917 |
918 | function noop() {}
919 | noop.exec = noop;
920 |
921 | function merge(obj) {
922 | var i = 1,
923 | target, key;
924 |
925 | for (; i < arguments.length; i++) {
926 | target = arguments[i];
927 | for (key in target) {
928 | if (Object.prototype.hasOwnProperty.call(target, key)) {
929 | obj[key] = target[key];
930 | }
931 | }
932 | }
933 |
934 | return obj;
935 | }
936 |
937 | /**
938 | * Marked
939 | */
940 |
941 | function marked(src, opt, callback) {
942 | if (callback || typeof opt === 'function') {
943 | if (!callback) {
944 | callback = opt;
945 | opt = null;
946 | }
947 |
948 | opt = merge({}, marked.defaults, opt || {});
949 |
950 | var highlight = opt.highlight,
951 | tokens, pending, i = 0;
952 |
953 | try {
954 | tokens = Lexer.lex(src, opt)
955 | } catch (e) {
956 | return callback(e);
957 | }
958 |
959 | pending = tokens.length;
960 |
961 | var done = function() {
962 | var out, err;
963 |
964 | try {
965 | out = Parser.parse(tokens, opt);
966 | } catch (e) {
967 | err = e;
968 | }
969 |
970 | opt.highlight = highlight;
971 |
972 | return err ? callback(err) : callback(null, out);
973 | };
974 |
975 | if (!highlight || highlight.length < 3) {
976 | return done();
977 | }
978 |
979 | delete opt.highlight;
980 |
981 | if (!pending) return done();
982 |
983 | for (; i < tokens.length; i++) {
984 | (function(token) {
985 | if (token.type !== 'code') {
986 | return --pending || done();
987 | }
988 | return highlight(token.text, token.lang, function(err, code) {
989 | if (code == null || code === token.text) {
990 | return --pending || done();
991 | }
992 | token.text = code;
993 | token.escaped = true;
994 | --pending || done();
995 | });
996 | })(tokens[i]);
997 | }
998 |
999 | return;
1000 | }
1001 | try {
1002 | if (opt) opt = merge({}, marked.defaults, opt);
1003 | return Parser.parse(Lexer.lex(src, opt), opt);
1004 | } catch (e) {
1005 | e.message += '\nPlease report this to https://github.com/chjj/marked.';
1006 | if ((opt || marked.defaults).silent) {
1007 | return 'An error occured:
' + escape(e.message + '', true) + '
';
1008 | }
1009 | throw e;
1010 | }
1011 | }
1012 |
1013 | /**
1014 | * Options
1015 | */
1016 |
1017 | marked.options =
1018 | marked.setOptions = function(opt) {
1019 | merge(marked.defaults, opt);
1020 | return marked;
1021 | };
1022 |
1023 | marked.defaults = {
1024 | gfm: true,
1025 | tables: true,
1026 | breaks: false,
1027 | pedantic: false,
1028 | sanitize: false,
1029 | smartLists: false,
1030 | silent: false,
1031 | highlight: null,
1032 | langPrefix: 'lang-',
1033 | smartypants: false
1034 | };
1035 |
1036 | /**
1037 | * Expose
1038 | */
1039 |
1040 | marked.Parser = Parser;
1041 | marked.parser = Parser.parse;
1042 |
1043 | marked.Lexer = Lexer;
1044 | marked.lexer = Lexer.lex;
1045 |
1046 | marked.InlineLexer = InlineLexer;
1047 | marked.inlineLexer = InlineLexer.output;
1048 |
1049 | marked.parse = marked;
1050 |
1051 | if (typeof exports === 'object') {
1052 | module.exports = marked;
1053 | } else if (typeof define === 'function' && define.amd) {
1054 | define(function() {
1055 | return marked;
1056 | });
1057 | } else {
1058 | this.marked = marked;
1059 | }
1060 |
1061 | }).call(function() {
1062 | return this || (typeof window !== 'undefined' ? window : global);
1063 | }());
1064 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-worker-digest-demo",
3 | "version": "0.0.1",
4 | "description": "Running Angular digest cycle in the web worker demo site",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/bahmutov/web-worker-digest-demo.git"
12 | },
13 | "keywords": [
14 | "angular",
15 | "demom",
16 | "digest",
17 | "web",
18 | "worker"
19 | ],
20 | "author": "Gleb Bahmutov ",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/bahmutov/web-worker-digest-demo/issues"
24 | },
25 | "homepage": "https://github.com/bahmutov/web-worker-digest-demo",
26 | "devDependencies": {
27 | "grunt": "0.4.5",
28 | "grunt-gh-pages": "0.10.0",
29 | "matchdep": "0.3.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/step-0/console-log-div.js:
--------------------------------------------------------------------------------
1 | (function initConsoleLogDiv() {
2 |
3 | if (console.log.toDiv) {
4 | return;
5 | }
6 |
7 | function toString(x) {
8 | return typeof x === 'string' ? x : JSON.stringify(x);
9 | }
10 |
11 | var log = console.log.bind(console);
12 | var error = console.error.bind(console);
13 | var warn = console.warn.bind(console);
14 |
15 | var id = 'console-log-div';
16 | function createOuterElement() {
17 | var outer = document.getElementById(id);
18 | if (!outer) {
19 | outer = document.createElement('fieldset');
20 | outer.id = id;
21 | document.body.appendChild(outer);
22 | }
23 | outer.classList.add('id');
24 |
25 | var style = outer.style;
26 | style.width = '100%';
27 | // style.minHeight = '200px';
28 | style.fontFamily = 'monospace';
29 | style.marginTop = '20px';
30 | style.whiteSpace = 'pre';
31 | style.border = '1px solid black';
32 | style.borderRadius = '5px';
33 | style.padding = '5px 10px';
34 | return outer;
35 | }
36 |
37 | var logTo = (function createLogDiv() {
38 |
39 | var outer = createOuterElement();
40 |
41 | var caption = document.createTextNode('console output');
42 | var legend = document.createElement('legend');
43 | legend.appendChild(caption);
44 | outer.appendChild(legend);
45 |
46 | var div = document.createElement('div');
47 | div.id = 'console-log-text';
48 | outer.appendChild(div);
49 |
50 | return div;
51 | }());
52 |
53 | function printToDiv() {
54 | var msg = Array.prototype.slice.call(arguments, 0)
55 | .map(toString)
56 | .join(' ');
57 | var text = logTo.textContent;
58 | logTo.textContent = text + msg + '\n';
59 | }
60 |
61 | function logWithCopy() {
62 | log.apply(null, arguments);
63 | printToDiv.apply(null, arguments);
64 | }
65 |
66 | console.log = logWithCopy;
67 | console.log.toDiv = true;
68 |
69 | console.error = function errorWithCopy() {
70 | error.apply(null, arguments);
71 | var args = Array.prototype.slice.call(arguments, 0);
72 | args.unshift('ERROR:');
73 | printToDiv.apply(null, args);
74 | };
75 |
76 | console.warn = function logWarning() {
77 | warn.apply(null, arguments);
78 | var args = Array.prototype.slice.call(arguments, 0);
79 | args.unshift('WARNING:');
80 | printToDiv.apply(null, args);
81 | };
82 |
83 | window.addEventListener('error', function (err) {
84 | printToDiv('EXCEPTION:', err.message + '\n ' + err.filename, err.lineno + ':' + err.colno);
85 | });
86 |
87 | }());
88 |
--------------------------------------------------------------------------------
/step-0/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Run digest cycle in web worker
6 |
26 |
27 |
28 |
29 | Run digest cycle in web worker
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/step-0/micro-angular.js:
--------------------------------------------------------------------------------
1 | function Scope() {
2 | this.$$watchers = [];
3 | }
4 | Scope.prototype.$watch = function(watchFn, listenerFn) {
5 | var watcher = {
6 | watchFn: watchFn,
7 | listenerFn: listenerFn || function() { }
8 | };
9 | this.$$watchers.push(watcher);
10 | };
11 |
12 | Scope.prototype.$digest = function(cb) {
13 | var self = this;
14 | var dirty = this.$$watchers.some(function (watch) {
15 | var newValue = watch.watchFn(self);
16 | var oldValue = watch.last;
17 | if (newValue !== oldValue) {
18 | watch.listenerFn(newValue, oldValue, self);
19 | watch.last = newValue;
20 | return true;
21 | }
22 | });
23 | dirty && cb && cb(this);
24 | };
25 |
--------------------------------------------------------------------------------
/step-0/primes.js:
--------------------------------------------------------------------------------
1 | (function (root) {
2 |
3 |
4 | function isPrime(n) {
5 | var k;
6 | var limit = Math.sqrt(n);
7 | for (k = 2; k <= limit; k += 1) {
8 | if (n % k === 0) {
9 | return false;
10 | }
11 | }
12 | return true;
13 | }
14 | console.assert(isPrime(1));
15 | console.assert(isPrime(2));
16 | console.assert(isPrime(3));
17 | console.assert(!isPrime(4));
18 | console.assert(isPrime(5));
19 | console.assert(!isPrime(6));
20 | console.assert(isPrime(7));
21 | console.assert(isPrime(37));
22 | console.assert(!isPrime(38));
23 |
24 | // finds Nth prime
25 | function findPrime(n) {
26 | var foundPrimes = [];
27 | var k;
28 | if (foundPrimes.length) {
29 | k = foundPrimes[foundPrimes.length - 1] + 1;
30 | } else {
31 | k = 1;
32 | }
33 | while (foundPrimes.length < n) {
34 | if (isPrime(k)) {
35 | foundPrimes.push(k);
36 | }
37 | k += 1;
38 | };
39 | return foundPrimes[n - 1];
40 | }
41 | console.assert(findPrime(1) === 1);
42 | console.assert(findPrime(2) === 2);
43 | console.assert(findPrime(3) === 3);
44 | console.assert(findPrime(4) === 5);
45 | console.assert(findPrime(5) === 7);
46 |
47 | function findPrimes(n) {
48 | var k, primes = [];
49 | for (k = 0; k < n; k += 1) {
50 | var prime = findPrime(k + 2);
51 | primes.push(prime);
52 | }
53 | return primes;
54 | }
55 |
56 | root.findPrimes = findPrimes;
57 | }(this));
58 |
--------------------------------------------------------------------------------
/step-1/console-log-div.js:
--------------------------------------------------------------------------------
1 | (function initConsoleLogDiv() {
2 |
3 | if (console.log.toDiv) {
4 | return;
5 | }
6 |
7 | function toString(x) {
8 | return typeof x === 'string' ? x : JSON.stringify(x);
9 | }
10 |
11 | var log = console.log.bind(console);
12 | var error = console.error.bind(console);
13 | var warn = console.warn.bind(console);
14 |
15 | var id = 'console-log-div';
16 | function createOuterElement() {
17 | var outer = document.getElementById(id);
18 | if (!outer) {
19 | outer = document.createElement('fieldset');
20 | outer.id = id;
21 | document.body.appendChild(outer);
22 | }
23 | outer.classList.add('id');
24 |
25 | var style = outer.style;
26 | style.width = '100%';
27 | // style.minHeight = '200px';
28 | style.fontFamily = 'monospace';
29 | style.marginTop = '20px';
30 | style.whiteSpace = 'pre';
31 | style.border = '1px solid black';
32 | style.borderRadius = '5px';
33 | style.padding = '5px 10px';
34 | return outer;
35 | }
36 |
37 | var logTo = (function createLogDiv() {
38 |
39 | var outer = createOuterElement();
40 |
41 | var caption = document.createTextNode('console output');
42 | var legend = document.createElement('legend');
43 | legend.appendChild(caption);
44 | outer.appendChild(legend);
45 |
46 | var div = document.createElement('div');
47 | div.id = 'console-log-text';
48 | outer.appendChild(div);
49 |
50 | return div;
51 | }());
52 |
53 | function printToDiv() {
54 | var msg = Array.prototype.slice.call(arguments, 0)
55 | .map(toString)
56 | .join(' ');
57 | var text = logTo.textContent;
58 | logTo.textContent = text + msg + '\n';
59 | }
60 |
61 | function logWithCopy() {
62 | log.apply(null, arguments);
63 | printToDiv.apply(null, arguments);
64 | }
65 |
66 | console.log = logWithCopy;
67 | console.log.toDiv = true;
68 |
69 | console.error = function errorWithCopy() {
70 | error.apply(null, arguments);
71 | var args = Array.prototype.slice.call(arguments, 0);
72 | args.unshift('ERROR:');
73 | printToDiv.apply(null, args);
74 | };
75 |
76 | console.warn = function logWarning() {
77 | warn.apply(null, arguments);
78 | var args = Array.prototype.slice.call(arguments, 0);
79 | args.unshift('WARNING:');
80 | printToDiv.apply(null, args);
81 | };
82 |
83 | window.addEventListener('error', function (err) {
84 | printToDiv('EXCEPTION:', err.message + '\n ' + err.filename, err.lineno + ':' + err.colno);
85 | });
86 |
87 | }());
88 |
--------------------------------------------------------------------------------
/step-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Run digest cycle in web worker
6 |
26 |
27 |
28 |
29 | Run digest cycle in web worker
30 |
31 |
32 |
33 |
34 |
35 |
36 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/step-1/micro-angular-worker.js:
--------------------------------------------------------------------------------
1 | importScripts('micro-angular.js', 'primes.js');
2 |
3 | var scopes = {};
4 |
5 | onmessage = function digestOnMessage(e) {
6 | console.log('micro-angular-worker received:', e.data);
7 | switch (e.data.cmd) {
8 | case 'Scope':
9 | scopes[e.data.id] = new Scope(e.data.id);
10 | break;
11 | case 'set':
12 | scopes[e.data.id][e.data.name] = e.data.value;
13 | break;
14 | case '$watch':
15 | scopes[e.data.id].$watch(
16 | eval('(' + e.data.watchFn + ')'),
17 | e.data.listenerFn && eval('(' + e.data.listenerFn + ')')
18 | );
19 | break;
20 | case '$digest':
21 | scopes[e.data.id].$digest(function digestFinished() {
22 | var $compile, scope, html;
23 | if (e.data.$compile) {
24 | $compile = eval('(' + e.data.$compile + ')');
25 | scope = scopes[e.data.id];
26 | html = $compile(scope);
27 | }
28 |
29 | postMessage({
30 | cmd: 'digestFinished',
31 | html: html
32 | });
33 | });
34 | break;
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/step-1/micro-angular.js:
--------------------------------------------------------------------------------
1 | function Scope() {
2 | this.$$watchers = [];
3 | }
4 | Scope.prototype.$watch = function(watchFn, listenerFn) {
5 | var watcher = {
6 | watchFn: watchFn,
7 | listenerFn: listenerFn || function() { }
8 | };
9 | this.$$watchers.push(watcher);
10 | };
11 |
12 | Scope.prototype.$digest = function(cb) {
13 | var self = this;
14 | var dirty = this.$$watchers.some(function (watch) {
15 | var newValue = watch.watchFn(self);
16 | var oldValue = watch.last;
17 | if (newValue !== oldValue) {
18 | watch.listenerFn(newValue, oldValue, self);
19 | watch.last = newValue;
20 | return true;
21 | }
22 | });
23 | dirty && cb && cb(this);
24 | };
25 |
26 |
27 |
--------------------------------------------------------------------------------
/step-1/mock-scopes.js:
--------------------------------------------------------------------------------
1 | (function initMockScopes(root) {
2 |
3 | var digestWorker = new Worker('./micro-angular-worker.js');
4 |
5 | digestWorker.onmessage = function (e) {
6 | root.render && root.render(e.data.html);
7 | };
8 |
9 | var scopes = 0;
10 | function Scope() {
11 | this.id = '$' + scopes;
12 | scopes += 1;
13 | digestWorker.postMessage({
14 | cmd: 'Scope',
15 | id: this.id
16 | });
17 | console.log('created mock scope', this.id);
18 | }
19 |
20 | Scope.prototype.set = function (name, value) {
21 | digestWorker.postMessage({
22 | cmd: 'set',
23 | id: this.id,
24 | name: name,
25 | value: value
26 | });
27 | console.log('set mock scope', this.id, 'property', name, '=', value);
28 | };
29 |
30 | Scope.prototype.$watch = function (watchFn, listenerFn) {
31 | digestWorker.postMessage({
32 | cmd: '$watch',
33 | id: this.id,
34 | watchFn: watchFn.toString(),
35 | listenerFn: listenerFn && listenerFn.toString()
36 | });
37 | };
38 |
39 | Scope.prototype.$digest = function ($compile) {
40 | digestWorker.postMessage({
41 | cmd: '$digest',
42 | id: this.id,
43 | $compile: $compile && $compile.toString()
44 | });
45 | };
46 |
47 | root.Scope = Scope;
48 | }(this));
49 |
--------------------------------------------------------------------------------
/step-1/primes.js:
--------------------------------------------------------------------------------
1 | (function (root) {
2 |
3 |
4 | function isPrime(n) {
5 | var k;
6 | var limit = Math.sqrt(n);
7 | for (k = 2; k <= limit; k += 1) {
8 | if (n % k === 0) {
9 | return false;
10 | }
11 | }
12 | return true;
13 | }
14 | console.assert(isPrime(1));
15 | console.assert(isPrime(2));
16 | console.assert(isPrime(3));
17 | console.assert(!isPrime(4));
18 | console.assert(isPrime(5));
19 | console.assert(!isPrime(6));
20 | console.assert(isPrime(7));
21 | console.assert(isPrime(37));
22 | console.assert(!isPrime(38));
23 |
24 | // finds Nth prime
25 | function findPrime(n) {
26 | var foundPrimes = [];
27 | var k;
28 | if (foundPrimes.length) {
29 | k = foundPrimes[foundPrimes.length - 1] + 1;
30 | } else {
31 | k = 1;
32 | }
33 | while (foundPrimes.length < n) {
34 | if (isPrime(k)) {
35 | foundPrimes.push(k);
36 | }
37 | k += 1;
38 | };
39 | return foundPrimes[n - 1];
40 | }
41 | console.assert(findPrime(1) === 1);
42 | console.assert(findPrime(2) === 2);
43 | console.assert(findPrime(3) === 3);
44 | console.assert(findPrime(4) === 5);
45 | console.assert(findPrime(5) === 7);
46 |
47 | function findPrimes(n) {
48 | var k, primes = [];
49 | for (k = 0; k < n; k += 1) {
50 | var prime = findPrime(k + 2);
51 | primes.push(prime);
52 | }
53 | return primes;
54 | }
55 |
56 | root.findPrimes = findPrimes;
57 | }(this));
58 |
--------------------------------------------------------------------------------
/step-1/worker.js:
--------------------------------------------------------------------------------
1 | importScripts('micro-angular.js', 'primes.js');
2 |
3 | onmessage = function (e) {
4 | console.log('worker received message:', e.data);
5 | switch (e.data.cmd) {
6 | case 'primes':
7 | var scope = new Scope();
8 | scope.$watch(function watcherFn(scope) {
9 | return scope.n;
10 | }, function listenerFn(newValue, oldValue, scope) {
11 | if (newValue) {
12 | console.log('finding', newValue, 'primes');
13 | scope.primes = findPrimes(newValue);
14 | }
15 | });
16 | scope.n = e.data.n;
17 |
18 | scope.$digest(function afterDigest(scope) {
19 | var n = scope.n;
20 | var str = '';
21 | for (k = 0; k < n; k += 1) {
22 | str += '- ' + (k + 1) + ' prime ' + scope.primes[k] + '
';
23 | }
24 | str += '
';
25 | postMessage({
26 | html: str
27 | });
28 | });
29 | break;
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/step-2/console-log-div.js:
--------------------------------------------------------------------------------
1 | (function initConsoleLogDiv() {
2 |
3 | if (console.log.toDiv) {
4 | return;
5 | }
6 |
7 | function toString(x) {
8 | return typeof x === 'string' ? x : JSON.stringify(x);
9 | }
10 |
11 | var log = console.log.bind(console);
12 | var error = console.error.bind(console);
13 | var warn = console.warn.bind(console);
14 |
15 | var id = 'console-log-div';
16 | function createOuterElement() {
17 | var outer = document.getElementById(id);
18 | if (!outer) {
19 | outer = document.createElement('fieldset');
20 | outer.id = id;
21 | document.body.appendChild(outer);
22 | }
23 | outer.classList.add('id');
24 |
25 | var style = outer.style;
26 | style.width = '100%';
27 | // style.minHeight = '200px';
28 | style.fontFamily = 'monospace';
29 | style.marginTop = '20px';
30 | style.whiteSpace = 'pre';
31 | style.border = '1px solid black';
32 | style.borderRadius = '5px';
33 | style.padding = '5px 10px';
34 | return outer;
35 | }
36 |
37 | var logTo = (function createLogDiv() {
38 |
39 | var outer = createOuterElement();
40 |
41 | var caption = document.createTextNode('console output');
42 | var legend = document.createElement('legend');
43 | legend.appendChild(caption);
44 | outer.appendChild(legend);
45 |
46 | var div = document.createElement('div');
47 | div.id = 'console-log-text';
48 | outer.appendChild(div);
49 |
50 | return div;
51 | }());
52 |
53 | function printToDiv() {
54 | var msg = Array.prototype.slice.call(arguments, 0)
55 | .map(toString)
56 | .join(' ');
57 | var text = logTo.textContent;
58 | logTo.textContent = text + msg + '\n';
59 | }
60 |
61 | function logWithCopy() {
62 | log.apply(null, arguments);
63 | printToDiv.apply(null, arguments);
64 | }
65 |
66 | console.log = logWithCopy;
67 | console.log.toDiv = true;
68 |
69 | console.error = function errorWithCopy() {
70 | error.apply(null, arguments);
71 | var args = Array.prototype.slice.call(arguments, 0);
72 | args.unshift('ERROR:');
73 | printToDiv.apply(null, args);
74 | };
75 |
76 | console.warn = function logWarning() {
77 | warn.apply(null, arguments);
78 | var args = Array.prototype.slice.call(arguments, 0);
79 | args.unshift('WARNING:');
80 | printToDiv.apply(null, args);
81 | };
82 |
83 | window.addEventListener('error', function (err) {
84 | printToDiv('EXCEPTION:', err.message + '\n ' + err.filename, err.lineno + ':' + err.colno);
85 | });
86 |
87 | }());
88 |
--------------------------------------------------------------------------------
/step-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Run digest cycle in web worker
6 |
26 |
27 |
28 |
29 | Run digest cycle in web worker
30 |
31 |
32 |
33 |
34 |
35 |
36 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/step-2/micro-angular-worker.js:
--------------------------------------------------------------------------------
1 | importScripts('micro-angular.js', 'primes.js');
2 |
3 | var scopes = {};
4 |
5 | onmessage = function digestOnMessage(e) {
6 | console.log('micro-angular-worker received:', e.data);
7 | switch (e.data.cmd) {
8 | case 'Scope':
9 | scopes[e.data.id] = new Scope(e.data.id);
10 | break;
11 | case 'set':
12 | scopes[e.data.id][e.data.name] = e.data.value;
13 | break;
14 | case '$watch':
15 | scopes[e.data.id].$watch(
16 | eval('(' + e.data.watchFn + ')'),
17 | e.data.listenerFn && eval('(' + e.data.listenerFn + ')')
18 | );
19 | break;
20 | case '$digest':
21 | scopes[e.data.id].$digest(function digestFinished() {
22 | var $compile, scope, html;
23 | if (e.data.$compile) {
24 | $compile = eval('(' + e.data.$compile + ')');
25 | scope = scopes[e.data.id];
26 | html = $compile(scope);
27 | }
28 |
29 | postMessage({
30 | cmd: 'digestFinished',
31 | html: html
32 | });
33 | });
34 | break;
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/step-2/micro-angular.js:
--------------------------------------------------------------------------------
1 | function Scope() {
2 | this.$$watchers = [];
3 | }
4 | Scope.prototype.$watch = function(watchFn, listenerFn) {
5 | var watcher = {
6 | watchFn: watchFn,
7 | listenerFn: listenerFn || function() { }
8 | };
9 | this.$$watchers.push(watcher);
10 | };
11 |
12 | Scope.prototype.$digest = function(cb) {
13 | var self = this;
14 | var dirty = this.$$watchers.some(function (watch) {
15 | var newValue = watch.watchFn(self);
16 | var oldValue = watch.last;
17 | if (newValue !== oldValue) {
18 | watch.listenerFn(newValue, oldValue, self);
19 | watch.last = newValue;
20 | return true;
21 | }
22 | });
23 | dirty && cb && cb(this);
24 | };
25 |
26 |
27 |
--------------------------------------------------------------------------------
/step-2/mock-scopes.js:
--------------------------------------------------------------------------------
1 | (function initMockScopes(root) {
2 |
3 | var digestWorker = new Worker('./micro-angular-worker.js');
4 |
5 | digestWorker.onmessage = function (e) {
6 | root.render && root.render(e.data.html);
7 | };
8 |
9 | var scopes = 0;
10 | function Scope() {
11 | this.id = '$' + scopes;
12 | scopes += 1;
13 | digestWorker.postMessage({
14 | cmd: 'Scope',
15 | id: this.id
16 | });
17 |
18 | var self = this;
19 | Object.observe(this, function (changes) {
20 | console.log('changed object', self.id, changes);
21 | changes.forEach(function (change) {
22 | switch (change.type) {
23 | case 'add':
24 | case 'update':
25 | console.log('change', change.name, 'to', change.object[change.name]);
26 | self.set(change.name, change.object[change.name]);
27 | break;
28 | }
29 | });
30 | });
31 | console.log('created mock scope', this.id);
32 | }
33 |
34 | Scope.prototype.set = function (name, value) {
35 | digestWorker.postMessage({
36 | cmd: 'set',
37 | id: this.id,
38 | name: name,
39 | value: value
40 | });
41 | console.log('set mock scope', this.id, 'property', name, '=', value);
42 | };
43 |
44 | Scope.prototype.$watch = function (watchFn, listenerFn) {
45 | digestWorker.postMessage({
46 | cmd: '$watch',
47 | id: this.id,
48 | watchFn: watchFn.toString(),
49 | listenerFn: listenerFn && listenerFn.toString()
50 | });
51 | };
52 |
53 | Scope.prototype.$digest = function ($compile) {
54 | var self = this;
55 | setTimeout(function () {
56 | digestWorker.postMessage({
57 | cmd: '$digest',
58 | id: self.id,
59 | $compile: $compile && $compile.toString()
60 | });
61 | }, 0);
62 | };
63 |
64 | root.Scope = Scope;
65 | }(this));
66 |
--------------------------------------------------------------------------------
/step-2/primes.js:
--------------------------------------------------------------------------------
1 | (function (root) {
2 |
3 |
4 | function isPrime(n) {
5 | var k;
6 | var limit = Math.sqrt(n);
7 | for (k = 2; k <= limit; k += 1) {
8 | if (n % k === 0) {
9 | return false;
10 | }
11 | }
12 | return true;
13 | }
14 | console.assert(isPrime(1));
15 | console.assert(isPrime(2));
16 | console.assert(isPrime(3));
17 | console.assert(!isPrime(4));
18 | console.assert(isPrime(5));
19 | console.assert(!isPrime(6));
20 | console.assert(isPrime(7));
21 | console.assert(isPrime(37));
22 | console.assert(!isPrime(38));
23 |
24 | // finds Nth prime
25 | function findPrime(n) {
26 | var foundPrimes = [];
27 | var k;
28 | if (foundPrimes.length) {
29 | k = foundPrimes[foundPrimes.length - 1] + 1;
30 | } else {
31 | k = 1;
32 | }
33 | while (foundPrimes.length < n) {
34 | if (isPrime(k)) {
35 | foundPrimes.push(k);
36 | }
37 | k += 1;
38 | };
39 | return foundPrimes[n - 1];
40 | }
41 | console.assert(findPrime(1) === 1);
42 | console.assert(findPrime(2) === 2);
43 | console.assert(findPrime(3) === 3);
44 | console.assert(findPrime(4) === 5);
45 | console.assert(findPrime(5) === 7);
46 |
47 | function findPrimes(n) {
48 | var k, primes = [];
49 | for (k = 0; k < n; k += 1) {
50 | var prime = findPrime(k + 2);
51 | primes.push(prime);
52 | }
53 | return primes;
54 | }
55 |
56 | root.findPrimes = findPrimes;
57 | }(this));
58 |
--------------------------------------------------------------------------------
/step-2/worker.js:
--------------------------------------------------------------------------------
1 | importScripts('micro-angular.js', 'primes.js');
2 |
3 | onmessage = function (e) {
4 | console.log('worker received message:', e.data);
5 | switch (e.data.cmd) {
6 | case 'primes':
7 | var scope = new Scope();
8 | scope.$watch(function watcherFn(scope) {
9 | return scope.n;
10 | }, function listenerFn(newValue, oldValue, scope) {
11 | if (newValue) {
12 | console.log('finding', newValue, 'primes');
13 | scope.primes = findPrimes(newValue);
14 | }
15 | });
16 | scope.n = e.data.n;
17 |
18 | scope.$digest(function afterDigest(scope) {
19 | var n = scope.n;
20 | var str = '';
21 | for (k = 0; k < n; k += 1) {
22 | str += '- ' + (k + 1) + ' prime ' + scope.primes[k] + '
';
23 | }
24 | str += '
';
25 | postMessage({
26 | html: str
27 | });
28 | });
29 | break;
30 | }
31 | };
32 |
--------------------------------------------------------------------------------