├── .gitignore ├── Readme.md ├── package.json ├── pages └── speaking │ ├── images │ ├── github.jpg │ ├── slides.png │ └── youtube.png │ ├── index.css │ ├── index.html │ └── markdown.css ├── tips ├── 01-08-2017 │ ├── Readme.md │ └── bench.png ├── 01-09-2017 │ ├── Readme.md │ └── meta.json ├── 01-10-2017 │ ├── Readme.md │ └── meta.json ├── 02-08-2017 │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── Readme.md │ └── live-template.gif ├── 02-09-2017 │ ├── Readme.md │ └── meta.json ├── 02-10-2017 │ ├── Readme.md │ └── meta.json ├── 03-08-2017 │ ├── Readme.md │ └── contents.png ├── 03-09-2017 │ ├── Readme.md │ ├── histogram.png │ ├── histogram_area.png │ └── meta.json ├── 03-10-2017 │ ├── Readme.md │ └── meta.json ├── 04-08-2017 │ ├── .babelrc │ ├── Readme.md │ ├── image.jpg │ ├── index.js │ ├── package.json │ └── test.js ├── 04-09-2017 │ ├── Readme.md │ └── meta.json ├── 04-10-2017 │ ├── Readme.md │ └── meta.json ├── 05-08-2017 │ ├── Readme.md │ ├── devtools-2.png │ └── devtools.png ├── 05-09-2017 │ ├── Readme.md │ ├── coding.jpg │ ├── gh.png │ └── meta.json ├── 05-10-2017 │ ├── Readme.md │ └── meta.json ├── 06-08-2017 │ ├── JavaScript.xml │ ├── React.xml │ ├── Readme.md │ └── User.xml ├── 06-09-2017 │ ├── Readme.md │ ├── meta.json │ └── run.jpg ├── 06-10-2017 │ ├── Readme.md │ └── meta.json ├── 07-08-2017 │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── Readme.md │ └── astexplorer.png ├── 07-09-2017 │ ├── Readme.md │ └── meta.json ├── 07-10-2017 │ ├── Readme.md │ ├── meta.json │ └── webpack.png ├── 08-08-2017 │ └── Readme.md ├── 08-09-2017 │ ├── Readme.md │ └── meta.json ├── 08-10-2017 │ ├── Readme.md │ └── meta.json ├── 09-08-2017 │ └── Readme.md ├── 09-09-2017 │ ├── Readme.md │ └── meta.json ├── 09-10-2017 │ ├── Readme.md │ └── meta.json ├── 10-08-2017 │ ├── Readme.md │ ├── image.gif │ ├── index.html │ └── index.js ├── 10-09-2017 │ ├── Readme.md │ ├── meta.json │ └── photo.jpg ├── 10-10-2017 │ ├── Readme.md │ └── meta.json ├── 11-08-2017 │ ├── Readme.md │ ├── index.html │ └── index.js ├── 11-09-2017 │ ├── Readme.md │ └── meta.json ├── 11-10-2017 │ ├── Readme.md │ ├── dns.png │ └── meta.json ├── 12-08-2017 │ ├── Readme.md │ └── escape.png ├── 12-09-2017 │ ├── Readme.md │ └── meta.json ├── 12-10-2017 │ ├── Readme.md │ └── meta.json ├── 13-08-2017 │ └── Readme.md ├── 13-09-2017 │ ├── Readme.md │ ├── index.cmd │ ├── index.js │ └── meta.json ├── 13-10-2017 │ ├── Readme.md │ └── meta.json ├── 14-08-2017 │ └── Readme.md ├── 14-09-2017 │ ├── Readme.md │ ├── amp.gif │ ├── amp.png │ └── meta.json ├── 14-10-2017 │ ├── Readme.md │ └── meta.json ├── 15-08-2017 │ └── Readme.md ├── 15-09-2017 │ ├── 1.png │ ├── 2.png │ ├── Readme.md │ ├── meta.json │ └── src │ │ ├── client.mjs │ │ └── server.mjs ├── 15-10-2017 │ ├── Readme.md │ └── meta.json ├── 16-08-2017 │ └── Readme.md ├── 16-09-2017 │ ├── Readme.md │ └── meta.json ├── 16-10-2017 │ ├── Readme.md │ └── meta.json ├── 17-08-2017 │ └── Readme.md ├── 17-09-2017 │ ├── Readme.md │ └── meta.json ├── 17-10-2017 │ ├── Readme.md │ └── meta.json ├── 18-07-2017 │ ├── Readme.md │ └── index.js ├── 18-08-2017 │ └── Readme.md ├── 18-09-2017 │ ├── Readme.md │ ├── index.mjs │ └── meta.json ├── 18-10-2017 │ ├── Readme.md │ └── meta.json ├── 19-07-2017 │ ├── Readme.md │ └── preferences.png ├── 19-08-2017 │ ├── Readme.md │ └── image.png ├── 19-09-2017 │ ├── Readme.md │ └── meta.json ├── 19-10-2017 │ ├── Readme.md │ ├── meta.json │ └── phpmyadmin.png ├── 20-07-2017 │ └── Readme.md ├── 20-08-2017 │ ├── Readme.md │ └── index.py ├── 20-09-2017 │ ├── Readme.md │ ├── meta.json │ ├── rearrange.gif │ └── settings.png ├── 20-10-2017 │ ├── Readme.md │ └── meta.json ├── 21-07-2017 │ └── Readme.md ├── 21-08-2017 │ ├── .gitignore │ ├── Readme.md │ ├── measure.js │ ├── package.json │ ├── src │ │ └── index.js │ ├── webpack.config.js │ └── yarn.lock ├── 21-09-2017 │ ├── Readme.md │ └── meta.json ├── 21-10-2017 │ ├── Readme.md │ ├── meta.json │ └── tw.png ├── 22-07-2017 │ ├── Readme.md │ └── ts-params-naming.png ├── 22-08-2017 │ └── Readme.md ├── 22-10-2017 │ ├── Readme.md │ └── meta.json ├── 23-07-2017 │ └── Readme.md ├── 23-08-2017 │ ├── Readme.md │ └── index.py ├── 23-09-2017 │ ├── Readme.md │ └── meta.json ├── 23-10-2017 │ ├── Readme.md │ ├── index.html │ ├── meta.json │ └── script.js ├── 24-07-2017 │ ├── Readme.md │ ├── bundle.js │ ├── index.js │ └── webpack.conf.js ├── 24-08-2017 │ └── Readme.md ├── 24-09-2017 │ ├── Readme.md │ └── meta.json ├── 24-10-2017 │ ├── Readme.md │ ├── index.html │ ├── meta.json │ └── scrn.png ├── 25-07-2017 │ └── Readme.md ├── 25-08-2017 │ └── Readme.md ├── 25-09-2017 │ ├── Readme.md │ ├── capture.png │ └── meta.json ├── 25-10-2017 │ ├── Readme.md │ └── meta.json ├── 26-07-2017 │ └── Readme.md ├── 26-08-2017 │ ├── Readme.md │ ├── meta.json │ └── toolbox.jpg ├── 26-09-2017 │ ├── Readme.md │ └── meta.json ├── 26-10-2017 │ ├── Readme.md │ └── meta.json ├── 27-07-2017 │ └── Readme.md ├── 27-08-2017 │ ├── Readme.md │ ├── meta.json │ └── zsh.gif ├── 27-09-2017 │ ├── Readme.md │ └── meta.json ├── 28-07-2017 │ └── Readme.md ├── 28-08-2017 │ ├── Readme.md │ └── meta.json ├── 28-09-2017 │ ├── Readme.md │ └── meta.json ├── 29-07-2017 │ └── Readme.md ├── 29-08-2017 │ ├── Readme.md │ ├── autocomplete.gif │ ├── meta.json │ ├── preferences.png │ └── unknown.png ├── 29-09-2017 │ ├── Readme.md │ └── meta.json ├── 30-07-2017 │ └── Readme.md ├── 30-08-2017 │ ├── Readme.md │ ├── meta.json │ └── screenshot.png ├── 30-09-2017 │ ├── Readme.md │ └── meta.json ├── 31-07-2017 │ ├── .babelrc │ ├── Readme.md │ ├── index.js │ ├── package.json │ └── test.js └── 31-08-2017 │ ├── Readme.md │ ├── ava-puppeteer.png │ └── meta.json ├── web ├── .eslintrc ├── build │ ├── configs │ │ ├── webpack.client.js │ │ └── webpack.server.js │ └── webpack.js ├── package.json ├── src │ ├── components │ │ └── Document │ │ │ ├── Document.hbs │ │ │ └── Document.js │ ├── entities │ │ ├── Tip.js │ │ └── errors.js │ ├── entries │ │ ├── client.js │ │ └── server.js │ ├── libs │ │ ├── helpers │ │ │ ├── getMarkdown.js │ │ │ ├── getTipMeta.js │ │ │ ├── getTipTitle.js │ │ │ └── getTips.js │ │ └── middleware │ │ │ ├── createAssetsServe.js │ │ │ ├── createErrorHandler.js │ │ │ ├── createGetIndex.js │ │ │ ├── createGetTip.js │ │ │ └── createSlashRedirect.js │ └── styles │ │ └── index.css └── yarn.lock └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | dist/ 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webtip", 3 | "version": "2.0.0", 4 | "description": "Web & related tips 🛠", 5 | "main": "", 6 | "dependencies": { 7 | "global": "^4.3.2", 8 | "source-map-explorer": "^1.4.0" 9 | }, 10 | "devDependencies": { 11 | }, 12 | "scripts": { 13 | "push": "git push && cd web && npm run deploy" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+ssh://git@github.com/jakwuh/webtip.git" 18 | }, 19 | "keywords": [ 20 | "javscript", 21 | "tips" 22 | ], 23 | "author": "jakwuh ", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/jakwuh/webtip/issues" 27 | }, 28 | "homepage": "https://github.com/jakwuh/webtip#readme" 29 | } 30 | -------------------------------------------------------------------------------- /pages/speaking/images/github.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/pages/speaking/images/github.jpg -------------------------------------------------------------------------------- /pages/speaking/images/slides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/pages/speaking/images/slides.png -------------------------------------------------------------------------------- /pages/speaking/images/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/pages/speaking/images/youtube.png -------------------------------------------------------------------------------- /pages/speaking/index.css: -------------------------------------------------------------------------------- 1 | img.icon { 2 | height: 16px; 3 | vertical-align: middle; 4 | background: none; 5 | text-decoration: none; 6 | } 7 | 8 | img.icon ~ span { 9 | vertical-align: middle; 10 | } 11 | 12 | .talk-footer { 13 | font-size: 14px; 14 | line-height: 20px; 15 | } 16 | 17 | .event { 18 | font-size: 14px; 19 | } 20 | 21 | .location { 22 | font-size: 14px; 23 | color: #0009; 24 | } 25 | 26 | .title { 27 | } 28 | 29 | .margin-xs { 30 | margin: 0 10px 10px 0; 31 | } 32 | 33 | table { 34 | display: table !important; 35 | } 36 | 37 | tr { 38 | border-top: none !important; 39 | } 40 | 41 | tr:first-child td { 42 | padding-top: 0 !important; 43 | } 44 | 45 | td { 46 | padding: 20px !important; 47 | border: none !important; 48 | } 49 | 50 | .date { 51 | font-size: 14px; 52 | color: #0009; 53 | } 54 | -------------------------------------------------------------------------------- /tips/01-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | You might have heard about the [release of npm@5](http://blog.npmjs.org/post/161081169345/v500) this may. 2 | 3 | The question is: should we move back to `npm` from `yarn`? 4 | 5 | *tldr; it depends, most likely no.* 6 | 7 | ### How to install 8 | 9 | ```bash 10 | npm i -g npm@latest 11 | ``` 12 | 13 | ### Comparison 14 | 15 | Comparison is taken from the [`npm-vs-yarn`](https://github.com/thomaschaaf/npm-vs-yarn) project. For the sake of justice I've run a similar benchmark and the results were pretty much the same. 16 | 17 | ![npm@latest vs yarn](bench.png) 18 | 19 | 20 | ### Conlusion 21 | 22 | It's not hard to notice `npm` and `yarn` have almost identical performance when they work without cache & with lockfile. That means you would probably like to choose `npm` as a package installer when dealing with cloud build machines (e.g. travis), because you would not need to install yarn additionally before a build process. 23 | However, you could build a `node` artifact with `yarn` being bundled inside and in that case the choice is obvious. 24 | 25 | --- 26 | 27 | Special credits to [Thomas Schaaf](https://github.com/thomaschaaf) for kindly granting permission to use all diagrams & data from his project [`npm-vs-yarn`](https://github.com/thomaschaaf/npm-vs-yarn). 28 | -------------------------------------------------------------------------------- /tips/01-08-2017/bench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/01-08-2017/bench.png -------------------------------------------------------------------------------- /tips/01-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | #### [Interleaving String][1] 2 | 3 | ##### Task 4 | 5 | Given `s1`, `s2`, `s3`, find whether `s3` is formed by the interleaving of `s1` and `s2`. 6 | 7 | Example inputs: 8 | 9 | ``` 10 | s1 = "a" 11 | s2 = "a" 12 | s3 = "a" 13 | Result: False 14 | ``` 15 | 16 | ``` 17 | s1 = "aa" 18 | s2 = "ab" 19 | s3 = "abaa" 20 | Result: True 21 | ``` 22 | 23 | ``` 24 | s1 = "aabcc" 25 | s2 = "dbbca" 26 | s3 = "aadbbcbcac" 27 | Result: True 28 | ``` 29 | 30 | ``` 31 | s1 = "aabcc" 32 | s2 = "dbbca" 33 | s3 = "aadbbbaccc" 34 | Result: False 35 | ``` 36 | 37 | ##### Analysis 38 | 39 | Let `F(s1, s2, s3)` be the solution for the task. It's easy to see the task could be broken down in the following way: 40 | 41 | ```python 42 | if s1[0] == s3[0] and F(s1[1:], s2, s3[1:]) == True: 43 | return True 44 | if s2[0] == s3[0] and F(s1, s2[1:], s3[1:]) == True: 45 | return True 46 | return False 47 | ``` 48 | 49 | At this point it is clear we're dealing with a DP task. We are solving it using a 2-dimensional array of size `(len(s1) + 1) * (len(s2) + 1)` of booleans. If `table[i][j] == True` then it is possible to interleave `s1[:i] and s2[:j]` into a `s3[:i + j]`. The initial value is `table[0][0] = True`. 50 | 51 | 52 | 53 | ##### Solution 54 | 55 | ```python 56 | class Solution(object): 57 | def isInterleave(self, s1, s2, s3): 58 | """ 59 | :type s1: str 60 | :type s2: str 61 | :type s3: str 62 | :rtype: bool 63 | """ 64 | n = len(s1) 65 | m = len(s2) 66 | 67 | if n + m != len(s3): 68 | return False 69 | 70 | table = [None] * (n + 1) 71 | 72 | for i in range(0, n + 1): 73 | table[i] = [False] * (m + 1) 74 | 75 | table[0][0] = True 76 | 77 | for i in range(0, n + 1): 78 | for j in range(0, m + 1): 79 | if table[i][j]: 80 | k = i + j 81 | if i != n and s1[i] == s3[k]: 82 | table[i + 1][j] = True 83 | if j != m and s2[j] == s3[k]: 84 | table[i][j + 1] = True 85 | 86 | return table[n][m] 87 | 88 | ``` 89 | 90 | [1]: https://leetcode.com/problems/interleaving-string/ 91 | -------------------------------------------------------------------------------- /tips/01-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Given `s1`, `s2`, `s3`, find whether `s3` is formed by the interleaving of `s1` and `s2`." 3 | } 4 | -------------------------------------------------------------------------------- /tips/01-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | A TC39 proposal called [**Optional catch binding**][1] is on its Stage 3 now. It allows to avoid creating unnecessary variable binding in case you want to simply ignore an error (which is useful in a number of cases). 2 | 3 | #### Syntax: 4 | 5 | ```js 6 | try { 7 | // code 8 | } catch { 9 | // code 10 | } 11 | ``` 12 | 13 | instead of 14 | 15 | ```js 16 | try { 17 | // code 18 | } catch (e) { 19 | // code 20 | } 21 | ``` 22 | 23 | #### Usage 24 | 25 | ```bash 26 | yarn add babel-plugin-transform-optional-catch-binding --dev 27 | ``` 28 | 29 | ```js 30 | // .babelrc 31 | { 32 | plugins: [ 33 | 'transform-optional-catch-binding' 34 | ] 35 | } 36 | ``` 37 | 38 | #### Use case 39 | 40 | ```js 41 | let data; 42 | 43 | try { 44 | data = JSON.parse(string); 45 | } catch { 46 | data = []; 47 | } 48 | 49 | return data; 50 | ``` 51 | 52 | With the release of `babel@7` the plugin will be included in `env` preset by default. 53 | 54 | [1]: https://github.com/tc39/proposal-optional-catch-binding 55 | -------------------------------------------------------------------------------- /tips/01-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "A TC39 proposal called *Optional catch binding* is on its Stage 3 now. It allows to avoid creating unnecessary variable binding in case you want to simply ignore an error (which is useful in a number of cases)." 3 | } 4 | -------------------------------------------------------------------------------- /tips/02-08-2017/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/02-08-2017/1.png -------------------------------------------------------------------------------- /tips/02-08-2017/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/02-08-2017/2.png -------------------------------------------------------------------------------- /tips/02-08-2017/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/02-08-2017/3.png -------------------------------------------------------------------------------- /tips/02-08-2017/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/02-08-2017/4.png -------------------------------------------------------------------------------- /tips/02-08-2017/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/02-08-2017/5.png -------------------------------------------------------------------------------- /tips/02-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Today we will learn how to create a smart WebStorm Live Template: 2 | 3 | ![chai describe live template](live-template.gif) 4 | 5 | As an example we will create a template for unit tests `describe` method. 6 | 7 | ### Step 1 8 | 9 | Press `cmd(ctrl)` + `shift` + `a` to open an action menu. Type and choose **Live Templates**. 10 | 11 | ![Step 1](1.png) 12 | 13 | ### Step 2 14 | 15 | Open the `Javascript` dropdown and click `plus` icon at the right side of the panel. 16 | 17 | ![Step 2](2.png) 18 | 19 | ### Step 3 20 | 21 | **Fill up the suggested form:** 22 | 23 | ```js 24 | 25 | import {$CLASS$} from './$FILE$'; 26 | import {expect} from 'chai'; 27 | 28 | describe('$DESCRIPTION$', function () { 29 | 30 | it('$SUITE$', function () { 31 | $END$ 32 | ); 33 | 34 | }); 35 | ``` 36 | 37 | ![Step 3](3.png) 38 | 39 | - **Abbrevation** - the keyword which you will use to find your template during autocomplete 40 | - **Description** - a few words which will help you distinct the template 41 | - **[Template text](https://www.jetbrains.com/help/webstorm/live-templates-2.html)** - the code snippet. `$END$` and `$SELECTION$` are keywords, all other `$$` are variables. 42 | 43 | ### Step 4 (the key) 44 | 45 | Fill up the **Edit variables** form. Here are some gotchas before doing this: 46 | 47 | - [You can change the order of variables](https://twitter.com/jamesakwuh/status/814377069194907648) which will affect the order of completing them during live template insertion 48 | - You can refer to already defined variables in **expression** field which will work as a smart default 49 | - There are a bit more available predefined functions than described in the [official doc](https://www.jetbrains.com/help/webstorm/live-templates-2.html) 50 | 51 | ![Step 4](4.png) 52 | 53 | ### Step 5 54 | 55 | Choose applicable contexts for the created live template. 56 | 57 | ![Step 5](5.png) 58 | 59 | 60 | Here we go! To use a template simply start typing the keyword and you will see the live template. The smart one! 61 | -------------------------------------------------------------------------------- /tips/02-08-2017/live-template.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/02-08-2017/live-template.gif -------------------------------------------------------------------------------- /tips/02-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | #### [Distinct Subsequences][1] 2 | 3 | ##### Task 4 | 5 | Given a string `S` and a string `T`, count the number of distinct subsequences of `S` which equals `T`. 6 | 7 | A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE" is a subsequence of "ABCDE" while "AEC" is not). 8 | 9 | Example inputs: 10 | 11 | ``` 12 | S = "ab" 13 | T = "ab" 14 | Result: 1 15 | ``` 16 | 17 | ``` 18 | S = "rabbbit" 19 | T = "rabbit" 20 | Result: 3 21 | ``` 22 | 23 | ##### Analysis 24 | 25 | Let `F[i][j]` be the number of distinct subsequences of `S[:j]` which equals `T[:i]`. When building such a subsequence we can use or skip `S[j-1]`. If we skip it, then the number of possible subsequences will be `F[i][j-1]`. If we use it and `S[j-1] == T[i-1]` then the number of possible subsequences will be `F[i-1][j-1]`. 26 | 27 | With that we've come to the following DP recurring formula: 28 | 29 | ``` 30 | F[i][j] = F[i][j-1] + (S[j-1] == T[i-1]) * F[i-1][j-1] 31 | F[0][j] = 1 32 | F[i][j] = 0, i > 0 33 | ``` 34 | 35 | Which represents the solution. 36 | 37 | ##### Solution 38 | 39 | ```python 40 | class Solution(object): 41 | def numDistinct(self, s, t): 42 | """ 43 | :type s: str 44 | :type t: str 45 | :rtype: int 46 | """ 47 | n = len(t) + 1 48 | m = len(s) + 1 49 | 50 | table = [None] * n 51 | 52 | table[0] = [1] * m 53 | 54 | for i in range(1, n): 55 | table[i] = [0] * m 56 | 57 | for i in range(1, n): 58 | for j in range(1, m): 59 | table[i][j] = table[i][j - 1] \ 60 | + (s[j - 1] == t[i - 1]) * table[i - 1][j - 1] 61 | 62 | return table[n - 1][m - 1] 63 | ``` 64 | 65 | [1]: https://leetcode.com/problems/distinct-subsequences/ 66 | -------------------------------------------------------------------------------- /tips/02-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Given a string `S` and a string `T`, count the number of distinct subsequences of `S` which equals `T`." 3 | } 4 | -------------------------------------------------------------------------------- /tips/02-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Matrix Median][1] 2 | 3 | #### Task 4 | 5 | Given a `N` cross `M` matrix of positive integers in which each row is sorted, find the overall median of the matrix. Assume `N` * `M` is odd. No extra memory is allowed. 6 | 7 | For example: 8 | 9 | ``` 10 | Matrix: 11 | [1, 3, 5] 12 | [2, 6, 9] 13 | [3, 6, 9] 14 | 15 | A: [1, 2, 3, 3, 5, 6, 6, 9, 9] 16 | 17 | Result: 5 18 | ``` 19 | 20 | #### Analysis 21 | 22 | As far as no extra memory is allowed we will do a binary search on the hole possible range of medians. First we will try to find the median in a range `[1, INT_MAX]`. We will go through each row and calculate with a binary search how many elements of it are lower, equal or upper `(1 + INT_MAX) / 2`. The complexity for processing one row is `O(log(m))`, the complexity for making one iteration is `O(n log(m))`. Finally, we'll get the median after a maximum of `log_2(INT_MAX)` iterations (due to usage of binary search). So, the overall complexity is `O(log(INT_MAX) n log(m))`. 23 | 24 | #### Solution 25 | 26 | ```cpp 27 | int find(vector > &A, int low, int high) { 28 | int lower_count = 0; 29 | int upper_count = 0; 30 | int M = (1ll + high + low) / 2; 31 | 32 | for (auto &row : A) { 33 | auto left = std::lower_bound(row.begin(), row.end(), M); 34 | auto right = std::upper_bound(row.begin(), row.end(), M); 35 | lower_count += std::distance(row.begin(), left); 36 | upper_count += std::distance(right, row.end()); 37 | } 38 | 39 | int n = A.size(); 40 | int m = A[0].size(); 41 | 42 | int equal_count = n * m - upper_count - lower_count; 43 | int median_index = (n * m) / 2; 44 | 45 | if (median_index < lower_count) { 46 | return find(A, low, M - 1); 47 | } else if (median_index >= lower_count + equal_count) { 48 | return find(A, M + 1, high); 49 | } else { 50 | return M; 51 | } 52 | } 53 | 54 | int Solution::findMedian(vector > &A) { 55 | return find(A, 1, INT_MAX); 56 | } 57 | ``` 58 | 59 | 60 | [1]: https://www.interviewbit.com/problems/matrix-median/ 61 | -------------------------------------------------------------------------------- /tips/02-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Given a N cross M matrix of positive integers in which each row is sorted, find the overall median of the matrix. Assume N * M is odd. No extra memory is allowed." 3 | } 4 | -------------------------------------------------------------------------------- /tips/03-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | To analyze a bundle contents built w/ webpack you don't even need a webpack `stats` json. [source-map-explorer](https://www.npmjs.com/package/source-map-explorer) is here to help you: 2 | 3 | ```bash 4 | yarn global add source-map-explorer 5 | 6 | source-map-explorer dist/client/index.bundle.js # w/ inline sourcemaps 7 | source-map-explorer dist/client/index.bundle.js.map # w/ generated sourcemaps 8 | ``` 9 | 10 | This will open [the following window][1]: 11 | 12 | [![source-map-explorer](contents.png)][1] 13 | 14 | As you could notice from above, the only thing you need is sourcemaps, which are enabled with [`devtool` option](https://webpack.js.org/configuration/devtool/) inside webpack config. 15 | 16 | 17 | [1]: https://twitter.com/addyosmani/status/801834908578553856/photo/1?ref_src=twsrc%5Etfw&ref_url=https%3A%2F%2Fmedium.com%2Fmedia%2Fa4215e200110bc61e34063d539df0f83%3FpostId%3D41096559beca 18 | -------------------------------------------------------------------------------- /tips/03-08-2017/contents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/03-08-2017/contents.png -------------------------------------------------------------------------------- /tips/03-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Largest Rectangle in Histogram][1] 2 | 3 | #### Task 4 | 5 | Given `n` non-negative integers representing the histogram's bar height where the width of each bar is 1, find the area of largest rectangle in the histogram. 6 | 7 | ![histogram](./histogram.png) 8 | 9 | ![histogram area](./histogram_area.png) 10 | 11 | Example inputs: 12 | 13 | ``` 14 | [2,1,5,6,2,3] 15 | Result: 10 16 | ``` 17 | 18 | ``` 19 | [2,1,2] 20 | Result: 3 21 | ``` 22 | 23 | #### Analysis 24 | 25 | Consider bar at position `i`. To find the area of a rectangle with `heights[i]` as a minimal height we need to know the index of a first smaller bar to the right and to the left relatively to the considered bar. To find it we use a stack and the following algorithm: 26 | 27 | 1. Let `S` be an empty stack 28 | 2. Let `i` be the current index and `h` be the current height. If `h` is `>=` that the last element in `S` then simply push `h` to `S`. Otherwise while the last element in `S` is `>` `h` repeat the following: assign `S.pop()` to `x`, calculate area with `x` being a minimal height and width equal to `i - index`, where `index` is the index corresponding to `x`. While `i` < `len(heights)` increment it and repeat the step 29 | 30 | #### Solution 31 | 32 | ```python 33 | class Solution(object): 34 | def largestRectangleArea(self, heights): 35 | """ 36 | :type heights: List[int] 37 | :rtype: int 38 | """ 39 | heights.append(0) 40 | indices = [] 41 | area = 0 42 | 43 | for index, current in enumerate(heights): 44 | while len(indices) and heights[indices[-1]] > current: 45 | h = heights[indices.pop()] 46 | w = index - indices[-1] - 1 if len(indices) else index 47 | area = max(area, w * h) 48 | 49 | indices.append(index) 50 | 51 | return area 52 | ``` 53 | 54 | [1]: https://leetcode.com/problems/largest-rectangle-in-histogram/description/ 55 | -------------------------------------------------------------------------------- /tips/03-09-2017/histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/03-09-2017/histogram.png -------------------------------------------------------------------------------- /tips/03-09-2017/histogram_area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/03-09-2017/histogram_area.png -------------------------------------------------------------------------------- /tips/03-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Given `n` non-negative integers representing the histogram's bar height where the width of each bar is 1, find the area of largest rectangle in the histogram." 3 | } 4 | -------------------------------------------------------------------------------- /tips/03-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Mono repository in comparison to multiple repositories has a lot of cons and pros. In this post we explore a tool called `lerna` which is built to start, evolve and manage monorepository." 3 | } 4 | -------------------------------------------------------------------------------- /tips/04-08-2017/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@ava/stage-4", 4 | "@ava/transform-test-files" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tips/04-08-2017/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/04-08-2017/image.jpg -------------------------------------------------------------------------------- /tips/04-08-2017/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "04-08-2017", 3 | "main": "index.js", 4 | "scripts": { 5 | "test": "ava test.js" 6 | }, 7 | "author": "jakwuh ", 8 | "license": "ISC", 9 | "ava": { 10 | "require": [ 11 | "babel-register" 12 | ], 13 | "babel": "inherit" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tips/04-08-2017/test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {StateMachine} from './index'; 3 | 4 | let spec = [ 5 | ["aa", "a", false], 6 | ["aa", "aa", true], 7 | ["aaa", "aa", false], 8 | ["aa", "a*", true], 9 | ["aa", ".*", true], 10 | ["ab", ".*", true], 11 | ["aab", "c*a*b", true], 12 | ["a", "b*..*.a", false], 13 | ["aaa", "ab*a*c*a", true], 14 | ["abcd", "d*", false], 15 | ]; 16 | 17 | spec.forEach(([string, pattern, expected]) => { 18 | 19 | test(t => { 20 | t.is(new StateMachine(pattern).match(string), expected); 21 | }); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /tips/04-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Given two words (`beginWord` and `endWord`), and a dictionary's word list, find all shortest transformation sequence(s) from `beginWord` to `endWord`, such that only one letter can be changed at a time" 3 | } 4 | -------------------------------------------------------------------------------- /tips/04-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | As the `babel`'s [blog post][1] states, `typescript` support has been already planned to be included in `7.0` release. Meanwhile the `7.0.0-beta.2` babel version was published offering same features for early adopters. 2 | 3 | > By the way, `babel` itself is a monorepository. It is orchestrated by `lerna` described in [the previous tip][4] 4 | 5 | First, you need to update a bunch of packages: 6 | 7 | ```bash 8 | yarn upgrade \ 9 | babel-cli@7.0.0-beta.2 \ 10 | babel-loader@7 \ 11 | babel-plugin-transform-runtime@7.0.0-beta.2 \ 12 | babel-runtime@7.0.0-beta.2 \ 13 | babel-preset-env@2.0.0-beta.2 \ 14 | babel-plugin-transform-typescript@7.0.0-beta.2 15 | ``` 16 | 17 | At this point you could add `transform-typescript` to your babel config: 18 | 19 | ```js 20 | { 21 | ... 22 | plugins: [ 23 | ... 24 | 'transform-typescript' 25 | ] 26 | } 27 | ``` 28 | 29 | The `transform-typescript` plugin works similar to `transform-flow` plugin - it strips types during compilation. 30 | 31 | However, currently there is [a known bug][2] when using `transform-typescript` along with `transform-runtime`. As far as I can understand it, the bug happens due to the following steps: 32 | 33 | 1. `babel` start traversing a module. `transform-typescript` creates a module metadata which it will then use to strip unnecessary imports 34 | 2. Finally `transform-typescript` strips unnecessary imports and remove a module metadata 35 | 3. `transform-runtime` then adds a few more imports 36 | 4. New imports force `babel` to start traversing a module once more 37 | 5. `transform-typescript` has already removed a module metadata so it throws an exception. 38 | 39 | To make `transform-typescript` work I've [forked][3] it and made a quick fix based on assumption that any `import` statement added by plugins dynamically shouldn't be transpiled and could be left as-is. 40 | 41 | These are the only steps needed to start using `typescript`. Give it a try! 42 | 43 | [1]: https://babeljs.io/blog/2017/09/12/planning-for-7.0 44 | [2]: https://github.com/babel/babel/issues/6093 45 | [3]: https://github.com/jakwuh/babel-plugin-transform-typescript 46 | [4]: https://github.com/jakwuh/webtip/tree/master/tips/03-10-2017 47 | -------------------------------------------------------------------------------- /tips/04-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "As the babel's blog post states, `typescript` support has been already planned to be included in 7.0 release. Meanwhile the 7.0.0-beta.2 babel version was published offering same features for early adopters." 3 | } 4 | -------------------------------------------------------------------------------- /tips/05-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | With Chrome 60 / Canary debugging Node became easier: 2 | 3 | 1. Go to `chrome://inspect` 4 | 2. Click **Open dedicated DevTools for Node** 5 | 6 | ![Dedicated DevTools](devtools.png) 7 | 8 | Now you could leave the opened window for a long time. Each time you will run node w/ `--inspect` flag the opened DevTools will automatically connect to the process. 9 | 10 | 3. Run your app w/ `--inspect --debug-brk` flags: 11 | 12 | ```js 13 | node --inspect --debug-brk index.js 14 | ``` 15 | 16 | ![DevTools CPU Profiling](devtools-2.png) 17 | -------------------------------------------------------------------------------- /tips/05-08-2017/devtools-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/05-08-2017/devtools-2.png -------------------------------------------------------------------------------- /tips/05-08-2017/devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/05-08-2017/devtools.png -------------------------------------------------------------------------------- /tips/05-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | This is the **50th** tip (what is quite good!) so I have decided to devote it to describing what is going on behind the `dailytip` project. 2 | 3 | Some of you have asked me about my goals and expectations for the project. Let me shed some light on the goals. 4 | 5 | #### Form the character (e.v.e.r.y. d.a.m.n. d.a.y.) 6 | 7 | ![Uber coding](./coding.jpg) 8 | *(Me writing dailytip in Uber on the way home)* 9 | 10 | Usually a man counts places where he had a sex with his paramour: home, local park, car backseat, forest, swimming pool. I count places where I have written dailytip. Just to name a few: home, office, airport, swimming pool, car backseat, Airbnb accommodation, outdoors. 11 | 12 | That is, I definitely start planning my schedule in a better way. What is quite important for me as I am constantly trying to find the most optimal work-life-projects balance. 13 | 14 | #### Learn English 15 | 16 | I am not going to explain the importance of English (or as they say in math evidences: let English be an important language by definition). Writing `dailytip` as you might guess helps me to improve a bit the way I write and read in English. This would not be possible without help of my beloved [girlfriend][1]. 17 | 18 | #### Force myself to get a new knowledge everyday 19 | 20 | Such a project is a perfect motivation to learn new things constantly. When you have started it, you cannot post the same things again and again. You are forced to read news, learn technologies, explore and write libraries, solve algorithmic tasks and much much more. Moreover, it shows you the lack of experience in a specific area, what in turn forces you to learn even more. 21 | 22 | #### Help / mentor people 23 | 24 | At the moment I am writing this tip there are 120 telegram subscribers in our [channel][2]. Once I have more time I will start to work on increasing channel subscribers. Seeing your channel grows (slow or fast, it doesn't matter too much at the beginning) is quite motivating. People finding bugs/typos in your posts or saying *Thank you* are even more motivating. 25 | 26 | #### Open source & a beautiful GH history 😊 27 | 28 | ![Github history](./gh.png) 29 | *(My github contribution timeline. Beautiful, isn't it?)* 30 | 31 | --- 32 | 33 | I hope you enjoy the `dailytip` project. Don't hesitate to contact me to leave a feedback at any time via [Telegram](https://t.me/jakwuh) or [email](mailto:jakwuh@gmail.com). 34 | 35 | [1]: https://www.instagram.com/marina_amani_/ 36 | [2]: https://t.me/webtip 37 | -------------------------------------------------------------------------------- /tips/05-09-2017/coding.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/05-09-2017/coding.jpg -------------------------------------------------------------------------------- /tips/05-09-2017/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/05-09-2017/gh.png -------------------------------------------------------------------------------- /tips/05-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Usually a man counts places where he had a sex with his paramour: home, local park, car backseat, forest, swimming pool. I count places where I have written dailytip." 3 | } 4 | -------------------------------------------------------------------------------- /tips/05-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | [Daniel Ehrenberg][3]'s [pipeline operator][1] proposal is at the stage 1. It works similar to `lodash` `chain` wrapper, allowing to transform nested functions calls into a waterfall: 2 | 3 | ```js 4 | let result = [1, 2, 3] 5 | |> _ => filter(_, i => i > 1) 6 | |> _ => map(_, i => i ** 2) 7 | |> sum 8 | 9 | // 13 10 | ``` 11 | 12 | In case you want to remove `lodash` `chain` functionality there is already [a tip][4] for that. But keep in mind that `lodash` `chain` is much more powerful wrapper because it applies a lot of optimisations based on heuristics. 13 | 14 | 15 | [1]: https://github.com/tc39/proposal-pipeline-operator 16 | [2]: https://www.npmjs.com/package/babel-plugin-transform-pipeline 17 | [3]: https://twitter.com/littledan 18 | [4]: https://github.com/jakwuh/webtip/tree/master/tips/22-08-2017 19 | -------------------------------------------------------------------------------- /tips/05-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Daniel Ehrenberg's pipeline operator proposal is at the stage 1. It works similar to lodash chain wrapper, allowing to transform nested functions calls into a waterfall:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/06-08-2017/User.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /tips/06-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | tldr; The result of `Promise.race(promises)` is the first finished promise, regardless of it being resolved or rejected. 2 | 3 | --- 4 | 5 | While using `Promise.race` remember not to draw an analogy with an ordinary race. Usually although a participant has fallen during a race other participants continue to run. This is not the case for `Promise.race` as it is rejected as soon as one of its promises is rejected. 6 | 7 | ![Promise.race()](./run.jpg) 8 | 9 | The following pieces of code are equal and help understand `Promise.race` better. 10 | 11 | ```js 12 | return new Promise((resolve, reject) => { 13 | makePromiseCall().then(resolve, reject); 14 | 15 | setTimeout(reject, timeout); 16 | }); 17 | ``` 18 | 19 | 20 | ```js 21 | return Promise.race([ 22 | makePromiseCall(), 23 | new Promise((resolve, reject) => setTimeout(reject, timeout)) 24 | ]); 25 | ``` 26 | 27 | |makePromiseCall|setTimeout|Promise.race| 28 | |-|-|-| 29 | |resolved||resolved| 30 | |rejected||rejected| 31 | ||resolved|resolved| 32 | ||rejected|rejected| 33 | -------------------------------------------------------------------------------- /tips/06-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "tldr; The result of `Promise.race(promises)` is the first finished promise, regardless of it being resolved or rejected." 3 | } 4 | -------------------------------------------------------------------------------- /tips/06-09-2017/run.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/06-09-2017/run.jpg -------------------------------------------------------------------------------- /tips/06-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Median of Array][1] 2 | 3 | #### Task 4 | 5 | There are two sorted arrays A and B of size m and n respectively. 6 | 7 | Find the median of the two sorted arrays (the median of the array formed by merging both the arrays ). 8 | 9 | The overall run time complexity should be `O(log (m+n))`. 10 | 11 | Example: 12 | 13 | ``` 14 | A: [1 4 5] 15 | B: [2 3] 16 | Result: 3 17 | ``` 18 | 19 | 20 | > Note: if the number of elements in the merged array is even, then the median is the average of `n / 2`th and `n/2 + 1`th element. 21 | For example, if the array is `[1 2 3 4]`, the median is `(2 + 3) / 2.0 = 2.5` 22 | 23 | [1]: https://www.interviewbit.com/problems/median-of-array/ 24 | 25 | #### Analysis 26 | 27 | ``` 28 | A[0] ... A[i - 1] | A[i] ... A[n - 1] 29 | B[0] ... B[j - 1] | B[i] ... B[m - 1] 30 | ``` 31 | 32 | Let say `A` is splitted by `i` and `B` is splitted by `j`. We can "move" `i` and `j` in such way that: 33 | 34 | `i + j == (n - i) + (m - j)` 35 | 36 | If with some `i` and `j` the following condition becomes true: 37 | 38 | ``` 39 | B[j - 1] <= A[i] and A[i - 1] <= B[j] 40 | ``` 41 | 42 | then we found the median. 43 | 44 | To "move" `i` and `j` we use binary search, so the overall complexity is `O(log(m + n))`. 45 | 46 | #### Solution 47 | 48 | ```cpp 49 | double Solution::findMedianSortedArrays(const vector &A, const vector &B) { 50 | 51 | if (A.empty()) { 52 | return Solution::findMedianSortedArrays(B, A); 53 | } 54 | 55 | int n = A.size(); 56 | int m = B.size(); 57 | 58 | int l = 0; 59 | int r = n; 60 | int s = n + m; 61 | double ans; 62 | 63 | if (s == 1) { 64 | return A[0]; 65 | } 66 | 67 | while (1) { 68 | int i = (l + r + 1) / 2; 69 | int j = (s + 1) / 2 - i; 70 | 71 | if (j > m) { 72 | l = i + 1; 73 | } else if (i > 0 && j < m && A[i - 1] > B[j]) { 74 | r = i - 1; 75 | } else if (j > 0 && i < n && B[j - 1] > A[i]) { 76 | l = i + 1; 77 | } else { 78 | 79 | int max = INT_MIN; 80 | 81 | if (i > 0 && A[i - 1] > max) max = A[i - 1]; 82 | if (j > 0 && B[j - 1] > max) max = B[j - 1]; 83 | 84 | int min = INT_MAX; 85 | 86 | if (i < n && A[i] < min) min = A[i]; 87 | if (j < m && B[j] < min) min = B[j]; 88 | 89 | if (s % 2 == 1) ans = max; 90 | else ans = (min + max) * 1. / 2; 91 | break; 92 | } 93 | 94 | } 95 | 96 | return ans; 97 | } 98 | 99 | ``` 100 | -------------------------------------------------------------------------------- /tips/06-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "There are two sorted arrays A and B of size m and n respectively. Find the median of the two sorted arrays (the median of the array formed by merging both the arrays). The overall run time complexity should be `O(log (m+n))`." 3 | } 4 | -------------------------------------------------------------------------------- /tips/07-08-2017/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/07-08-2017/1.png -------------------------------------------------------------------------------- /tips/07-08-2017/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/07-08-2017/2.png -------------------------------------------------------------------------------- /tips/07-08-2017/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/07-08-2017/3.png -------------------------------------------------------------------------------- /tips/07-08-2017/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/07-08-2017/4.png -------------------------------------------------------------------------------- /tips/07-08-2017/astexplorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/07-08-2017/astexplorer.png -------------------------------------------------------------------------------- /tips/07-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | [Earlier][1] I have explained why `JSDoc` doesn't worth making effort. Still sometimes we have to use it because moving to a type system like Flow or TypeScript may be quite expensive. Here we come to a few questions: 2 | 3 | 1. How to maintain `JSDoc`? (as far as `JSDoc` is based on comments it is prone to become obsolete) 4 | 2. How to increase code quality with `JSDoc`? 5 | 6 | 7 | [next0][2] suggested me a remarkable idea: we could create runtime assertions out of `JSDoc` comments and put them right into a code while we are in the development environment. 8 | 9 | So I have made one step forward: created a [`jsdoc-to-condition`][3] npm package. It has a single clear aim: transform a `jsdoc` comment to a set of validation rules. 10 | 11 | This is the example: 12 | 13 | ```js 14 | // @param {[string, number?]} a 15 | Array.isArray(a) && 16 | (typeof a[0] === 'string') && 17 | ((a[1] == null) || (typeof a[1] === 'number')) 18 | 19 | // @param {Object.} b 20 | ( 21 | !!b && 22 | (typeof b === 'object') && 23 | !Array.isArray(b) 24 | ) && Object.keys(b).every(function (k) { 25 | return 26 | (typeof k === 'string') && 27 | (typeof b[k] === 'number'); 28 | }) 29 | 30 | // @param {string|number} c 31 | (typeof c === 'string') || (typeof c === 'number') 32 | ``` 33 | 34 | The next step is to implement a babel plugin which will read comments, build assertions with the `jsdoc-to-condition` package and put them right into the code. 35 | 36 | --- 37 | 38 | Special credits to [next0][2] for sharing the idea 39 | 40 | [1]: https://github.com/jakwuh/webtip/tree/master/tips/27-07-2017/Readme.md 41 | [2]: https://github.com/next0 42 | [3]: https://www.npmjs.com/package/jsdoc-to-condition 43 | -------------------------------------------------------------------------------- /tips/07-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "I have made one step forward: created a `jsdoc-to-condition` npm package. It has a single clear aim: transform a jsdoc comment to a set of validation rules." 3 | } 4 | -------------------------------------------------------------------------------- /tips/07-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | In the last [Totally Tooling Tips episode][1] Addy Osmani & Matt Gaunt show a few interesting tools for managing bundle size. This is a short overview of them: 2 | 3 | #### [bundlesize][2] 4 | 5 | [bundlesize][2] is a package which could be seamlessly integrated with Github. It helps you track that bundle sizes are not exceed their max sizes. The configuration looks like the following: 6 | 7 | ```js 8 | // package.json 9 | { 10 | ... 11 | "scripts": { 12 | "test": "bundlesize" 13 | }, 14 | "bundlesize": [ 15 | { 16 | "path": "./index.js", 17 | "maxSize": "1 kB" 18 | } 19 | ] 20 | } 21 | ``` 22 | 23 | And the output: 24 | 25 | ``` 26 | > npm test 27 | PASS index.js: 53B < maxSize 1KB gzip 28 | ``` 29 | 30 | As you could see `bundlesize` works with gzip sizes out of the box. 31 | 32 | #### Webpack performance budgets 33 | 34 | [Starting from][3] `webpack@2.2` you can use webpack performance budgets: 35 | 36 | ```js 37 | // webpack.config.js 38 | performance: { 39 | maxAssetSize: 25000, 40 | hints: 'warning' 41 | } 42 | ``` 43 | 44 | ![webpack performance budgets](./webpack.png) 45 | 46 | #### [Speedcurve][4] and [Calibre][5] 47 | 48 | These are paid services which measure a lot of metrics on a regular basis. 49 | 50 | [1]: https://youtu.be/Da6VxdGU2Ig 51 | [2]: https://github.com/siddharthkp/bundlesize 52 | [3]: https://medium.com/webpack/webpack-performance-budgets-13d4880fbf6d 53 | [4]: https://speedcurve.com/ 54 | [5]: https://calibreapp.com/ 55 | -------------------------------------------------------------------------------- /tips/07-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "In the last Totally Tooling Tips episode Addy Osmani & Matt Gaunt show a few interesting tools for managing bundle size. This is a short overview of them:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/07-10-2017/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/07-10-2017/webpack.png -------------------------------------------------------------------------------- /tips/08-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Scramble String][1] 2 | 3 | While I'm working on the 2nd part of [the JSDoc type checker][2] let's solve one more task from [leetcode][3]. 4 | 5 | #### Task 6 | 7 | Given a string `s1`, we may represent it as a binary tree by partitioning it to two non-empty substrings recursively. 8 | 9 | Below is one possible representation of `s1` = "great": 10 | 11 | ``` 12 | great 13 | / \ 14 | gr eat 15 | / \ / \ 16 | g r e at 17 | / \ 18 | a t 19 | ``` 20 | 21 | To scramble the string, we may choose any non-leaf node and swap its two children. 22 | 23 | For example, if we choose the node "gr" and swap its two children, it produces a scrambled string "rgeat". 24 | 25 | ``` 26 | rgeat 27 | / \ 28 | rg eat 29 | / \ / \ 30 | r g e at 31 | / \ 32 | a t 33 | ``` 34 | 35 | We say that "rgeat" is a scrambled string of "great". 36 | 37 | Similarly, if we continue to swap the children of nodes "eat" and "at", it produces a scrambled string "rgtae". 38 | 39 | ``` 40 | rgtae 41 | / \ 42 | rg tae 43 | / \ / \ 44 | r g ta e 45 | / \ 46 | t a 47 | ``` 48 | We say that "rgtae" is a scrambled string of "great". 49 | 50 | Given two strings `s1` and `s2` of the same length, determine if `s2` is a scrambled string of `s1`. 51 | 52 | Example inputs: 53 | ``` 54 | s1: "abcd" 55 | s2: "bdac" 56 | Result = False 57 | ``` 58 | 59 | ``` 60 | s1: "abcd" 61 | s2: "bacd" 62 | Result = True 63 | ``` 64 | 65 | #### Analysis 66 | 67 | > To be honest I didn't like the task very much because of solution being not as elegant as I want it to be 68 | 69 | The solution is rather straight forward and could be represented in a few simple steps: 70 | 71 | Given two strings `s1` and `s2`: 72 | 73 | 1. Check if they are equal. If so, return `True` 74 | 2. Check if they consist of the same letters. If no, return `False` 75 | 3. If there is a partition of a size `i`, such that `isScramble(s1[:i], s2[:i]) and isScramble(s1[i:], s2[i:])` or `isScramble(s1[:i], s2[-i:]) and isScramble(s1[i:], s2[:-i])` return `True`. Otherwise return `False`. 76 | 77 | #### Solution 78 | 79 | ```python 80 | class Solution(object): 81 | def isScramble(self, s1, s2): 82 | if s1 == s2: 83 | return True 84 | 85 | letters = collections.defaultdict(lambda: 0) 86 | 87 | for letter in s1: 88 | letters[letter] += 1 89 | 90 | for letter in s2: 91 | letters[letter] = max(0, letters[letter] - 1) 92 | 93 | if not sum(letters.values()) == 0: 94 | return False 95 | 96 | for i in range(1, len(s1)): 97 | if self.isScramble(s1[0:i], s2[0:i]) and self.isScramble(s1[i:], s2[i:]): 98 | return True 99 | if self.isScramble(s1[0:i], s2[-i:]) and self.isScramble(s1[i:], s2[0:-i]): 100 | return True 101 | 102 | return False 103 | ``` 104 | 105 | [1]: https://leetcode.com/problems/scramble-string/description/ 106 | [2]: https://github.com/jakwuh/webtip/tree/master/tips/07-09-2017/Readme.md 107 | [3]: https://leetcode.com 108 | -------------------------------------------------------------------------------- /tips/08-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Given two strings `s1` and `s2` of the same length, determine if `s2` is a scrambled string of `s1`." 3 | } 4 | -------------------------------------------------------------------------------- /tips/08-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | #### Should I [DRY][1] or not? 2 | 3 | There are a lot of different opinions when it comes to decision on code (de)duplication. Usually I use the following set of rules: 4 | 5 | - Don't try to deduplicate each line of a code. The advantage of such optimisation (premature) is doubtful. Don't forget your code is compressed by gzip (brotli / zopfli) and it does compress similar pieces of code way much better than you. 6 | 7 | - Remember you are working in a team. When making a decision think about your teammate who may want the same functionality in a future or had already implemented the functionality you need. So, if you have a relatively large (or complex) piece of code take it out in a separate function. The naming convention across the team is important. 8 | 9 | - If you are in doubt whether a piece of code should be deduplicated, then deduplicate it. Both 2 most popular bundlers (webpack & rollup) now have [scope hoisting][3] feature. That means excess deduplication will have a 0 cost. 10 | 11 | 12 | [1]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself 13 | [2]: https://en.wikipedia.org/wiki/Bus_factor 14 | [3]: https://medium.com/webpack/webpack-3-official-release-15fd2dd8f07b 15 | -------------------------------------------------------------------------------- /tips/08-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "There are a lot of different opinions when it comes to decision on code (de)duplication. Usually I use the following set of rules:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/09-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | #### Subscribe to our [Telegram channel](https://t.me/webtip) to get daily updates 2 | 3 | --- 4 | 5 | Today we will overview the differences between open-source licenses: MIT, Apache and GPL. 6 | 7 | First of all, MIT, Apache and GPL licenses are **all permissive licenses**, meaning: you are free to do whatever you want with a software. 8 | 9 | They differ a bit though: 10 | 11 | - **MIT**: "Do whatever you want, even if you want to use the code to create a commercial closed-source software. Though if you start contributing back, it will be nice." 12 | - **Apache**: "Do whatever you want, like with the MIT license. Still, I will prevent any attempt of creating a patent on the software or litigating" 13 | - **GPL**: "Do whatever you want, but remember: I'm share-alike. Once you use the code you are to share your software with the same license" 14 | 15 | Thus, a specific license signifies our concern about the code: 16 | - **MIT** - we actually don't bother 17 | - **Apache** - we don't bother too, but we are big enough (e.g. an organization) to think about preventing litigations 18 | - **GPL** - we want you to open-source too 19 | 20 | > Note: ISC (npm's default license) is very similar to MIT 21 | 22 | ### Links 23 | [Discussion 1][1] 24 | [Discussion 2][2] 25 | 26 | [1]: http://exygy.com/which-license-should-i-use-mit-vs-apache-vs-gpl/ 27 | [2]: https://gist.github.com/indexzero/10602128 28 | -------------------------------------------------------------------------------- /tips/09-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "You are giving candies to children subjected to the following requirements: each child must have at least one candy and children with a higher rating get more candies than their neighbors. What is the minimum candies you must give?" 3 | } 4 | -------------------------------------------------------------------------------- /tips/09-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | If you have a file input which is supposed to open user's camera on mobile phones (due to business requirements or some UX decisions) then it is possible to indicate that capture of media directly from device's camera is preferred using [html media `capture`][2] attribute. 2 | 3 | This is the code: 4 | 5 | ```js 6 | // index.html 7 | 8 | 9 | ``` 10 | 11 | ```js 12 | // index.js 13 | let input = document.querySelector('input'), 14 | img = document.querySelector('img'); 15 | 16 | input.addEventListener('change', (event) => { 17 | let [file] = event.target.files; 18 | 19 | if (file) { 20 | img.src = URL.createObjectURL(file) 21 | } 22 | }); 23 | ``` 24 | 25 | Available values for `capture` attribute: 26 | 27 | - `user` - open frontal camera 28 | - `environment` - open rear camera 29 | - no value - implementation-specific 30 | 31 | In case if there is no available camera then input still acts like an input without `capture` attribute (e.g. on desktop). 32 | 33 | Try [the result][1] on your phone. 34 | 35 | [1]: https://jsfiddle.net/poe3wf99/4/embedded/result/ 36 | [2]: https://www.w3.org/TR/html-media-capture/ 37 | -------------------------------------------------------------------------------- /tips/09-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "If you have a file input which is supposed to open user's camera on mobile phones (due to business requirements or some UX decisions) then it is possible to indicate that capture of media directly from device's camera is preferred using html media `capture` attribute." 3 | } 4 | -------------------------------------------------------------------------------- /tips/10-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | [Performance][1] API has two useful methods: `.mark()` and `.measure()`. 2 | 3 | ![performance.measure() demo](./image.gif) 4 | 5 | `performance.mark(name)` - adds a new performance entry 6 | 7 | `performance.measure(name, startMark, endMark)` - measures time between two given performance entries, identified by a mark name. 8 | 9 | The most interesting part here is that `performance.measure()` adds a new event into the **User Timing** section under the **Performance** tab. (checked in Chrome 60 / Canary). This could be very useful to build a general picture of an execution flow. 10 | 11 | **Source code** 12 | 13 | ```js 14 | function asyncOperation() { 15 | setTimeout(() => { 16 | performance.mark('async-done'); 17 | performance.measure('time-to-async', 'async-start', 'async-done'); 18 | }, 1000); 19 | } 20 | 21 | performance.mark('async-start'); 22 | asyncOperation(); 23 | ``` 24 | 25 | [1]: https://developer.mozilla.org/en/docs/Web/API/Performance 26 | -------------------------------------------------------------------------------- /tips/10-08-2017/image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/10-08-2017/image.gif -------------------------------------------------------------------------------- /tips/10-08-2017/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | performance.mark() demo 6 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tips/10-08-2017/index.js: -------------------------------------------------------------------------------- 1 | function asyncOperation() { 2 | setTimeout(() => { 3 | performance.mark('async-done'); 4 | performance.measure('time-to-async', 'async-start', 'async-done'); 5 | }, 1000); 6 | } 7 | 8 | performance.mark('async-start'); 9 | asyncOperation(); 10 | -------------------------------------------------------------------------------- /tips/10-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Single Number][1] 2 | 3 | While working hard to prepare [#CSSMinskJS][2] conference (see the photo at the end of the post) I relax by solving algorithmic tasks. We have actually done it! The conference was pretty awesome. I will have a separate post devoted to the inside and outside parts of it. 4 | 5 | #### Task 6 | 7 | Given an array of integers, every element appears twice except for one. Find that single one. 8 | 9 | *Note: Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?* 10 | 11 | #### Analysis 12 | 13 | I guess just saying `x ^ x = 0` would be enough to solve the task because obviously we have: 14 | ``` 15 | a[1] ^ a[1] ^ ... a[i-1] ^ a[i-1] ^ a[i] ^ a[i+1] ^ a[i+1] ^ ... ^ a[j] ^ a[j] = a[i] 16 | ``` 17 | 18 | #### Solution 19 | 20 | ```cpp 21 | int Solution::singleNumber(const vector &A) { 22 | int x = A[0]; 23 | for (int i = 1; i < A.size(); ++i) { 24 | x ^= A[i]; 25 | } 26 | return x; 27 | } 28 | ``` 29 | 30 | ![#CSSMinskJS](./photo.jpg) 31 | *(At the [#CSSMinskJS][2] conference)* 32 | 33 | [1]: https://www.interviewbit.com/problems/single-number/ 34 | [2]: https://www.instagram.com/explore/tags/cssminskjs/ 35 | -------------------------------------------------------------------------------- /tips/10-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "While working hard to prepare #CSSMinskJS conference (see the photo at the end of the post) I relax by solving algorithmic tasks. We have actually done it! The conference was pretty awesome. I will have a separate post devoted to the inside and outside parts of it." 3 | } 4 | -------------------------------------------------------------------------------- /tips/10-09-2017/photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/10-09-2017/photo.jpg -------------------------------------------------------------------------------- /tips/10-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Permutations][1] 2 | 3 | #### Task 4 | 5 | Given a collection of numbers, return all possible permutations. 6 | 7 | Example: 8 | 9 | ``` 10 | Input: 11 | [1,2,3] 12 | 13 | Result: 14 | [1,2,3] 15 | [1,3,2] 16 | [2,1,3] 17 | [2,3,1] 18 | [3,1,2] 19 | [3,2,1] 20 | ``` 21 | 22 | > 1. No two entries in the permutation sequence should be the same. 23 | > 2. For the purpose of this problem, assume that all the numbers in the collection are **unique**. 24 | 25 | #### Analysis 26 | 27 | Let us think in terms of recursion. We want to place each number at the first place and then finding all permutations for the reduced set of numbers. This is, actually, the whole solution. 28 | 29 | #### Solution 30 | 31 | ```cpp 32 | void generate(vector ¤t, set &candidates, vector > &ans) { 33 | int n = candidates.size(); 34 | 35 | if (n == 0) { 36 | ans.push_back(current); 37 | } else { 38 | vector cands(candidates.begin(), candidates.end()); 39 | 40 | for (auto it = cands.begin(); it != cands.end(); ++it) { 41 | auto v = current; 42 | v.push_back(*it); 43 | candidates.erase(*it); 44 | generate(v, candidates, ans); 45 | candidates.insert(*it); 46 | } 47 | } 48 | } 49 | 50 | vector > Solution::permute(vector &A) { 51 | vector > ans = {}; 52 | vector current = {}; 53 | set candidates(A.begin(), A.end()); 54 | generate(current, candidates, ans); 55 | return ans; 56 | } 57 | 58 | ``` 59 | 60 | [1]: https://www.interviewbit.com/problems/permutations/ 61 | -------------------------------------------------------------------------------- /tips/10-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Given a collection of numbers, return all possible permutations. No two entries in the permutation sequence should be the same. For the purpose of this problem, assume that all the numbers in the collection are unique" 3 | } 4 | -------------------------------------------------------------------------------- /tips/11-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Today we will learn more about cookies in a browser by asking a few questions to ourselves. 2 | 3 | > How browser defines a `path` value for a cookie when not specified? 4 | 5 | It strips out the current page path from the last `/` char to the end. That means: 6 | 7 | 1. On a page `/sub` the cookie path will be `/` 8 | 2. On a page `/sub/` the cookie path will be `/sub` 9 | 10 | > What is the difference between cookie with a path `/`, a path `/sub` and a path `/sub/`? 11 | 12 | Page `/sub/` will inherit cookies from all 3 domains. 13 | Page `/sub` will inherit cookies from domains `/` and `/sub` only. 14 | Page `/` will inherit cookies only from domain `/`. 15 | 16 | > Do cookies from domain `mydomain.com` are available on its subdomains (e.g. `sub.mydomain.com`) 17 | 18 | It depends on setting the `domain` property for a cookie. 19 | 20 | If cookie doesn't have a `domain` property explicitly set, then it will be available only on its own domain (cookie set on `mydomain.com` will only be available for `mydomain.com` and vice versa cookie set on `sub.mydomain.com` will only be available on `sub.mydomain.com`). 21 | 22 | If cookie has a `domain` property set, then it will be propagated to all subdomains of the current domain (regardless of the current domain level). 23 | 24 | > Does it mean that using `www.` is not so pointless? 25 | 26 | Yes, it does. If your site is available at `www.mysite.com` and your static resources are available at `mysite.com` (without `www`) then browser will not send cookies when loading static resources. This leads to a lower network usage. Still an aesthetic side of using `mysite.com` (without `www`) as a domain will be more important cause. 27 | 28 | [Source code](./index.js) 29 | -------------------------------------------------------------------------------- /tips/11-08-2017/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/11-08-2017/index.html -------------------------------------------------------------------------------- /tips/11-08-2017/index.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | 3 | const mockPage = ` 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | `; 15 | 16 | const server = http.createServer((req, res) => { 17 | const {url, headers: {host}} = req; 18 | const encodedUrl = encodeURIComponent(url); 19 | const encodedHost = encodeURIComponent(host); 20 | const rawDomain = host.replace(/:\d+$/, ''); 21 | const rawPath = url.split('?')[0]; 22 | 23 | console.log(encodedHost, encodedUrl); 24 | 25 | const setDomain = /domain\=1/.test(url); 26 | const setPath = /path\=1/.test(url); 27 | const cookie = `${encodedHost}${encodedUrl}=1;` 28 | + (setDomain ? `domain=${rawDomain};` : '') 29 | + (setPath ? `path=${rawPath};` : ''); 30 | 31 | res.writeHead(200, { 32 | 'Set-Cookie': cookie, 33 | 'Content-Type': 'text/plain' 34 | }); 35 | 36 | res.end(); 37 | }); 38 | 39 | const port = 6070; 40 | 41 | server.listen(port, () => console.log(`Listening on ${port}`)); 42 | -------------------------------------------------------------------------------- /tips/11-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | [3 tips before][2] we discussed the idea about making assertions based on `jsdoc` comments. As the result there is a [`babel-plugin-jsdoc-to-condition`][3] package. In this post we have a closer look at it. 2 | 3 | ### How it works 4 | 5 | The idea behind the plugin is pretty simple. Assume we have the following function (or `FunctionDeclaration` in terms of babel): 6 | 7 | ```js 8 | /** 9 | * @param {number} param - this is a param. 10 | * @param {string} b - this is a param. 11 | * @param {string[]} [c] - this is a param. 12 | */ 13 | function lonelyFunction(param, b, c) { 14 | } 15 | ``` 16 | 17 | With the plugin enabled the code above transforms to the following: 18 | 19 | ```js 20 | /** 21 | * @param {number} param - this is a param. 22 | * @param {string} b - this is a param. 23 | * @param {string[]} [c] - this is a param. 24 | */ 25 | function lonelyFunction(param, b, c) { 26 | if (!(typeof param === 'number')) { 27 | console._warn('actual.js:6:0: Expected `param` to have type number, got: ' + typeof param); 28 | } 29 | 30 | if (!(typeof b === 'string')) { 31 | console._warn('actual.js:6:0: Expected `b` to have type string, got: ' + typeof b); 32 | } 33 | 34 | if (!(c === undefined || Array.isArray(c) && c.every(function (n) { 35 | return typeof n === 'string'; 36 | }))) { 37 | console._warn('actual.js:6:0: Expected `c` to have type Array.=, got: ' + typeof c); 38 | } 39 | } 40 | ``` 41 | 42 | ### Use cases 43 | 44 | The plugin is intended to be used in a *development* / *testing* environment to catch wrong `jsdoc` declarations and thus prevent possible bugs. One should not enable it for production builds. 45 | 46 | ### Status 47 | 48 | ✅ Support for `@param` annotation 49 | 🔲 Support for `@type` annotation 50 | 🔲 Support for `@returns` annotation 51 | 🔲 Support for `@typedef` structures 52 | 🔲 Support for not imported classes 53 | 54 | Let me give some more explanations on the *Support for custom classes* further improvement. Have a look at the annotation: 55 | 56 | ```js 57 | @param {Foo} model 58 | ``` 59 | 60 | To make an assertion for the annotation above we need a `Foo` class constructor. If it is already imported then the created assertion will simply work. Otherwise we need to import the class ourselves. Currently, created assertion will silently pass, but actually we need to import the class automatically and make the assertion work fairly. 61 | 62 | ### TIL 63 | 64 | - It is possible to validate `jsdoc` at https://eslint.org/doctrine/demo/ 65 | - `@param {string=} v` is equal to `@param {string} [v]` 66 | - Babel plugin API has a lot of methods to deal with JS scopes. One of those I have used: `path.scope.hasBinding()`. 67 | - `babel-plugin-transform-es2015-function-name` changes function parameters names in some specific cases. For instance: 68 | 69 | ```js 70 | className: function(options, className) { 71 | } 72 | ``` 73 | transpiles to the following: 74 | ```js 75 | className: function className(options, _className) { 76 | } 77 | ``` 78 | This is important for creating proper assertions from `jsdoc` comments. 79 | 80 | [1]: https://github.com/jakwuh/babel-plugin-jsdoc-to-condition 81 | [2]: https://github.com/jakwuh/webtip/tree/master/tips/07-09-2017 82 | [3]: https://www.npmjs.com/package/babel-plugin-jsdoc-to-condition 83 | -------------------------------------------------------------------------------- /tips/11-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "3 tips before we discussed the idea about making assertions based on `jsdoc` comments. As the result there is a `babel-plugin-jsdoc-to-condition` package. In this post we have a closer look at it." 3 | } 4 | -------------------------------------------------------------------------------- /tips/11-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Resource hint is a way of prematurely giving resource information to a browser to make it faster: whether it is looking up for a domain or fetching a script. Lets explore existing resource hints and their purpose. 2 | 3 | #### dns-prefetch 4 | 5 | ```html 6 | 7 | ``` 8 | 9 | The link is used to force the lookup for a specific domain. This can be used to reduce DNS lookup time. BTW, here we understand why CDN is better for a user: usually CDN's domain is already resolved and cached in a browser thus DNS lookup time will be 0. 10 | 11 | ![DNS lookup](./dns.png) 12 | 13 | #### preconnect 14 | 15 | ```html 16 | 17 | ``` 18 | 19 | `preconnect` is even more powerful technique - it initiates an early TCP connection to a domain, which includes DNS lookup and TCP \ TLS negotiations (e.g. handshakes). 20 | 21 | #### preload 22 | 23 | ```html 24 | 25 | ``` 26 | 27 | The resource hint is used to identify a resource that is likely to be required on a current page. If used for scripts, browser only loads a resource and do not executes it. 28 | 29 | #### prefetch 30 | 31 | 32 | ```html 33 | 34 | ``` 35 | 36 | This is similar to `preload` resource hint, but is used to identify a resource that is likely to be required on future pages. This means a resource will have a lower priority in a download queue. 37 | 38 | #### prerender 39 | 40 | ```html 41 | 42 | ``` 43 | 44 | The last (but not the least) resource hint, `prerender`, is used to identify a page that might be required by the next navigation. 45 | -------------------------------------------------------------------------------- /tips/11-10-2017/dns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/11-10-2017/dns.png -------------------------------------------------------------------------------- /tips/11-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Resource hint is a way of prematurely giving resource information to a browser to make it faster: whether it is looking up for a domain or fetching a script. Lets explore existing resource hints and their purpose." 3 | } 4 | -------------------------------------------------------------------------------- /tips/12-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | After seeing a bunch of implicit and really explosive `RegExp`-related bugs I've realized we have to do something with it. 2 | 3 | Imagine we have a following `RegExp`: 4 | 5 | ```js 6 | const {pathname} = location; 7 | const logRegExp = new RegExp('^\\/' + pathname + '\\/log\\/'); 8 | logRegExp.test(url); 9 | ``` 10 | 11 | Lets understand what we can do better. 12 | 13 | #### Avoid using RegExp 14 | 15 | First of all we could use [`.startsWith()`][1] string method instead of `RegExp`. 16 | 17 | ```js 18 | url.startsWith(`/${pathname}/log/`); 19 | ``` 20 | 21 | A few more examples: 22 | 23 | ```js 24 | url.endsWith(`/${expectedPath}/`); 25 | url.includes(`/${expectedPath}/`); 26 | ``` 27 | 28 | These 3 handy string methods could let us avoid using `RegExp`s in a certain amount of cases. Don't forget these are **ES2015 (formerly ES6)** features so you need to shim them (I prefer [shimming w/ babel][2]). 29 | 30 | #### Don't forget to escape RegExp 31 | 32 | ![Escape RegExp](./escape.png) 33 | 34 | One could say *"Sure, it's obvious"*, still it is so **easy to forget** about escaping (and I've seen one forgets it for so many times, including myself). Have a look at the following code: 35 | 36 | ```js 37 | new RegExp('^\/' + pathname + '\/log\/'); 38 | ``` 39 | 40 | It has 3(!) mistakes, 2 of which are *"obvious"*. 41 | 42 | 1. `\` should be escaped, thus: `new RegExp('^\\/' + pathname + '\\/log\\/');` 43 | 2. `pathname` should be escaped, thus: `new RegExp('^\\/' + escapeForRegExp(pathname) + '\\/log\\/');` 44 | 3. `RegExp` here is **not necessary**, thus: ``str.startsWith(`/${pathname}/log/`)`` 45 | 46 | If you still are going to use a `new RegExp` expression, an [**escape-string-regexp**][3] npm library will provide you an `escapeForRegExp` function. 47 | 48 | #### Test it 49 | 50 | I'll tell you a funny fact about myself: everytime I write more or less comlicated `RegExp` I start convincing myself the `RegExp` is really simple and doesn't need to be tested. After some negotiations I decide to test it and then I **bless myself** for doing it because the `RegExp` **almost always** has at least one bug. 51 | 52 | Don't neglect this rule, encapsulate a `RegExp` in a separate function and test it. 53 | 54 | --- 55 | 56 | Do you have any other important and useful ideas about dealing with `RegExp`s? Feel free to share your knowledge! 57 | 58 | [1]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith 59 | [2]: http://babeljs.io/docs/usage/polyfill/ 60 | [3]: https://www.npmjs.com/package/escape-string-regexp 61 | -------------------------------------------------------------------------------- /tips/12-08-2017/escape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/12-08-2017/escape.png -------------------------------------------------------------------------------- /tips/12-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Subsets][1] 2 | 3 | #### Task 4 | 5 | Given a set of distinct integers `S`, return all possible subsets. 6 | 7 | *Note:* 8 | 9 | - Elements in a subset must be in the non-descending order. 10 | - The solution set must not contain duplicate subsets. 11 | - Also subsets should be sorted in the ascending ( lexicographic ) order. 12 | - The list is not necessarily sorted. 13 | 14 | #### Analysis 15 | 16 | The key idea is to use a bitmask to represent a subset. A subset `X` contains `A[i]` if and only if `x[i] == 1`. Thus, a bitmask set `0..2^n` where `n = A.size()` represents all the possible subsets. 17 | 18 | Example: 19 | ``` 20 | A = [1, 2, 3] 21 | 22 | x = 000, X = [] 23 | x = 001, X = [3] 24 | x = 010, X = [2] 25 | x = 011, X = [2, 3] 26 | x = 100, X = [1] 27 | x = 101, X = [1, 3] 28 | x = 110, X = [1, 2] 29 | x = 111, X = [1, 2, 3] 30 | ``` 31 | 32 | Finally, we have to sort received subsets according to the task. 33 | 34 | #### Solution 35 | 36 | ```cpp 37 | vector > Solution::subsets(vector &A) { 38 | std::sort(A.begin(), A.end()); 39 | 40 | // 2 ^ A.size() 41 | int n = 1 << A.size(); 42 | 43 | vector< vector > ans; 44 | 45 | for (int i = 0; i < n; ++i) { 46 | vector cur; 47 | for (int j = 0; j < A.size(); ++j) { 48 | if (1 << j & i) cur.push_back(A[j]); 49 | } 50 | ans.push_back(cur); 51 | } 52 | 53 | std::sort(ans.begin(), ans.end(), [](const vector& a, const vector &b) { 54 | auto it_a = a.begin(); 55 | auto it_b = b.begin(); 56 | while (it_a != a.end() && it_b != b.end() && *it_a == *it_b) { 57 | ++it_a; 58 | ++it_b; 59 | } 60 | return it_a == a.end() || (it_b != b.end() && *it_a < *it_b); 61 | }); 62 | 63 | return ans; 64 | } 65 | 66 | ``` 67 | 68 | [1]: https://www.interviewbit.com/problems/subset/ 69 | -------------------------------------------------------------------------------- /tips/12-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Given a set of distinct integers S, return all possible subsets. Elements in a subset must be in non-descending order. The solution set must not contain duplicate subsets. Also, subsets should be sorted in ascending ( lexicographic ) order." 3 | } 4 | -------------------------------------------------------------------------------- /tips/12-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Rearrange Array][1] 2 | 3 | #### Task 4 | 5 | Rearrange a given array so that `arr[i]` becomes `arr[arr[i]]` with `O(1)` extra space. 6 | 7 | Example: 8 | 9 | ``` 10 | Input : [1, 0] 11 | Result : [0, 1] 12 | ``` 13 | 14 | > Lets say N = size of the array. Then, following holds true: 15 | - All elements in the array are in the range [0, N-1] 16 | - N * N does not overflow for a signed integer 17 | 18 | #### Analysis 19 | 20 | As far as we should use `O(1)` extra space it is most likely we should transform an array in such a way that each element holds information about initial value and its future value. Here we come to the trick: 21 | 22 | ``` 23 | X = A[i] + (A[A[i]] % n) * n; 24 | ``` 25 | 26 | `A[i] < n` and that means `X % n == A[i]` and `X / n == A[A[i]]`. 27 | 28 | #### Solution 29 | 30 | ```cpp 31 | void Solution::arrange(vector &A) { 32 | int n = A.size(); 33 | 34 | for (int i = 0; i < n; ++i) { 35 | A[i] = A[i] + (A[A[i]] % n) * n; 36 | } 37 | 38 | for (int i = 0; i < n; ++i) { 39 | A[i] = A[i] / n; 40 | } 41 | } 42 | ``` 43 | 44 | [1]: https://www.interviewbit.com/problems/rearrange-array/ 45 | -------------------------------------------------------------------------------- /tips/12-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Rearrange a given array so that `arr[i]` becomes `arr[arr[i]]` with O(1) extra space." 3 | } 4 | -------------------------------------------------------------------------------- /tips/13-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Today I will only left 2 links for you: 2 | 3 | - [BB is watching you: demo][1] 4 | - [BB is watching you: explanation][2] 5 | 6 | Steps to do on a demo page: 7 | 1. Type any input and press **save** 8 | 2. Open the same page in incognito mode 9 | 3. Be surprised 10 | 11 | **The principles** 12 | 13 | The demo was created using a fingerprintjs library. It identifies a user by calculating a hash of a bunch of unique parameters: 14 | 15 | - UserAgent 16 | - Language settings 17 | - TimeZone 18 | - Plugins list 19 | - Installed fonts (the most significant part) 20 | 21 | --- 22 | 23 | **BB** === Big Brother 24 | 25 | [1]: https://akwuh.me/bb/ 26 | [2]: https://youtu.be/RcB-JqgaLeA 27 | [3]: https://github.com/Valve/fingerprintjs 28 | -------------------------------------------------------------------------------- /tips/13-09-2017/index.cmd: -------------------------------------------------------------------------------- 1 | docker pull ubuntu:16.10 2 | docker run -ti ubuntu:16.10 /bin/bash 3 | 4 | apt-get update 5 | apt-get install -y git curl python 6 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 7 | export PATH=`pwd`/depot_tools:"$PATH" 8 | gclient 9 | fetch v8 10 | -------------------------------------------------------------------------------- /tips/13-09-2017/index.js: -------------------------------------------------------------------------------- 1 | let x = [1, 2, 3] 2 | x[9] = 4; 3 | 4 | console.log('Actual array: ', x); 5 | 6 | let str = 'Array values in .map(): [ '; 7 | 8 | x.forEach((v, i) => str += (i ? ', ' : '') + v); 9 | 10 | str += ' ]'; 11 | 12 | console.log(str); 13 | -------------------------------------------------------------------------------- /tips/13-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Recently I've started learning about JS V8's internals. Mathias Bynens, a V8 contributor from Google, wrote an interesting article named “Elements kinds” in V8. He explains how JS engines usually deal with numeric properties and which patterns not to use in order to avoid performance penalty." 3 | } 4 | -------------------------------------------------------------------------------- /tips/13-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Nearest Smaller Element][1] 2 | 3 | #### Task 4 | 5 | Given an array, find the nearest smaller element `G[i]` for every element `A[i]` in the array such that the element has an index smaller than `i`. 6 | 7 | More formally, 8 | 9 | `G[i]` for an element `A[i]` = an element `A[j]` such that: 10 | - `j` is maximum possible AND 11 | - `j` < `i` AND 12 | - `A[j]` < `A[i]` 13 | 14 | Elements for which no smaller element exist, consider next smaller element as -1. 15 | 16 | Example: 17 | 18 | ``` 19 | Input: [4, 5, 2, 10, 8] 20 | Result: [-1, 4, -1, 2, 2] 21 | 22 | Input: [3, 2, 1] 23 | Result: [-1, -1, -1] 24 | ``` 25 | 26 | #### Analysis 27 | 28 | To store the smallest elements we create stack. To save the invariant we use the following piece of code before inserting `A[i]`: 29 | 30 | ```cpp 31 | while (!s.empty() && s.top() >= A[i]) s.pop(); 32 | ``` 33 | 34 | #### Solution 35 | 36 | ```cpp 37 | vector Solution::prevSmaller(vector &A) { 38 | stack s; 39 | vector v(A.size()); 40 | 41 | for (int i = 0; i < A.size(); ++i) { 42 | 43 | while (!s.empty() && s.top() >= A[i]) s.pop(); 44 | 45 | if (s.empty()) { 46 | v[i] = -1; 47 | } else { 48 | v[i] = s.top(); 49 | } 50 | 51 | s.push(A[i]); 52 | } 53 | 54 | return v; 55 | } 56 | ``` 57 | 58 | [1]: https://www.interviewbit.com/problems/nearest-smaller-element/ 59 | -------------------------------------------------------------------------------- /tips/13-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Given an array, find the nearest smaller element `G[i]` for every element `A[i]` in the array such that the element has an index smaller than `i`." 3 | } 4 | -------------------------------------------------------------------------------- /tips/14-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | While writing utility tools sometimes there is a strong need in making async function work synchronously: even this is not the *NodeJS way*, an opposite solution could force a large amount of code to be rewritten. For such cases there is a [`deasync`][1] *NodeJS* module. 2 | 3 | It simplifies things a lot: 4 | 5 | ```js 6 | const cp = require('child_process'); 7 | const execSync = deasync(cp.exec); 8 | console.log(execSync('ls -la')); // execSync work synchronously 9 | ``` 10 | 11 | I went even further, and create a library called [`deasync-promise`][2], which uses `deasync` under the hood. It makes promises work synchronously: 12 | 13 | ```js 14 | console.log(deasyncPromise(fetchUsers())); 15 | console.log('Users are fetched.'); 16 | ``` 17 | 18 | `deasync` library exposes method named `run` which forces *NodeJS* event loop to start (once): 19 | 20 | ```cpp 21 | uv_run(uv_default_loop(), UV_RUN_ONCE); 22 | info.GetReturnValue().Set(Nan::Undefined()); 23 | ``` 24 | 25 | --- 26 | 27 | PS. Do not misuse this *NodeJS* feature 28 | 29 | [1]: https://github.com/vkurchatkin/deasync 30 | [2]: https://github.com/jakwuh/deasync-promise 31 | -------------------------------------------------------------------------------- /tips/14-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | For a long time I had no idea what [Accelerated Mobile Pages (AMP)][1] actually is, when it should be used and how one could benefit from it. Today I decided to find it out. 2 | 3 | To cut a long story short, [AMP][1] is a Google alternative to Telegram's *InstantView*, Facebook's *Instant Articles*, Yandex's *Turbo Pages* and Apple's *News* just to name a few. It allows to open pages "instantly" at Google search results and a few partners (e.g. Twitter AFAIK). 4 | 5 | ![AMP](./amp.png) 6 | 7 | 8 | #### What are AMP requirements? 9 | 10 | [There are a lot][2]: 11 | 12 | - Author-written / 3rd party JS is allowed **only in iframes having different domain** (for security reasons). This means an AMP page itself will work without JS at all 13 | - All JS scripts must be asynchronous 14 | - All static content should have their sizes defined in a markup 15 | - All CSS must be **inlined** 16 | - Animations are forbidden except those which run on a GPU (basically `transform` and `opacity` properties) 17 | - ... (a few more) 18 | 19 | #### Is it possible to invalidate cache? 20 | 21 | Yes. 22 | 23 | Less than a year ago AMP introduced `update-ping` API for invalidating cache. Currently [AMP says][4] the API will eventually be deprecated, instead `update-cache` API is available. Such an unstable API also shows AMP is still in an infantile stage. 24 | 25 | #### Could I have both AMP and non-AMP versions? 26 | 27 | Yes. 28 | 29 | Actually you could serve it in [a number of ways][6]: by using prefix (e.g. `example.com/amp`), by using a subdomain (e.g. `amp.example.com`) or even by using a different domain (`amp-example.com`). 30 | 31 | #### Is it still alive? 32 | 33 | Yes. More so, I'd say it is *still an unstable technology*. 34 | 35 | #### Will I benefit from it? 36 | 37 | From what I learned if your service: 38 | 39 | - is read-only in most cases (e.g. news website) 40 | - has a large mobile audience 41 | - has a lot of Google traffic 42 | 43 | you will probably benefit from having AMP version **along** with the usual one. 44 | 45 | Reading further: 46 | [A story about switching to AMP][5] 47 | [Short AMP overview][8] 48 | [Big AMP overview][7] 49 | 50 | ![AMP](./amp.gif) 51 | 52 | [1]: https://www.ampproject.org/ 53 | [2]: https://www.ampproject.org/learn/about-how/ 54 | [3]: https://github.com/ampproject/amphtml/issues/1901 55 | [4]: https://developers.google.com/amp/cache/update-cache 56 | [5]: https://evertpot.com/switching-to-amp-and-back-again/ 57 | [6]: http://www.thesempost.com/websites-can-serve-amp-pages-on-subdomain-or-different-domain/ 58 | [7]: https://www.copyblogger.com/google-amp/ 59 | [8]: https://css-tricks.com/taking-amp-for-a-spin/ 60 | -------------------------------------------------------------------------------- /tips/14-09-2017/amp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/14-09-2017/amp.gif -------------------------------------------------------------------------------- /tips/14-09-2017/amp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/14-09-2017/amp.png -------------------------------------------------------------------------------- /tips/14-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "For a long time I had no idea what AMP actually is, when it should be used and how one could benefit from it. Today I decided to find it out. To cut a long story short, AMP is a Google alternative to Telegram's `InstantView`, Facebook's `Instant Articles`, Yandex's `Turbo Pages` and Apple's `News` just to name a few. It allows to open pages \"instantly\" at Google search results and a few partners (e.g. Twitter AFAIK)." 3 | } 4 | -------------------------------------------------------------------------------- /tips/14-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | GitHub files, both from gists and repositories could be fetched in a raw format. This could help when using a container (e.g. Docker) which do not has `git` installed: 2 | 3 | ```bash 4 | # curl -L https://github.com/:username/:reponame/blob/:commithash/:filepath 5 | curl -L https://github.com/jakwuh/webtip/blob/master/Readme.md 6 | ``` 7 | 8 | ```bash 9 | # curl -L https://gist.github.com/:username/:gistid/raw/:filename 10 | curl -L https://gist.github.com/jakwuh/0321347f3a16e72b58729e3a79c8b804/raw/.babelrc 11 | ``` 12 | -------------------------------------------------------------------------------- /tips/14-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "GitHub files, both from gists and repositories could be fetched in a raw format. This could help when using a container (e.g. Docker) which do not has git installed:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/15-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | > tldr; Use [lodash-webpack-plugin][1] to significally reduce your bundle size 2 | 3 | --- 4 | 5 | (A real story) 6 | 7 | *"[Lodash][2] has so much useless code to support Buffer, Map, Set, Symbol, unicode and other exotics - I have to do something w/ it"* - thought I while looking through the `lodash` codebase. Three days later I have had a fully rewritten `lodash` library with all tests ported, which was 3 times smaller than the original one. Not sleeping too much for those days, I fall into asleep and dreamed about `lodash` having a solution to reduce its size. I waked up quickly and [asked][3] the question. And it turned out `lodash` have **already solved** the size problem with a [lodash-webpack-plugin][1] library: 8 | 9 | ```js 10 | 'plugins': [ 11 | new LodashModuleReplacementPlugin({ 12 | 'collections': true, 13 | 'paths': true 14 | }); 15 | new webpack.optimize.UglifyJsPlugin() 16 | ] 17 | ``` 18 | 19 | Moral: don't afraid of asking questions :) 20 | 21 | [1]: https://www.npmjs.com/package/lodash-webpack-plugin 22 | [2]: https://lodash.com/docs/ 23 | [3]: https://github.com/lodash/lodash/issues/3030 24 | -------------------------------------------------------------------------------- /tips/15-09-2017/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/15-09-2017/1.png -------------------------------------------------------------------------------- /tips/15-09-2017/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/15-09-2017/2.png -------------------------------------------------------------------------------- /tips/15-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "At the initial implementation TCP sent data immediately. Thus, if on a server you emitted small chunks of data then TCP did not buffer them but simply sent to a client. This resulted into tinygrams (aka small packets) going over network and creating unfairly congestion. John Nagle created an algorithm which eliminates the described problem. It works as following:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/15-09-2017/src/client.mjs: -------------------------------------------------------------------------------- 1 | import net from 'net'; 2 | 3 | let client = new net.Socket(); 4 | let port = process.env.PORT; 5 | 6 | client.connect(port, '127.0.0.1', () => { 7 | console.log('Connected'); 8 | }); 9 | 10 | client.on('data', data => { 11 | console.log(Date.now()); 12 | console.log(`Received: ${data}`); 13 | }); 14 | 15 | client.on('close', () => { 16 | console.log('Connection closed'); 17 | }); 18 | -------------------------------------------------------------------------------- /tips/15-09-2017/src/server.mjs: -------------------------------------------------------------------------------- 1 | import net from 'net'; 2 | 3 | let port = process.env.PORT; 4 | let noDelay = Boolean(process.env.TCP_NODELAY); 5 | 6 | let server = net.createServer(socket => { 7 | socket.setNoDelay(noDelay); 8 | for (let i = 0; i < 10; i++) 9 | socket.write('' + i); 10 | console.log(Date.now()); 11 | }); 12 | 13 | server.listen(port, '127.0.0.1', () => { 14 | console.log('Server is running...'); 15 | }); 16 | -------------------------------------------------------------------------------- /tips/15-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | There is an interesting way of shipping browser polyfills by using so called "split points". You could split all browsers into 2+ categories, for instance: "Modern browsers" and "Obsolete browsers". Then, depending on a feature set your code depends on, you could define a condition which will detect to which group a browser belongs: 2 | 3 | ```js 4 | let isBrowserModern = 5 | typeof Promise === 'function' 6 | && navigator.serviceWorker 7 | && Number.prototype.isFinite; 8 | ``` 9 | 10 | By using a condition you could ship a bundle with an appropriate set of polyfills and transformations (e.g. babel): 11 | 12 | ```js 13 | let script = document.createElement('script'); 14 | 15 | script.defer = true; 16 | 17 | if (isBrowserModern) { 18 | script.src = '/index.modern.js'; 19 | } else { 20 | script.src = '/index.obsolete.js'; 21 | } 22 | 23 | document.body.appendChild(script); 24 | ``` 25 | -------------------------------------------------------------------------------- /tips/15-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "There is an interesting way of shipping browser polyfills by using so called \"split points\". You could split all browsers into 2+ categories, for instance: \"Modern browsers\" and \"Obsolete browsers\". Then, depending on a feature set your code depends on, you could define a condition which will detect to which group a browser belongs:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/16-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | [`webpack.CommonsChunkPlugin`][1] has an option named `minChunks` which looks pretty clear and simple at first sight but actually provides a neat control on what is included into `commons` chunk. Also there are a few more obscure options like `children` and `async`. We are giving a human-understandable explanations for them here and solving an interesting bundling task. 2 | 3 | `children: boolean` - should we use child chunks of an entry chunks for building the `commons` one? `child` chunk is a chunk which is loaded asynchronously and usually created with `require.ensure` or `import()` syntax. That means setting `children` to `true` will build the `commons` chunk out of usual chunks and their asynchronous children. The `commons` chunk will become larger, while having a common code not only for entry chunks but also for their asynchronous children. Still if we want to separate a common code into two parts: a common code for entry chunks and a common code for asynchronous chunks - we could use an `async` option for that: 4 | 5 | `async: boolean|string` - should we create a separate chunk for a common code of asynchronous chunks? If so and we also want to provide a custom filename for that chunk we should use a string as an `async` value. 6 | 7 | `minChunks: Infinity` - create a chunk with **only** webpack bootstrapping code. 8 | 9 | `minChunks: number` - include a module in the `commons` chunk if only it has not less than `number` references from other modules. 10 | 11 | `minChunks: (module, count) => boolean` - include a module in a `commons` chunk if a function returns `true`. 12 | 13 | The `minChunks` power is hidden behind the `function` syntax. Lets see it. 14 | 15 | #### Task 16 | 17 | Include a `lodash` library in the `commons` chunk. Also the `commons` chunk should include all other modules which were referenced more that 2 times. 18 | 19 | #### Solution 20 | 21 | ```js 22 | 23 | plugins: [ 24 | entry: { 25 | vendor: [ 26 | ] 27 | }, 28 | // ... 29 | new webpack.optimize.CommonsChunkPlugin({ 30 | name: 'vendor', 31 | minChunks: function (module, count) { 32 | return module.resource && module.resource.indexOf('node_modules/lodash') > -1 || count > 2; 33 | } 34 | }) 35 | ] 36 | ``` 37 | 38 | Common mistakes or why this cannot be implemented in another way: 39 | - You cannot use `minChunks: 3` because of some lodash functions may not be referenced for more than 2 times. 40 | - You cannot use `vendor: ['lodash']` because it will include the whole lodash codebase. We want to include only those functions we use. 41 | 42 | 43 | [1]: https://webpack.js.org/plugins/commons-chunk-plugin/ 44 | -------------------------------------------------------------------------------- /tips/16-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Find Median from Data Stream][1] 2 | 3 | #### Task 4 | 5 | Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value. 6 | 7 | Examples: 8 | `[2,3,4]` - the median is `3` 9 | `[2,3]` - the median is `(2 + 3) / 2 = 2.5` 10 | 11 | Design a data structure that supports the following two operations: 12 | 13 | `void addNum(int num)` - add an integer number from the data stream to the data structure. 14 | `double findMedian()` - return the median of all elements so far. 15 | 16 | For example: 17 | 18 | ``` 19 | addNum(1) 20 | addNum(2) 21 | findMedian() -> 1.5 22 | addNum(3) 23 | findMedian() -> 2 24 | ``` 25 | 26 | #### Analysis 27 | 28 | If we slice an ordered list in the middle, then the median will be the last element of the left part or the first element of the right part. The last element of the left part is the maximum element of that part and the first element of the right part is the minimum element of that part. Thus, the solution is to keep two heaps: minimum heap for the largest elements and maximum heap for the smallest elements. This gives us `O(log(n))` *insert* and `O(1)` *find* time complexity. 29 | 30 | #### Solution 31 | 32 | ```python 33 | class MedianFinder(object): 34 | 35 | def __init__(self): 36 | self.maxHeap = [] 37 | self.minHeap = [] 38 | 39 | def addNum(self, num): 40 | if not self.minHeap or num >= self.minHeap[0]: 41 | heapq.heappush(self.minHeap, num) 42 | else: 43 | heapq.heappush(self.maxHeap, -num) 44 | 45 | diff = len(self.minHeap) - len(self.maxHeap) 46 | 47 | if diff == 2: 48 | item = heapq.heappop(self.minHeap) 49 | heapq.heappush(self.maxHeap, -item) 50 | elif diff == -2: 51 | item = heapq.heappop(self.maxHeap) 52 | heapq.heappush(self.minHeap, -item) 53 | 54 | def findMedian(self): 55 | comparison = cmp(len(self.minHeap), len(self.maxHeap)) 56 | 57 | if comparison == 0: 58 | return (self.minHeap[0] - self.maxHeap[0]) / 2. 59 | elif comparison < 0: 60 | return -self.maxHeap[0] 61 | else: 62 | return self.minHeap[0] 63 | ``` 64 | 65 | [1]: https://leetcode.com/problems/find-median-from-data-stream/description/ 66 | -------------------------------------------------------------------------------- /tips/16-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Design a data structure that supports the following two operations: add an integer number from the data stream and return the median of all elements so far." 3 | } 4 | -------------------------------------------------------------------------------- /tips/16-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | By saying `instance` decorator I mean decorator which has to be applied on a class `instance` rather than on a class itself. For example, `debounce` decorator should have a separate "tracking pipe" for each instance. In other words if we have two variables `a` and `b` - both instances of class `X`, then calling debounced `a.debouncedFn()` should not affect `b` at all. 2 | 3 | The only way to implement such behavior is to mutate instance method at the moment it is accessed for the first time. The initial implementation: 4 | 5 | ```js 6 | function Debounce(wait) { 7 | return function (target, key, descriptor) { 8 | let fn = descriptor.value; 9 | 10 | return { 11 | configurable: true, 12 | enumerable: descriptor.enumerable, 13 | 14 | get() { 15 | let decoratedFn = debounce(fn, wait); 16 | 17 | Object.defineProperty(this, key, { 18 | value: decoratedFn, 19 | configurable: true, 20 | enumerable: descriptor.enumerable 21 | }); 22 | 23 | return decoratedFn; 24 | } 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | There is at least one case unhandled though. We obviously want to preserve the property value in case it is set manually: 31 | 32 | ```js 33 | let a = new X(); 34 | a.f = myCustomFunction 35 | a.f() // should call myCustomFunction() 36 | ``` 37 | 38 | We could achieve that requirement by using `hasOwnProperty`. Therefore we come to the final implementation: 39 | 40 | ```js 41 | /** 42 | * Usage: 43 | * 44 | * class X { 45 | * @Debounce(100) 46 | * sendData() {} 47 | * } 48 | * 49 | */ 50 | function Debounce(wait) { 51 | return function (target, key, descriptor) { 52 | let fn = descriptor.value; 53 | 54 | let defineProperty = (target, key, value) => { 55 | Object.defineProperty(target, key, { 56 | value, 57 | configurable: true, 58 | enumerable: descriptor.enumerable 59 | }); 60 | }; 61 | 62 | return { 63 | configurable: true, 64 | enumerable: descriptor.enumerable, 65 | 66 | get() { 67 | if (this.hasOwnProperty(key)) { 68 | return fn; 69 | } 70 | 71 | let decoratedFn = debounce(fn, wait); 72 | 73 | defineProperty(this, key, decoratedFn); 74 | 75 | return decoratedFn; 76 | }, 77 | 78 | set(value) { 79 | defineProperty(this, key, value); 80 | } 81 | 82 | } 83 | 84 | } 85 | } 86 | ``` 87 | 88 | [Source code][1] 89 | 90 | [1]: https://github.com/jakwuh/es-decorators/blob/master/src/DecorateInstance.js 91 | -------------------------------------------------------------------------------- /tips/16-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "By saying instance decorator I mean decorator which has to be applied on a class instance rather than on a class itself. For example, debounce decorator should have a separate \"tracking pipe\" for each instance. In other words if we have two variables a and b - both instances of class X, then calling debounced `a.debouncedFn()` should not affect b at all." 3 | } 4 | -------------------------------------------------------------------------------- /tips/17-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Webpack 2 introduced a new way for dynamic module importing: 2 | 3 | ```js 4 | import('./a.js').then(module => { 5 | // do whatever you want 6 | }) 7 | ``` 8 | 9 | However, it has 2 major issues: 10 | 11 | 1. How to name a chunk? 12 | 2. How to create an async bundle containing multiple modules? 13 | 14 | With webpack 1 we have used to do it with the following syntax: 15 | 16 | ```js 17 | require.ensure(['./a.js', './b.js'], () => { 18 | // here we go 19 | }, 'async-chunk-name'); 20 | ``` 21 | 22 | This creates an async chunk named **async-chunk-name** which contains 2 modules: `a.js` and `b.js`. 23 | 24 | After arguing a bit about which syntax to choose to support the same features in new webpack versions, the community [has come to the next decision][1]: 25 | 26 | ```js 27 | import(/* webpackChunkName: "async-import" */'./a.js'); 28 | 29 | Promise.all([ 30 | import(/* webpackChunkName: "async-imports" */'./b.js'), 31 | import(/* webpackChunkName: "async-imports" */'./c.js') 32 | ]); 33 | ``` 34 | 35 | So, you may think I have written something weird: naming chunks by commenting them out. However this is the [current solution][2]. 36 | 37 | From my point of view [a better solution][3] would be: 38 | 39 | ```js 40 | // unnamed chunk w/ exactly 1 module 41 | importModules('./a.js'); 42 | 43 | // named chunk w/ exactly 1 module 44 | importModules('./a.js', 'async-import') 45 | 46 | // unnamed chunk containing multiple modules 47 | importModules([ 48 | './b.js', 49 | './c.js' 50 | ]) 51 | 52 | // named chunk containing multiple modules 53 | importModules([ 54 | './b.js', 55 | './c.js' 56 | ], 'async-imports') 57 | ``` 58 | 59 | This is primarly the case when `babel` comes out. 60 | 61 | Keep your eyes open and look forward to the plugin implementation in the next tip! 62 | 63 | [1]: https://github.com/webpack/webpack/issues/1949 64 | [2]: https://github.com/webpack/webpack/issues/1949#issuecomment-289289959 65 | [3]: https://github.com/jakwuh/webpack-named-imports-demo/blob/master/src/index.js 66 | [4]: https://github.com/jakwuh/babel-plugin-webpack-named-dynamic-imports 67 | -------------------------------------------------------------------------------- /tips/17-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Intersection of Linked Lists][1] 2 | 3 | #### Task 4 | 5 | Write a program to find the node at which the intersection of two singly linked lists begins. 6 | 7 | For example, the following two linked lists: 8 | 9 | ``` 10 | A: a1 → a2 11 | ↘ 12 | c1 → c2 → c3 13 | ↗ 14 | B: b1 → b2 → b3 15 | ``` 16 | 17 | begin to intersect at node `c1`. 18 | 19 | Notes: 20 | - If the two linked lists have no intersection at all, return null. 21 | - The linked lists must retain their original structure after the function returns. 22 | - You may assume there are no cycles anywhere in the entire linked structure. 23 | - Your code should preferably run in O(n) time and use only O(1) memory. 24 | 25 | 26 | #### Analysis 27 | 28 | Let `min_length` be the minimum of `first_list_length` and `second_list_length`. The intersection of lists can not begin earlier than `min_length` left to the last node (true for both first and second lists). Thus we will create `pointer_first` and move it forward by `first_list_length - min_length` nodes and `pointer_second` moving it forward by `second_list_length - min_length` nodes. After that we compare `pointer_first` and `pointer_second` and move them forward until they become equal. 29 | 30 | #### Solution 31 | 32 | ```cpp 33 | /** 34 | * Definition for singly-linked list. 35 | * struct ListNode { 36 | * int val; 37 | * ListNode *next; 38 | * ListNode(int x) : val(x), next(NULL) {} 39 | * }; 40 | */ 41 | int len(ListNode* A) { 42 | int l = 0; 43 | while (A && A->next) { 44 | A = A->next; 45 | l++; 46 | } 47 | return l; 48 | } 49 | 50 | ListNode* Solution::getIntersectionNode(ListNode* A, ListNode* B) { 51 | int len_a = len(A); 52 | int len_b = len(B); 53 | 54 | int min_len = std::min(len_a, len_b); 55 | 56 | for (int i = 0; i < len_a - min_len; ++i) A = A->next; 57 | for (int i = 0; i < len_b - min_len; ++i) B = B->next; 58 | 59 | while (A && A->next && B && B->next && A != B) { 60 | A = A->next; 61 | B = B->next; 62 | } 63 | 64 | return A == B ? A : NULL; 65 | } 66 | 67 | ``` 68 | 69 | [1]: https://www.interviewbit.com/problems/intersection-of-linked-lists/ 70 | -------------------------------------------------------------------------------- /tips/17-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Write a program to find the node at which the intersection of two singly linked lists begins. Your code should preferably run in O(n) time and use only O(1) memory." 3 | } 4 | -------------------------------------------------------------------------------- /tips/17-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | In Typescript `?T` behaves equal to `T|undefined`. However there is a caveat: `undefined` as well as `null` types could be assigned to any type. That is why following pieces of code work: 2 | 3 | ```js 4 | // 1. 5 | let c: Human = null; 6 | 7 | console.log(c.surname) 8 | 9 | // 2. 10 | interface Human { 11 | surname?: string 12 | } 13 | 14 | function printName(human: Human) { 15 | let message: string = human.surname; 16 | 17 | console.log(message); 18 | } 19 | ``` 20 | 21 | According to [Typescript Deep Dive][1] book such a decision was taken by Typescript contributors because a lot of people used to write stuff in that way. However [`strictNullChecks`][2] compiler option change things: 22 | 23 | ```js 24 | // 1. 25 | let c: Human = null; 26 | // Type 'null' is not assignable to type 'Human'. 27 | 28 | console.log(c.surname) 29 | 30 | // 2. 31 | interface Human { 32 | surname?: string 33 | } 34 | 35 | function printName(human: Human) { 36 | let message: string = human.surname; 37 | // Type 'string | undefined' is not assignable to type 'string'. 38 | // Type 'undefined' is not assignable to type 'string'. 39 | 40 | console.log(message); 41 | } 42 | ``` 43 | 44 | The conclusion is obvious: to avoid rather popular kind of bugs use `strictNullChecks` compiler option whenever possible. 45 | 46 | [1]: https://basarat.gitbooks.io/typescript/docs/options/strictNullChecks.html 47 | [2]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html 48 | -------------------------------------------------------------------------------- /tips/17-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "In Typescript `?T` behaves equal to `T|undefined`. However there is a caveat: `undefined` as well as `null` types could be assigned to any type. That is why following pieces of code work:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/18-07-2017/index.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import co from 'co'; 3 | 4 | test('In ES6 you can derive from Error', t => { 5 | class CommonError extends Error { 6 | } 7 | 8 | class HTTPError extends CommonError { 9 | } 10 | 11 | let error = new HTTPError(); 12 | t.true(error instanceof Error); // true 13 | t.true(error instanceof CommonError); // true 14 | t.true(error instanceof HTTPError); // true 15 | }); 16 | -------------------------------------------------------------------------------- /tips/18-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | In [the previous tip][1] we have a look at a better webpack dynamic imports: 2 | 3 | ```js 4 | // unnamed chunk w/ exactly 1 module 5 | importModules('./a.js'); 6 | 7 | // named chunk w/ exactly 1 module 8 | importModules('./a.js', 'async-import') 9 | 10 | // unnamed chunk containing multiple modules 11 | importModules([ 12 | './b.js', 13 | './c.js' 14 | ]) 15 | 16 | // named chunk containing multiple modules 17 | importModules([ 18 | './b.js', 19 | './c.js' 20 | ], 'async-imports'); 21 | ``` 22 | 23 | Finally, there is a package in npm named [babel-plugin-webpack-named-dynamic-imports][2] which does exactly the same. 24 | 25 | While implementing it, I've learned a few *gotchas*. 26 | 27 | - The only way to create a valid AST of a code with comments is to use [`babylon`][3]: 28 | 29 | ```js 30 | let ast = babylon.parse('import(/* webpackChunkName: "test" */"./a.js")') 31 | ``` 32 | 33 | - For some reason `babylon` wasn't able to parse `dynamic import` statement. Both with `babel-plugin-syntax-dynamic-import` plugin enabled and `sourceType: 'module'` option used. This forced me to [hack a bit][4]. 34 | 35 | Hope you'll find the plugin useful! 36 | 37 | **Source code** 38 | 39 | ```js 40 | CallExpression(path) { 41 | const node = path.node; 42 | 43 | if (t.isIdentifier(node.callee, {name: 'importModules'})) { 44 | const elements = [], 45 | modules = node.arguments[0], 46 | chunkName = node.arguments[1]; 47 | 48 | if (t.isArrayExpression(modules)) { 49 | modules.elements.forEach(el => elements.push(el)); 50 | } else if (t.isStringLiteral(modules)) { 51 | elements.push(modules); 52 | } else { 53 | throw new Error('Invalid importModules() syntax'); 54 | } 55 | 56 | if (elements.length === 0) { 57 | path.remove(); 58 | } else if (elements.length === 1) { 59 | path.replaceWith(generateImport(elements[0], chunkName)); 60 | } else { 61 | path.replaceWith(generateImports(elements, chunkName)); 62 | } 63 | } 64 | 65 | } 66 | } 67 | ``` 68 | 69 | [1]: https://github.com/jakwuh/web/tree/master/tips/17-08-2017 70 | [2]: https://github.com/jakwuh/babel-plugin-webpack-named-dynamic-imports 71 | [3]: https://www.npmjs.com/package/babylon 72 | [4]: https://github.com/jakwuh/babel-plugin-webpack-named-dynamic-imports/blob/master/index.js#L7-L21 73 | -------------------------------------------------------------------------------- /tips/18-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Today I learned from the [`WebStandarts`][1] podcast (which I am listening to) about a fun tool named `npx` that is installed alongside `npm@5.2.0+`. It allows to execute npm packages without installing them neither globally nor locally (think of it as of disposable `npm`). 2 | 3 | A few examples of using it: 4 | 5 | ``` 6 | # see the forever's help without installing it 7 | npx forever --help 8 | 9 | # execute gist 10 | npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32 11 | ``` 12 | 13 | Unfortunately I did not found any sufficient way to "install" a few packages and run `node` where I can play with them. It'd be definitely a great addition to the `npx` itself. 14 | 15 | More details could be found [at the npm's blog][2]. 16 | 17 | 18 | [1]: https://soundcloud.com/web-standards 19 | [2]: http://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner 20 | -------------------------------------------------------------------------------- /tips/18-09-2017/index.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | console.log('typeof fs.readdir is ' + typeof fs.readdir); 4 | -------------------------------------------------------------------------------- /tips/18-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Today I learned from the WebStandarts podcast (which I am listening to) about a fun tool named npx that is installed alongside npm@5.2.0+. It allows to execute npm packages without installing them neither globally nor locally (think of it as of disposable npm)." 3 | } 4 | -------------------------------------------------------------------------------- /tips/18-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Max Continuous Series of 1s][1] 2 | 3 | #### Task 4 | 5 | Given an array of 1s and 0s and an integer M, which signifies number of flips allowed, find the position of zeros which when flipped will produce maximum continuous series of 1s. 6 | 7 | Return the indices of maximum continuous series of 1s in order. 8 | 9 | Example: 10 | 11 | ``` 12 | Input : 13 | Array = {1 1 0 1 1 0 0 1 1 1 } 14 | M = 1 15 | 16 | Output : 17 | [0, 1, 2, 3, 4] 18 | ``` 19 | 20 | If there are multiple possible solutions, return the sequence which has the minimum start index. 21 | 22 | #### Analysis 23 | 24 | We will store 2 pointers reflecting the following condition: at any moment there must be exactly M zeros between the first and the second pointers. Simultaneously we will track the distance between pointers to find the maximum. This is the only thing we need to give the answer. 25 | 26 | #### Solution 27 | 28 | ```cpp 29 | vector Solution::maxone(vector &A, int B) { 30 | auto left = A.begin(); 31 | auto right = left; 32 | 33 | while (right != A.end() && (*right == 1 || B--)) right++; 34 | 35 | int max_len = 0; 36 | auto it = A.begin(); 37 | 38 | while (left != A.end()) { 39 | 40 | if (std::distance(left, right) > max_len) { 41 | max_len = std::distance(left, right); 42 | it = left; 43 | } 44 | 45 | if (*left == 0 && right != A.end()) ++right; 46 | 47 | ++left; 48 | 49 | while (right != A.end() && *right == 1) ++right; 50 | 51 | } 52 | 53 | vector s(max_len); 54 | int index = std::distance(A.begin(), it); 55 | for (int k = 0; k < max_len; ++k) { 56 | s[k] = k + index; 57 | } 58 | 59 | return s; 60 | 61 | } 62 | 63 | ``` 64 | 65 | [1]: https://www.interviewbit.com/problems/max-continuous-series-of-1s/ 66 | -------------------------------------------------------------------------------- /tips/18-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Given an array of 1s and 0s and an integer M, which signifies number of flips allowed, find the position of zeros which when flipped will produce maximum continuous series of 1s. Return the indices of maximum continuous series of 1s in order." 3 | } 4 | -------------------------------------------------------------------------------- /tips/19-07-2017/Readme.md: -------------------------------------------------------------------------------- 1 | You can force WebStorm to autoimport from resources roots rather than using relative paths. 2 | 3 | ![WebStorm preferences](preferences.png) 4 | 5 | This creates: 6 | ```js 7 | import {Prototype} from 'decorators/Prototype'; 8 | ``` 9 | instead of 10 | 11 | ```js 12 | import {Prototype} from '../../../decorators/Prototype'; 13 | ``` 14 | -------------------------------------------------------------------------------- /tips/19-07-2017/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/19-07-2017/preferences.png -------------------------------------------------------------------------------- /tips/19-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | When creating small or medium sized repositories, I prefer using ordinary npm scripts to *`gulp`/`grunt`/``* tasks (to avoid unreasonable complexity and dependencies). There is an understandable need of running a few scripts in parallel: the same as `gulp.parallel` does (e.g. running `webpack -w` and *node.js server* listening on a specific port). Today I have learned a very convenient way of doing this: using [`concurrently`][1] library. 2 | 3 | ```json 4 | "scripts": { 5 | "webpack:watch": "node ./build/webpack --development --watch", 6 | "serve:watch": "nodemon ./dist/server/index.js", 7 | "start": "concurrently --kill-others \"npm run webpack:watch\" \"npm run serve:watch\"" 8 | } 9 | ``` 10 | 11 | Now with `npm start` there are 2 concurrently running processes, both prefixed with its own appropriate prefix: 12 | 13 | ![concurrently](./image.png) 14 | 15 | Still, keep in mind that on a UNIX based machines you could achieve the similar result without any 3rd party tools by just using a **pipe** operator: 16 | 17 | ```json 18 | "start": "npm run webpack:watch | npm run serve:watch 19 | ``` 20 | 21 | [1]: https://www.npmjs.com/package/concurrently 22 | -------------------------------------------------------------------------------- /tips/19-08-2017/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/19-08-2017/image.png -------------------------------------------------------------------------------- /tips/19-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | If you worry about your JS code quality, consistency and maintainability then consider reading [Angular 2 Style Guide][1] (regardless of using it or not). Here are a few most useful / most useless key points from my point of view: 2 | 3 | ### Do agree: 4 | 5 | [**Import line spacing**][3] 6 | 7 | - Consider leaving one empty line between third party imports and application imports. 8 | - Consider listing import lines alphabetized by the module. 9 | - Consider listing destructured imported symbols alphabetically. 10 | 11 | ```js 12 | import { Injectable } from '@angular/core'; 13 | import { Http } from '@angular/http'; 14 | 15 | import { Hero } from './hero.model'; 16 | import { ExceptionService, SpinnerService, ToastService } from '../../core'; 17 | ``` 18 | 19 | [**Extract templates and styles to their own files**][4] 20 | 21 | - Do extract templates and styles into a separate file, when **more** than 3 lines. 22 | 23 | Having separated templates along with a syntax for inline templates simplifies a lot of things. 24 | 25 | [**Member sequence**][5] 26 | 27 | - Do place properties up top followed by methods. 28 | - Do place private members after public members, **alphabetized**. 29 | 30 | ### Do not agree 31 | 32 | [**Constants**][2] 33 | 34 | *Angular*: 35 | - Consider spelling const variables in lower camel case. 36 | - Do tolerate existing const variables that are spelled in UPPER_SNAKE_CASE. 37 | 38 | ```js 39 | export const mockHeroes = ['Sam', 'Jill']; // prefer 40 | export const heroesUrl = 'api/heroes'; // prefer 41 | export const VILLAINS_URL = 'api/villains'; // tolerate 42 | ``` 43 | 44 | *Me*: 45 | - Consistency is important as much as convention. Because of the fact that the majority of packages use UPPER_CASE it'd be better to have a convention reflecting that fact. 46 | 47 | [1]: https://angular.io/guide/styleguide 48 | [2]: https://angular.io/guide/styleguide#constants 49 | [3]: https://angular.io/guide/styleguide#import-line-spacing 50 | [4]: https://angular.io/guide/styleguide#extract-templates-and-styles-to-their-own-files 51 | [5]: https://angular.io/guide/styleguide#member-sequence 52 | -------------------------------------------------------------------------------- /tips/19-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "If you worry about your JS code quality, consistency and maintainability then consider reading Angular 2 Style Guide (regardless of using it or not). Here are a few most useful / most useless key points from my point of view:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/19-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Creating `docker-compose` files for running `mysql` & `mongo` with GUI took some time. So I've decided to expose configs and leave here for future references: 2 | 3 | #### MongoDB 4 | 5 | Config: 6 | 7 | ```yml 8 | # mongo-compose.yml 9 | version: '3.1' 10 | 11 | services: 12 | mongo: 13 | image: mongo 14 | ports: 15 | - 27017:27017 16 | 17 | mongo_gui: 18 | image: mongo-express 19 | ports: 20 | - 8081:8081 21 | ``` 22 | 23 | Command: 24 | 25 | ```bash 26 | docker-compose -f ./mongo-compose.yml up 27 | ``` 28 | 29 | MongoDB server is exposed at `localhost:27017`. 30 | GUI is available at `localhost:8081`. 31 | 32 | #### Mysql 33 | 34 | Config: 35 | 36 | ```yml 37 | # mysql-compose.yml 38 | version: '3.1' 39 | 40 | services: 41 | db: 42 | image: mysql 43 | ports: 44 | - 3306:3306 45 | environment: 46 | MYSQL_ROOT_PASSWORD: admin 47 | 48 | mysql_gui: 49 | image: phpmyadmin/phpmyadmin 50 | ports: 51 | - 8080:80 52 | ``` 53 | 54 | Command: 55 | 56 | ```bash 57 | docker-compose -f ./mysql-compose.yml up 58 | ``` 59 | 60 | MySQL server is exposed at `localhost:3306`. 61 | GUI is available at `localhost:8080`. 62 | 63 | ![phpmyadmin](./phpmyadmin.png) 64 | -------------------------------------------------------------------------------- /tips/19-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Creating docker-compose files for running mysql & mongo with GUI took some time. So I've decided to expose configs and leave here for future references:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/19-10-2017/phpmyadmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/19-10-2017/phpmyadmin.png -------------------------------------------------------------------------------- /tips/20-07-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Always call `clock.restore()` while using `sinon` package. Your tests may still pass, but forgetting this will likely lead to unobvious errors in future tests which relies on `Date`, `setTimeout`, etc. It happens because `useFakeTimers` modifies global objects thus affecting literally all tests. 2 | 3 | ```js 4 | describe('My sexy test', function () { 5 | beforeEach(function () { 6 | this.clock = sinon.useFakeTimers(); 7 | }); 8 | 9 | afterEach(function () { 10 | this.clock.restore(); // this is essential! 11 | }); 12 | }); 13 | ``` 14 | -------------------------------------------------------------------------------- /tips/20-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | #### [Minimum Window Substring][1] 2 | 3 | ##### Task 4 | 5 | Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity `O(n)`. 6 | 7 | Example inputs: 8 | 9 | ``` 10 | S = "ADOBECODEBANC" 11 | T = "ABC" 12 | Result: "BANC" 13 | ``` 14 | 15 | ``` 16 | S = "a" 17 | T = "aa" 18 | Result: "" (no solution) 19 | ``` 20 | 21 | ``` 22 | S = "aa" 23 | T = "a" 24 | Result: "a" 25 | ``` 26 | 27 | ##### Analysis 28 | 29 | First, we create a string `tape` made by concatenating a pattern string `T` and a test string `S`: 30 | 31 | ``` 32 | S = "ADOBECODEBANC" 33 | T = "ABC" 34 | tape = "ABCADOBECODEBANC" 35 | ``` 36 | 37 | Then we store two pointers, left and right, and iterate with them through the string: 38 | 39 | ``` 40 | |ABC|ADOBECODEBANC 41 | ``` 42 | 43 | On each iteration we perform the following operation: 44 | 45 | 1. Lets suppose the left pointer points to a symbol `x`. 46 | 2. Check if we have enough `x` symbols among the left and right pointers. If its count is more than we need, then move the left pointer forward and stop the iteration. Otherwise, move the right pointer forward. 47 | 3. If it is not possible to move both pointers forward then stop the algorithm. 48 | 49 | For storing how much symbols do we need and how much symbols current segment between pointers has, we use an array of length 256 (because the maximum index in ASCII is 255) and track the count during iterations. Alternatively, a hashmap could be used. 50 | 51 | 52 | ##### Solution 53 | 54 | ```python 55 | class Solution(object): 56 | def minWindow(self, s, t): 57 | if not t or not s: 58 | return '' 59 | 60 | tape = t + s 61 | tape_len = len(tape) 62 | pattern_len = len(t) 63 | left_cursor = min_left_cursor = 0 64 | right_cursor = min_right_cursor = pattern_len - 1 65 | 66 | counts = [0] * 256 67 | current_counts = [0] * 256 68 | 69 | for c in t: 70 | counts[ord(c)] += 1 71 | current_counts[ord(c)] += 1 72 | 73 | while True: 74 | left_char = ord(tape[left_cursor]) 75 | 76 | if current_counts[left_char] == 0: 77 | left_cursor += 1 78 | elif current_counts[left_char] > counts[left_char]: 79 | left_cursor += 1 80 | current_counts[left_char] -= 1 81 | else: 82 | if min_left_cursor < pattern_len or right_cursor - left_cursor < min_right_cursor - min_left_cursor: 83 | min_left_cursor = left_cursor 84 | min_right_cursor = right_cursor 85 | 86 | right_cursor += 1 87 | 88 | if right_cursor >= tape_len: 89 | break 90 | 91 | right_char = ord(tape[right_cursor]) 92 | 93 | if counts[right_char] > 0: 94 | current_counts[right_char] += 1 95 | 96 | if min_left_cursor < pattern_len: 97 | return '' 98 | 99 | return tape[min_left_cursor:min_right_cursor + 1] 100 | ``` 101 | 102 | [1]: https://leetcode.com/problems/minimum-window-substring/description/ 103 | -------------------------------------------------------------------------------- /tips/20-08-2017/index.py: -------------------------------------------------------------------------------- 1 | class Solution(object): 2 | def minWindow(self, s, t): 3 | if not t or not s: 4 | return '' 5 | 6 | tape = t + s 7 | tape_len = len(tape) 8 | pattern_len = len(t) 9 | left_cursor = min_left_cursor = 0 10 | right_cursor = min_right_cursor = pattern_len - 1 11 | 12 | counts = [0] * 256 13 | current_counts = [0] * 256 14 | 15 | for c in t: 16 | counts[ord(c)] += 1 17 | current_counts[ord(c)] += 1 18 | 19 | while True: 20 | left_char = ord(tape[left_cursor]) 21 | 22 | if current_counts[left_char] == 0: 23 | left_cursor += 1 24 | elif current_counts[left_char] > counts[left_char]: 25 | left_cursor += 1 26 | current_counts[left_char] -= 1 27 | else: 28 | if min_left_cursor < pattern_len or right_cursor - left_cursor < min_right_cursor - min_left_cursor: 29 | min_left_cursor = left_cursor 30 | min_right_cursor = right_cursor 31 | 32 | right_cursor += 1 33 | 34 | if right_cursor >= tape_len: 35 | break 36 | 37 | right_char = ord(tape[right_cursor]) 38 | 39 | if counts[right_char] > 0: 40 | current_counts[right_char] += 1 41 | 42 | if min_left_cursor < pattern_len: 43 | return '' 44 | 45 | return tape[min_left_cursor:min_right_cursor + 1] 46 | 47 | 48 | print(Solution().minWindow('a', 'aa')) 49 | -------------------------------------------------------------------------------- /tips/20-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | 2 | In [the previous tip][1] we have mentioned a rule from [Angular Style Guide][2]: 3 | 4 | - Do place private members after public members, **alphabetized**. 5 | 6 | WebStorm (as well as other JetBrains IDEA's) has an automatic code arrangement feature. Look at the demo: 7 | 8 | ![Rearrange code](./rearrange.gif) 9 | 10 | To start using it you need to do a few simple steps: 11 | 12 | 1) Set up the **Arrangement** settings as shown at the screenshot below 13 | 14 | ![Arrangement settings](./settings.png) 15 | 16 | 2) Press `cmd (ctrl)` + `shift` + `a` and choose **Rearrange Code** 17 | 18 | [1]: https://github.com/jakwuh/web/tree/master/tips/19-09-2017 19 | [2]: https://angular.io/guide/styleguide 20 | -------------------------------------------------------------------------------- /tips/20-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "WebStorm (as well as other JetBrains IDEA's) has an automatic code arrangement feature. To start using it you need to do a few simple steps: 1) Set up the *Arrangement* settings, then Press `cmd (ctrl)` + `shift` + `a` and choose *Rearrange Code*" 3 | } 4 | -------------------------------------------------------------------------------- /tips/20-09-2017/rearrange.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/20-09-2017/rearrange.gif -------------------------------------------------------------------------------- /tips/20-09-2017/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/20-09-2017/settings.png -------------------------------------------------------------------------------- /tips/20-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Now I'm reading [Getting Real][1] - a book about building successful web apps from Basecamp (former [37signals][2]). While I disagree with a number of thoughts discussed (e.g. the advice to start coding having only core system principles and aims rather than having a detailed look at it), there are still a plenty of highly useful pieces of advice. Actually there are a lot of headlines in the book: 2 | 3 | - Build less 4 | - Build software for yourself 5 | - Fund Yourself 6 | - Fix Time and Budget, Flex Scope 7 | - Have an Enemy 8 | - Lower Your Cost of Change 9 | - Embrace Constraints 10 | - Ignore Details Early On 11 | - Scale Later 12 | 13 | \- and they all convince us in pretty much the same idea - be precise in what you build. That involves a lot of things to remember about. First of all - no matter you are a developer or a PM - you should have a strong vision of what you do. Having it will help making right decisions throughout the project. Second, it's better to embrace constraints and build software in such a way that you will be in time with deadlines. Try to avoid estimating long milestones - because there are a lot of reasons why these estimates will fail. Instead of having long 6-weeks project split it down by 6 1-week projects with more precise estimates and cleaner scope. And lastly, avoid everything that is not essential: premature optimisations and scaling, preferences and low priority features. It is definitely better to build less but launch earlier. Once again, no matter you are a developer or a PM or a business owner. 14 | 15 | Have a look at [Getting Real][1] book for more explanations and live examples. 16 | 17 | [1]: https://gettingreal.37signals.com/ 18 | [2]: https://37signals.com/ 19 | -------------------------------------------------------------------------------- /tips/20-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Now I'm reading Getting Real - a book about building successful web apps from Basecamp (former 37signals). While I disagree with a number of thoughts discussed (e.g. the advice to start coding having only core system principles and aims rather than having a detailed look at it), there are still a plenty of highly useful pieces of advice. Actually there are a lot of headlines in the book:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/21-07-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Don't forget `JSON.stringify` is an unsafe method. Alternatively, you can use a common approach to avoid this: 2 | 3 | ```js 4 | function stringify(json) { 5 | let output; 6 | try { 7 | // `null, 2` is for proper formatting 8 | output = JSON.stringify(json, null, 2); 9 | } catch (e) { 10 | output = `[Circular ${e.message}]`; 11 | } 12 | return output; 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /tips/21-08-2017/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .idea/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /tips/21-08-2017/measure.js: -------------------------------------------------------------------------------- 1 | const LodashWebpackPlugin = require('lodash-webpack-plugin'); 2 | const webpack = require('webpack'); 3 | const {cloneDeep, sortBy} = require('lodash'); 4 | 5 | const options = { 6 | shorthands: false, 7 | cloning: false, 8 | currying: false, 9 | caching: false, 10 | collections: false, 11 | exotics: false, 12 | guards: false, 13 | metadata: false, // (requires currying) 14 | deburring: false, 15 | unicode: false, 16 | chaining: false, 17 | memoizing: false, 18 | coercions: false, 19 | flattening: false, 20 | paths: false, 21 | placeholders: false // (requires currying) 22 | } 23 | 24 | const config = require('./webpack.config.js'); 25 | const keys = [null].concat(Object.keys(options)); 26 | let results = []; 27 | 28 | function report() { 29 | sortBy(results, 'size').forEach(({key, size}) => { 30 | console.log(size, key); 31 | }); 32 | } 33 | 34 | keys.forEach(key => { 35 | let currentConfig = cloneDeep(config); 36 | let opts = key ? {[key]: true} : {}; 37 | 38 | currentConfig.plugins = [ 39 | new LodashWebpackPlugin(opts) 40 | ]; 41 | 42 | const compiler = webpack(currentConfig); 43 | 44 | compiler.run((err, stats) => { 45 | let json = stats.toJson(); 46 | 47 | results.push({ 48 | key, 49 | size: json.assets[0].size 50 | }); 51 | 52 | if (results.length == keys.length) { 53 | report(); 54 | } 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /tips/21-08-2017/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "babel-core": "^6.26.0", 4 | "babel-loader": "^7.1.2", 5 | "babel-plugin-lodash": "^3.2.11", 6 | "babel-preset-env": "^1.6.0", 7 | "lodash": "^4.17.4", 8 | "lodash-webpack-plugin": "^0.11.4", 9 | "webpack": "^3.5.5" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tips/21-08-2017/src/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | assignIn, bind, bindAll, chunk, clone, compact, create, 3 | debounce, defaults, defer, delay, difference, escape, every, fill, filter, 4 | find, flatten, flattenDeep, forEach, get, has, head, identity, indexOf, 5 | invoke, invokeMap, isArray, isArrayLike, isBoolean, isDate, isEmpty, 6 | isEqual, isFinite, isFunction, isMatch, isNaN, isNull, isNumber, isObject, 7 | isRegExp, isString, isSymbol, isUndefined, iteratee, keyBy, keys, last, 8 | map, mapValues, matches, matchesProperty, max, memoize, merge, 9 | min, negate, noop, omit, once, partial, pick, property, random, range, 10 | reduce, result, sample, shuffle, size, slice, some, sortBy, sum, times, 11 | toArray, trim, unescape, uniq, uniqBy, uniqueId, values, without 12 | } from 'lodash'; 13 | 14 | console.log(assignIn, bind, bindAll, chunk, clone, cloneDeep, compact, create, 15 | debounce, defaults, defer, delay, difference, escape, every, fill, filter, 16 | find, flatten, flattenDeep, forEach, get, has, head, identity, indexOf, 17 | invoke, invokeMap, isArray, isArrayLike, isBoolean, isDate, isEmpty, 18 | isEqual, isFinite, isFunction, isMatch, isNaN, isNull, isNumber, isObject, 19 | isRegExp, isString, isSymbol, isUndefined, iteratee, keyBy, keys, last, 20 | map, mapValues, matches, matchesProperty, max, memoize, merge, 21 | min, negate, noop, omit, once, partial, pick, property, random, range, 22 | reduce, result, sample, shuffle, size, slice, some, sortBy, sum, times, 23 | toArray, trim, unescape, uniq, uniqBy, uniqueId, values, without); 24 | -------------------------------------------------------------------------------- /tips/21-08-2017/webpack.config.js: -------------------------------------------------------------------------------- 1 | const {resolve} = require('path'); 2 | const root = __dirname; 3 | const LodashWebpackPlugin = require('lodash-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: { 7 | index: resolve(root, 'src/index') 8 | }, 9 | output: { 10 | path: resolve(root, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | module: { 14 | rules: [{ 15 | test: /\.js$/, 16 | exclude: /node_modules/, 17 | use: { 18 | loader: 'babel-loader', 19 | options: { 20 | presets: [['env', { 21 | modules: false 22 | }]], 23 | plugins: ['lodash'] 24 | } 25 | } 26 | }] 27 | }, 28 | plugins: [ 29 | new LodashWebpackPlugin({ 30 | cloning: true, 31 | coercions: true, 32 | flattening: true, 33 | memoizing: true, 34 | shorthands: true, 35 | paths: true, 36 | guards: true, 37 | caching: true, 38 | collections: true 39 | }) 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /tips/21-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | ### [Shortest Unique Prefix][1] 2 | 3 | #### Task 4 | 5 | Find shortest unique prefix to represent each word in the list. 6 | 7 | Example: 8 | 9 | ``` 10 | Input: [zebra, dog, duck, dove] 11 | Output: {z, dog, du, dov} 12 | where we can see that 13 | zebra = z 14 | dog = dog 15 | duck = du 16 | dove = dov 17 | ``` 18 | 19 | Note: assume that no word is prefix of another. In other words, the representation is always possible. 20 | 21 | #### Analysis 22 | 23 | First, we create a multimap containing prefixes of length 1 with appropriate words: 24 | 25 | ``` 26 | MultiMap: 27 | a -> [ack] 28 | m -> [mod, mobile] 29 | z -> [zebra, zack] 30 | 31 | Answer: [] 32 | ``` 33 | 34 | Then we iterate through each key and check the following condition: if there is only one value associated with a key then the key is the shortest unique prefix for an appropriate word. 35 | 36 | At the second iteration we use prefixes of length 2: 37 | 38 | ``` 39 | MultiMap: 40 | mo -> [mod, mobile] 41 | za -> [zack] 42 | ze -> [zebra] 43 | 44 | Answer: [ack] 45 | ``` 46 | 47 | Finally we have: 48 | 49 | ``` 50 | MultiMap: 51 | mod -> [mod] 52 | mob -> [mobile] 53 | 54 | Answer: [ack, zack, zebra] 55 | ``` 56 | 57 | 58 | #### Solution 59 | 60 | ```cpp 61 | 62 | vector Solution::prefix(vector &A) { 63 | std::multimap m; 64 | std::map ans; 65 | 66 | for (auto &word : A) { 67 | m.insert(make_pair(string(1, word[0]), word)); 68 | } 69 | 70 | while (!m.empty()) { 71 | auto pair = m.begin(); 72 | 73 | if (m.count(pair->first) == 1) { 74 | ans[pair->second] = pair->first; 75 | m.erase(pair); 76 | } else { 77 | auto key = pair->first; 78 | auto range = m.equal_range(key); 79 | size_t len = key.size() + 1; 80 | std::multimap mc; 81 | for (auto it = range.first; it != range.second; ++it) { 82 | mc.insert(make_pair(it->second.substr(0, len), it->second)); 83 | } 84 | m.erase(range.first, range.second); 85 | for (auto &it: mc) { 86 | m.insert(std::move(it)); 87 | } 88 | } 89 | } 90 | 91 | std::vector v(A.size()); 92 | for (size_t i = 0; i < A.size(); ++i) { 93 | v[i] = ans[A[i]]; 94 | } 95 | 96 | return v; 97 | } 98 | 99 | ``` 100 | 101 | [1]: https://www.interviewbit.com/problems/shortest-unique-prefix/ 102 | -------------------------------------------------------------------------------- /tips/21-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Find shortest unique prefix to represent each word in the list. Example: `Input: [zebra, dog, duck, dove]` `Output: {z, dog, du, dov}`" 3 | } 4 | -------------------------------------------------------------------------------- /tips/21-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | This day was crazy: [WSD][1] conference with ~300 participants, an [updated talk][2] on SSR and a bunch of new friends and people. 2 | 3 | Big thanks to [WSD community][3] for such a great day! 4 | 5 | ![Twitter WSD SSR DIY](./tw.png) 6 | 7 | [1]: https://wsd.events/2017/10/21/ 8 | [2]: https://github.com/jakwuh/ssr-demo/tree/master/slides 9 | [3]: https://web-standards.ru/ 10 | -------------------------------------------------------------------------------- /tips/21-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "This day was crazy: WSD conference with ~300 participants, an updated talk on SSR and a bunch of new friends and people. Big thanks to WSD community for such a great day!" 3 | } 4 | -------------------------------------------------------------------------------- /tips/21-10-2017/tw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/21-10-2017/tw.png -------------------------------------------------------------------------------- /tips/22-07-2017/Readme.md: -------------------------------------------------------------------------------- 1 | WebStorm support for TypeScript becomes better every release. Now it automatically highlights parameter names of any typed function. This feature is called [parameter hints](https://goo.gl/4YBsCu) and it is damn cool: 2 | 3 | ![WebStorm auto highlight](ts-params-naming.png) 4 | -------------------------------------------------------------------------------- /tips/22-07-2017/ts-params-naming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/22-07-2017/ts-params-naming.png -------------------------------------------------------------------------------- /tips/22-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Today I've understood we misuse sometimes a link tag in our project. Namely, we have the following elements: 2 | 3 | - It's a link (``) 4 | - It has an event listener which calls `.preventDefault()` 5 | 6 | If you think a bit about it you'll find the better solution: 7 | 8 | - Replace link tag with button (` 24 |
25 |
26 |

 27 |         
 99 |     
100 | 
101 | 


--------------------------------------------------------------------------------
/tips/23-10-2017/meta.json:
--------------------------------------------------------------------------------
1 | {
2 |     "description": "PaymentRequest API is something unbelievably simple and interesting. There are a lot of talks on that right now at ChromeDevSummit 2017 (see the live stream) and meanwhile I've decided to test it."
3 | }
4 | 


--------------------------------------------------------------------------------
/tips/23-10-2017/script.js:
--------------------------------------------------------------------------------
 1 | document.getElementById('donate').addEventListener('click', pay);
 2 | 
 3 | function pay() {
 4 |   var supportedInstruments = [{
 5 |     supportedMethods: 'basic-card',
 6 |     data: {
 7 |       supportedNetworks: ['visa', 'mastercard', 'amex', 'jcb',
 8 |         'diners', 'discover', 'mir', 'unionpay'],
 9 |       supportedTypes: ['credit', 'debit']
10 |     }
11 |   }];
12 | 
13 |   var details = {
14 |     total: {
15 |       label: 'Donation',
16 |       amount: {
17 |         currency: 'USD',
18 |         value: '0.01'
19 |       }
20 |     },
21 |     displayItems: [
22 |       {
23 |         label: 'Original donation amount',
24 |         amount: {
25 |           currency: 'USD',
26 |           value: '0.01'
27 |         }
28 |       }
29 |     ]
30 |   };
31 | 
32 |   var options = {
33 |   };
34 | 
35 |   try {
36 |     var request = new PaymentRequest(supportedInstruments, details, options);
37 |     // Add event listeners here.
38 |     // Call show() to trigger the browser's payment flow.
39 |     request.show().then(function(instrumentResponse) {
40 |       console.dir(instrumentResponse)
41 |     })
42 |       .catch(function(err) {
43 |         console.error(err);
44 |       });
45 |   } catch (e) {
46 |     console.error(err);
47 |   }
48 | 
49 | }
50 | 
51 | function sendPaymentToServer(instrumentResponse) {
52 |   instrumentResponse.complete('success')
53 |     .then(function() {
54 |       document.getElementById('result').innerHTML = instrumentToJsonString(instrumentResponse);
55 |     })
56 |     .catch(function(err) {
57 |       console.error(err);
58 |     });
59 | }
60 | 
61 | function instrumentToJsonString(instrument) {
62 |   let details = instrument.details;
63 |   details.cardNumber = 'XXXX-XXXX-XXXX-' + details.cardNumber.substr(12);
64 |   details.cardSecurityCode = '***';
65 | 
66 |   return JSON.stringify({
67 |     methodName: instrument.methodName,
68 |     details: details,
69 |   }, undefined, 2);
70 | }
71 | 


--------------------------------------------------------------------------------
/tips/24-07-2017/Readme.md:
--------------------------------------------------------------------------------
 1 | Webpack DefinePlugin supports objects:
 2 | 
 3 | ```js
 4 | // webpack.conf.js
 5 | new webpack.DefinePlugin({
 6 |   FEATURES: {
 7 |     ADMIN: {
 8 |       ENABLED: JSON.stringify(false)
 9 |     }
10 |   }
11 | })
12 | 
13 | // index.js
14 | if (FEATURES.ADMIN.ENABLED) {
15 |   console.log('Admin is enabled');
16 | }
17 | ```
18 | 
19 | In the example `JSON.stringify` is not necessary, but is preferable. This is because you usually want primitive values to be placed as-is. Without `JSON.stringify` in this case you should pass `ENABLED: false` (or `ENABLED: 'false'`) and `VERSION: "'1.44.0'"` (note double quotes). To avoid mistakes with quotes you'd better always use `JSON.stringify`: `ENABLED: JSON.stringify(false)` and `VERSION: JSON.stringify('1.44.0')`.
20 | 


--------------------------------------------------------------------------------
/tips/24-07-2017/bundle.js:
--------------------------------------------------------------------------------
 1 | module.exports =
 2 | /******/ (function(modules) { // webpackBootstrap
 3 | /******/ 	// The module cache
 4 | /******/ 	var installedModules = {};
 5 | /******/
 6 | /******/ 	// The require function
 7 | /******/ 	function __webpack_require__(moduleId) {
 8 | /******/
 9 | /******/ 		// Check if module is in cache
10 | /******/ 		if(installedModules[moduleId]) {
11 | /******/ 			return installedModules[moduleId].exports;
12 | /******/ 		}
13 | /******/ 		// Create a new module (and put it into the cache)
14 | /******/ 		var module = installedModules[moduleId] = {
15 | /******/ 			i: moduleId,
16 | /******/ 			l: false,
17 | /******/ 			exports: {}
18 | /******/ 		};
19 | /******/
20 | /******/ 		// Execute the module function
21 | /******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
22 | /******/
23 | /******/ 		// Flag the module as loaded
24 | /******/ 		module.l = true;
25 | /******/
26 | /******/ 		// Return the exports of the module
27 | /******/ 		return module.exports;
28 | /******/ 	}
29 | /******/
30 | /******/
31 | /******/ 	// expose the modules object (__webpack_modules__)
32 | /******/ 	__webpack_require__.m = modules;
33 | /******/
34 | /******/ 	// expose the module cache
35 | /******/ 	__webpack_require__.c = installedModules;
36 | /******/
37 | /******/ 	// define getter function for harmony exports
38 | /******/ 	__webpack_require__.d = function(exports, name, getter) {
39 | /******/ 		if(!__webpack_require__.o(exports, name)) {
40 | /******/ 			Object.defineProperty(exports, name, {
41 | /******/ 				configurable: false,
42 | /******/ 				enumerable: true,
43 | /******/ 				get: getter
44 | /******/ 			});
45 | /******/ 		}
46 | /******/ 	};
47 | /******/
48 | /******/ 	// getDefaultExport function for compatibility with non-harmony modules
49 | /******/ 	__webpack_require__.n = function(module) {
50 | /******/ 		var getter = module && module.__esModule ?
51 | /******/ 			function getDefault() { return module['default']; } :
52 | /******/ 			function getModuleExports() { return module; };
53 | /******/ 		__webpack_require__.d(getter, 'a', getter);
54 | /******/ 		return getter;
55 | /******/ 	};
56 | /******/
57 | /******/ 	// Object.prototype.hasOwnProperty.call
58 | /******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
59 | /******/
60 | /******/ 	// __webpack_public_path__
61 | /******/ 	__webpack_require__.p = "";
62 | /******/
63 | /******/ 	// Load entry module and return exports
64 | /******/ 	return __webpack_require__(__webpack_require__.s = 0);
65 | /******/ })
66 | /************************************************************************/
67 | /******/ ([
68 | /* 0 */
69 | /***/ (function(module, exports, __webpack_require__) {
70 | 
71 | const $ = jQuery;
72 | const gitTag = 'v' + "1.0.0";
73 | 
74 | if (true) {
75 |   console.log('Prerender enabled');
76 | }
77 | 
78 | if (false) {
79 |   console.log('Animations enabled');
80 | }
81 | 
82 | if (false) {
83 |   console.log('Admin enabled');
84 | }
85 | 
86 | 
87 | /***/ })
88 | /******/ ]);
89 | 


--------------------------------------------------------------------------------
/tips/24-07-2017/index.js:
--------------------------------------------------------------------------------
 1 | const $ = GLOBAL_JQUERY_NAME;
 2 | const gitTag = 'v' + VERSION;
 3 | 
 4 | if (FEATURES.PRERENDER) {
 5 |   console.log('Prerender is enabled');
 6 | }
 7 | 
 8 | if (FEATURES.ANIMATIONS) {
 9 |   console.log('Animations is enabled');
10 | }
11 | 
12 | if (FEATURES.ADMIN.ENABLED) {
13 |   console.log('Admin is enabled');
14 | }
15 | 


--------------------------------------------------------------------------------
/tips/24-07-2017/webpack.conf.js:
--------------------------------------------------------------------------------
 1 | const webpack = require('webpack');
 2 | const {join} = require('path');
 3 | 
 4 | module.exports = {
 5 |   entry: join(__dirname, 'index.js'),
 6 |   output: {
 7 |     path: __dirname,
 8 |     filename: 'bundle.js',
 9 |     libraryTarget: 'commonjs2'
10 |   },
11 |   plugins: [
12 |     new webpack.DefinePlugin({
13 |       GLOBAL_JQUERY_NAME: 'jQuery',
14 |       VERSION: JSON.stringify('1.0.0'), // === VERSION: "'1.0.0'",
15 |       FEATURES: {
16 |         PRERENDER: 'true',
17 |         ANIMATIONS: false,
18 |         ADMIN: {
19 |           ENABLED: JSON.stringify('false') // preferably
20 |         }
21 |       }
22 |     })
23 |   ]
24 | }
25 | 


--------------------------------------------------------------------------------
/tips/24-08-2017/Readme.md:
--------------------------------------------------------------------------------
 1 | [`#TIL`][1]
 2 | 
 3 | ##### Task
 4 | 
 5 | Create a function that accepts a list of functions, and calls them with provided arguments checking if they all result in a *truthy* value.
 6 | 
 7 | ##### Solution
 8 | 
 9 | ```js
10 | import {overEvery, isNumber} from 'lodash';
11 | 
12 | function isEven(n) {
13 |     return n % 2 === 0;
14 | }
15 | 
16 | function isPositive(n) {
17 |     return n > 0;
18 | }
19 | 
20 | const isSuitable = overEvery(isNumber, isEven, isPositive);
21 | 
22 | console.log(isSuitable(2)); // true
23 | console.log(isSuitable(-2)); // false
24 | console.log(isSuitable('abc')); // false
25 | ```
26 | 
27 | [1]: http://www.urbandictionary.com/define.php?term=TIL
28 | 


--------------------------------------------------------------------------------
/tips/24-09-2017/Readme.md:
--------------------------------------------------------------------------------
 1 | ### [Wave Array][1]
 2 | 
 3 | #### Task
 4 | 
 5 | Given an array of integers, sort the array into a wave-like array and return it,
 6 | In other words, arrange the elements into a sequence such that a1 >= a2 <= a3 >= a4 <= a5 ...
 7 | 
 8 | Example
 9 | 
10 | ```
11 | Given [1, 2, 3, 4]
12 | 
13 | One possible answer : [2, 1, 4, 3]
14 | Another possible answer : [4, 1, 3, 2]
15 | ```
16 | 
17 | > Note: If there are multiple answers possible, return the one thats lexicographically smallest. So, in example case, you will return [2, 1, 4, 3]
18 | 
19 | #### Analysis
20 | 
21 | Sort the array in ascending order. Swap odd and even elements.
22 | 
23 | #### Solution
24 | 
25 | ```cpp
26 | vector Solution::wave(vector &A) {
27 |     std::sort(A.begin(), A.end());
28 |     for (int i = 0; i < A.size() - 1; i+=2) {
29 |         std::swap(A[i], A[i + 1]);
30 |     }
31 |     return A;
32 | }
33 | ```
34 | 
35 | [1]: https://www.interviewbit.com/problems/wave-array/
36 | 


--------------------------------------------------------------------------------
/tips/24-09-2017/meta.json:
--------------------------------------------------------------------------------
1 | {
2 |     "description": "Given an array of integers, sort the array into a wave-like array and return it. In other words, arrange the elements into a sequence such that a1 >= a2 <= a3 >= a4 <= a5 ..."
3 | }
4 | 


--------------------------------------------------------------------------------
/tips/24-10-2017/Readme.md:
--------------------------------------------------------------------------------
 1 | We continue to explore new browser APIs which are slowly begin to spread out. Today's choice is [WebShare API][1]. WebShare API provides a simple yet powerful API to open native sharing popup right out from a web page.
 2 | 
 3 | Here is a screenshot:
 4 | 
 5 | ![WebShare API](./scrn.png)
 6 | 
 7 | The source code is really eloquent:
 8 | 
 9 | ```js
10 | document.getElementById('share').addEventListener('click', () => {
11 | 	if (navigator.share) {
12 |         navigator.share({
13 |             title: 'Daily tip #98: WebShare API',
14 |             text: 'Check out the new dailytip - WebShare API!',
15 |             url: 'https://akwuh.me/t/98/',
16 |         })
17 |         .then(() => alert('Successful share'))
18 |         .catch((error) => alert('Error sharing: ' + error));
19 |     } else {
20 |     	alert('Your browser doesn\'t support WebShare API :\(');
21 |     }
22 | });
23 | ```
24 | 
25 | Similar to [PaymentRequest API][2], WebShare API requires a website to be on `https`. Also, `navigator.share` can't be called at any time, but only during [**triggered by user activation task**][3], which basically includes touch/mouse interactions (e.g. `click`).
26 | 
27 | Unfortunately on my phone WebShare API has an annoying bug: if you click `share` and then close the modal, then all following clicks on `share` will emit `AbortError: Share cancelled`. Also, the feature is supported only by Chrome 61+. So the API is not ready pretty much for production use, but definitely worth checking out in the nearest future.
28 | 
29 | WebShare API along with [WebShare Target API][4], that specifies the way to make a sharing target from a website, form [Ballista project][5].
30 | 
31 | [1]: https://developers.google.com/web/updates/2016/09/navigator-share
32 | [2]: https://github.com/jakwuh/webtip/tree/master/tips/23-10-2017
33 | [3]: https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
34 | [4]: https://github.com/mgiuca/web-share-target
35 | [5]: https://github.com/chromium/ballista
36 | 


--------------------------------------------------------------------------------
/tips/24-10-2017/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |         
 5 |         Web Share API
 6 |         
21 |     
22 |     
23 |         
24 |         
39 |     
40 | 
41 | 


--------------------------------------------------------------------------------
/tips/24-10-2017/meta.json:
--------------------------------------------------------------------------------
1 | {
2 |     "description": "We continue to explore new browser APIs which are slowly begin to spread out. Today's choice is WebShare API. WebShare API provides a simple yet powerful API to open native sharing popup right out from a web page."
3 | }
4 | 


--------------------------------------------------------------------------------
/tips/24-10-2017/scrn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/24-10-2017/scrn.png


--------------------------------------------------------------------------------
/tips/25-07-2017/Readme.md:
--------------------------------------------------------------------------------
 1 | **Task:** log all tcp packets to a specific destination (i.e. see which http requests our node app sends)  
 2 | 
 3 | **Solution:**
 4 | ```bash
 5 | > host yandex.ru # find out ipv4 & ipv6 adresses for the destination hostname
 6 | 
 7 | collections.yandex.ru has address 87.250.250.29
 8 | collections.yandex.ru has IPv6 address 2a02:6b8::3:29
 9 | ```
10 | 
11 | ```bash
12 | > tcpdump dst host 87.250.250.29 -vv
13 | ```
14 | 
15 | The command will print out all tcp packets to the specified host.  
16 | To filter out redundant data you could pipe it with grep:
17 | 
18 | ```bash
19 | > tcpdump dst host 87.250.250.29 -vv | grep 'body:' -A 2 -B 2
20 | ```
21 | 


--------------------------------------------------------------------------------
/tips/25-08-2017/Readme.md:
--------------------------------------------------------------------------------
 1 | In this post I describe the most frequent `git` use cases.
 2 | 
 3 | Add files to an old commit. Let commit **sha** be a `8bf5cc`:
 4 | ```bash
 5 | git add -A
 6 | git commit --fixup=8bf5cc
 7 | git rebase --interactive --autosquash 8bf5cc^
 8 | ```
 9 | 
10 | Add files to the last commit without changing a message:
11 | 
12 | ```bash
13 | git add -A
14 | git commit --amend --no-edit
15 | ```
16 | 
17 | Delete the last commit from the history but leave changes:
18 | 
19 | ```bash
20 | git reset HEAD~1 --soft
21 | ```
22 | 


--------------------------------------------------------------------------------
/tips/25-09-2017/Readme.md:
--------------------------------------------------------------------------------
 1 | 
 2 | Chrome 62 introduces a lot of useful features. The full list is available at [developers.google.com][1]. Some of the most interesting from my point of view:
 3 | 
 4 | - Top-level `await`:
 5 | 
 6 | ```js
 7 | await fetch('https://akwuh.me/t/1') // returns Response
 8 | (await fetch('https://akwuh.me/t/1')).text() // returns string
 9 | ```
10 | 
11 | - An API for searching for all instances of a specific constructor (!).
12 | 
13 | ```js
14 | queryObjects(Promise) // returns an array of all instantiated promises
15 | ```
16 | 
17 | We should definitely think about approach to easily find memory leaks by using `queryObjects`.
18 | 
19 | - Screenshots of a specific HTML node (which is broken if there is a horizontal scrollbar at the page though)
20 | 
21 | To use it press `cmd(ctrl) + shift + p` and type `capture`.
22 | 
23 | ![Capture a node](./capture.png)
24 | 
25 | [1]: https://developers.google.com/web/updates/2017/08/devtools-release-notes
26 | 


--------------------------------------------------------------------------------
/tips/25-09-2017/capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/25-09-2017/capture.png


--------------------------------------------------------------------------------
/tips/25-09-2017/meta.json:
--------------------------------------------------------------------------------
1 | {
2 |     "description": "Chrome 62 introduces a lot of useful features: top-level `await`, `queryObjects(Constructor)`, screenshots of a specific HTML node."
3 | }
4 | 


--------------------------------------------------------------------------------
/tips/25-10-2017/Readme.md:
--------------------------------------------------------------------------------
 1 | 
 2 | Chrome 63 has enabled by default a powerful feature called [**Async Iteration / Async Generators**][1]. The spec consists of the [following parts][2]:
 3 | 
 4 | - An async generator function, which is pretty much similar to generator function except the fact that it returns promise, not value:
 5 | 
 6 | ```js
 7 | async function * emitAsyncChunks() {
 8 |     let chunk1 = await fetchChunk(1);
 9 | 
10 |     yield chunk1;
11 | 
12 |     let chunk2 = await fetchChunk(2);
13 |     let chunk3 = await fetchChunk(3);
14 | 
15 |     yield chunk2 + chunk3;
16 | 
17 |     yield await fetchChunk(4);
18 | }
19 | ```
20 | 
21 | - A `Symbol.asyncIterator` which plays the same role as `Symbol.iterator` but for asynchronous iteration: defines the way an object should be iterated on:
22 | 
23 | ```js
24 | class Document {
25 |     [Symbol.asyncIterator]() {
26 |         return emitAsyncChunks();
27 |     }
28 | }
29 | ```
30 | 
31 | - An asynchronous iteration statement itself:
32 | 
33 | ```js
34 | let document = new Document();
35 | 
36 | for await (let chunk of document) {
37 |     socket.push(chunk);
38 | }
39 | ```
40 | 
41 | An async generator + async iterator present a lot of concepts in a web: asynchronous emitting of data, dom events or animations.
42 | 
43 | Here is an example of asynchronous html page emitter that simplifies a lot writing [SSR app with streams][3]. It is currently works in Chrome Canary but the feature is expected to appear in NodeJS soon, so the piece of code is really useful:
44 | 
45 | ```js
46 | async function fetchData() {
47 |     return new Promise(resolve => {
48 |         setTimeout(resolve({
49 |             name: 'Daily tip'
50 |         }), 100);
51 |     })
52 | }
53 | 
54 | class Document {
55 |     async * emitAsyncChunks() {
56 |         yield 'Async iteration demo';
57 | 
58 |         let data = await fetchData();
59 | 
60 |         yield `
${data.name}
`; 61 | 62 | yield ''; 63 | } 64 | 65 | [Symbol.asyncIterator]() { 66 | return this.emitAsyncChunks(); 67 | } 68 | } 69 | 70 | let doc = new Document(); 71 | 72 | (async () => { 73 | let content = ''; 74 | 75 | for await (let chunk of doc) { 76 | content += chunk; 77 | } 78 | 79 | console.log(content); 80 | })(); 81 | ``` 82 | 83 | [1]: https://www.chromestatus.com/feature/5727385018171392 84 | [2]: http://2ality.com/2016/10/asynchronous-iteration.html 85 | [3]: https://youtu.be/LmG1KxKcevE?t=48m47s 86 | -------------------------------------------------------------------------------- /tips/25-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Chrome 63 has enabled by default a powerful feature called Async Iteration / Async Generators. The spec consists of the following parts:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/26-07-2017/Readme.md: -------------------------------------------------------------------------------- 1 | The tip supplements tip of 23-07-2017. 2 | 3 | To push git commits along **with** corresponding tags, use: 4 | 5 | ```bash 6 | git push --follow-tags 7 | ``` 8 | 9 | In the most common case (create npm version + git tag and push it to the remote) this command is equal to: 10 | 11 | ```bash 12 | git push && git push --tags 13 | ``` 14 | 15 | --- 16 | Special credits to [@nexidan](https://github.com/NeXidan) for asking a good question which led to this tip. 17 | 18 | --- 19 | As noted by [@vlyahovich](https://github.com/vlyahovich) `git push origin --tags` pushes commits and tags as well. The key here is the `` parameter. 20 | -------------------------------------------------------------------------------- /tips/26-08-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "A software digest made by developers. #tooling #editors" 3 | } 4 | -------------------------------------------------------------------------------- /tips/26-08-2017/toolbox.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/26-08-2017/toolbox.jpg -------------------------------------------------------------------------------- /tips/26-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Chrome has the feature to trace all runtime optimizations / deoptimizations of a webpage. This can shed some light on efficiency of a specific piece of code and help understand how to optimize it. 2 | 3 | To enable Chrome (de)optimizations tracing run it with the flags enabled: 4 | 5 | ```bash 6 | # macos 7 | "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \ 8 | --js-flags="--trace-deopt --trace-opt-verbose" 9 | 10 | # windows 11 | chrome.exe --js-flags="--trace-deopt --trace-opt-verbose" 12 | ``` 13 | 14 | The command above will run Chrome and start tracing (de)optimizations to *stdout*: 15 | 16 | ``` 17 | [optimizing 0x32e643bd8d89 - took 0.663, 9.130, 0.083 ms] 18 | [completed optimizing 0x32e643bd8d89 ] 19 | [not yet optimizing keys, not enough ticks: 0/2 and ICs changed] 20 | [not yet optimizing 25.Events.trigger, not enough ticks: 0/2 and ICs changed] 21 | [not yet optimizing getDefault, not enough ticks: 0/2 and ICs changed] 22 | [not yet optimizing get$1, not enough ticks: 0/2 and ICs changed] 23 | [not yet optimizing eventsApi, not enough ticks: 0/2 and ICs changed] 24 | [not yet optimizing difference, not enough ticks: 0/2 and ICs changed] 25 | [not yet optimizing contexified, not enough ticks: 0/2 and ICs changed] 26 | [not yet optimizing onChange, not enough ticks: 0/2 and too large for small function optimization: 10360/120] 27 | [not yet optimizing ConvertToString, not enough type info for small function optimization: 0/3 (0%)] 28 | [resetting ticks for onChange due from 1 due to IC change] 29 | [not yet optimizing get$2, not enough ticks: 0/2 and too large for small function optimization: 288/120] 30 | ``` 31 | 32 | What helpful information do we see from the output above? 33 | 34 | - calling a function with a different signature leads to resetting IC 35 | - to become optimized a function needs to have 2 subsequent calls with parameters which have same hidden classes 36 | 37 | --- 38 | 39 | To print all available flags use: 40 | 41 | ```bash 42 | ... --js-flags="--help" 43 | ``` 44 | -------------------------------------------------------------------------------- /tips/26-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Chrome has the feature to trace all runtime optimizations / deoptimizations of a webpage. This can shed some light on efficiency of a specific piece of code and help understand how to optimize it. To enable Chrome (de)optimizations tracing run it with the flags enabled:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/26-10-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Whoa! Looking back and seeing all the big job we've done together inspires me a lot. Sure, together - because **100** tips written day-by-day can't be possible without your support. I'm truly happy for that. 2 | 3 | Back then, at [**50th**][1] tip I've described all those aims I've set myself. There are: 4 | 5 | ##### Form the character (e.v.e.r.y. d.a.m.n. d.a.y.) 6 | 7 | There is a ["21 days" myth][2] saying that 21 days of task completion forms a habit. I am not aware of truthfulness of it, but could say doing something more than 50 times sequentially definitely forms a habit. Thus saying, writing tips became less stressful for me after some critical point: regardless of time being left - I was sure I would do it. 8 | 9 | [1]: https://github.com/jakwuh/webtip/blob/master/tips/05-09-2017 10 | [2]: https://www.forbes.com/sites/jasonselk/2013/04/15/habit-formation-the-21-day-myth/#150989ecdebc 11 | 12 | ##### Learn English 13 | 14 | I do become feeling myself more confident when I write text in English. Each day I wrote a tip made me asking less and less questions about its correctness. 15 | 16 | ##### Force myself to get a new knowledge everyday 17 | 18 | Not only I share new knowledge with you, but also learn. There are a lot of "side effects" of doing that: learning algorithms, implementing some concepts in my job projects, following news and a lot more. 19 | 20 | ##### Help / mentor people 21 | 22 | Same as I wrote 50 tips before, people reading tips, discussing problems, finding mistakes or just saying *Thank you* are the best motivation for leading such a project. 23 | 24 | ### What's next? 25 | 26 | I am restructuring the project to match new aims and expectations. Lets introspect the old and the new. 27 | 28 | What remains the same: 29 | - The project is alive and is going to 30 | - Tips are only on important topics - I appreciate your time 31 | - "IMO" is implied 32 | 33 | What is changing: 34 | 35 | - `Daily Tip` is renamed to `Web Tip` 36 | - Tips become bigger, deeper and on more complicated things 37 | - Tips will have a short description which will be posted to Telegram as well 38 | - Tips will not be posted on a daily basis 39 | 40 | I believe the new project structure will play a good game and make it even more interesting and spectacular. 41 | 42 | Thank you for your time and see you soon! 43 | 44 | --- 45 | 46 | I hope you enjoy the project. Don't hesitate to contact me to leave a feedback at any time via [Telegram](https://t.me/jakwuh) or [email](mailto:jakwuh@gmail.com). 47 | -------------------------------------------------------------------------------- /tips/26-10-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Whoa! Looking back and seeing all the big job we've done together inspires me a lot. Sure, together - because 100 tips written day-by-day can't be possible without your support." 3 | } 4 | -------------------------------------------------------------------------------- /tips/27-07-2017/Readme.md: -------------------------------------------------------------------------------- 1 | tldr; JSDoc is pretty bad and buggy ~~typesystem~~ 2 | 3 | ```js 4 | /** 5 | * @param {{foo: number}} a 6 | * @param {Object} b 7 | */ 8 | function test(a, b) { 9 | console.log(a.foo); // ok 10 | console.log(a.bar); // unresolved variable bar 11 | console.log(b.foo); // ok 12 | console.log(b.bar); // ok 13 | console.log(b.foo.bar); // ok 14 | console.log(b.bar.baz); // unresolved variable bar - wtf ?? 15 | } 16 | 17 | test({}, {}); // ok 18 | test({foo: 1}, {bar: '2'}) // ok 19 | test({foo: '2'}, {bar: '2'}) // error 20 | ``` 21 | 22 | #### JSDoc `{{}}` vs `{Object}`: 23 | 24 | `{{foo: number}}` means `a` is expected to have `foo` property which type is `number`. This basically does not mean `a` couldn't have any other properties. That is, every type which could be narrowed to `{{foo: number}}` will be a valid function parameter. The difference between `a` and `b` **inside** the `test` function body is that typecheker deals with `a` as if it would have only property `foo` of the corresponding type, unlike `b` which is supposed to have any property of type `any`. 25 | 26 | 27 | #### WTF: 28 | 29 | In the example we have 2 equal (regarding types) expressions: `console.log(b.foo.bar)` & `console.log(b.bar.baz)`. Despite of that the latter is considered to be invalid. The difference? To be frankly, I don't know. Also, I didn't manage to find answers on such questions like: *How to define a type for an object which will not use narrowing?* or *How to define a type for an object which has one property of type number and all other its keys have a type of string?* 30 | 31 | Described questions along with my experience of using JSDoc led me to the conclusion: 32 | 33 | #### JSDoc does not longer worth the effort. 34 | -------------------------------------------------------------------------------- /tips/27-08-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "A software digest made by developers. #tooling #shells #gui" 3 | } 4 | -------------------------------------------------------------------------------- /tips/27-08-2017/zsh.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/27-08-2017/zsh.gif -------------------------------------------------------------------------------- /tips/27-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | #### Task 2 | 3 | Solve the NodeJS riddle. Why `fs` resolves file unlike `require`? 4 | 5 | ``` 6 | > fs.readFileSync('babel-plugin-transform-es2015-instanceof/lib/index.js').toString() 7 | 8 | '"use strict";\n\nexport... 9 | 10 | > require('babel-plugin-transform-es2015-instanceof/lib/index.js') 11 | 12 | Error: Cannot find module 'babel-plugin-transform-es2015-instanceof/lib/index.js' 13 | at Function.Module._resolveFilename (module.js:527:15) 14 | at Function.Module._load (module.js:476:23) 15 | at Module.require (module.js:568:17) 16 | at require (internal/module.js:11:18) 17 | at repl:1:1 18 | at ContextifyScript.Script.runInThisContext (vm.js:44:33) 19 | at REPLServer.defaultEval (repl.js:239:29) 20 | at bound (domain.js:301:14) 21 | at REPLServer.runBound [as eval] (domain.js:314:12) 22 | at REPLServer.onLine (repl.js:440:10) 23 | ``` 24 | 25 | #### Theory 26 | 27 | `fs` works relative to `process.cwd()` and that is why it finds the requested JS file. `require` works in a bit different way. It tries to find the requested module within `module.paths`: 28 | 29 | ```js 30 | > module.paths 31 | [ '/Users/jakwuh/Source/job/dailytip/repl/node_modules', 32 | '/Users/jakwuh/Source/job/dailytip/node_modules', 33 | '/Users/jakwuh/Source/job/node_modules', 34 | '/Users/jakwuh/Source/node_modules', 35 | '/Users/jakwuh/node_modules', 36 | '/Users/node_modules', 37 | '/node_modules', 38 | '/Users/jakwuh/.node_modules', 39 | '/Users/jakwuh/.node_libraries', 40 | '/Users/jakwuh/.nvm/versions/node/v8.5.0/lib/node' ] 41 | ``` 42 | 43 | However it is possible to alter `module.paths` by using `NODE_PATH` env variable: 44 | 45 | ```bash 46 | NODE_PATH=./tips:./web node 47 | ``` 48 | ```js 49 | > module.paths 50 | [ '/Users/jakwuh/Source/job/dailytip/repl/node_modules', 51 | '/Users/jakwuh/Source/job/dailytip/node_modules', 52 | '/Users/jakwuh/Source/job/node_modules', 53 | '/Users/jakwuh/Source/node_modules', 54 | '/Users/jakwuh/node_modules', 55 | '/Users/node_modules', 56 | '/node_modules', 57 | './tips', // 1 58 | './web', // 2 59 | '/Users/jakwuh/.node_modules', 60 | '/Users/jakwuh/.node_libraries', 61 | '/Users/jakwuh/.nvm/versions/node/v8.5.0/lib/node' ] 62 | ``` 63 | 64 | #### Solution 65 | 66 | To make the example work we need to add `.` to the `module.paths`: 67 | 68 | ```bash 69 | NODE_PATH=. node 70 | ``` 71 | 72 | ```js 73 | > require('babel-plugin-transform-es2015-instanceof/lib/index.js') 74 | { __esModule: true, default: [Function] } 75 | ``` 76 | -------------------------------------------------------------------------------- /tips/27-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "`fs` works relative to `process.cwd()` and that is why it finds the requested JS file. `require` works in a bit different way. It tries to find the requested module within `module.paths`:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/28-07-2017/Readme.md: -------------------------------------------------------------------------------- 1 | DOM API has a handy [`Element.scrollIntoView`](https://developer.mozilla.org/en/docs/Web/API/Element/scrollIntoView) method which scrolls the element into the viewport. 2 | 3 | Although it's supported pretty well among browsers, most of them do not support neither `smooth` option nor timing function, which make the API less useful. 4 | 5 | [Demo](https://jsfiddle.net/sqzpwbdc/1/show/) 6 | 7 | --- 8 | 9 | Special credits to [@ftdebugger](https://github.com/ftdebugger) for sharing the API. 10 | -------------------------------------------------------------------------------- /tips/28-08-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "To ssh to a remote server and execute commands there use the following syntax" 3 | } 4 | -------------------------------------------------------------------------------- /tips/28-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | To measure asset's parse time one could do something like the following (at first sight): 2 | 3 | ```html 4 | 10 | ``` 11 | 12 | However, the code above does not take into account a parse delay, which takes place before a script evaluation. 13 | 14 | Thus, to measure a real asset's parsing time we should [use the following][1]. 15 | 16 | ```html 17 | 21 | 24 | 27 | ``` 28 | 29 | Keep in mind this will still work improperly if a user's script is cached. 30 | 31 | [1]: https://twitter.com/nolanlawson/status/817077573012180992 32 | -------------------------------------------------------------------------------- /tips/28-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "To measure asset's parse time one could do something like the following (at first sight):" 3 | } 4 | -------------------------------------------------------------------------------- /tips/29-07-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Chrome 60 / Canary DevTools highlights: 2 | 3 | #### [Code coverage](https://developers.google.com/web/updates/2017/04/devtools-release-notes#coverage) 4 | 5 | [![Code coverage](https://developers.google.com/web/updates/images/2017/04/coverage-breakdown.png)](https://developers.google.com/web/updates/2017/04/devtools-release-notes#coverage) 6 | 7 | Highly useful tool for looking through the code and defining which parts of it are not being used. 8 | 9 | #### [Debug gestures(continue here) + step into async](https://developers.google.com/web/updates/2017/04/devtools-release-notes#async) 10 | 11 | [![Debug gestures video](http://i3.ytimg.com/vi/-q7eMEU9eXw/maxresdefault.jpg)](https://developers.google.com/web/updates/2017/04/devtools-release-notes#async) 12 | 13 | To see this in action have a look at the [video](https://youtu.be/-q7eMEU9eXw). The feature is activated by pressing **cmd (ctrl)** and pointing mouse in *sources* area 14 | 15 | #### [Better object preview](https://developers.google.com/web/updates/2017/05/devtools-release-notes#object-previews) 16 | 17 | [![Better object preview](https://developers.google.com/web/updates/images/2017/05/newobjpreview.png)](https://developers.google.com/web/updates/2017/05/devtools-release-notes#object-previews) 18 | -------------------------------------------------------------------------------- /tips/29-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Many JavaScript libraries still exist in a **CommonJS** style. This becomes a problem when it comes to resolving library exports in order to enable proper autocomplete. 2 | 3 | ![chai unknown](./unknown.png) 4 | 5 | Fortunately, there is a [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) TypeScript project to fix this. There are two ways of enabling typings in your project. 6 | 7 | #### 1. By using npm `@types` scope (recommended) 8 | 9 | To make an autocomplete work in the example above we have to install 2 `@types` packages: 10 | 11 | ``` 12 | npm i @types/mocha @types/chai --save-dev 13 | ``` 14 | 15 | #### 2. By enabling typings in IDE 16 | 17 | WebStorm gives the possibility to enable custom typings through the project preferences: 18 | 19 | ![WebStorm preferences](./preferences.png) 20 | 21 | The difference between the 1st and the 2nd option is that while using `@types` npm scope you add your typings into `package.json` so all of your teammates automatically get the typings; however with the 2nd option typings are binded to a current IDE. If you work independently the 2nd option might still be a better choice because in this case you need to download them only once and they will become cached for all of your projects. 22 | 23 | Finally, we have an autocomplete working: 24 | 25 | ![Autocomplete](./autocomplete.gif) 26 | -------------------------------------------------------------------------------- /tips/29-08-2017/autocomplete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/29-08-2017/autocomplete.gif -------------------------------------------------------------------------------- /tips/29-08-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Many JavaScript libraries still exist in a CommonJS style. This becomes a problem when it comes to resolving library exports in order to enable proper autocomplete." 3 | } 4 | -------------------------------------------------------------------------------- /tips/29-08-2017/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/29-08-2017/preferences.png -------------------------------------------------------------------------------- /tips/29-08-2017/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/29-08-2017/unknown.png -------------------------------------------------------------------------------- /tips/29-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | Finally there is an API which allows one to abort `fetch`. This sounds really exciting to me. In [his article][1] [Jake Archibald][2] describes in details the API with samples and underlying history. We will have a short look at it: 2 | 3 | > Currently only **Firefox 57 (Nightly)** has implemented the feature 4 | 5 | #### `AbortController` 6 | 7 | The first part of a spec is a new class `AbortController`. It has only one method `abort` and one property `signal`: 8 | 9 | ```js 10 | const controller = new AbortController(); 11 | const signal = controller.signal; 12 | 13 | signal.addEventListener('abort', () => { 14 | console.log(signal.aborted); 15 | }); 16 | 17 | controller.abort(); 18 | ``` 19 | 20 | #### `fetch` 21 | 22 | The second part of a spec is adding support for `signal` option to the [`fetch` API][3]: 23 | 24 | ```js 25 | const controller = new AbortController(); 26 | const signal = controller.signal; 27 | 28 | // abort request if it takes more that 5 seconds 29 | setTimeout(() => controller.abort(), 5000); 30 | 31 | fetch(url, { signal }).then(response => { 32 | return response.text(); 33 | }).then(text => { 34 | console.log(text); 35 | }); 36 | 37 | ``` 38 | 39 | That's it! 40 | 41 | --- 42 | 43 | BTW, for some reason implementing `signal` support in `fetch` polyfill seems to [get stucked][4]. 44 | 45 | 46 | [1]: https://developers.google.com/web/updates/2017/09/abortable-fetch?utm_source=feed&utm_medium=feed&utm_campaign=updates_feed 47 | [2]: https://twitter.com/jaffathecake 48 | [3]: https://fetch.spec.whatwg.org/ 49 | [4]: https://github.com/github/fetch/issues/547 50 | -------------------------------------------------------------------------------- /tips/29-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Finally there is an API which allows one to abort fetch. This sounds really exciting to me. In his article Jake Archibald describes in details the API with samples and underlying history. We will have a short look at it:" 3 | } 4 | -------------------------------------------------------------------------------- /tips/30-07-2017/Readme.md: -------------------------------------------------------------------------------- 1 | `arr.push(el)` returns (surprisingly) the new `length` of the array. 2 | 3 | ```js 4 | [1, 2].push(5); // 3 5 | ``` 6 | -------------------------------------------------------------------------------- /tips/30-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | I plan to set up a CI/CD process soon in order to deploy dailytip automatically as soon as I push a new commit to the repository. The most important step of CI as I see it is having a good tests coverage. 2 | 3 | [`gemini`][1] and [a plenty of other tools][2] provide tooling for regression testing using screenshots. This is a simple, chip and, well, rather reliable way of covering visual contents of a webapp with tests. 4 | 5 | ![gemini gui](./screenshot.png) 6 | 7 | Unfortunately, `gemini` works with either `PhantomJS` or `Selenium`. Both of them are not as fast as we wish them to be and both have a slightly intricate setup. It would be much better if we could start our screenshot testing with literally zero-cost: 8 | 9 | ```bash 10 | npm install chai-puppeteer 11 | ``` 12 | 13 | ```js 14 | // spec-bootstrap.js 15 | import chaiPuppeteer from 'chaiPuppeteer'; 16 | import chai from 'chai'; 17 | 18 | chai.use(chaiPuppeteer({ 19 | root: 'screenshots' 20 | })); 21 | ``` 22 | 23 | ```js 24 | // suite.js 25 | describe('Tips index', () => { 26 | it('works', () => { 27 | return capture({ 28 | url: 'http://localhost:9706/t/', 29 | selector: 'article' 30 | }); 31 | }); 32 | }); 33 | ``` 34 | 35 | As you might guess we are going to implement this using the new [`puppeteer`][3] library from the Chrome DevTools team. Also we will try to run the same suite against both `gemini` and `puppeteer` to see which is faster. 36 | 37 | Keep your eyes open! 38 | 39 | [1]: https://github.com/gemini-testing/gemini 40 | [2]: https://gist.github.com/cvrebert/adf91e429906a4d746cd 41 | [3]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions 42 | -------------------------------------------------------------------------------- /tips/30-08-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "`gemini` and a plenty of other tools provide tooling for regression testing using screenshots. This is a simple, chip and, well, rather reliable way of covering visual contents of a webapp with tests." 3 | } 4 | -------------------------------------------------------------------------------- /tips/30-08-2017/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/30-08-2017/screenshot.png -------------------------------------------------------------------------------- /tips/30-09-2017/Readme.md: -------------------------------------------------------------------------------- 1 | With the release of [puppeteer][2] it's now very easy to start using Chrome headless for solving various tasks. One such task is using it as a browser for running tests with `karma`. Back then we usually used `PhantomJS` that had a lot of JS syntax unsupported and really wasn't updated for years. 2 | 3 | #### Using [karma-chrome-launcher][3] 4 | 5 | There is a `karma-chrome-launcher` package that launches Chrome and executes tests in it. A typical config looks like: 6 | 7 | ```js 8 | // karma.conf.js 9 | process.env.CHROME_BIN = require('puppeteer').executablePath(); 10 | 11 | module.exports = function (config) { 12 | config.set({ 13 | basePath: '', 14 | frameworks: ['mocha'], 15 | files: [ 16 | 'tests/index.js' 17 | ], 18 | 19 | preprocessors: { 20 | '**/*.js': ['sourcemap'] 21 | }, 22 | 23 | reporters: ['dots'], 24 | port: 32345, 25 | colors: true, 26 | 27 | browsers: ['CustomChrome'], 28 | 29 | customLaunchers: { 30 | 'CustomChrome': { 31 | base: 'ChromeHeadless', 32 | flags: [ 33 | '--no-sandbox' 34 | ], 35 | debug: true 36 | } 37 | }, 38 | 39 | plugins: [ 40 | 'karma-chrome-launcher', 41 | 'karma-mocha', 42 | 'karma-sourcemap-loader' 43 | ] 44 | }); 45 | }; 46 | ``` 47 | 48 | 49 | #### Running ChromeHeadless on a raw linux 50 | 51 | To run ChromeHeadless on a bare linux a few packages are required. To install them use the following command: 52 | 53 | ```bash 54 | apt-get update && \ 55 | apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ 56 | libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \ 57 | libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \ 58 | libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ 59 | ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget 60 | ``` 61 | 62 | #### Links 63 | 64 | [Discussion on running `puppeteer` on a raw linux][1] 65 | [karma-chrome-launcher][2] 66 | 67 | [1]: https://github.com/GoogleChrome/puppeteer/issues/290 68 | [2]: https://github.com/GoogleChrome/puppeteer 69 | [3]: https://github.com/karma-runner/karma-chrome-launcher 70 | -------------------------------------------------------------------------------- /tips/30-09-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "With the release of puppeteer it's now very easy to start using Chrome headless for solving various tasks. One such task is using it as a browser for running tests with karma. Back then we usually used PhantomJS that had a lot of JS syntax unsupported and really wasn't updated for years." 3 | } 4 | -------------------------------------------------------------------------------- /tips/31-07-2017/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@ava/stage-4", 4 | "@ava/transform-test-files" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tips/31-07-2017/index.js: -------------------------------------------------------------------------------- 1 | export class DoubleLinkedList { 2 | constructor() { 3 | this._size = 0; 4 | this._head = {}; 5 | this._tail = {}; 6 | 7 | this.link(this._head, this._tail); 8 | } 9 | 10 | link(left, right) { 11 | left.right = right; 12 | right.left = left; 13 | } 14 | 15 | last() { 16 | let left = this._tail.left; 17 | 18 | if (left !== this._head) { 19 | return left; 20 | } 21 | } 22 | 23 | size() { 24 | return this._size; 25 | } 26 | 27 | erase(item) { 28 | --this._size; 29 | this.link(item.left, item.right); 30 | } 31 | 32 | unshift(item) { 33 | ++this._size; 34 | this.link(item, this._head.right); 35 | this.link(this._head, item); 36 | } 37 | } 38 | 39 | export class LRUCache { 40 | constructor(capacity) { 41 | this._history = new DoubleLinkedList(); 42 | this._capacity = capacity; 43 | this._map = {}; 44 | 45 | if (capacity < 1) { 46 | throw new Error('Capacity should be a positive value'); 47 | } 48 | } 49 | 50 | get(key) { 51 | let _mapValue = this._map[key]; 52 | 53 | if (_mapValue) { 54 | let [value, listItem] = _mapValue; 55 | this.touch(listItem); 56 | return value; 57 | } 58 | } 59 | 60 | put(key, value) { 61 | let {_map, _history} = this; 62 | let _mapValue = _map[key]; 63 | 64 | if (_mapValue) { 65 | let [_, listItem] = _mapValue; 66 | 67 | this.touch(listItem); 68 | _mapValue[0] = value; 69 | } else { 70 | let size = _history.size(), 71 | listItem = {key}; 72 | 73 | if (size >= this._capacity) { 74 | let lastItem = _history.last(); 75 | 76 | _history.erase(lastItem); 77 | _map[lastItem.key] = undefined; 78 | } 79 | 80 | _map[key] = [value, listItem]; 81 | _history.unshift(listItem); 82 | } 83 | } 84 | 85 | touch(item) { 86 | this._history.erase(item); 87 | this._history.unshift(item); 88 | } 89 | 90 | createNew(capacity) { 91 | return new LRUCache(capacity); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /tips/31-07-2017/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "31-07-2017", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "ava" 8 | }, 9 | "author": "jakwuh ", 10 | "license": "ISC", 11 | "ava": { 12 | "files": [ 13 | "test.js" 14 | ], 15 | "babel": "inherit", 16 | "require": [ 17 | "babel-register" 18 | ] 19 | }, 20 | "dependencies": { 21 | "ava": "^0.21.0", 22 | "babel": "^6.23.0", 23 | "babel-register": "^6.24.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tips/31-07-2017/test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {LRUCache, DoubleLinkedList} from './index.js'; 3 | 4 | test('DoubleLinkedList size', t => { 5 | let list = new DoubleLinkedList(); 6 | 7 | t.is(list.size(), 0); 8 | 9 | let item = {}; 10 | list.unshift(item); 11 | t.is(list.size(), 1); 12 | 13 | list.erase(item); 14 | t.is(list.size(), 0); 15 | }); 16 | 17 | test('LRUCache', t => { 18 | let lru = new LRUCache(5); 19 | 20 | lru.put(1, 1); 21 | lru.put(2, 2); 22 | lru.put(3, 3); 23 | lru.put(4, 4); 24 | lru.put(5, 5); 25 | lru.put(6, 6); 26 | 27 | t.is(lru.get(1), undefined); 28 | t.is(lru.get(6), 6); 29 | }); 30 | -------------------------------------------------------------------------------- /tips/31-08-2017/Readme.md: -------------------------------------------------------------------------------- 1 | tldr; 2 | An [`ava-puppeteer`][3] library is available at GitHub already and will become available at `npm` soon. 3 | 4 | > `ava` was chosen for a first-class support only for the current iteration. This library will actually work well even with `mocha`, `tape` and other test runners. 5 | 6 | After having a small research followed to [the previous tip][1] the following API has been evolved: 7 | 8 | ```js 9 | // bootstrap.js 10 | const {createCapture} = require('ava-puppeteer'); 11 | 12 | global.capture = createCapture(); 13 | ``` 14 | 15 | ```js 16 | // test-module-A.js 17 | import test from 'ava'; 18 | 19 | test('Dailytip homepage has Telegram subscribe banner', async t => { 20 | await capture({ 21 | url: 'https://akwuh.me/t/', 22 | selector: 'h4' 23 | }); 24 | t.pass(); 25 | }); 26 | ``` 27 | 28 | This is an example of a zero (!) config snapshot testing which is now possible thanks to [the puppeteer library][2]. 29 | 30 | ![ava-puppeteer](./ava-puppeteer.png) 31 | 32 | ### Highlights 33 | 34 | 1. `createCapture` - the exported function of the `ava-puppeteer` launches Chromium headless browser. It accepts `{root}` option which allows you to change the root directory for storing snapshots 35 | 2. `capture` function opens a new tab at Chromium, loads up a page, makes a screenshot for the provided selector, then saves it to disk and compares with the reference. It uses input parameters to identify where a screenshot should be saved. 36 | 37 | ### Workflow 38 | 39 | Workflow of working with screenshots is literally identical of the workflow of working with [ava's snapshots][4]. Once you add a new `capture` test or change an existing one you should run ava with the `--update-snapshots` flag: 40 | 41 | ```bash 42 | ava --update-snapshots 43 | ``` 44 | 45 | This will update reference screenshots. Later you simply run: 46 | 47 | ```bash 48 | ava 49 | ``` 50 | 51 | and `ava-puppeteer` will automatically make new snapshots and compare them with appropriate references. 52 | 53 | > P.S. Don't hesitate to leave a feedback on the library. Just reach me out via [Telegram](https://t.me/jakwuh) or [email](mailto:jakwuh@gmail.com) 54 | 55 | 56 | [1]: https://akwuh.me/t/44/ 57 | [2]: https://github.com/GoogleChrome/puppeteer 58 | [3]: https://github.com/jakwuh/ava-puppeteer 59 | [4]: https://github.com/avajs/ava#snapshot-testing 60 | -------------------------------------------------------------------------------- /tips/31-08-2017/ava-puppeteer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakwuh/webtip/9d21d7f9056548a374bb46651014ca620603cdba/tips/31-08-2017/ava-puppeteer.png -------------------------------------------------------------------------------- /tips/31-08-2017/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "tldr; An ava-puppeteer library is available at GitHub already and will become available at npm soon." 3 | } 4 | -------------------------------------------------------------------------------- /web/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "globals": { 4 | "MANIFEST_PATH": false, 5 | "ASSETS_PATH": false, 6 | "TIPS_PATH": false, 7 | "ROOT_PATH": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /web/build/configs/webpack.client.js: -------------------------------------------------------------------------------- 1 | const WebpackAssetsManifest = require('webpack-assets-manifest'); 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | const webpack = require('webpack'); 4 | const {resolve} = require('path'); 5 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 6 | 7 | module.exports = function ({paths: {root, dist}, env: {development}}) { 8 | const baseName = development ? '[name]' : '[name].[chunkhash:8]'; 9 | 10 | return { 11 | context: root, 12 | entry: { 13 | index: resolve(root, 'src/entries/client.js'), 14 | styles: [ 15 | resolve(root, 'node_modules/highlight.js/styles/github.css'), 16 | resolve(root, 'src/styles/index.css') 17 | ] 18 | }, 19 | output: { 20 | filename: `${baseName}.js`, 21 | path: resolve(root, 'dist/client/assets'), 22 | publicPath: '/' 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.js$/, 28 | exclude: /node_modules/, 29 | use: { 30 | loader: 'babel-loader', 31 | options: { 32 | presets: ['env'] 33 | } 34 | } 35 | }, 36 | { 37 | test: /\.css$/, 38 | use: ExtractTextPlugin.extract({ 39 | fallback: 'style-loader', 40 | use: 'css-loader' 41 | }) 42 | }] 43 | }, 44 | plugins: [ 45 | new CleanWebpackPlugin('client', {root: dist}), 46 | new ExtractTextPlugin(`${baseName}.css`), 47 | new WebpackAssetsManifest({ 48 | output: '../manifest.json' 49 | }) 50 | ] 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /web/build/configs/webpack.server.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const {resolve} = require('path'); 3 | const nodeExternals = require('webpack-node-externals'); 4 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 5 | 6 | module.exports = function ({paths: {root, dist}, env: {development}}) { 7 | return { 8 | devtool: development ? 'source-map' : false, 9 | target: 'node', 10 | externals: [nodeExternals()], 11 | entry: { 12 | index: [resolve(root, 'src/entries/server.js')] 13 | }, 14 | output: { 15 | filename: '[name].js', 16 | path: resolve(root, 'dist/server') 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.js$/, 22 | exclude: /node_modules/, 23 | use: { 24 | loader: 'babel-loader', 25 | options: { 26 | presets: ['env'] 27 | } 28 | } 29 | }, 30 | { 31 | test: /\.hbs$/, 32 | loader: 'handlebars-loader' 33 | } 34 | ] 35 | }, 36 | plugins: [ 37 | new CleanWebpackPlugin('server', {root: dist}), 38 | new webpack.DefinePlugin({ 39 | MANIFEST_PATH: JSON.stringify(resolve(root, 'dist/client/manifest.json')), 40 | ASSETS_PATH: JSON.stringify(resolve(root, 'dist/client/assets')), 41 | SPEAKING_ASSETS_PATH: JSON.stringify(resolve(root, '../pages/speaking')), 42 | TIPS_PATH: JSON.stringify(resolve(root, '../tips')), 43 | ROOT_PATH: JSON.stringify(resolve(root, '..')) 44 | }) 45 | ] 46 | 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /web/build/webpack.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const minimist = require('minimist'); 3 | const chalk = require('chalk'); 4 | const {resolve} = require('path'); 5 | 6 | const generateClientConfig = require('./configs/webpack.client.js'); 7 | const generateServerConfig = require('./configs/webpack.server.js'); 8 | 9 | const args = minimist(process.argv); 10 | const root = resolve(__dirname, '..'); 11 | const watch = !!args.watch; 12 | const development = !!args.development; 13 | 14 | const configOptions = { 15 | paths: { 16 | root, 17 | dist: resolve(root, 'dist') 18 | }, 19 | env: { 20 | development 21 | } 22 | }; 23 | 24 | const clientConfig = generateClientConfig(configOptions); 25 | const serverConfig = generateServerConfig(configOptions); 26 | 27 | function run(config) { 28 | const name = config.target === 'node' ? 'server' : 'client'; 29 | 30 | function logCallback(err, stats) { 31 | console.log(chalk.bold.blue(`[webpack:${name}]:`)); 32 | 33 | if (err) { 34 | console.error(err); 35 | return; 36 | } 37 | 38 | console.log(stats.toString({ 39 | modules: false, 40 | chunks: false, 41 | colors: true 42 | })); 43 | } 44 | 45 | const compiler = webpack(config); 46 | 47 | if (watch) { 48 | compiler.watch({}, logCallback); 49 | } else { 50 | compiler.run(logCallback); 51 | } 52 | } 53 | 54 | run(clientConfig); 55 | run(serverConfig); 56 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "webpack": "node ./build/webpack", 4 | "webpack:watch": "node ./build/webpack --development --watch", 5 | "serve": "node ./dist/server/index.js", 6 | "serve:watch": "nodemon ./dist/server/index.js", 7 | "start": "concurrently --kill-others \"npm run webpack:watch\" \"npm run serve:watch\"", 8 | "deploy": "ssh -A me 'bash -ic \"cd dailytip && git fetch && git reset origin/master --hard && cd web && yarn && npm run webpack && pm2 restart dailytip\"'" 9 | }, 10 | "dependencies": { 11 | "babel-core": "^6.26.0", 12 | "babel-loader": "^7.1.2", 13 | "babel-polyfill": "^6.26.0", 14 | "babel-preset-env": "^1.6.0", 15 | "chalk": "^2.1.0", 16 | "clean-webpack-plugin": "^0.1.16", 17 | "concurrently": "^3.5.0", 18 | "css-loader": "^0.28.5", 19 | "es6-promisify": "^5.0.0", 20 | "extract-text-webpack-plugin": "^3.0.0", 21 | "handlebars": "^4.0.10", 22 | "handlebars-loader": "^1.5.0", 23 | "highlight.js": "^9.12.0", 24 | "koa": "2", 25 | "koa-compose": "^4.0.0", 26 | "koa-logger": "2", 27 | "koa-mount": "2", 28 | "koa-route": "^3.2.0", 29 | "koa-static": "3", 30 | "markdown-to-html": "^0.0.13", 31 | "minimist": "^1.2.0", 32 | "stream-to-array": "^2.3.0", 33 | "style-loader": "^0.18.2", 34 | "webpack": "^3.5.5", 35 | "webpack-assets-manifest": "^1.0.0", 36 | "webpack-node-externals": "^1.6.0" 37 | }, 38 | "devDependencies": { 39 | "eslint": "^4.5.0", 40 | "eslint-config-airbnb-base": "^11.3.2", 41 | "eslint-plugin-import": "^2.7.0", 42 | "nodemon": "^1.11.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /web/src/components/Document/Document.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{title}} 7 | {{#each meta}} 8 | 9 | {{/each}} 10 | 11 | 12 | 13 |
21 | 22 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /web/src/components/Document/Document.js: -------------------------------------------------------------------------------- 1 | import {readFileSync} from 'fs'; 2 | import template from './Document.hbs'; 3 | 4 | export class Document { 5 | getMeta(tip) { 6 | return tip ? [ 7 | ['description', tip.description], 8 | ['og:title', tip.title], 9 | ['og:type', 'article'], 10 | ['og:url', `https://akwuh.me/t/${tip.id}/`], 11 | ['og:description', tip.description], 12 | ['og:site_name', 'Web Tip'] 13 | ] : [ 14 | ['description', 'Web, algos & related tips 🛠'], 15 | ['og:title', 'Web Tip @ James Akwuh'], 16 | ['og:type', 'website'], 17 | ['og:url', 'https://akwuh.me/t/'], 18 | ['og:description', 'Web, algos & related tips 🛠'], 19 | ['og:site_name', 'Web Tip'] 20 | ] 21 | } 22 | 23 | getTitle(tip) { 24 | return (tip ? `${tip.title} - ` : '') + 'Web Tip @ James Akwuh'; 25 | } 26 | 27 | render({tip, content = tip.content}) { 28 | let manifest = JSON.parse(readFileSync(MANIFEST_PATH)), 29 | meta = this.getMeta(tip), 30 | title = this.getTitle(tip); 31 | 32 | return template({ 33 | tip, 34 | title, 35 | content, 36 | meta, 37 | stylesName: manifest['styles.css'], 38 | scriptsName: manifest['index.js'] 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /web/src/entities/Tip.js: -------------------------------------------------------------------------------- 1 | import {join} from 'path'; 2 | import {getTips} from '../libs/helpers/getTips'; 3 | import {getReadmeMarkdown} from '../libs/helpers/getMarkdown'; 4 | import {NotFoundError} from './errors'; 5 | import {getTipTitle} from '../libs/helpers/getTipTitle'; 6 | import {getTipMeta} from '../libs/helpers/getTipMeta'; 7 | 8 | export class Tip { 9 | constructor({id}) { 10 | this.id = id; 11 | } 12 | 13 | getRoot() { 14 | const tips = getTips(); 15 | 16 | return join(TIPS_PATH, tips[this.id - 1]); 17 | } 18 | 19 | async fetchContent() { 20 | let content = await getReadmeMarkdown(this.getRoot()); 21 | let meta = await getTipMeta(this.id); 22 | 23 | this.content = content; 24 | this.title = meta.title || getTipTitle(this.id); 25 | this.description = meta.description || content.slice(0, 160); 26 | } 27 | 28 | static findIndexByDate(date) { 29 | const tips = getTips(); 30 | 31 | return tips.findIndex(tip => tip === date) + 1; 32 | } 33 | 34 | static fromId(id) { 35 | const tips = getTips(); 36 | 37 | id = Number(id); 38 | 39 | if (!Number.isFinite(id) || id > tips.length || id < 1) { 40 | throw new NotFoundError('Tip not found'); 41 | } 42 | 43 | return new this({id}); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /web/src/entities/errors.js: -------------------------------------------------------------------------------- 1 | // Error can not be extended in ES6+. See https://github.com/babel/babel/issues/4269 2 | export function CommonError(message) { 3 | Error.call(this, message); 4 | this.name = this.constructor.name; 5 | this.message = message; 6 | 7 | if (Error.captureStackTrace) { 8 | Error.captureStackTrace(this, this.constructor); 9 | } else { 10 | this.stack = (new Error()).stack; 11 | } 12 | } 13 | 14 | CommonError.prototype = Object.create(Error.prototype); 15 | 16 | export class NotFoundError extends CommonError { 17 | 18 | constructor(message) { 19 | super(message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /web/src/entries/client.js: -------------------------------------------------------------------------------- 1 | import hljs from 'highlight.js/lib/highlight'; 2 | 3 | hljs.registerLanguage('bash', require('highlight.js/lib/languages/bash')); 4 | hljs.registerLanguage('css', require('highlight.js/lib/languages/css')); 5 | hljs.registerLanguage('javascript', require('highlight.js/lib/languages/javascript')); 6 | hljs.registerLanguage('json', require('highlight.js/lib/languages/json')); 7 | hljs.registerLanguage('python', require('highlight.js/lib/languages/python')); 8 | hljs.registerLanguage('cpp', require('highlight.js/lib/languages/cpp')); 9 | hljs.registerLanguage('yml', require('highlight.js/lib/languages/yaml')); 10 | 11 | hljs.initHighlightingOnLoad(); 12 | -------------------------------------------------------------------------------- /web/src/entries/server.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import Koa from 'koa'; 3 | import minimist from 'minimist'; 4 | import createLogger from 'koa-logger'; 5 | 6 | import createSlashRedirect from '../libs/middleware/createSlashRedirect'; 7 | import createAssetsServe from '../libs/middleware/createAssetsServe'; 8 | import createGetTip from '../libs/middleware/createGetTip'; 9 | import createGetIndex from '../libs/middleware/createGetIndex'; 10 | import createErrorHandler from '../libs/middleware/createErrorHandler'; 11 | 12 | const args = minimist(process.argv); 13 | const app = new Koa(); 14 | 15 | app.use(createErrorHandler()); 16 | app.use(createLogger()); 17 | app.use(createAssetsServe()); 18 | app.use(createSlashRedirect()); 19 | app.use(createGetTip()); 20 | app.use(createGetIndex()); 21 | 22 | const {port = 3000} = args; 23 | 24 | app.listen(port, (err) => { 25 | if (err) { 26 | console.error(err); 27 | } else { 28 | console.log(`Server is listening on http://localhost:${port}`); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /web/src/libs/helpers/getMarkdown.js: -------------------------------------------------------------------------------- 1 | import {Markdown} from 'markdown-to-html'; 2 | import toArray from 'stream-to-array'; 3 | import {join, dirname} from 'path'; 4 | import {readFileSync, existsSync} from 'fs'; 5 | 6 | export function getMarkdown(path) { 7 | return new Promise((resolve, reject) => { 8 | let md = new Markdown(); 9 | md.bufmax = 2048; 10 | 11 | md.render(path, {}, (err) => { 12 | if (err) { 13 | return reject(err); 14 | } 15 | 16 | toArray(md, (err, arr) => { 17 | if (err) { 18 | return reject(err); 19 | } 20 | 21 | let content = arr.join(''); 22 | content = content.replace('https://github.com/jakwuh/webtip/tree/master', 'https://akwuh.me'); 23 | content = content.replace(/(\/)?tips\/([\d\-]+)(\/)?(readme\.md)?/ig, '/t/$2/'); 24 | 25 | let dir = dirname(path); 26 | let htmlPath = join(dir, 'index.html'); 27 | 28 | if (existsSync(htmlPath)) { 29 | content += readFileSync(htmlPath); 30 | } 31 | 32 | resolve(content); 33 | }); 34 | }); 35 | }); 36 | } 37 | 38 | export function getReadmeMarkdown(dir = ROOT_PATH) { 39 | return getMarkdown(join(dir, 'Readme.md')); 40 | } 41 | -------------------------------------------------------------------------------- /web/src/libs/helpers/getTipMeta.js: -------------------------------------------------------------------------------- 1 | import {getTips} from './getTips'; 2 | import {join} from 'path'; 3 | import * as fs from 'fs'; 4 | import promisify from 'es6-promisify'; 5 | 6 | const readFile = promisify(fs.readFile); 7 | const tips = getTips(); 8 | 9 | export async function getTipMeta(id) { 10 | let date = tips[id - 1]; 11 | 12 | try { 13 | let content = await readFile(join(TIPS_PATH, date, 'meta.json')); 14 | return JSON.parse(content); 15 | } catch (e) { 16 | return {}; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /web/src/libs/helpers/getTipTitle.js: -------------------------------------------------------------------------------- 1 | import {getTips} from './getTips'; 2 | import {readFileSync} from 'fs'; 3 | import {join} from 'path'; 4 | 5 | const tips = getTips(); 6 | 7 | const TIP_REGEXP = /\[[\d\-]+:\s*(.*)\s*]/; 8 | const ALL_TIPS_REGEXP = new RegExp(TIP_REGEXP.source, 'g'); 9 | 10 | export function getTipTitle(id) { 11 | let date = tips[id - 1]; 12 | let content = readFileSync(join(ROOT_PATH, 'Readme.md')).toString(); 13 | 14 | let matches = content.match(ALL_TIPS_REGEXP); 15 | 16 | let line = matches.find(match => match.slice(1).startsWith(date)); 17 | 18 | return line.match(TIP_REGEXP)[1]; 19 | } 20 | -------------------------------------------------------------------------------- /web/src/libs/helpers/getTips.js: -------------------------------------------------------------------------------- 1 | import {readdirSync} from 'fs'; 2 | 3 | function splitTip(tip) { 4 | return tip.match(/(\d+)-(\d+)-(\d+)/).slice(1).map(Number); 5 | } 6 | 7 | function compareTips(a, b) { 8 | let [dayA, monthA, yearA] = splitTip(a); 9 | let [dayB, monthB, yearB] = splitTip(b); 10 | 11 | if (yearB !== yearB) { 12 | return yearA - yearB; 13 | } 14 | 15 | if (monthA !== monthB) { 16 | return monthA - monthB; 17 | } 18 | 19 | if (dayA !== dayB) { 20 | return dayA - dayB; 21 | } 22 | 23 | return 0; 24 | } 25 | 26 | export function getTips() { 27 | return readdirSync(TIPS_PATH).sort(compareTips); 28 | } 29 | -------------------------------------------------------------------------------- /web/src/libs/middleware/createAssetsServe.js: -------------------------------------------------------------------------------- 1 | import route from 'koa-route'; 2 | import compose from 'koa-compose'; 3 | import mount from 'koa-mount'; 4 | import serve from 'koa-static'; 5 | import send from 'koa-send'; 6 | import {Tip} from '../../entities/Tip'; 7 | 8 | export default function () { 9 | return compose([ 10 | mount('/speaking/', serve(SPEAKING_ASSETS_PATH)), 11 | mount('/assets/', serve(ASSETS_PATH)), 12 | 13 | route.get('/t/:id/:asset+', async (ctx, id, asset) => { 14 | let tip = Tip.fromId(id), 15 | root = tip.getRoot(); 16 | 17 | await send(ctx, asset, {root}); 18 | }) 19 | ]); 20 | } 21 | -------------------------------------------------------------------------------- /web/src/libs/middleware/createErrorHandler.js: -------------------------------------------------------------------------------- 1 | import {NotFoundError} from '../../entities/errors'; 2 | 3 | export default function () { 4 | return async (ctx, next) => { 5 | try { 6 | await next(); 7 | } catch (e) { 8 | if (e instanceof NotFoundError) { 9 | ctx.status = 404; 10 | } else { 11 | ctx.status = 500; 12 | console.error(e); 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /web/src/libs/middleware/createGetIndex.js: -------------------------------------------------------------------------------- 1 | import route from 'koa-route'; 2 | import compose from 'koa-compose'; 3 | import {getReadmeMarkdown} from '../helpers/getMarkdown'; 4 | import {Document} from '../../components/Document/Document'; 5 | 6 | export default function () { 7 | let document = new Document(); 8 | 9 | return compose([ 10 | route.get('/', ctx => ctx.redirect('/t/')), 11 | 12 | route.get('/t/', async (ctx) => { 13 | let content = await getReadmeMarkdown(); 14 | 15 | if (content) { 16 | ctx.body = document.render({content: content}); 17 | } 18 | }) 19 | ]); 20 | } 21 | -------------------------------------------------------------------------------- /web/src/libs/middleware/createGetTip.js: -------------------------------------------------------------------------------- 1 | import route from 'koa-route'; 2 | import {Document} from '../../components/Document/Document'; 3 | import {Tip} from '../../entities/Tip'; 4 | 5 | export default function () { 6 | let document = new Document(); 7 | 8 | return route.get('/t/:id/', async (ctx, id) => { 9 | if (id.includes('-')) { 10 | let index = Tip.findIndexByDate(id); 11 | return ctx.redirect(`/t/${index}/`); 12 | } 13 | 14 | let tip = Tip.fromId(id); 15 | 16 | await tip.fetchContent(); 17 | 18 | ctx.body = document.render({tip}); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /web/src/libs/middleware/createSlashRedirect.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return async (ctx, next) => { 3 | let {path} = ctx; 4 | 5 | if (!path.endsWith('/')) { 6 | let url = ctx.origin + ctx.path + '/' + (ctx.querystring ? '?' + ctx.querystring : ''); 7 | return ctx.redirect(url); 8 | } 9 | 10 | await next(); 11 | }; 12 | } 13 | --------------------------------------------------------------------------------