📜 ⬆️ ⬇️

JS1k - we write a great web application in 1024 bytes



For the second year I have been participating in JS1k, last year there was a test script, now I decided to go thoroughly. I already wrote applications and sent. In the article I want to share my experience: how to write an application for JS1k , how to compress, how to reduce the code by 4 times and generally how to cram something interesting in 1 KB.

The theme of the current JS1k " Oregon Trail " is a classic game for Apple II (that you can google it). Therefore it is recommended to write something in this spirit, but it is not necessary.
')

Start


You need to come up with a small application or demo, which in your opinion can get into 1kb (think over the details and management). If you doubt your estimate, you can see what was pushed last year : Legend Of The Bouncing Beholder , Tiny chess . Read the rules and use the html template - then your application will work 100% in the demo environment.
Start writing a script, without any optimizations, but watch out for its size. If it has become more than 4-5Kb - you should think up another topic or in the future you will have to sweat (I had 4393 bytes).

Primary Package Code


Your code must be in closure, otherwise it will not work. Of all the existing packers, the best one is UglifyJS . If you are too lazy to put UglifyJS, then use the web interface .
Pack your code, if it turned out to be within 1024 bytes - excellent - you can not continue to read, but send the application immediately. The fact that your code immediately got into 1024 bytes suggests that your application is not interesting enough or not detailed. If your snatched code has become within 2Kb - everything is fine you should not make such eyes “o_O”, I assure you that it can be cut another 2 times (I had about 1600 bytes).

Manual compression or compress incompressible


This part is about manual compression of those moments that UglifyJS cannot. Every byte is important now!
Every time you fix something, package the UglifyJS code and see the size. Make sure everything works.

0. Do not do the work for the minifier - immediately give clear names to all the properties / functions! Write the readable code, the minifier will remove the extra brackets and semicolons for you.
1. If you sharpened under IE - remove all hacks - it does not participate (ch9 +, o11 +, fx3.6 +, sa5 +).
2. Remove all hacks (added bind, forEach functions), right now.
3. You need to get rid of the reserved words - delete the extra var, typeof. Reduce the number of functions to a minimum (combine if possible) example1 example2 .
4. Copy all HTML created through DOM to manual creation (strings).
5. Find frequently used names of property methods (2 or more times) and string and numeric constants — compile a dictionary and replace the point call with a bracket.
// : var canvas, div1, div2; canvas.moveTo(150, 150); canvas.moveTo(153, 151); canvas.moveTo(11, 151); canvas.moveTo(153, 120); canvas.moveTo(153, 1); div1 = '<div style="width:150px;height:200px;color:red"></div>'; div2 = '<div style="width:150px;height:200px;color:blue"></div>'; // : var canvas, div1, div2, __moveTo__ = 'moveTo'; __width_height__ = 'width:150px;height:200px;'; canvas[__moveTo__](150, 150); canvas[__moveTo__](153, 151); canvas[__moveTo__](11, 151); canvas[__moveTo__](153, 120); canvas[__moveTo__](153, 1); div1 = '<div style="' + __width_height__ + 'color:red"></div>'; div2 = '<div style="' + __width_height__ + 'color:blue"></div>'; 

Formula showing how much the code will be reduced
For methods: (N * (L+1)) - (N * (2 + V) + V + 4 + L)
L - the length of the method without a point
N is the number of calls
V is the length of the variable after minifix reduction (usually 1)
In our case, we get 9 bytes.

For strings (worst case): N * L - (N * (4 + V) + V + 4 + L)
L - string length
N is the number of replacements
V is the length of the variable after minifix reduction (usually 1)
In our case, we get 13 bytes.

6. Move the frequently used globals to the variable var __document__ = document;
7. See if you need to with
8. Use only your closure global variables.
9. Replace for loops with while habrahabr.ru/blogs/javascript/115369/#comment_3737137 , get rid of counter optimizations c = smth.length
 // : for (i = 0;i<smth.length; i++)do(i); // : i=smth.length;while(i--)do(i); 

Another minus 6 * N bytes
10. If you create functions through Function Expression var blabla= function (){}; redo them into the Function Defination function blabla(){} Another minus a couple of bytes.
11. Instead of getElementById, getElementsByTagName use querySelector, querySelectorAll
12. Instead of Math.round (num) use ~~ num
13. Remove double and single quotes from attributes in your HTML code <div id="aaa"> - <div a=aaa>
14. Replace the lines in the id elements with the numbers <div id=aaa> - <div a=1>
15. Reduce colors to 3-4 characters. You should not use exact colors; you can always replace them with identical ones for perception. '#ff0000' -> 'red', '#fedc52' -> '#fd5'
16. js1k provides us with 3 variables for free, use them and enter them into your closure in the order a, b, c
 var b = document.body; var c = document.getElementsByTagName('canvas')[0]; var a = c.getContext('2d'); (function (ctx, __document__body__, canvas) { //   }(a,b,c)) 
The order a, b, c is important because the minifikator will remake your code into this form:
 (function(a,b,c){ //   }(a,b,c)) 
And you will not need to worry about matching the names of internal variables with external ones and you can remove the global closure. Minus as many as 26 bytes!
17. Use short comparison e.keyCode^27||e.preventDefault() instead of e.keyCode==27&&e.preventDefault()
18. If your code is still more than 1024 - start deleting unimportant blocks (details), if possible.
19. If you do not have a demo or a game, then you need to hint to the user how to use it without reading the description.
20. If it is possible, use html attributes to bind events, or assign events through a dot, use shorter event names onkeyup instead of onkeypress (in my code in the 1st place the attribute cannot be used)

Tips from comments

1. Use, it has the alphabetic tags a, b, i, p, q, s, u (but do not create your own - Opera does not know how to trigger events in attributes in custom tags)
2. Try other packers, maybe Uglifay compressorrater.thruhere.net doesn't suit you

An example of what you can do after manual optimization


This year I decided to create a sticky notes application with data stored in localStorage. Here is the code I got after optimization:
 // Full version of Notes ll be on my web site soon. // Creating closure to compile with UglifyJS // (!) After compile remove global closure manually (function (ctx, __document__body__, canvas) { // Dictionary for some frequently used method names and strings var __fillStyle__ = 'fillStyle', __dblclick__ = 'dblclick', __setAttribute__ = 'setAttribute', __background_and_left__ = 'position:absolute;left:', __fillRect__ = 'fillRect', __addColorStop__ = 'addColorStop', __innerHTML__ = 'innerHTML', __textarea__ = 'textarea', __ffe__ = '#ffe', // Other shorthands __document__ = document, __localStorage__ = localStorage, i,j,c,imageData, width = 200, height = 250, paperGradient = ctx.createLinearGradient(width, height/2, width, 0); // Making fixed canvas size canvas[__setAttribute__]('width', 202); canvas[__setAttribute__]('height', 275); // Creates note at e.clientX e.clientY function createPaper(e) { // Opera haven't onbeforeuload event // I must save document.body content on each keyup and each keyCode=27 keydown // Sorry... :3 // (!) replace b and save to actual after min __document__body__[__innerHTML__] += '<a onkeydown=event.keyCode^27||b.removeChild(this),w() style=' + __background_and_left__ + e.clientX + 'px;top:' + e.clientY + 'px;><i style=' + __background_and_left__ + '30px;top:10px;color:#a53;font-size:9px>'+Date()+'</i><img src=' + imageData + '><' + __textarea__ + ' onkeyup=this.'+__innerHTML__+'=this.value,w() style=' + __background_and_left__ + '28px;top:33px;background:transparent;width:170px;height:200px;border:0;line-height:20px;overflow:hidden>Esc</' + __textarea__ + '></a>'; save(); } // Saves document.body to localStorage function save() { __localStorage__[__dblclick__] = __document__body__[__innerHTML__]; } // Making sexy paper // Gradient paperGradient[__addColorStop__](0, '#ff9'); paperGradient[__addColorStop__](1, __ffe__); ctx[__fillStyle__] = paperGradient; ctx.strokeStyle = '#aa7'; // Draw paper body ctx.strokeRect(1,1, width,height); ctx[__fillRect__](1,1, width,height); // Creating paper texture i = width; ctx[__fillStyle__] = __ffe__; while(--i) { while(--j) { Math.random()>.7&&ctx[__fillRect__](i,j,1,1); } j = height; } // 2 Vertical red lines ctx[__fillStyle__] = '#a51'; ctx[__fillRect__](20, 1, 1, height); ctx[__fillRect__](22, 1, 1, height); // Some horizontal gray lines ctx[__fillStyle__] = '#aaa'; for (i = 50; i < height; i += 20) ctx[__fillRect__](0, i, width, 1); // Grabbing image source imageData = canvas.toDataURL(); // Print "Hello message" or load localStorage content __document__body__[__innerHTML__]=__localStorage__[__dblclick__] || __dblclick__+', uses ' + __localStorage__; // Some action events __document__.addEventListener(__dblclick__, createPaper, 0); }(a,b,c)) // Vars must be in a, b, c order 

This code is compressed by Uglyfeer to 1048 bytes, then the global closure is removed and it is reduced to 1022 bytes. When optimizing, I had to cut some details.

Links to my demo


The original is what was at the beginning (double click creates a note, Esc for a note deletes, the data is beautifully in localStorage) JS - 4393 bytes: azproduction.ru/labs/1kjs-ios-javascript-notes/index.original.html
JS1k demo (control is similar to the original) JS - 1022 bytes: js.gd/1hn (old version)

If you compare these 2 applications, then in fact they are no different. From the second tape removed and there is no number of creation.

What was not included in the tips, but will be useful in the future


-one. Using bind can significantly reduce the length of the method call. FF 3.6 does not know how to bind so this method does not suit us yet .
 //   c.lineTo(150,150);c.lineTo(150,200);c.lineTo(150,250);c.lineTo(250,350);c.lineTo(450,350); //    var a='lineTo';c[a](150,150);c[a](150,200);c[a](150,250);c[a](250,350);c[a](450,350); //    var a=z.lineTo.bind(z);a(150,150);a(150,200);a(150,250);a(250,350);a(450,350); 

If you want to participate, then you should not postpone everything for the last moment - invent demos, reduce the code. Deadline JS1k is still very far away - April 24, 2011. Good luck!

Criticism, suggestions, tips to reduce the code are welcome!

UPD Added reader tips and a couple of his. After reading the comments, looking at other scripts, reduced the size of the script from 1022 to 975 bytes and added one remote functionality.

Source: https://habr.com/ru/post/115369/


All Articles