From c5a34bb4a45d503c9a4c1e059ad220b4f4e90c92 Mon Sep 17 00:00:00 2001 From: johnnyreilly Date: Sat, 26 Aug 2023 20:15:51 +0100 Subject: [PATCH 01/14] feat: open ai generated descriptions --- .gitignore | 3 +- .../index.md | 2 +- open-ai-description/bun.lockb | Bin 0 -> 41278 bytes open-ai-description/index.ts | 90 +++++++++++++++ open-ai-description/package.json | 18 +++ open-ai-description/sample.env | 3 + open-ai-description/summarizer.ts | 103 +++++++++++++++++ open-ai-description/tsconfig.json | 107 ++++++++++++++++++ trim-xml/bun.lockb | Bin 3734 -> 4222 bytes trim-xml/tsconfig.json | 2 +- 10 files changed, 325 insertions(+), 3 deletions(-) create mode 100755 open-ai-description/bun.lockb create mode 100644 open-ai-description/index.ts create mode 100644 open-ai-description/package.json create mode 100644 open-ai-description/sample.env create mode 100644 open-ai-description/summarizer.ts create mode 100644 open-ai-description/tsconfig.json diff --git a/.gitignore b/.gitignore index 4bf599805d4..1df8913216e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules img.sh devto.sh -APIClientGenerator \ No newline at end of file +APIClientGenerator +.env \ No newline at end of file diff --git a/blog-website/blog/2023-03-18-migrating-from-ts-node-to-bun/index.md b/blog-website/blog/2023-03-18-migrating-from-ts-node-to-bun/index.md index 9382037e966..43a3e118643 100644 --- a/blog-website/blog/2023-03-18-migrating-from-ts-node-to-bun/index.md +++ b/blog-website/blog/2023-03-18-migrating-from-ts-node-to-bun/index.md @@ -149,7 +149,7 @@ The error message was suggesting I needed to explicitly state that I wanted to u { "compilerOptions": { - // "moduleResolution": "node", -+ "moduleResolution": "nodenext", ++ "moduleResolution": "Bundler", }, } ``` diff --git a/open-ai-description/bun.lockb b/open-ai-description/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..5696b905cda96457d16673ae460a8521f28835a0 GIT binary patch literal 41278 zcmeIb2{=_<^gn)ar8H5J424ok*Gy8O(4=UPCdtLcEv}1eYNRNYN;GPsxm5H<{d8Qo zJRJ@%P}L=XCk?{#@^PK-&h_H3+a z`{T(4u0baU8kIz?dslos9&Uo*C5#yDe~Uv!*Td(Feo#?&1|!tXM*!tB7)FrN8LkEX zUR)RW-Cj(1T>-y)LHa;YLl*DnxCD4Wnzujv4?;MkOGA1uh-Dz=O9=0if$I)%EyWb( zdvm=xEH@WME#!BF^c+a<0x{b^gy%Y+D`1H5_o3XLkX{Bc>URgqK;GvNBknB3nBQM? zJ)aXIVEZu`x21&lErhCfg8b_ccZ3)+S?-{#6)1yx?u8h6+Q9{uUkle5b9tT|Kd!)k z3tS_9HNUVwV7ScMg=x<#0Gdj0_oy00KxTKfs%h_fLiL zk=NDJCx{34`NB1pzZyu9HwgZR^&0>;;eCFth+;63;R4gWxje2n*Ixh;mA)U+u|7N> zHx7&K!u98P2V(g;U>qzrgze=8w=>vK4%*8CV&u_-80*^~V#H^Fov^%s0InMnFNbTy z`+|)y9O)*kek(L z)4l!Kl;ueOninL*&kJJX?F^2*P+uE}6@iBn$l(d#7IrY#2k%@BjgIy57qxRg1))B! zqU%d=4NVYA>_Yk{4>~XEJr`oMgAK%T5Dyn!4~7_dl|^x9QTp%tB>_KF@n^qO7G4`c z`IruuRQrNCC_5Afujd}Eubj2;gk#Log$3o^19n+5s0MbB;)D-W@k zu#xTTyUtRvII8yCw?tWU!+jnV8BdlUQ27vTcPuZctgx5SwxW%vCa-v-o4q84f3p3H z!13<>J!j2kmpho~zp~UiF5P3?YlqG;{faBEEavYhyqRCUCHcPMBDHGgm$qK!T^*M_ zj4(@18Zd9Ya!tft#}3~Xcz#_J$}~?Mqfqig+h%>hHKp{TNc zC|z6^zi#`s8;ZW(XF8fYRR`8t?mlYTutGjjvPfLLa{ptijSpW2m+alx=f_8bnRc3q z$JH*KCjCq4V8YwoHACba`mcUJD1u$Y>!~yR>zU8Sf#xv_%)U-yK9f2y%38c~w@LE% z=nZl+;g*Q^aXv)1-c9WhjA@yF-~t9N~DKVq>*O!ljPh+Rnk z^kIEm_2JUzyn)-22kMxJ4U5l9}9G8Wq9=9U9 zeNczx;eQmwZ_hYivy{OY1uU@kg~A-bfTfAiT3&(=0t|43{*H6u2-w;{%qs3Xyx?g5 z2e|E*K=3^Ps1JBp{uu2*Fm6pC_%VPVMk^nd)m8<9Uj}%45gz8xRtJJN1%<~0UQ&eo z-40C=68t*AWBpNYw0~Rm&j9>jz@y!;Y>ZnINcp7z7!G)}J%+a8H9_gIfG71Avb6kv zQhpfVM~L)q8TIoHg1-QGQ^2>@ZiIp0WdTUaNBi|9H?$BFye;5|K>3I@Yym^hCin=z zj~3xEAGRs23IzWU@K!W@tH5v*!K*;SnE)PfK-KC%@Y4W4QdItb!EXkK-KC%@ZNwohw^dUAilLWAo!zz$N2;GMrkYli>Q3WV)<_4r4a_~@P1$a{L*6KjYpFzW;4y_FY zzXR|%e~@z9+JEl>-h!roTg#V&hnrC%JeJ+o@*M!LOT)Ld{1t%5{_|h$p9Xkbzfo_( z(Hb3y{ojbn$No>oU|aBgVbfzQ(jVKfwK|aUX96DAU&NsfZNTd*x z9B4nZTWiav<3j*H81RV2{?pd|%Vof${)olUS{;b~vT&$@^Ur@Re;nYoMR>G9TlEhD zJgz^edt2Io=${Jsp@7G-b&#Z$f#B-_Pu6ej|D+$bL__c<@Zg8zw=KFOH^KV>9_NqN zmP_;^_(Omn3wYA*q#uy`nu-a&67bl6n!2y4bb>d7LtR{dNx5yEKf(cz?N9I|ZmL|8 zPRh>&ycOWleyuGVIS9Tx9KPuS9{Vlg+giRQ;K}?=+5q`m3Z(qSfM-GZ*neOtYIFIg z0B-?!Qg&x;; zV}XRH*}@@{;7tKf<}bvwwR}&&llo)XZ7u)EZ~CJRTN8->uK~ef;rA>KVr&1*H6@;t?j1+ zc-(&=_P_N1C4ip-_}|MFKsp?agn9+b57^aEe;??73B>*{;XXXSM*M%#zmLk#{S%JA z*0upDpAY!qP(IfEzu5mM;7tJ!aO&CXcROHuQvu6F!GJN@gW@%=L&$}DLO5ch=62K% zrduig0%Uw_Bf-|0kmef=AY=g+|On3YA6AhsQ>JNw8#(dD+#U5p`X!a^ev4c)( zpd;bMI+MU0Wn34s@L?5Msk#Q91NKPEi`BEpMv6bVemXuUo~#R^m3em}OyzLWgNRNkaj zviGLhYxb@mA|YAMpW4OZ!!h&g2cBgeRTqp5c6fXypE;z@>&?S1?{hh9p!*Pr2rtg9 zBrwx*=8st4DM^!)(7t{sdspp(2iM;miY)c_7L zoI6){>^E8QoUHdz8|ip!4<1H*2QN>n&-STubA{IhF*bU^-*n z#yj%T9Sw9(O_wz5Sh?Ni+yRjPfEU5ue#rB^6jvt=alL*4IenI>@;@+5E0(?gcM?CGPmpX*RJ{x zLrboHDGxP~leHQ=X`4s*8K;9QhVqj>wIuDM{Bm%V4Rxn;hV#<8s_QF#~R0<5m2 zpEDXhdgWspFYeh$U|PrWjk2vPin}#jySzMIF|PBMpaI)@?QrSxs=r6KHRAStlY*~h zonP|&aKE=#yOfTI4ip<}F-YgE);8IJPv6O=%>g2!FRrm9Fn4>-s9Nc1HPNN~LR5ZN z%f4Mkhs6q%cy=H4Gp)O7tz9^E>cUdPNn?L}Uj2Pk@9u|&>RB7SORQvkexsA%_sRTw zJ`fRJ+;5V=bktfc_ruO+m-CLRyrRc`m3tqhq;1@*^UziIBf~kmGS*spRN-xxVdwKQ z$Gm>$y*X;8_{H>&+mlYda!FDxPVevyhzKw42}xke>h;^Cw&zWejNbM=PNC+OiBER- z>}c`BC$Oa3f#>>b_H9yR?b!J7UA+QxMX{o?&Pl}%HTMn%KHGHq^^fZ}#wlI~BEk!= zjT!^f&DhNA>)Bg|SHG?vbyRZ1j$?A6p<73ckVzdek;C?RnR{WvL*+1s*t=aWyihsb zcX{7Y!>g0|0l7+>b#|)VI=E#GjhB8$&9sJ9(m+#c?iJ0orJ*8SK zW#~9B%l3WTC$G7^EKO!uI~j|Y>jz{+dfBDQ8gWdQrA}fUoEofffyRqxDW;lPDcaJ{tz#S$9Fm>< zE|;mk*!O^SW|^^P2i3CUcWl5kss}pP+6H&ruoV$1GO5Zc3xc4M>=uSu>X4SACjT5bj-ai5$km| zJ9%hoO;MOTt^Kx|-V@`zvRB?uvYT?!VdZScv$kn@PY0!0$6U(TdFAn%WHV*Ol{8*F zmm-1LH+qHsva-4F#}=NN6FFt(^6nkPw@be_dA9vF`+#&%!qLpY;||x_2WQ-<539(Z z$Vrvj6qZ)qrSJ5cQ>*5iNJu9E5z!aV>PTRoeNxiyO}m0go+`7{I-6Du8h&!YoZ8z@ z>)R?x(9ASg!r*qFF2u5ng#h3NiDE zi;Ldm`i415X^yuw=Y3$kxOhf&OhbY9wF6Q7J{<@6$dA-&!3xswE@^(YyWuHExIl#Hj(Ny>8F;;P+o!=}-PqcT3fmz=Gd zes@>$`3nm=S2rAf_VnPXB{dy%*E;vy?;U1q|59$&m?)EZKt%M#=T{P#7mn_-J{`Db z?BbrQj?9jk9sS*R@qzaPJEv}GFxr!uu$r&Xb)o_Pg#c zxshqrXP5Q4433RbFJ^j7-$8jdI-F$5->rJrX`uWGAR@eQ+|(GDQIe&;0a1MQ^NNf^ z%5SrhESIm`yrt%^4Xwo|kZE*?pIL0V!*1`-|zE?T{Qhc1Q8IDY}=t zOSzTYG`(A6qn-BE&~ICKMwjs^fkxY->uJ2?JOJsL$9LY{YkR}@;emlClEU`-j#Qst zc4E`GnL};z2Hl%`cz=p#h27SW_^Q}0mO+>8hP+t*?#x>k?@edwmpRSjxqMwlUx(n> zqcJe2kN@KOSb1A=-r&AY*BcUChrMeUzI9#IY~_#6)AV0{>|x&ZYQFN9^0Xkrnh!8*;?^dT6^&ojhba?3%c;^V;Q`1A5gz zV)~|!zhyMnIrvcCNK*mt^X_9Rl_Mfw?)BO9I`P;7_jTJBrf-S3clIWS#*615Brv~J zl~Ib zFsJgI!~n182FcA=j|}XVAvSf|v*=t#-3YUfF&2t(-h~IX3Ot=l_9*$R?l*Pspe^yX z>pvVn_)R((hzPGbA%&P3TYrta#N5T<^z7kL(x2`O4eYJD@V!Z*$Dy9QTYB$qZ|rrm zV?WKTnGce}cPPXdJh?W0nPl*d>00t<0#tAWLENC z8ZVA%5}0+RMyvg#>_2I8XRo}ayd}ung`c})*HiJEX7gtkk1A|`dvd?qCtp1+otjkI zeq#Bj^y_u)Z*?3PVOxCeEgkSS_nJ&v5PiJ$r}J(J zKm5(mYr*R885hFS=UlLTHQ+?b~cXoCw7)sRLcyD(o4NG z^2M2f7M~o)2*lG%bA4(04xsZEcn{;}I@`oQ(cY5CshYC>&3*0r>wEQGIjj3iC8Ia# zQ)XM89{q8?)VIUmx)r24?B67@i!tzf!W-L>JU^w3IWIoYcs1#~GC}&Pm6M*!t=L?9 zsqpa7L5vQMlOvM@%^yU&caU@1yI%iG$`HN%1~V7maF{gfh3(Dk#l|WHu6K@S7P*H# z9d-`aG-BU@bl!lQN5Y2{-qSYS6zJ#>e^~2W_)|i#(xsd^-Yw# zrZu)>`3>h|=>rRH4-Vqh=+`FCESc8fy(f)#5S^ECWR}b1x7AO(O*^@=qt5oXFIO-7 z6nx>FMD>@7V`rlKZy&ZOK0jutM3VO2HRJE~*G-8vz7S-WP?c(v%f zI_>=GcHWi0Wp|@+>Dra7Vrie^yqC^{c7E>V_g49KWJZSH{E=^XRb1!m6};{~T`d+xj_{mvvFz&Nuf{_ZUH5v?_to&tLStY??NmxM{%5m` zw}Zd)tsya{xe_B1-+4zelfHa%zG(iy_4JhuYU;l3;d|wFjGDA|`vI+gwkXW7&&jnr zp6RgZuo#}rl6Hf45siV#J6C^jQ+%JXZ!F?IyXijDG>8e|#Mi#|+vl@b;)jp3>-zym zd{-spNK*#c=J{BX4Fq!F{9$cUFo}W z1)Q$y7TI|O*Iqn+*ZuOQzACT%9MYJFHs#*GE&dUCeC}-HI=i43hO9D^yIt2|G+qNb?@Pv_oyUePe%r&rGV-00$-AQ2M zWgp(n&MvdxrL{0y)vast^}E`^Yqu6UJmYm@-F{(+W0&X)?=~9)^YP%&F*m}Ziyox) zTs1FX;0$g1qElb@WK?I|Su{qsVB>HJl{96>_NPYy8s?&~c=UmZ0IksEDh7W5L?wyeCIzPjN#yf=0 z`{;Jq1C4D44X?~P4LGw_`dC!wwKXHNCZ4@rsn{(pU3G~8|FPoHrR`T#+&jBui*A)> zsIFejYsrvpb$2)2QkFjWgvN_ykiaZRj*-<^VAinb@#Knwia}lb93E?~p<2J_?2No5 z8M)J6AD?WeTz5Ck)xB)hsN$gYyLTN68L{->VMb=@+p;pQF21uM_8m${A!hbpw{w84 z_%_Y*+lxAkW4)XF`an?Cr^TOMZwPpPY3qdr$Lyu&MQ3O(2|lMVG&I@uS-=|htd)@g zOiS_Wt7F66OCxE#@UE*dz#pNFUUlCuG4@ja?Vf|J&f3PtSaq&k%hynIRZH{UXPwhf zY=7|KyXd?pJ4W)fIet&qizh8l7@U5ZV}CKic?_$R#*2NK1g6Qu4O8}81yM&_{8e4n)69p`6EKPNDw^G;rJ zRPkNXg?({OQsaBssB-4*n5y2>qyC}o*~xabhj{7<3h4$qD}(l2Dt`KY|0b95bvj4a z$lPtKuKjx#4U0T-SKahgp@UMpc@=7YXV{mtBSkieDH|5@V z6d|*0WweKj?&=4HXG4pwyDu-7_)woks6oYXCl*r4c8D9#W2xM%2#$5-T*hNs>r*jIO( zGiBJ=(vdmCmY78;bsZ6<^CZe6ho7IAa*d^U`22kuuQi?b!H~h(m10{;dMJ;{9J=qr ztWL-3?$_z5wcC2+LiD;Z8K<&b+CSfVy+`FfllyO0=Q{dM&ia$xN+%3@uzpyUdx%0_u3J$pjdu*4w>-ftvSHoVGm@XPt=_)YWIA5?ux)=k4cDi4 zmmQxv=}w96b_>`2=Fhdl_p?H8W+%URwKl(l{=wm2QsOV2>u0RBg2p?R&b!pZXPC8p z$=khUz2y7Nd%D8-qi*}F2X_}})`g^+I;c5yP!1elSn2C!@4d8MUBYei#Ig~-@oyaN zE8JqY_mVr4OXD3!=gnHJBVo|9__1Wws*tKr%VzBIN>?wR*FX2Fpo84mu%RjC(&?R+ zPT6C&dbB~li@eEJX8&j0V-YJ4dD3i@KfJ|5ySdg7WvM`^q^ zbY8v5MFJy!NT7AmQPxK0h4)*fwP#-J-)Uxz-9(~$5Wr9n(f2Q$Hpz|&|nsMxymck}EDY3B7hf?mZN;ug+YzwaMe;yxa z*+KW3-u){Q?D&`)`Xk5oKec1Ar6RBN>0-}z3)B+qFL^%Aq3^3F(s_H9oMI+Q9$k{Q zYQLoa1kJ8F9dojJpPJA;Ge2xy{m_!Z!P4TE!TWk2{`M`So%*j#cp;bP zE`H&&HvL?~mdbmmI=;)wiubll33umwHu){8`XfA)^-Y2s7j7jG8 zNpxP#p-&bMUFW_$YQp3*TYHVZyT85Zbi<;rC;PlFf2W`@ZL3|Z=06QZ>oR9Ats2?+ z>+$Y$RIEpSn5!ORRVh6uy}%diPIxENdDoo^wCod5Vqvx<$x%jXnbqs{6DyO~OzF{k z-RE&b#xFMv=Uh z*Wf@JO&z~-9ihEvS2YWj2woYabzsh)tf3!>$=&fyk z_+g>p+t`#JT6d=|&P|3-OSUXK7+043a*a2`Ga^O!8=yKz5Wp!#wg{{ zc&E{M)1LY)?4W)wt5~7uynVM#<|>p+o2CyI-=#A1(6p+8T%F#%JNYJF*)C?^c~50V zHOD1?Th}|IFYf+q;H&mz!p1(h=Oy|&(0QkcUwgK|@cDu0>No0-3MP6#x_{7K{z|^n z{C96udo%5CoxeEN-Q4M8Swd<(qu&stdCw1Su|4+XvE!~c`?Af8#%0iWr_*_TE-zW6 zDq*!k;i$W9>~Gm2iF zx432y5-AmH9wz=~_5Hb}U9GpCj!TrO(V4X6fOi4?xn(9Hg_y|^H!7I$dHu-jJ@Vf* zV)DA){1g}AXl7%?n0CgpYR8u2KSov6dM0!@rTr{wfcn0Xz1SDu-%MFoWgF=(@!-Z| zbv!E~_MJuNbsFAdv0U0(gTaT_7_qvAGxNDg_}UHn6I)VzOA zeV8aOANb1mQ1Yx}RXIP{o>MsgC=57Ys-rh$iAqiF=V$G@Emr!Ne0HCq(Onv^Bb~Qo zrvKTL95?Umhe@~WRW*Xgy52tSc}=X^Q)5JSLi!LhFEIZ^?m8GRt;k4N1U3{k@n)A+Hy1X~@ z-lwzmlI@n$c<0i26Er?@y$-j(^lCWk)_GaQ;X$7UWekn`_`#%BX2vk3<=ORnP6gIj z8n$!3xQ;pVWle_?!;BTCrQ;%1PWW7{y@Y=<->9!Mop)64}C?r<$`iP3*BTi4AltklY z(|J27vt(TZeG~4~)Eq6(1GILMcacZ1Hu8P&jnyVK6UE_N9*0FXPJN&^| zmc`LwhVCbZ##lNPtG_?APwHUmyxuEpe0x1}`XQg?l(Z=S`y~20gnK{|n6vr5?>*eC z^^R?f>1O)b@`uCwwL|SjSmf)6Om~}Ekf}3KbJwxR4yLT>oQT-|g{d=F-$)pkWw5W@ zA-Bj*`swU^AR_v@5mJbm&LK0~_uj8Jlrv-4l;MjSID*nOS5oeNU`|i`e9H8E%IjXa zT`uoey+19uu*>PRkRtw%Ex|Rh%l8G&823=t_F9TEjh92`J)&ONpkO64Wg}x(L}YiR z1-B|cF+bk0btql-XH8>6c`?)Ml1kMBI}PK}i-g zUUxe05A8XQxzXbJ7v3e#2#S+ZkkKo?)NX9}KF5NB)jjt|zUuu|T~gO{NZ6p$8!rUU zw$UD{I@tc(;G&Rx&#>2HgHXqqv!Cj z`m6DEruKu@tW(FTjrWb$)TTC=#|_`FJG|P{_1*s$4%_(N#`BrwL#;0 zs=`~-;hj!f%1(IxJ)&r4X_oewg9H1k4^>Xu zq0nXVsl!rE6UVJvG(arJep}q5haFyP;rY(r@Ye!p0sP*K{M&Z$j|FPa^B3CXZ`%JI zcER=~|Ms7@j;j|})WZLNr(wC|-^wfRD*QM6uxI_Bl7{+`f1|Knx1aj`zm$eHApd^i z&z|u&jox=d;N_UK@Xr+*`|jT~27}lDpEWRGOwy=?Celb=OR-o>H~h{2Yk|KO_-lc` z7Wiv{zZUpwfxi~`Yk|KO_-lc`7Wiv{zZUpwfxi~`Yk|KO_-ldxV1ZE4%T_}#Ay|62 zzn`l%m*+14CrH}Fg_gFxABSV6s;{f+&t1UraUZN|sLJ-@dhmSQ;lLQfj|O4D-}pWs z&omA20s^_lvu5%)4ESyy?~wo@--Y9QbxgzaRY?#G_)Z+t@Hz?v1HRA3w00m$AQxcEh zzn{SGzVPo7@NWq4dw={kAHPw@y5aZf_$@hp_iX_(5@Zwz>WKPkfZ+EVvLM|-#(|6nK|4+W!EZkC8$~-1{H8?}qz}jWiTHFDE`6CJQ@)Cu$P zKGXy6!*nhP=Aq5;ezfCskZB0HhyCUl4T=>>n7bfvAEYMgs)M&5bJ>$gBXFJUIrj0AVWb+K`cSWgII%Dfs6ye{yPQ)`!imnU_SQY38EPL z@njIxX)4GR(QmvaeG>aJ+7bJu1&AAnD~JmS8^jr8E{GEd_Ekra*&rN{-+8f5;5d)~ z!8XLUlLW!G#CBzZV4e&P4hZd*31jKuv-OSV1em~NY}`0k>HRtxBVPvwBb4}eYA@!b zt8JnUrb8{DhlUalP>^G!t*;FR#x{UH8A^PWiHkYuX&Yh=S{iz4DDk=`F3#fw3m}m} z{2da%Y~tcxYyk%x=P}MgE{vd1;sK3f)6<5=x&t{fkVAZ>{VeA>pWaT1S4N7h>7GdGM!q;A9vuN=qD}P>;(M0($Yi1h zR89t1sS8+%c!LCtp)iMhw@3VGLJn#Tb1(7GN<2wI4)ki&n)q-fz9vOEuwq!iR;@df zL%dOnivfV@qQrkJ@pB0nnD}s&A)aZ82TjP)6|PytcP#O7Nnt46h}T)-9TRd4g!Li* zZi!z`Du+^me8EOMZ&Gaf+OQcTzHo`pPRJoEhR9nl@hS>AA{!9DzQq41OH4KPh;4Ho@l zO{sE-pJn0?meK&M0Bj;p%*10Xl>;?3_;V{zqyCTeq1cFjX5vQ{*r2ze)=guGZ)f6z zmePR25HHWf8!hBu%K;nl>rDL5LXN0k6VK1YLoKzUQ?pAGV;k6r_h{nf7BDz|z&hl+ zPU626%E7q`Hp)6O!kstqbPGA6ni8MV#P==ah{h@L5>33>LJrgh#{%&KP5jl0azHoY zNt$@P73IK~Bfh4IPh2WzC|V~0CO6#26EC<_4%K^!|7qeUSHuQmj(Dgho^(YyU<2aA zn)urNiD|l=rgNIE5BX-3_~wOjM6($2icP%tQaMz8n#TNLf1sy56lWLWJDd3U{aMbx zM=+TaiGOY42N-vdR0nDFz)d^_i*mqjO|vzfL;Q6UzrvKQ^>7IHgCh$#SqLQ_hQ-D3 z*%?C;2>=LyA1@885rF{J@D z=7@iA;>Q?rARSwQcn~L^jzwC-SRg*ei7(`z7~+MTcuN*x;DLttDJTAvMLAF(;>nzN zTo&cPDEaqO4)iywSN&ns8wppM$4?^>X|DHb%?A--_s9TGyw0hnK7P zrV0H0Ievj0zd8A;VN={~YTe)v4988liP255;4VKhL>e53r)co(5zcG$nmVqbcHb;E zm*vLsclG1)1zaDVO8}202;swF3jU8uwO}s@@Z;!k-8eh}R}dn)#GN~v>nRZM{Y`ar z{5T$5e}P|!HjnQ;&tKce&qGIeNTb6N{b1=C!&0TEE$~>-4CohfzzR!YQ9XwxbjOVh z!rNGWpr($ij~|BxU)Fn<;-H@IXAmDnr`}ldY+}Hv(+JMFP^9c@Nu|2@BluiDX@aJ~_*S(%26(8Lg->BESh2&(E8H^XCO+U<0ET^bs9-5u}?7bXPp`Y613}o&f(Xg&IgE z$l}KFVuv)=sF`~FdLvN&nm{qOq!&341T^`{q7DRGsL$`?jj9mLUO4ukU~He}d#Z@w z_o0n?{5INw4kyi4(NVLBXi6X|i7qK~v~tpJoOTHZPQgHcL%sESz&uUx@DsY1QHx_; zXqA7h282eqHc$p>Y1Rs7pe6%)BMPa7s}tXk6UgNR!8A-;t&Bx?;|UlG z4`3{)ai-%h@Zs~>E?%$-iGDQGWrasnpp5VbYY03!P$m_g=(xH0@K|hE%ltSl0bDOP zmgpsVGmO9A3f#XZK`GdUsG%j`xC&TaTo+ePFrUqHV+lCke0<~L!4-H0xUk&)e7sqF zw(ESDJH6+-asBwP9<%-Zxvo4=izQSF`#2;4ki+ue2m}E<9>Q>Ivx_(FE?}Zv!TgM?MDtx#KpkqK3e*p3+M_UdEu2Q1vG>oLL<~X% zu!tr>4p`O4T}0&WkIOcE2*DHh!~E=q{iNkGe!CM2`YnZJKsGE;I;{S4-8in$BmJn^ z$BhFWz!loGnOgjMBNXs!0yS5(v|j#NG;sWy037&4)_hx#BPT$UpI}LxfSbqB?Fv}> zC8c{y<^Ix0K=8{oFc`L|0WRPGq}K7)%=bZlY(75(5W*j=vapL6-0$KAt9{E0fN;>@ z{nY4zk$|Hftcd$DV?)+F|7N;_=&TOVqF z1pPV{0rJ-bm_OhpZwqA#PoDuR`~iAmvo+t%eok_L`*}fazo zM|2wudl-Yw*!-9Ke$oqZRQG7fihn2^c>a(|ZGWjD0lPP@Yon^oX!q-lK>KR~v|7tu z($5ueey&ts67B3bd=|X;htY;}1s^&ktao5RZ+I|iu7CdFZnW6HIjG5_CA*RHTB=q& zun0UqVt=P}Y{n*V0t^uNKiqB5;@xhX#>xVUcJ*_$Yo;vnh6kYJC)nH&-%a6|hSLPm zX+*Q*T6hm|2!FsvK(qz(@(Wh~Q6 zR{gh42Q@5OT9^K?l>w?hq(VcqJoWsxPXeajQm8K1(&XdDY20`L1MP}Jw^XBFH-~5! zSnOGBvRee}idI@T=*l|SIqB|e~7n>k5_Zk zAMGwEfObV$q9p|z3lm|ff+=K66D1%;7;Na4%K4>+fcxbdZ3+K1zuY!9C!iaDQ<}Ea zLGf@AKp4R!XDIB@@OH<|$8~$=h4&8B-E3+i5-u*n4J5}m03LM7(+f+$_2zI~ z-Tc`sf361$PomfYI0tWLbp7FODC!TX!oJv2y+r%kKd1$)O=q^MqYJ-;LG1!@Cju*N z%a1P9J1~GyF)Sr;RzS^3?r=ow69juKczTBEr@3+c`>nwCdlHTte5h@{&3`|N0HR-~ zY09{wyKwPnrrBtB0S&d%=;Qv{xFsEg?-m-5M*!UTn`#7I;i*5xLqAKw{htpHw}L?& z7lF@w4zC&Q=(j-$^h;RDL{A=GK5lG(PxuQ2SFZqgAwn+wU3`4JIBZ_yHLMsMcumlF z%?t2$p#Q!mO}7@0rkcxugQ;qHGAO;C~=$-;pO z3W|mqJok!@Nt@~V^fT6G4yJWsKSbD2K?_{U1spGqHwWHD>cDeAb7lPNW>6U)9okS| z>d0Go2n=A_6_DVUIiM2`b@JY}8Et;PZUv&B7u4BgOM|}goh?8cf5YMeU*4kz!gs;{ I=l%cx1EYW(8vp fs.statSync(path.join(rootBlogPath, file)).isDirectory()) + .map((file) => path.join(rootBlogPath, file)); + + blogDirs.sort().reverse(); + + return blogDirs; +} + +interface BlogPost { + path: string; + content: string; +} + +interface BlogPostWithDescription extends BlogPost { + description: string; +} + +async function generatePostsWithDescription() { + const blogDirs = await getBlogDirsOrderedDescending(); + + const postsWithoutDescription: BlogPost[] = []; + + for (const blogDir of blogDirs) { + console.log(`Processing ${blogDir}`); + + const indexMdPath = path.join(blogDir, 'index.md'); + const blogPostContent = await fs.promises.readFile(indexMdPath, 'utf-8'); + + const frontMatter = blogPostContent.split('---')[1]; + const hasDescription = frontMatter.includes('\ndescription: '); + if (!hasDescription) { + postsWithoutDescription.push({ + path: indexMdPath, + content: blogPostContent, + }); + } + } + + console.log( + `Found ${postsWithoutDescription.length} posts without description` + ); + + const postsWithDescription: BlogPostWithDescription[] = []; + + for (const post of postsWithoutDescription) { + const article = post.content.split('---')[2]; + + console.log( + `** Generating description for ${post.path + .replace('/index.md', '') + .split('/') + .pop()}` + ); + const description = await produceSummary(article); + + if (description) { + postsWithDescription.push({ ...post, description }); + console.log(`** description: ${description}`); + } else { + console.log(`** no description generated`); + } + + break; + } + return postsWithDescription; +} + +async function main() { + const startedAt = new Date(); + + const postsWithDescription: BlogPostWithDescription[] = + await generatePostsWithDescription(); + + const finishedAt = new Date(); + const duration = (finishedAt.getTime() - startedAt.getTime()) / 1000; + console.log( + `${postsWithDescription.length} summaries generated in ${duration} seconds` + ); +} + +await main(); diff --git a/open-ai-description/package.json b/open-ai-description/package.json new file mode 100644 index 00000000000..7d0655eb87b --- /dev/null +++ b/open-ai-description/package.json @@ -0,0 +1,18 @@ +{ + "name": "open-ai-description", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "@azure/openai": "next", + "dotenv": "latest", + "@azure/identity": "^1.4.0" + }, + "devDependencies": { + "bun-types": "^0.8.1", + "typescript": "^5.2.0" + }, + "scripts": { + "start": "bun index.ts" + } +} diff --git a/open-ai-description/sample.env b/open-ai-description/sample.env new file mode 100644 index 00000000000..c1189593b24 --- /dev/null +++ b/open-ai-description/sample.env @@ -0,0 +1,3 @@ +# Used to connect with the Azure OpenAI. Retrieve these +# values from an Azure OpenAI instance in the Azure Portal. +ENDPOINT="https://.openai.azure.com" diff --git a/open-ai-description/summarizer.ts b/open-ai-description/summarizer.ts new file mode 100644 index 00000000000..fa49665b50e --- /dev/null +++ b/open-ai-description/summarizer.ts @@ -0,0 +1,103 @@ +import { OpenAIClient } from '@azure/openai'; +import { AzureCliCredential } from '@azure/identity'; + +// Make sure you have az login'd and have the correct subscription selected +// debug with: +// bun --inspect-brk open-ai-description/index.ts +// or: +// cd open-ai-description +// bun --inspect-brk index.ts + +interface OpenAiClientAndDeploymentName { + openAIClient: OpenAIClient; + deploymentName: string; +} + +let openAiClientAndDeploymentName: OpenAiClientAndDeploymentName | undefined; + +function getClientAndDeploymentName(): OpenAiClientAndDeploymentName { + if (openAiClientAndDeploymentName) { + return openAiClientAndDeploymentName; + } + + // You will need to set these environment variables or edit the following values + const endpoint = + process.env['ENDPOINT'] || 'https://.openai.azure.com'; + + // This will use the Azure CLI's currently logged in user; + // make sure you've done `az login` and have the correct subscription selected + const credential = new AzureCliCredential(); + const openAIClient = new OpenAIClient( + endpoint, + credential /* new AzureKeyCredential(azureApiKey) */ + ); + const deploymentName = 'OpenAi-gpt-35-turbo'; + + openAiClientAndDeploymentName = { openAIClient, deploymentName }; + + return openAiClientAndDeploymentName; +} + +export async function produceSummary(article: string): Promise { + const { openAIClient, deploymentName } = getClientAndDeploymentName(); + const minChars = 120; + const maxChars = 156; + + const messages = [ + { + role: 'system', + content: `You are a summarizer. You will be given the text of an article and will produce a summary / meta description which summarizes the article. The summary / meta descriptions you produce must be between ${minChars} and ${maxChars} characters long. If they are longer or shorter than that they cannot be used.`, + }, + { + role: 'user', + content: `Here is an article to summarize: + +${article}`, + }, + ]; + + let attempts = 0; + const maxAttempts = 10; + let summary = ''; + while (attempts++ < maxAttempts) { + console.log(`Attempt ${attempts} of ${maxAttempts}`); + + // This will use the Azure CLI's currently logged in user; + // make sure you've done `az login` and have the correct subscription selected + const result = await openAIClient.getChatCompletions( + deploymentName, + messages, + { + temperature: 0.7, + } + ); + + for (const choice of result.choices) { + summary = choice.message?.content || ''; + + if (summary.length >= minChars && summary.length <= maxChars) { + return summary; + } + console.log(`Summary was ${summary.length} characters long; no good`); + } + + messages.push( + { + role: 'assistant', + content: summary, + }, + { + role: 'user', + content: + summary.length < minChars + ? `Too short; try again please - we require a summary that is between ${minChars} and ${maxChars} characters long.` + : `Too long; try again please - we require a summary that is between ${minChars} and ${maxChars} characters long.`, + } + ); + + console.log(messages); + } + + console.log(`Failed to produce a summary in ${maxAttempts} attempts`); + return ''; +} diff --git a/open-ai-description/tsconfig.json b/open-ai-description/tsconfig.json new file mode 100644 index 00000000000..68d46c7ce39 --- /dev/null +++ b/open-ai-description/tsconfig.json @@ -0,0 +1,107 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "esnext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "lib": [ + "ESNext" + ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "esnext" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + "types": [ + "bun-types" + ] /* Specify type package names to be included without being referenced in a source file. */, + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "error" /* Specify emit/checking behavior for imports that are only used for types. */, + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/trim-xml/bun.lockb b/trim-xml/bun.lockb index 97481bf55fd8404bc8b757285f0fdcdbe41984c9..95fc704d10858474e1192cad809cd3a53a709312 100755 GIT binary patch delta 380 zcmbOx`%htlo>JnA&yUzx^5kFn|5-3KxpY}VW9KD~-)z-?4&2qya=D%Pj(=i+*yIb$ zEPM<=&;TNUAc<>Y#RjttjQmH=u`)2IKm-|Jd?_fu!1^>dBa{uIPJz{Lj$;&KVr2*N znI{LbXit8?&a!z0^HJuANvFB_XEy7#ZJ4pmwUH%owd_`@k?-8UkZxQn-b$z<@ob3F}Yh2~1Ijea0D5P)33GMSOXdJ;#) z~#Dv%x zAfN$CS8z{|DxS>Mme2DtZag1V2tUzT9?2`jov?o7c zXW6`h`6zS5``fdo%JnNw6V73Ju+b+nEdE z>~0njOz!%4Xuq1|0SjZT)E`dycT4AkY=yc&1nPnUAfIJ2BZu`QjtZ^=91zhf9Fudn z9Ap?6{{4plkT55lU|>+;oP3PSjVk~w#=ua)Ia!R`i)#gxyM}9W7PmFCsh;`d72I#Q mesDodwcwsSkEe+%1M2yIyqmRnw=%H Date: Sun, 27 Aug 2023 19:16:12 +0100 Subject: [PATCH 02/14] feat: do updates --- .../2021-12-29-preload-fonts-with-docusaurus/index.md | 1 + open-ai-description/index.ts | 9 ++++++++- open-ai-description/summarizer.ts | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/blog-website/blog/2021-12-29-preload-fonts-with-docusaurus/index.md b/blog-website/blog/2021-12-29-preload-fonts-with-docusaurus/index.md index 01bee11035b..66a3fec710c 100644 --- a/blog-website/blog/2021-12-29-preload-fonts-with-docusaurus/index.md +++ b/blog-website/blog/2021-12-29-preload-fonts-with-docusaurus/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [Docusaurus, webpack] image: ./title-image.png hide_table_of_contents: false +description: 'Improve website performance by preloading web fonts in Docusaurus using `webpack-font-preload-plugin` or `headTags` API, as described in this tutorial.' --- When we're using custom fonts in our websites, it's good practice to preload the fonts to minimise the [flash of unstyled text](https://css-tricks.com/fout-foit-foft/). This post shows how to achieve this with Docusaurus. diff --git a/open-ai-description/index.ts b/open-ai-description/index.ts index aec8313cdae..1a5d5779d30 100644 --- a/open-ai-description/index.ts +++ b/open-ai-description/index.ts @@ -52,7 +52,7 @@ async function generatePostsWithDescription() { const postsWithDescription: BlogPostWithDescription[] = []; for (const post of postsWithoutDescription) { - const article = post.content.split('---')[2]; + const [, frontmatter, article] = post.content.split('---'); console.log( `** Generating description for ${post.path @@ -65,12 +65,19 @@ async function generatePostsWithDescription() { if (description) { postsWithDescription.push({ ...post, description }); console.log(`** description: ${description}`); + + await fs.promises.writeFile( + post.path, + `---${frontmatter}description: '${description.replaceAll("'", "\\'")}' +---${article}` + ); } else { console.log(`** no description generated`); } break; } + return postsWithDescription; } diff --git a/open-ai-description/summarizer.ts b/open-ai-description/summarizer.ts index fa49665b50e..230b8c22e45 100644 --- a/open-ai-description/summarizer.ts +++ b/open-ai-description/summarizer.ts @@ -68,7 +68,7 @@ ${article}`, deploymentName, messages, { - temperature: 0.7, + temperature: 0.9, } ); From ff57eeb5ca92f82cb20297e4be875e5d58d8d386 Mon Sep 17 00:00:00 2001 From: johnnyreilly Date: Sun, 27 Aug 2023 19:24:55 +0100 Subject: [PATCH 03/14] feat: more descriptions --- .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 402 +----------------- .../index.md | 1 + .../index.md | 65 +-- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + open-ai-description/index.ts | 10 +- 13 files changed, 21 insertions(+), 468 deletions(-) diff --git a/blog-website/blog/2021-08-19-bicep-syntax-highlighting-with-prismjs/index.md b/blog-website/blog/2021-08-19-bicep-syntax-highlighting-with-prismjs/index.md index b2890ce4cc2..6cfdb2c06fc 100644 --- a/blog-website/blog/2021-08-19-bicep-syntax-highlighting-with-prismjs/index.md +++ b/blog-website/blog/2021-08-19-bicep-syntax-highlighting-with-prismjs/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [Bicep, PrismJS] image: ./bicep-syntax-highlighting-with-prismjs.webp hide_table_of_contents: false +description: 'Learn how to write attractive code snippets about Bicep using PrismJS and Docusaurus. This post shows you how to add syntax highlighting for Bicep.' --- Bicep is an amazing language, it's also very new. If you want to write attractive code snippets about Bicep, you can by using PrismJS (and Docusaurus). This post shows you how. diff --git a/blog-website/blog/2021-09-10-google-apis-authentication-with-typescript/index.md b/blog-website/blog/2021-09-10-google-apis-authentication-with-typescript/index.md index ac0a790676e..57db13c8e99 100644 --- a/blog-website/blog/2021-09-10-google-apis-authentication-with-typescript/index.md +++ b/blog-website/blog/2021-09-10-google-apis-authentication-with-typescript/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [Google APIs, typescript] image: ./app-registration.png hide_table_of_contents: false +description: 'This guide shows how to use TypeScript to authenticate and access Google APIs with OAuth 2.0, specifically the Google Calendar API.' --- Google has a wealth of APIs which we can interact with. At the time of writing, there's more than two hundred available; including YouTube, Google Calendar and GMail (alongside many others). To integrate with these APIs, it's necessary to authenticate and then use that credential with the API. This post will take you through how to do just that using TypeScript. It will also demonstrate how to use one of those APIs: the Google Calendar API. @@ -158,7 +159,7 @@ export function makeOAuth2Client({ return new google.auth.OAuth2( /* YOUR_CLIENT_ID */ clientId, /* YOUR_CLIENT_SECRET */ clientSecret, - /* YOUR_REDIRECT_URL */ 'urn:ietf:wg:oauth:2.0:oob' + /* YOUR_REDIRECT_URL */ 'urn:ietf:wg:oauth:2.0:oob', ); } ``` diff --git a/blog-website/blog/2021-09-12-permissioning-azure-pipelines-bicep-role-assignments/index.md b/blog-website/blog/2021-09-12-permissioning-azure-pipelines-bicep-role-assignments/index.md index 6567c1f5a85..5a7c6ca972c 100644 --- a/blog-website/blog/2021-09-12-permissioning-azure-pipelines-bicep-role-assignments/index.md +++ b/blog-website/blog/2021-09-12-permissioning-azure-pipelines-bicep-role-assignments/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [Role Assignments, Bicep, azure devops, Azure Pipelines] image: ./title-image.png hide_table_of_contents: false +description: 'Learn to permission Azure Pipelines to access resources through RBAC role assignments with Bicep. Includes examples and integration tests.' --- How can we deploy resources to Azure, and then run an integration test through them in the context of an Azure Pipeline? This post will show how to do this by permissioning our Azure Pipeline to access these resources using Azure RBAC role assignments. It will also demonstrate a dotnet test that runs in the context of the pipeline and makes use of those role assignments. diff --git a/blog-website/blog/2021-10-15-structured-data-seo-and-react/index.md b/blog-website/blog/2021-10-15-structured-data-seo-and-react/index.md index 85ccbbe7870..a028a7876b8 100644 --- a/blog-website/blog/2021-10-15-structured-data-seo-and-react/index.md +++ b/blog-website/blog/2021-10-15-structured-data-seo-and-react/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [Structured Data, SEO, React] image: ./title-image.png hide_table_of_contents: false +description: 'Add structured data to your website to help search engines understand your content & get it in front of more people. Example shown in a React app.' --- People being able to discover your website when they search is important. This post is about how you can add structured data to a site. Adding structured data will help search engines like Google understand your content, and get it in front of more eyeballs. We'll illustrate this by making a simple React app which incorporates structured data. diff --git a/blog-website/blog/2021-10-18-docusaurus-meta-tags-and-google-discover/index.md b/blog-website/blog/2021-10-18-docusaurus-meta-tags-and-google-discover/index.md index 331c00b653e..2a87f5990fa 100644 --- a/blog-website/blog/2021-10-18-docusaurus-meta-tags-and-google-discover/index.md +++ b/blog-website/blog/2021-10-18-docusaurus-meta-tags-and-google-discover/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [Docusaurus] image: ./title-image.png hide_table_of_contents: false +description: 'Boost your website\'s appearance in Google Discover with high-quality images and `max-image-preview:large` meta tag setting in Docusaurus.' --- Google Discover is a way that people can find your content. To make your content more attractive, Google encourage using high quality images which are enabled by setting the `max-image-preview:large` meta tag. This post shows you how to achieve that with Docusaurus. diff --git a/blog-website/blog/2021-10-31-nswag-generated-c-sharp-client-property-name-clash/index.md b/blog-website/blog/2021-10-31-nswag-generated-c-sharp-client-property-name-clash/index.md index cab92d46273..4350802e188 100644 --- a/blog-website/blog/2021-10-31-nswag-generated-c-sharp-client-property-name-clash/index.md +++ b/blog-website/blog/2021-10-31-nswag-generated-c-sharp-client-property-name-clash/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [NSwag, C#] image: ./title-image.png hide_table_of_contents: false +description: 'Generate C# and TypeScript client libraries from OpenAPI / Swagger definitions using NSwag while overcoming language conflicts and numeric types.' --- NSwag is a great tool for generating client libraries in C# and TypeScript from Open API / Swagger definitions. You can face issues where Open API property names collide due to the nature of the C# language, and when you want to use `decimal` for your floating point numeric type over `double`. This post demonstrates how to get over both issues. @@ -239,404 +240,5 @@ We'll tweak our `NSwag.csproj` file to ensure that the `json` file is included i ```xml - - - - PreserveNewest - - - + /gi, '\n--->\n'); - const images: string[] = []; - const dom = new jsdom.JSDOM(contentProcessed); - let markdown = ''; - try { - markdown = converter - .makeMarkdown(contentProcessed, dom.window.document) - // bigger titles - .replace(/#### /g, '## ') - - //
- - // The mechanism below extracts the underlying iframe - .replace(/).*<\/div>/g, (replacer) => { - const dom = new jsdom.JSDOM(replacer); - const iframe = dom?.window?.document?.querySelector('iframe'); - return iframe?.outerHTML ?? ''; - }) - - // The mechanism below strips class and style attributes from iframes - react hates them - .replace(//g, (replacer) => { - const dom = new jsdom.JSDOM(replacer); - const iframe = dom?.window?.document?.querySelector('iframe'); - iframe?.removeAttribute('class'); - iframe?.removeAttribute('style'); - return iframe?.outerHTML ?? ''; - }) - - // capitalise appropriately - .replace(/frameborder/g, 'frameBorder') - .replace(/allowfullscreen/g, 'allowFullScreen') - .replace(/charset/g, 'charSet') - - // Deals with these: - // [![null]( =640x484)]()We successfully wrote something into IndexedDB, read it back and printed that value to the console. Amazing! - .replace( - /\[!\[null\]\(<(.*?)\].*?>\)/g, - (match) => - `![](${match.slice(match.indexOf('<') + 1, match.indexOf('>'))})\n\n` - ) - - // Blogger tends to put images in HTML that looks like this: - //
traffic to app service
- - // The mechanism below extracts the underlying image path and it's alt text - .replace( - /()*\w*()*()(<\/a>)*.*(<\/div>)*/g, - (replacer) => { - const div = new jsdom.JSDOM(replacer); - const img = div?.window?.document?.querySelector('img'); - const alt = img?.getAttribute('alt') ?? ''; - const src = img?.getAttribute('src') ?? ''; - - if (src) images.push(src); - - return `![${alt}](${src})`; - } - ); - } catch (e) { - console.log(post.link); - console.log(e); - notMarkdownable.push(post.link); - return; - } - - for (const url of images) { - try { - const localUrl = await downloadImage(url, blogdirPath); - markdown = markdown.replace(url, localUrl); - } catch (e) { - console.error(`Failed to download ${url}`); - } - } - - const content = `--- -title: "${post.title}" -authors: ${author} -tags: [${post.tags.join(', ')}] -hide_table_of_contents: false ---- -${markdown} -`; - - await fs.promises.writeFile( - path.resolve(docusaurusDirectory, 'blog', blogdirPath, 'index.md'), - content - ); -} - -async function downloadImage(url: string, directory: string) { - console.log(`Downloading ${url}`); - const pathParts = new URL(url).pathname.split('/'); - const filename = decodeURIComponent(pathParts[pathParts.length - 1]); - - const pathTo = path.join(directory, filename); - - const writer = fs.createWriteStream(pathTo); - - const response = await axios({ - url, - method: 'GET', - responseType: 'stream', - }); - - response.data.pipe(writer); - - return new Promise((resolve, reject) => { - writer.on('finish', () => resolve(filename)); - writer.on('error', reject); - }); -} - -interface Post { - title: string; - content: string; - published: string; - link: string; - tags: string[]; -} - -// do it! -makePostsFromXML(); + // convert /gi, '\n--->\n'); + const images: string[] = []; + const dom = new jsdom.JSDOM(contentProcessed); + let markdown = ''; + try { + markdown = converter + .makeMarkdown(contentProcessed, dom.window.document) + // bigger titles + .replace(/#### /g, '## ') + + //
+ + // The mechanism below extracts the underlying iframe + .replace(/).*<\/div>/g, (replacer) => { + const dom = new jsdom.JSDOM(replacer); + const iframe = dom?.window?.document?.querySelector('iframe'); + return iframe?.outerHTML ?? ''; + }) + + // The mechanism below strips class and style attributes from iframes - react hates them + .replace(//g, (replacer) => { + const dom = new jsdom.JSDOM(replacer); + const iframe = dom?.window?.document?.querySelector('iframe'); + iframe?.removeAttribute('class'); + iframe?.removeAttribute('style'); + return iframe?.outerHTML ?? ''; + }) + + // capitalise appropriately + .replace(/frameborder/g, 'frameBorder') + .replace(/allowfullscreen/g, 'allowFullScreen') + .replace(/charset/g, 'charSet') + + // Deals with these: + // [![null]( =640x484)]()We successfully wrote something into IndexedDB, read it back and printed that value to the console. Amazing! + .replace( + /\[!\[null\]\(<(.*?)\].*?>\)/g, + (match) => + `![](${match.slice(match.indexOf('<') + 1, match.indexOf('>'))})\n\n`, + ) + + // Blogger tends to put images in HTML that looks like this: + //
traffic to app service
+ + // The mechanism below extracts the underlying image path and it's alt text + .replace( + /()*\w*()*()(<\/a>)*.*(<\/div>)*/g, + (replacer) => { + const div = new jsdom.JSDOM(replacer); + const img = div?.window?.document?.querySelector('img'); + const alt = img?.getAttribute('alt') ?? ''; + const src = img?.getAttribute('src') ?? ''; + + if (src) images.push(src); + + return `![${alt}](${src})`; + }, + ); + } catch (e) { + console.log(post.link); + console.log(e); + notMarkdownable.push(post.link); + return; + } + + for (const url of images) { + try { + const localUrl = await downloadImage(url, blogdirPath); + markdown = markdown.replace(url, localUrl); + } catch (e) { + console.error(`Failed to download ${url}`); + } + } + + const content = `--- +title: "${post.title}" +authors: ${author} +tags: [${post.tags.join(', ')}] +hide_table_of_contents: false +--- +${markdown} +`; + + await fs.promises.writeFile( + path.resolve(docusaurusDirectory, 'blog', blogdirPath, 'index.md'), + content, + ); +} + +async function downloadImage(url: string, directory: string) { + console.log(`Downloading ${url}`); + const pathParts = new URL(url).pathname.split('/'); + const filename = decodeURIComponent(pathParts[pathParts.length - 1]); + + const pathTo = path.join(directory, filename); + + const writer = fs.createWriteStream(pathTo); + + const response = await axios({ + url, + method: 'GET', + responseType: 'stream', + }); + + response.data.pipe(writer); + + return new Promise((resolve, reject) => { + writer.on('finish', () => resolve(filename)); + writer.on('error', reject); + }); +} + +interface Post { + title: string; + content: string; + published: string; + link: string; + tags: string[]; +} + +// do it! +makePostsFromXML(); +``` + +To summarise what the script does, it: + +- deletes the default blog posts +- creates a new `authors.yml` file with my details in +- parses the blog XML into an array of `Post`s +- each post is then converted from HTML into Markdown, a Docusaurus header is created and prepended, then the `index.md` file is saved to the `blog-website/blog/{POST_NAME}` directory +- the images of each post are downloaded with Axios and saved to the `blog-website/blog/{POST_NAME}` directory + +[To see the full code, you can find it on the GitHub repository that now represents the blog.](https://github.com/johnnyreilly/blog.johnnyreilly.com/tree/main/from-blogger-to-docusaurus) + +If you're trying to do this yourself, you'll want to change some of the variable values in the script; such as the author details. + +## Bringing it all together + +To run the script, we add the following script to the `package.json`: + +```json + "scripts": { + "start": "ts-node index.ts" + }, ``` + +And have ourselves a merry little `yarn start` to kick off the process. In a very short period of time, if you crack open the `blogs` directory of your Docusaurus site you'll see a collection of folders, Markdown files and images. These represent your blog and are ready to power Docusaurus: + +![Markdown files](blogs-as-markdown.webp) + +I have slightly papered over some details here. For my own case I discovered that I hadn't always written perfect HTML when blogging. I had to go in and fix the HTML in a number of historic blogs and re-download, to get cleanish Markdown. + +I also learned that a number of my blog's images had vanished from Blogger at some point. This makes me all the more convinced that storing your blog in a repo is a good idea. Things should not "go missing". + +If we now run `yarn start` in the `blog-website` directory we can see the blog in action: + +![Blog in Docusaurus](docusaurus.png) + +Congratulations! We're now the proud owners of a Docusaurus blog site based upon our Blogger content. + +If you've got some curiously named image files you might encounter some minor issues that need fixing up. This should get you 95% the way there though. Docusaurus does a great job of telling you when there's issues. + +## Redirecting from Blogger URLs to Docusaurus URLs + +The final step is to redirect from the old Blogger URLs to the new Docusaurus URLs. Blogger URLs look like this: `/2019/10/definitely-typed-movie.html`. On the other hand, Docusaurus URLs look like this: [`/2019/10/08/definitely-typed-movie`](https://johnnyreilly.com/definitely-typed-the-movie). + +I'll want to redirect from the former to the latter. I'll use the `@docusaurus/plugin-client-redirects` plugin to do this. Inside the `docusaurus.config.js` file, I'll add the following to the `plugins` section: + +```js +module.exports = { + // ... + plugins: [ + // ... + [ + 'client-redirects', + /** @type {import('@docusaurus/plugin-client-redirects').Options} */ + ({ + createRedirects: function (existingPath) { + if (existingPath.match(urlRegex)) { + const [, year, month, date, slug] = existingPath.split('/'); + const oldUrl = `/${year}/${month}/${slug}.html`; + + // eg redirect from /2019/10/definitely-typed-movie.html -> /2019/10/08/definitely-typed-movie + console.log(`redirect from ${oldUrl} -> ${existingPath}`); + + return [oldUrl, `/${year}/${month}/${slug}`]; + } + }, + }), + ], + // ... + ], +}; +``` + +The function above will be run during the build process for each URL. And consequently a client side redirect will be created to go from the landing URL to the Docusaurus URL. The `console.log` is there to help me see what's going on. I don't actually need it. + +Having this in place should protect my SEO when the domain switches from Blogger to Docusaurus. Long term I shouldn't need this approach in place. + +## Comments + +I'd always had comments on my blog. First with Blogger's in-built functionality and then with [Disqus](https://disqus.com/). One thing that Docusaurus doesn't support by default is comments for blog posts. [There's a feature request for it here.](https://docusaurus.io/feature-requests/p/comments-in-documents-or-blogs) However, it doesn't exist right now. + +For a while I considered this a dealbreaker, and wasn't planning to complete the migration. But then I had a discussion with [Josh Goldberg](https://twitter.com/JoshuaKGoldberg) as to the value of comments. Essentially that they are nice, but not essential. + +![discussion on Twitter with Josh Goldberg on the topic of the value of comments in blog posts](screenshot-do-we-need-comments-josh-goldberg.webp) + +I rather came to agree with the notion that comments were only slightly interesting as I looked back at the comments I'd received on my blog over the years. So I decided to go ahead _without_ comments. I remain happy with that choice, so thanks Josh! + +However, if it's important to you, there are ways to support comments. One example is using [Giscus](https://giscus.app/); [here is a guide on how to integrate it](https://dipakparmar.medium.com/how-to-add-giscus-to-your-docs-site-built-with-docusaurus-d57fa7f8e2f3). + +## DNS and RSS + +At this point I had a repository that represented my blog. I had a Docusaurus site that represented my blog. When I ran `yarn build` I got a Docusaurus site that looked like my blog. I had a redirect mechanism in place to protect my SEO. + +I was ready to make the switch. + +Hosting is a choice. When I initially migrated, I made use of GitHub Pages. I also experimented with Netlify. [Finally I moved to using Azure Static Web Apps to make use of preview environments.](../2023-02-01-migrating-from-github-pages-to-azure-static-web-apps/index.md) There are many choices out there - you can pick the one that works best for you. + +Once your site is up, the last stage of the migration is updating your DNS to point to the Docusaurus site. I use [Cloudflare](https://www.cloudflare.com/) to manage my domain names and so that's where I made the switch. + +![screenshot of the DNS settings in Cloudflare](screenshot-cloudflare-dns.webp) + +## RSS / Atom feeds + +If you're like me, you'll want to keep your RSS feed. I didn't want to disrupting people who consumed my RSS feed as I migrated. + +Happily, [Docusaurus ships with RSS / Atom in the box](https://docusaurus.io/docs/blog#feed). Even happier still, most of the feed URLs in Blogger match the same URLs in Docusaurus. There was one exception in the form of the `/feeds/posts/default` feed which is an Atom feed. Docusaurus has an `atom.xml` feed but it's not in the same place. + +This isn't a significant issue as I can create a page rule in Cloudflare to redirect from the old URL (https://johnnyreilly.com/feeds/posts/default) to the new URL (https://johnnyreilly.com/atom.xml): + +![screenshot of the page rule in Cloudflare](screenshot-cloudflare-atom-page-rule.png) + +## Conclusion + +I've migrated to Docusaurus and have been happily running there for a while now. I'm very happy with the result. + +This post is intended to be a community resource that helps folk migrate from Blogger to Docusaurus. If you should find issues with the migration, please do let me know and help make this resource even better. From 97f31b05a2699b430309305761c2bc1b058edeef Mon Sep 17 00:00:00 2001 From: johnnyreilly Date: Sun, 27 Aug 2023 20:11:16 +0100 Subject: [PATCH 07/14] fix: ' --- .../blog/2020-11-14-bulletproof-uniq-with-typescript/index.md | 2 +- .../index.md | 2 +- .../index.md | 2 +- .../index.md | 2 +- .../2021-01-30-aspnet-serilog-and-application-insights/index.md | 2 +- .../blog/2021-05-01-blog-archive-for-docusaurus/index.md | 2 +- .../index.md | 2 +- open-ai-description/index.ts | 2 +- open-ai-description/summarizer.ts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/blog-website/blog/2020-11-14-bulletproof-uniq-with-typescript/index.md b/blog-website/blog/2020-11-14-bulletproof-uniq-with-typescript/index.md index b9e482b83ea..1372cb3a08b 100644 --- a/blog-website/blog/2020-11-14-bulletproof-uniq-with-typescript/index.md +++ b/blog-website/blog/2020-11-14-bulletproof-uniq-with-typescript/index.md @@ -4,7 +4,7 @@ title: 'Bulletproof uniq with TypeScript generics (yay code reviews!)' authors: johnnyreilly tags: [typescript] hide_table_of_contents: false -description: 'Code reviews provide opportunities for improvement. A developer shares how their colleague\'s comment led to the creation of a better “uniq” function.' +description: 'Code reviews provide opportunities for improvement. A developer shares how their colleagues comment led to the creation of a better “uniq” function.' --- Never neglect the possibilities of a code review. There are times when you raise a PR and all you want is for everyone to hit approve so you can merge, merge and ship, ship! This can be a missed opportunity. For as much as I'd like to imagine my code is perfect, it's patently not. There's always scope for improvement. diff --git a/blog-website/blog/2020-12-09-azure-pipelines-task-lib-and-isoutput-setvariable/index.md b/blog-website/blog/2020-12-09-azure-pipelines-task-lib-and-isoutput-setvariable/index.md index c83f65e7a96..6dc2fc3e4c8 100644 --- a/blog-website/blog/2020-12-09-azure-pipelines-task-lib-and-isoutput-setvariable/index.md +++ b/blog-website/blog/2020-12-09-azure-pipelines-task-lib-and-isoutput-setvariable/index.md @@ -4,7 +4,7 @@ title: 'azure-pipelines-task-lib and isOutput setVariable' authors: johnnyreilly tags: [Azure Pipelines] hide_table_of_contents: false -description: 'This is a workaround for custom Azure Pipelines task extension to output variable since the library doesn\'t support "isOutput=true."' +description: 'This is a workaround for custom Azure Pipelines task extension to output variable since the library does not support "isOutput=true."' --- Some blog posts are insightful treatises on the future of web development, some are "here's how I solved my problem". This is most assuredly the latter. diff --git a/blog-website/blog/2020-12-20-nullable-reference-types-csharp-strictnullchecks/index.md b/blog-website/blog/2020-12-20-nullable-reference-types-csharp-strictnullchecks/index.md index dfa1b22b534..5156aaa9157 100644 --- a/blog-website/blog/2020-12-20-nullable-reference-types-csharp-strictnullchecks/index.md +++ b/blog-website/blog/2020-12-20-nullable-reference-types-csharp-strictnullchecks/index.md @@ -4,7 +4,7 @@ title: "Nullable reference types; CSharp's very own strictNullChecks" authors: johnnyreilly tags: [C#, nullable reference types] hide_table_of_contents: false -description: 'C# introduces nullable reference types similar to TypeScript\'s `strictNullChecks`. Enabling raises warnings and solves null reference risks.' +description: 'C# introduces nullable reference types similar to TypeScripts `strictNullChecks`. Enabling raises warnings and solves null reference risks.' --- 'Tis the season to play with new compiler settings! I'm a very keen TypeScript user and have been merrily using [`strictNullChecks`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#--strictnullchecks) since it shipped. I was dimly aware that C# was also getting a similar feature by the name of [nullable reference types](https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/nullable-reference-types). diff --git a/blog-website/blog/2021-01-03-strongly-typing-react-query-s-usequeries/index.md b/blog-website/blog/2021-01-03-strongly-typing-react-query-s-usequeries/index.md index 7614f71c122..cac6c6ca184 100644 --- a/blog-website/blog/2021-01-03-strongly-typing-react-query-s-usequeries/index.md +++ b/blog-website/blog/2021-01-03-strongly-typing-react-query-s-usequeries/index.md @@ -5,7 +5,7 @@ authors: johnnyreilly image: ./strongly-typing-usequeries.webp tags: [useQueries, react-query] hide_table_of_contents: false -description: 'Learn how to strongly type \'useQueries\' in \'react-query\' with \'useQueriesTyped\'. A wrapper function provides the strongly-typed API.' +description: 'Learn how to strongly type `useQueries` in `react-query` with `useQueriesTyped`. A wrapper function provides the strongly-typed API.' --- `react-query` has a weakly typed hook named `useQueries`. It's possible to turn that into a strong typed hook; this post shows you how. diff --git a/blog-website/blog/2021-01-30-aspnet-serilog-and-application-insights/index.md b/blog-website/blog/2021-01-30-aspnet-serilog-and-application-insights/index.md index 0f3ab20a3e1..dacdbc16acd 100644 --- a/blog-website/blog/2021-01-30-aspnet-serilog-and-application-insights/index.md +++ b/blog-website/blog/2021-01-30-aspnet-serilog-and-application-insights/index.md @@ -5,7 +5,7 @@ authors: johnnyreilly image: ./title-image.png tags: [asp.net, Azure, Application Insights, Serilog] hide_table_of_contents: false -description: 'Learn how to integrate Serilog into Azure\'s Application Insights for better diagnostic logging by following these steps and adding dependencies.' +description: 'Learn how to integrate Serilog into Azures Application Insights for better diagnostic logging by following these steps and adding dependencies.' --- If you're deploying an ASP.NET application to Azure App Services / Azure Container Apps or similar, there's a decent chance you'll also be using the fantastic [Serilog](https://serilog.net/) and will want to plug it into Azure's [Application Insights](https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview). diff --git a/blog-website/blog/2021-05-01-blog-archive-for-docusaurus/index.md b/blog-website/blog/2021-05-01-blog-archive-for-docusaurus/index.md index cdedf688855..fe7d4eb0b49 100644 --- a/blog-website/blog/2021-05-01-blog-archive-for-docusaurus/index.md +++ b/blog-website/blog/2021-05-01-blog-archive-for-docusaurus/index.md @@ -5,7 +5,7 @@ authors: johnnyreilly tags: [Docusaurus, webpack] image: ./docusaurus-blog-archive.png hide_table_of_contents: false -description: 'Learn how to add a blog archive to your Docusaurus blog and browse through historic posts. Follow the article\'s steps to implement.' +description: 'Learn how to add a blog archive to your Docusaurus blog and browse through historic posts. Follow the articles steps to implement.' --- Docusaurus doesn't ship with "blog archive" functionality. By which I mean, something that allows you to look at an overview of your historic blog posts. It turns out it is fairly straightforward to implement your own. This post does just that. diff --git a/blog-website/blog/2021-10-18-docusaurus-meta-tags-and-google-discover/index.md b/blog-website/blog/2021-10-18-docusaurus-meta-tags-and-google-discover/index.md index 2a87f5990fa..0a775d468a4 100644 --- a/blog-website/blog/2021-10-18-docusaurus-meta-tags-and-google-discover/index.md +++ b/blog-website/blog/2021-10-18-docusaurus-meta-tags-and-google-discover/index.md @@ -5,7 +5,7 @@ authors: johnnyreilly tags: [Docusaurus] image: ./title-image.png hide_table_of_contents: false -description: 'Boost your website\'s appearance in Google Discover with high-quality images and `max-image-preview:large` meta tag setting in Docusaurus.' +description: 'Boost your websites appearance in Google Discover with high-quality images and `max-image-preview:large` meta tag setting in Docusaurus.' --- Google Discover is a way that people can find your content. To make your content more attractive, Google encourage using high quality images which are enabled by setting the `max-image-preview:large` meta tag. This post shows you how to achieve that with Docusaurus. diff --git a/open-ai-description/index.ts b/open-ai-description/index.ts index ae1c8a5907a..6a5498e19fd 100644 --- a/open-ai-description/index.ts +++ b/open-ai-description/index.ts @@ -68,7 +68,7 @@ async function generatePostsWithDescription() { await fs.promises.writeFile( post.path, - `---${frontmatter}description: '${description.replaceAll("'", "\\'")}' + `---${frontmatter}description: '${description.replaceAll("'", '')}' ---${article}`, ); } else { diff --git a/open-ai-description/summarizer.ts b/open-ai-description/summarizer.ts index c35654aa2c4..653eef6a05c 100644 --- a/open-ai-description/summarizer.ts +++ b/open-ai-description/summarizer.ts @@ -46,7 +46,7 @@ export async function produceSummary(article: string): Promise { const messages = [ { role: 'system', - content: `You are a summarizer. You will be given the text of an article and will produce a summary / meta description which summarizes the article. The summary / meta descriptions you produce must be between ${minChars} and ${maxChars} characters long. If they are longer or shorter than that they cannot be used.`, + content: `You are a summarizer. You will be given the text of an article and will produce a summary / meta description which summarizes the article. The summary / meta descriptions you produce must be between ${minChars} and ${maxChars} characters long. If they are longer or shorter than that they cannot be used. Avoid using the \`'\` character as it is not supported by the blog website - you may use the \`’\` character instead.`, }, { role: 'user', From 1e9e09890ba89eddbe2cd867b1d92ef8519498cb Mon Sep 17 00:00:00 2001 From: johnnyreilly Date: Sun, 27 Aug 2023 20:23:20 +0100 Subject: [PATCH 08/14] feat: more descriptions --- .../index.md | 1 + .../index.md | 1 + .../index.md | 42 +------ .../2017-03-30-im-looking-for-work/index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 9 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../blog/2018-06-16-vsts-yaml-up/index.md | 1 + .../2018-07-09-cypress-and-auth0/index.md | 7 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../2018-10-27-making-a-programmer/index.md | 1 + .../index.md | 31 +---- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 49 +------- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 24 +--- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 3 +- .../index.md | 13 +- .../blog/2020-04-04-up-to-clouds/index.md | 1 + .../index.md | 1 + .../index.md | 111 +----------------- .../index.md | 1 + .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 1 + open-ai-description/summarizer.ts | 2 +- 59 files changed, 79 insertions(+), 267 deletions(-) diff --git a/blog-website/blog/2017-02-14-typescript-types-and-repeatable-builds/index.md b/blog-website/blog/2017-02-14-typescript-types-and-repeatable-builds/index.md index 73904d2b8cb..20ede1f85cb 100644 --- a/blog-website/blog/2017-02-14-typescript-types-and-repeatable-builds/index.md +++ b/blog-website/blog/2017-02-14-typescript-types-and-repeatable-builds/index.md @@ -4,6 +4,7 @@ title: '@types is rogue' authors: johnnyreilly tags: [] hide_table_of_contents: false +description: 'Type definitions from Definitely Typed under @types namespace on npm cannot be trusted to follow semantic versioning, leading to breakages.' --- Or perhaps I should call this "@types and repeatable builds".... diff --git a/blog-website/blog/2017-02-23-under-duck-afternoon-in-open-source/index.md b/blog-website/blog/2017-02-23-under-duck-afternoon-in-open-source/index.md index b5779374549..3cfc8971e48 100644 --- a/blog-website/blog/2017-02-23-under-duck-afternoon-in-open-source/index.md +++ b/blog-website/blog/2017-02-23-under-duck-afternoon-in-open-source/index.md @@ -4,6 +4,7 @@ title: 'Under the Duck: An Afternoon in Open Source' authors: johnnyreilly tags: [ts-loader, webpack] hide_table_of_contents: false +description: 'A minute-by-minute account of how open source developers fixed an issue with ts-loader and webpack, demonstrating the collaborative nature of the community.' --- Have you ever wondered what happens behind the scenes of open source projects? One that I'm involved with is [ts-loader](https://github.com/typestrong/ts-loader); a TypeScript loader for webpack. Yesterday was an interesting day in the life of ts-loader and webpack; things unexpectedly broke. Oh and don't worry, they're fixed now. diff --git a/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md b/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md index c4c0b17775f..6b002d57d96 100644 --- a/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md +++ b/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md @@ -4,6 +4,7 @@ title: 'Debugging ASP.Net Core in VS or Code' authors: johnnyreilly tags: [VS Code, ASP.Net Core, Visual Studio] hide_table_of_contents: false +description: 'Learn how the author became a fan of VS Code for TypeScript and how they managed to debug ASP.Net Core using the extension for C#.' --- I've been using Visual Studio for a long time. Very good it is too. However, it is heavyweight; it does far more than I need. What I really want when I'm working is a fast snappy editor, with intellisense and debugging. What I've basically described is [VS Code](https://code.visualstudio.com/). It rocks and has long become my go-to editor for TypeScript. @@ -39,45 +40,4 @@ So it wants me to `dotnet restore`. It's even offering to do that for me! Have a ```ts Welcome to .NET Core! ---------------------- -Learn more about .NET Core @ https://aka.ms/dotnet-docs. Use dotnet --help to see available commands or go to https://aka.ms/dotnet-cli-docs. - -Telemetry --------------- -The .NET Core tools collect usage data in order to improve your experience. The data is anonymous and does not include command-line arguments. The data is collected by Microsoft and shared with the community. -You can opt out of telemetry by setting a DOTNET_CLI_TELEMETRY_OPTOUT environment variable to 1 using your favorite shell. -You can read more about .NET Core tools telemetry @ https://aka.ms/dotnet-cli-telemetry. - -Configuring... -------------------- -A command is running to initially populate your local package cache, to improve restore speed and enable offline access. This command will take up to a minute to complete and will only happen once. -Decompressing Decompressing 100% 4026 ms -Expanding 100% 34814 ms - Restoring packages for c:\Source\Debugging\WebApplication1\WebApplication1\WebApplication1.csproj... - Restoring packages for c:\Source\Debugging\WebApplication1\WebApplication1\WebApplication1.csproj... - Restore completed in 734.05 ms for c:\Source\Debugging\WebApplication1\WebApplication1\WebApplication1.csproj. - Generating MSBuild file c:\Source\Debugging\WebApplication1\WebApplication1\obj\WebApplication1.csproj.nuget.g.props. - Writing lock file to disk. Path: c:\Source\Debugging\WebApplication1\WebApplication1\obj\project.assets.json - Restore completed in 1.26 sec for c:\Source\Debugging\WebApplication1\WebApplication1\WebApplication1.csproj. - - NuGet Config files used: - C:\Users\johnr\AppData\Roaming\NuGet\NuGet.Config - C:\Program Files (x86)\NuGet\Config\Microsoft.VisualStudio.Offline.config - - Feeds used: - https://api.nuget.org/v3/index.json - C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\ -Done: 0. ``` - -The other prompt says `"Required assets to build and debug are missing from 'WebApplication1'. Add them?"`. This also sounds very promising and I give it the nod. This creates a `.vscode` directory and 2 enclosed files; `launch.json` and `tasks.json`. - -So lets try that F5 thing again... http://localhost:5000/ is now serving the same app. That looks pretty good. So lets add a breakpoint to the `HomeController` and see if we can hit it: - -![](firstgo.webp) - -Well I can certainly add a breakpoint but all those red squigglies are unnerving me. Let's clean the slate. If you want to simply do that in VS Code hold down `CTRL+SHIFT+P` and then type "reload". Pick "Reload window". A couple of seconds later we're back in and Code is looking much happier. Can we hit our breakpoint? - -![](secondgo.webp) - -Yes we can! So you're free to develop in either Code or VS; the choice is yours. I think that's pretty awesome - and well done to all the peeople behind Code who've made this a pretty seamless experience! diff --git a/blog-website/blog/2017-03-30-im-looking-for-work/index.md b/blog-website/blog/2017-03-30-im-looking-for-work/index.md index 8abe6fb623b..e7a84eb57fa 100644 --- a/blog-website/blog/2017-03-30-im-looking-for-work/index.md +++ b/blog-website/blog/2017-03-30-im-looking-for-work/index.md @@ -4,6 +4,7 @@ title: "I'm looking for work!" authors: johnnyreilly tags: [] hide_table_of_contents: false +description: 'Full stack developer John Reilly seeks new work after finishing recent contract. 15 years’ experience includes telecoms, advertising, tech and finance.' --- My name is John Reilly. I'm a full stack developer based in London, UK. I'm just coming to the end of a contract (due to finish in April 2017) and I'm starting to look for my next role. diff --git a/blog-website/blog/2017-04-25-setting-build-version-using-appveyor/index.md b/blog-website/blog/2017-04-25-setting-build-version-using-appveyor/index.md index 22c850bd181..5a8df2cf1e0 100644 --- a/blog-website/blog/2017-04-25-setting-build-version-using-appveyor/index.md +++ b/blog-website/blog/2017-04-25-setting-build-version-using-appveyor/index.md @@ -4,6 +4,7 @@ title: 'Setting Build Version Using AppVeyor and ASP.Net Core' authors: johnnyreilly tags: [powershell, Version, dot net core, AppVeyor] hide_table_of_contents: false +description: 'AppVeyor doesnt have support for setting version of a binary in dot net core, but it can be done easily through PowerShell.' --- AppVeyor has [support for setting the version of a binary during a build](https://www.appveyor.com/docs/build-configuration/#assemblyinfo-patching). However - this deals with the classic ASP.Net world of `AssemblyInfo`. I didn't find any reference to support for doing the same with dot net core. Remember, dot net core [relies upon a `<Version>` or a `<VersionPrefix>` setting in the `.csproj` file](https://docs.microsoft.com/en-us/dotnet/articles/core/tools/project-json-to-csproj#version). Personally, `<Version>` is my jam. diff --git a/blog-website/blog/2017-05-20-typescript-spare-rod-spoil-code/index.md b/blog-website/blog/2017-05-20-typescript-spare-rod-spoil-code/index.md index 62aa837663b..a1b9b54982b 100644 --- a/blog-website/blog/2017-05-20-typescript-spare-rod-spoil-code/index.md +++ b/blog-website/blog/2017-05-20-typescript-spare-rod-spoil-code/index.md @@ -4,6 +4,7 @@ title: 'TypeScript: Spare the Rod, Spoil the Code' authors: johnnyreilly tags: [tsconfig.json, typescript] hide_table_of_contents: false +description: 'TypeScript settings catch bugs. Use compiler options to increase strictness gradually & catch errors such as unused variables with VS Code.' --- I've recently started a new role. Perhaps unsurprisingly, part of the technology stack is TypeScript. A couple of days into the new codebase I found a bug. Well, I say I found a bug, TypeScript and VS Code found the bug - I just let everyone else know. diff --git a/blog-website/blog/2017-06-11-windows-defender-step-away-from-npm/index.md b/blog-website/blog/2017-06-11-windows-defender-step-away-from-npm/index.md index d46cd0ca4d9..40b4dcb35a3 100644 --- a/blog-website/blog/2017-06-11-windows-defender-step-away-from-npm/index.md +++ b/blog-website/blog/2017-06-11-windows-defender-step-away-from-npm/index.md @@ -4,6 +4,7 @@ title: 'Windows Defender Step Away From npm' authors: johnnyreilly tags: [VS Code, Windows, npm] hide_table_of_contents: false +description: 'A bug causing issues with Windows Defender has been fixed with the release of VS Code 1.14. The bug was causing problems with the program open.' --- ## Updated 18/06/2017 @@ -16,8 +17,6 @@ The issue was VS Code. The bug has now been fixed and shipped last night with [V ---- - I've recently experienced many of my `npm install`s failing for no consistent reason. The error message would generally be something along the lines of: ```sh diff --git a/blog-website/blog/2017-07-02-dynamic-import-ive-been-await-ing-you/index.md b/blog-website/blog/2017-07-02-dynamic-import-ive-been-await-ing-you/index.md index c087591c4ff..283d24caca2 100644 --- a/blog-website/blog/2017-07-02-dynamic-import-ive-been-await-ing-you/index.md +++ b/blog-website/blog/2017-07-02-dynamic-import-ive-been-await-ing-you/index.md @@ -4,6 +4,7 @@ title: "Dynamic import: I've been awaiting you..." authors: johnnyreilly tags: [typescript, webpack] hide_table_of_contents: false +description: 'TypeScript 2.4 gains asynchronous, dynamic import expression for modules with no browser support. Webpack2 supports this feature.' --- One of the most exciting features to ship with TypeScript 2.4 was support for the dynamic import expression. To quote the [release blog post](https://blogs.msdn.microsoft.com/typescript/2017/06/27/announcing-typescript-2-4/#dynamic-import-expressions): diff --git a/blog-website/blog/2017-07-29-a-haiku-on-problem-with-semver-us/index.md b/blog-website/blog/2017-07-29-a-haiku-on-problem-with-semver-us/index.md index 5ef3af4180a..23e84440fb4 100644 --- a/blog-website/blog/2017-07-29-a-haiku-on-problem-with-semver-us/index.md +++ b/blog-website/blog/2017-07-29-a-haiku-on-problem-with-semver-us/index.md @@ -4,6 +4,7 @@ title: 'A Haiku on the Problem with SemVer: Us' authors: johnnyreilly tags: [semantic versioning] hide_table_of_contents: false +description: 'A Haiku on the Problem with SemVer: Us' --- Version numbers wrong diff --git a/blog-website/blog/2017-08-27-karma-from-phantomjs-to-headless-chrome/index.md b/blog-website/blog/2017-08-27-karma-from-phantomjs-to-headless-chrome/index.md index c60473f4635..0b81b1864bc 100644 --- a/blog-website/blog/2017-08-27-karma-from-phantomjs-to-headless-chrome/index.md +++ b/blog-website/blog/2017-08-27-karma-from-phantomjs-to-headless-chrome/index.md @@ -4,6 +4,7 @@ title: 'Karma: From PhantomJS to Headless Chrome' authors: johnnyreilly tags: [Chrome, Karma, PhantomJS] hide_table_of_contents: false +description: 'Replace PhantomJS with new Chrome Headless to run Chrome without a UI. Migrate a test and add Chrome to your build environment.' --- Like pretty much everyone else I've been using PhantomJS to run my JavaScript (or compiled-to-JS) unit tests. It's been great. So when I heard the news that [PhantomJS was dead](https://news.ycombinator.com/item?id=14105489) I was genuinely sad. However, the King is dead.... Long live the King! For there is a new hope; it's called [Chrome Headless ](https://developers.google.com/web/updates/2017/04/headless-chrome). It's not a separate version of Chrome; rather the ability to run Chrome without a UI is now baked into Google's favourite browser as of v59. (For those history buffs I might as well be clear: the main reason PhantomJS died is because Chrome Headless was in the works.) diff --git a/blog-website/blog/2017-08-30-oh-glamour-of-open-source/index.md b/blog-website/blog/2017-08-30-oh-glamour-of-open-source/index.md index 8c39c707a98..40db8574f2e 100644 --- a/blog-website/blog/2017-08-30-oh-glamour-of-open-source/index.md +++ b/blog-website/blog/2017-08-30-oh-glamour-of-open-source/index.md @@ -4,6 +4,7 @@ title: 'Oh the Glamour of Open Source' authors: johnnyreilly tags: [] hide_table_of_contents: false +description: 'A programmer recounts a sleepless night spent fixing a gap in an open source project, but accidentally deletes the repo and eventually seeks help.' --- Here's how my life panned out in the early hours of Wednesday 30th September 2017: diff --git a/blog-website/blog/2017-09-07-typescript-webpack-super-pursuit-mode/index.md b/blog-website/blog/2017-09-07-typescript-webpack-super-pursuit-mode/index.md index a345523e577..3b791a12c2d 100644 --- a/blog-website/blog/2017-09-07-typescript-webpack-super-pursuit-mode/index.md +++ b/blog-website/blog/2017-09-07-typescript-webpack-super-pursuit-mode/index.md @@ -4,6 +4,7 @@ title: 'TypeScript + Webpack: Super Pursuit Mode' authors: johnnyreilly tags: [typescript, fork-ts-checker-webpack-plugin, Webpack] hide_table_of_contents: false +description: 'Learn how to improve build speeds with TypeScript and webpack using fork-ts-checker-webpack-plugin, HappyPack, and thread-loader/cache-loader.' --- _[This post also featured as a webpack Medium publication](https://medium.com/webpack/typescript-webpack-super-pursuit-mode-83cc568dea79)._ diff --git a/blog-website/blog/2017-09-12-fork-ts-checker-webpack-plugin-code/index.md b/blog-website/blog/2017-09-12-fork-ts-checker-webpack-plugin-code/index.md index 4d45d35e724..38510fd50b3 100644 --- a/blog-website/blog/2017-09-12-fork-ts-checker-webpack-plugin-code/index.md +++ b/blog-website/blog/2017-09-12-fork-ts-checker-webpack-plugin-code/index.md @@ -4,6 +4,7 @@ title: 'fork-ts-checker-webpack-plugin code clickability' authors: johnnyreilly tags: [VS Code, fork-ts-checker-webpack-plugin, ts-loader, webpack] hide_table_of_contents: false +description: 'The `fork-ts-checker-webpack-plugin` can speed up builds, but TypeScript errors in the terminal are not clickable. The cause and solution are explained.' --- My name is John Reilly and I'm a VS Code addict. There I said it. I'm also a big fan of TypeScript and webpack. I've recently switched to using the awesome [`fork-ts-checker-webpack-plugin`](https://www.npmjs.com/package/fork-ts-checker-webpack-plugin) to speed up my builds. @@ -41,7 +42,7 @@ function clickableFormatter(message, useColors) { message.getLine() + ',' + message.getCharacter() + - ')' + ')', ) + messageColor(':'), diff --git a/blog-website/blog/2017-10-20-typescript-definitions-webpack-and-module-types/index.md b/blog-website/blog/2017-10-20-typescript-definitions-webpack-and-module-types/index.md index 88f80a1f6e1..006fb6d52e5 100644 --- a/blog-website/blog/2017-10-20-typescript-definitions-webpack-and-module-types/index.md +++ b/blog-website/blog/2017-10-20-typescript-definitions-webpack-and-module-types/index.md @@ -4,6 +4,7 @@ title: 'TypeScript Definitions, webpack and Module Types' authors: johnnyreilly tags: [Definitely Typed, typescript, webpack] hide_table_of_contents: false +description: 'Inconsistent module exports cause confusion while using the npm package big.js, leading to `one definition to rule them all.`' --- A funny thing happened on the way to the registry the other day. Something changed in an npm package I was using and confusion arose. You can read my unfiltered confusion [here](https://github.com/Microsoft/TypeScript/issues/18791) but here's the slightly clearer explanation. diff --git a/blog-website/blog/2017-11-19-the-typescript-webpack-pwa/index.md b/blog-website/blog/2017-11-19-the-typescript-webpack-pwa/index.md index 3fb6de8370e..fc67b437afe 100644 --- a/blog-website/blog/2017-11-19-the-typescript-webpack-pwa/index.md +++ b/blog-website/blog/2017-11-19-the-typescript-webpack-pwa/index.md @@ -4,6 +4,7 @@ title: 'The TypeScript webpack PWA' authors: johnnyreilly tags: [typescript, PWA, webpack] hide_table_of_contents: false +description: 'Learn how to turn a TypeScript, webpack setup into a PWA using Workbox. With service workers, build offline-capable web apps.' --- So, there you sit, conflicted. You've got a lovely build setup; it's a thing of beauty. Precious, polished like a diamond, sharpened like a circular saw. There at the core of your carefully crafted setup sits webpack. Heaving, mysterious... powerful. diff --git a/blog-website/blog/2017-12-24-ts-loader-2017-retrospective/index.md b/blog-website/blog/2017-12-24-ts-loader-2017-retrospective/index.md index 7db18ce505f..7750c54c567 100644 --- a/blog-website/blog/2017-12-24-ts-loader-2017-retrospective/index.md +++ b/blog-website/blog/2017-12-24-ts-loader-2017-retrospective/index.md @@ -4,6 +4,7 @@ title: 'ts-loader 2017 retrospective' authors: johnnyreilly tags: [typescript, ts-loader, webpack] hide_table_of_contents: false +description: 'ts-loader has improved in 2017, now sitting at v3.2.0 and supporting webpack 2 and 3. Future plans include using the new watch API.' --- 2017 is drawing to a close, and it's been a big, big year in webpack-land. It's been a big year for `ts-loader` too. At the start of the year v1.3.3 was the latest version available, officially supporting webpack 1. (Old school!) We end the year with `ts-loader` sitting pretty at v3.2.0 and supporting webpack 2 and 3. diff --git a/blog-website/blog/2018-01-28-webpack-4-ts-loader-fork-ts-checker/index.md b/blog-website/blog/2018-01-28-webpack-4-ts-loader-fork-ts-checker/index.md index 93f63d37c64..854fbdf7142 100644 --- a/blog-website/blog/2018-01-28-webpack-4-ts-loader-fork-ts-checker/index.md +++ b/blog-website/blog/2018-01-28-webpack-4-ts-loader-fork-ts-checker/index.md @@ -4,6 +4,7 @@ title: 'webpack 4 - ts-loader / fork-ts-checker-webpack-plugin betas' authors: johnnyreilly tags: [fork-ts-checker-webpack-plugin, ts-loader, Webpack] hide_table_of_contents: false +description: 'The TypeScript ts-loader beta to work with Webpack 4 is now available, along with the fork-ts-checker-webpack-plugin, which complements ts-loader.' --- [The first webpack 4 beta dropped on Friday](https://medium.com/webpack/webpack-4-beta-try-it-today-6b1d27d7d7e2). Very exciting! Following hot on the heels of those announcements, I've some news to share too. Can you guess what it is? diff --git a/blog-website/blog/2018-01-29-finding-webpack-4-use-map/index.md b/blog-website/blog/2018-01-29-finding-webpack-4-use-map/index.md index c74921decf4..2940f9f9248 100644 --- a/blog-website/blog/2018-01-29-finding-webpack-4-use-map/index.md +++ b/blog-website/blog/2018-01-29-finding-webpack-4-use-map/index.md @@ -4,6 +4,7 @@ title: 'Finding webpack 4 (use a Map)' authors: johnnyreilly tags: [webpack] hide_table_of_contents: false +description: 'Webpack 4s new plugin architecture requires migrating from "kebab-case" to "camelCase". A migration guide for plugins and loaders is available.' --- ## Update: 03/02/2018 @@ -37,7 +38,7 @@ this.compiler.hooks.watchClose.tap( 'name-to-identify-your-plugin-goes-here', () => { // do your thing here - } + }, ); ``` @@ -60,7 +61,7 @@ this.compiler.hooks.watchRun.tapAsync( (compiler, callback) => { // do your thing here callback(); - } + }, ); ``` @@ -141,7 +142,7 @@ With webpack 4 these become: ```js loader._compiler.hooks.afterCompile.tapAsync( - 'ts-loader' /* callback goes here */ + 'ts-loader' /* callback goes here */, ); loader._compiler.hooks.watchRun.tapAsync('ts-loader' /* callback goes here */); ``` @@ -162,7 +163,7 @@ What this means is, code that would once have looked like this: Object.keys(watching.compiler.fileTimestamps) .filter( (filePath) => - watching.compiler.fileTimestamps[filePath] > lastTimes[filePath] + watching.compiler.fileTimestamps[filePath] > lastTimes[filePath], ) .forEach((filePath) => { lastTimes[filePath] = times[filePath]; diff --git a/blog-website/blog/2018-02-25-ts-loader-400-fork-ts-checker-webpack/index.md b/blog-website/blog/2018-02-25-ts-loader-400-fork-ts-checker-webpack/index.md index 607538017bd..f098d1dc7f2 100644 --- a/blog-website/blog/2018-02-25-ts-loader-400-fork-ts-checker-webpack/index.md +++ b/blog-website/blog/2018-02-25-ts-loader-400-fork-ts-checker-webpack/index.md @@ -4,6 +4,7 @@ title: 'ts-loader 4 / fork-ts-checker-webpack-plugin 0.4' authors: johnnyreilly tags: [webpack, fork-ts-checker-webpack-plugin, ts-loader] hide_table_of_contents: false +description: 'Webpack 4 has been released, along with updates for ts-loader and fork-ts-checker-webpack-plugin. See links for details and examples.' --- webpack 4 has shipped! diff --git a/blog-website/blog/2018-03-07-its-not-dead-webpack-and-dead-code/index.md b/blog-website/blog/2018-03-07-its-not-dead-webpack-and-dead-code/index.md index a29c5f39302..0a13d8dce7f 100644 --- a/blog-website/blog/2018-03-07-its-not-dead-webpack-and-dead-code/index.md +++ b/blog-website/blog/2018-03-07-its-not-dead-webpack-and-dead-code/index.md @@ -4,6 +4,7 @@ title: "It's Not Dead: webpack and dead code elimination limitations" authors: johnnyreilly tags: [webpack] hide_table_of_contents: false +description: 'Webpack eliminates dead code through DefinePlugin. Directly use `process.env.NODE_ENV !== production` for smarter code elimination by UglifyJSPlugin.' --- Webpack has long supported the notion of dead code elimination. webpack facilitates this through use of the `DefinePlugin`. The compile time value of `process.env.NODE_ENV` is set either to `'production'` or something else. If it's set to `'production'` then some dead code hackery can happen. [Libraries like React make use of this to serve up different, and crucially smaller, production builds.](https://reactjs.org/docs/optimizing-performance.html#webpack) diff --git a/blog-website/blog/2018-03-26-its-not-dead-2-mobx-react-devtools-and-the-undead/index.md b/blog-website/blog/2018-03-26-its-not-dead-2-mobx-react-devtools-and-the-undead/index.md index 6a598c6f99f..c852a4b9bda 100644 --- a/blog-website/blog/2018-03-26-its-not-dead-2-mobx-react-devtools-and-the-undead/index.md +++ b/blog-website/blog/2018-03-26-its-not-dead-2-mobx-react-devtools-and-the-undead/index.md @@ -4,6 +4,7 @@ title: "It's Not Dead 2: mobx-react-devtools and the undead" authors: johnnyreilly tags: [uglifyjs, mobx, webpack] hide_table_of_contents: false +description: 'Using `mobx-react-devtools` with `process.env.NODE_ENV` caused problems with webpack production mode. A different approach fixed the issue.' --- I spent today digging through our webpack 4 config trying to work out why a production bundle contained code like this: diff --git a/blog-website/blog/2018-05-13-compromising-guide-for-developers/index.md b/blog-website/blog/2018-05-13-compromising-guide-for-developers/index.md index 49d8cbdd4dd..3f728745fa9 100644 --- a/blog-website/blog/2018-05-13-compromising-guide-for-developers/index.md +++ b/blog-website/blog/2018-05-13-compromising-guide-for-developers/index.md @@ -3,6 +3,7 @@ slug: compromising-guide-for-developers title: 'Compromising: A Guide for Developers' authors: johnnyreilly hide_table_of_contents: false +description: 'Weighing opinions with a voting system can reduce friction and boost productivity when working with developers of different opinions.' --- It is a truth universally acknowledged, that a single developer, will not be short of an opinion. Opinions on tabs vs spaces. Upon OOP vs FP. Upon `class`es vs `function`s. Just opinions, opinions, opinions. Opinions that are felt with all the sincerity of a Witchfinder General. And, alas, not always the same level of empathy. diff --git a/blog-website/blog/2018-06-16-vsts-yaml-up/index.md b/blog-website/blog/2018-06-16-vsts-yaml-up/index.md index 35660b92b14..fe4ac91c42e 100644 --- a/blog-website/blog/2018-06-16-vsts-yaml-up/index.md +++ b/blog-website/blog/2018-06-16-vsts-yaml-up/index.md @@ -4,6 +4,7 @@ title: 'VSTS... YAML up!' authors: johnnyreilly tags: [yaml, vsts, travis, AppVeyor] hide_table_of_contents: false +description: 'Visual Studio Team Services now has a YAML build definition preview feature that enables users to keep their build scripts with their code.' --- For the longest time I've been using the likes of [Travis](https://travis-ci.org/) and [AppVeyor](https://www.appveyor.com/) to build open source projects that I work on. They rock. I've also recently been dipping my toes back in the water of [Visual Studio Team Services](https://www.visualstudio.com/team-services/). VSTS offers a whole stack of stuff, but my own area of interest has been the Continuous Integration / Continuous Deployment offering. diff --git a/blog-website/blog/2018-07-09-cypress-and-auth0/index.md b/blog-website/blog/2018-07-09-cypress-and-auth0/index.md index dc5a866cc49..e0eaec305c3 100644 --- a/blog-website/blog/2018-07-09-cypress-and-auth0/index.md +++ b/blog-website/blog/2018-07-09-cypress-and-auth0/index.md @@ -4,6 +4,7 @@ title: 'Cypress and Auth0' authors: johnnyreilly tags: [auth0-js, Auth0, cypress, auth] hide_table_of_contents: false +description: 'The article explains how to automate Auth0 login using Cypress, by using the auth0-js client library, and creating a custom command.' --- [Cypress](https://www.cypress.io/) is a fantastic way to write UI tests for your web apps. Just world class. Wait, no. Galaxy class. I'm going to go one further: universe class. You get my drift. @@ -70,13 +71,13 @@ Cypress.Commands.add('loginAsAdmin', (overrides = {}) => { window.sessionStorage.setItem( 'my-super-duper-app:storage_token', - JSON.stringify(token) + JSON.stringify(token), ); } else { console.error('Problem logging into Auth0', err); throw err; } - } + }, ); }); ``` @@ -128,6 +129,6 @@ You now have a test which automates your Auth0 login using Cypress and goes on t ## One More Thing... -It's worth saying that it's worth setting up different tenants in Auth0 to support your testing scenarios. This is generally a good idea so you can separate your testing accounts from Production accounts. Further to that, you don't need to have your Production setup supporting the ` Password``Grant Type `. +It's worth saying that it's worth setting up different tenants in Auth0 to support your testing scenarios. This is generally a good idea so you can separate your testing accounts from Production accounts. Further to that, you don't need to have your Production setup supporting the `Password``Grant Type`. Also, if you're curious about what the application under test is like then read [this](../2018-01-14-auth0-typescript-and-aspnet-core/index.md). diff --git a/blog-website/blog/2018-07-28-azure-app-service-web-app-containers-asp-net-nested-configuration/index.md b/blog-website/blog/2018-07-28-azure-app-service-web-app-containers-asp-net-nested-configuration/index.md index e6d86a5ffd8..9b20138b8ff 100644 --- a/blog-website/blog/2018-07-28-azure-app-service-web-app-containers-asp-net-nested-configuration/index.md +++ b/blog-website/blog/2018-07-28-azure-app-service-web-app-containers-asp-net-nested-configuration/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [] image: ./appservice_classic.webp hide_table_of_contents: false +description: 'Learn how to configure an ASP.NET application in Azure App Service Web App for Containers without colons. Use a double underscore instead.' --- How can we configure an ASP.NET application with nested properties [Azure App Service Web App for Containers](https://azure.microsoft.com/en-gb/services/app-service/containers/) using Application Settings in Azure? Colons don't work. diff --git a/blog-website/blog/2018-08-21-typescript-webpack-alias-goodbye-relative-paths/index.md b/blog-website/blog/2018-08-21-typescript-webpack-alias-goodbye-relative-paths/index.md index 0692d6a2842..6afc9eaa4d0 100644 --- a/blog-website/blog/2018-08-21-typescript-webpack-alias-goodbye-relative-paths/index.md +++ b/blog-website/blog/2018-08-21-typescript-webpack-alias-goodbye-relative-paths/index.md @@ -4,6 +4,7 @@ title: 'Using TypeScript and webpack alias: goodbye relative paths' authors: johnnyreilly tags: [typescript, webpack] hide_table_of_contents: false +description: 'Use TypeScript with webpack alias to avoid long relative paths in imports. Try `path mapping` or `resolve.alias`. Use `tsconfig-paths-webpack-plugin`.' --- This post shows how you can use TypeScript with webpack `alias` to move away from using relative paths in your `import` statements. diff --git a/blog-website/blog/2018-09-23-ts-loader-project-references-first-blood/index.md b/blog-website/blog/2018-09-23-ts-loader-project-references-first-blood/index.md index a66498521c1..31b1919c6cf 100644 --- a/blog-website/blog/2018-09-23-ts-loader-project-references-first-blood/index.md +++ b/blog-website/blog/2018-09-23-ts-loader-project-references-first-blood/index.md @@ -4,6 +4,7 @@ title: 'ts-loader Project References: First Blood' authors: johnnyreilly tags: [typescript, project references, ts-loader, Webpack] hide_table_of_contents: false +description: 'ts-loader now supports TypeScripts project references. However, composite projects built with `outDir` on Windows cannot be consumed by ts-loader... yet' --- So [project references](https://www.typescriptlang.org/docs/handbook/project-references.html) eh? They shipped with [TypeScript 3](https://blogs.msdn.microsoft.com/typescript/2018/07/30/announcing-typescript-3-0/#project-references). We've just shipped initial support for project references in [`ts-loader v5.2.0`](https://github.com/TypeStrong/ts-loader/releases/tag/v5.2.0). All the hard work was done by the amazing [Andrew Branch](https://twitter.com/atcb). In fact I'd recommend taking a gander at [the PR](https://github.com/TypeStrong/ts-loader/pull/817). Yay Andrew! diff --git a/blog-website/blog/2018-10-07-font-awesome-brand-icons-react/index.md b/blog-website/blog/2018-10-07-font-awesome-brand-icons-react/index.md index 75545aa1048..20572367c38 100644 --- a/blog-website/blog/2018-10-07-font-awesome-brand-icons-react/index.md +++ b/blog-website/blog/2018-10-07-font-awesome-brand-icons-react/index.md @@ -4,6 +4,7 @@ title: 'Brand New Fonting Awesomeness' authors: johnnyreilly tags: [React] hide_table_of_contents: false +description: 'Learn how to use brand icons with Font Awesome 5 in React with these helpful instructions on @fortawesome/free-brands-svg-icons.' --- Love me some [Font Awesome](https://fontawesome.com). Absolutely wonderful. However, I came a cropper when following the instructions [on using the all new Font Awesome 5 with React](https://fontawesome.com/how-to-use/on-the-web/using-with/react). The instructions for standard icons work _fine_. But if you want to use brand icons then this does not help you out much. There's 2 problems: diff --git a/blog-website/blog/2018-10-27-making-a-programmer/index.md b/blog-website/blog/2018-10-27-making-a-programmer/index.md index 4f99b6b4abe..87e5689bd7f 100644 --- a/blog-website/blog/2018-10-27-making-a-programmer/index.md +++ b/blog-website/blog/2018-10-27-making-a-programmer/index.md @@ -3,6 +3,7 @@ slug: making-a-programmer title: 'Making a Programmer' authors: johnnyreilly hide_table_of_contents: false +description: 'Learn programming in a relaxed atmosphere with a coding bootcamp that values repetition, feedback and team facilitation.' --- I recently had the good fortune to help run a coding bootcamp. The idea was simple: there are many people around us who are interested in programming but don't know where to start. Let's take some folk who do and share the knowledge. diff --git a/blog-website/blog/2018-11-17-snapshot-testing-for-c/index.md b/blog-website/blog/2018-11-17-snapshot-testing-for-c/index.md index 0b69b07187f..83c80157aaf 100644 --- a/blog-website/blog/2018-11-17-snapshot-testing-for-c/index.md +++ b/blog-website/blog/2018-11-17-snapshot-testing-for-c/index.md @@ -4,6 +4,7 @@ title: 'Snapshot Testing for C#' authors: johnnyreilly tags: [snapshot testing, c#, jest] hide_table_of_contents: false +description: 'Snapshot testing is an efficient test technique for comparing outputs with JSON. Its applicable to C# too, using Fluent Assertions and a helper tool.' --- If you're a user of Jest, you've no doubt heard of and perhaps made use of [snapshot testing](https://jestjs.io/docs/en/snapshot-testing). @@ -167,35 +168,5 @@ Someone decides that the implementation of `GetTheLeopards` needs to change. Def If we make that change we'd ideally expect our trusty test to fail. Let's see what happens: ``` ------ Test Execution Summary ----- -Leopard.Tests.Services.LeopardServiceTests.GetTheLeopards_should_return_expected_Leopards: - Outcome: Failed - Error Message: - Expected item[1].Spots to be 90, but found 900. ``` - -Boom! We are protected! - -Since this is a change we're completely happy with we want to update our `leopardsSnapshot.json` file. We could make our test pass by manually updating the JSON. That'd be fine. But why work when you don't have to? Let's uncomment our `Snapshot.Make...` line and run the test the once. - -```json -[ - { - "name": "Nimoy", - "spots": 42 - }, - { - "name": "Dotty", - "spots": 90 - } -] -``` - -That's right, we have an updated snapshot! Minimal effort. - -## Next Steps - -This is a basic approach to getting the goodness of snapshot testing in C#. It could be refined further. To my mind the uncommenting / commenting of code is not the most elegant way to approach this and so there's some work that could be done around this area. - -Happy snapshotting! diff --git a/blog-website/blog/2018-12-22-you-might-not-need-thread-loader/index.md b/blog-website/blog/2018-12-22-you-might-not-need-thread-loader/index.md index 5c092aeda7d..122ccec79fc 100644 --- a/blog-website/blog/2018-12-22-you-might-not-need-thread-loader/index.md +++ b/blog-website/blog/2018-12-22-you-might-not-need-thread-loader/index.md @@ -4,6 +4,7 @@ title: 'You Might Not Need thread-loader' authors: johnnyreilly tags: [fork-ts-checker-webpack-plugin, ts-loader, webpack] hide_table_of_contents: false +description: 'Jan Nicklas, the creator of webpack-config-plugins, suggests limiting the use of thread-loader for costly operations via `poolTimeout: Infinity`.' --- It all started with a GitHub issue. [Ernst Ammann reported](https://github.com/namics/webpack-config-plugins/issues/24): diff --git a/blog-website/blog/2019-01-05-github-actions-and-yarn/index.md b/blog-website/blog/2019-01-05-github-actions-and-yarn/index.md index 2a9c219d47d..ad0bec8d344 100644 --- a/blog-website/blog/2019-01-05-github-actions-and-yarn/index.md +++ b/blog-website/blog/2019-01-05-github-actions-and-yarn/index.md @@ -4,6 +4,7 @@ title: 'GitHub Actions and Yarn' authors: johnnyreilly tags: [docker, yarn, GitHub Actions] hide_table_of_contents: false +description: 'Automate npm publishing using GitHub Actions; use `npm` GitHub Action with yarn or any Docker container with Node/npm installed.' --- I'd been meaning to automate the npm publishing of [`ts-loader`](https://github.com/TypeStrong/ts-loader) for the longest time. I had attempted to use Travis to do this in the same way as [`fork-ts-checker-webpack-plugin`](https://github.com/Realytics/fork-ts-checker-webpack-plugin). Alas using secure environment variables in Travis has unfortunate implications for ts-loader's test pack. diff --git a/blog-website/blog/2019-01-13-typescript-and-webpack-watch-it/index.md b/blog-website/blog/2019-01-13-typescript-and-webpack-watch-it/index.md index 68291057f33..daec41dc0fb 100644 --- a/blog-website/blog/2019-01-13-typescript-and-webpack-watch-it/index.md +++ b/blog-website/blog/2019-01-13-typescript-and-webpack-watch-it/index.md @@ -4,6 +4,7 @@ title: 'TypeScript and webpack: Watch It' authors: johnnyreilly tags: [typescript, webpack] hide_table_of_contents: false +description: 'TypeScripts "watch" API shortens time between incremental builds for quicker development; updates are available for fork-ts-checker-webpack-plugin.' --- All I ask for is a compiler and a tight feedback loop. Narrowing the gap between making a change to a program and seeing the effect of that is a productivity boon. The TypeScript team are wise cats and dig this. They've taken strides to improve the developer experience of TypeScript users by [introducing a "watch" API which can be leveraged by other tools](https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#writing-an-incremental-program-watcher). To quote the docs: diff --git a/blog-website/blog/2019-03-06-fork-ts-checker-webpack-plugin-v1/index.md b/blog-website/blog/2019-03-06-fork-ts-checker-webpack-plugin-v1/index.md index 7b6fef36126..19e7442e040 100644 --- a/blog-website/blog/2019-03-06-fork-ts-checker-webpack-plugin-v1/index.md +++ b/blog-website/blog/2019-03-06-fork-ts-checker-webpack-plugin-v1/index.md @@ -4,6 +4,7 @@ title: 'fork-ts-checker-webpack-plugin v1.0' authors: johnnyreilly tags: [typescript, fork-ts-checker-webpack-plugin, ts-loader, tslint, webpack] hide_table_of_contents: false +description: '`fork-ts-checker-webpack-plugin` released v1.0.0 with TypeScript 3+ support, incremental watch API by default, and compatibility with webpack and TSLint.' --- [It's time for the first major version of `fork-ts-checker-webpack-plugin`](https://github.com/Realytics/fork-ts-checker-webpack-plugin/releases/tag/v1.0.0). It's been a long time coming :-) diff --git a/blog-website/blog/2019-03-22-google-analytics-api-and-aspnet-core/index.md b/blog-website/blog/2019-03-22-google-analytics-api-and-aspnet-core/index.md index c7ce935d48a..73e18e7b37d 100644 --- a/blog-website/blog/2019-03-22-google-analytics-api-and-aspnet-core/index.md +++ b/blog-website/blog/2019-03-22-google-analytics-api-and-aspnet-core/index.md @@ -4,6 +4,7 @@ title: 'Google Analytics API and ASP.Net Core' authors: johnnyreilly tags: [asp net core, google analytics] hide_table_of_contents: false +description: 'Accessing Google Analytics API from ASP.Net Core can be tough due to lack of examples. This article provides an example code to get page access stats.' --- I recently had need to be able to access the API for Google Analytics from ASP.Net Core. Getting this up and running turned out to be surprisingly tough because of an absence of good examples. So here it is; an example of how you can access a simple page access stat using [the API](https://www.nuget.org/packages/Google.Apis.AnalyticsReporting.v4/): diff --git a/blog-website/blog/2019-03-24-template-tricks-for-dainty-dom/index.md b/blog-website/blog/2019-03-24-template-tricks-for-dainty-dom/index.md index d3333990fb6..00a4219223a 100644 --- a/blog-website/blog/2019-03-24-template-tricks-for-dainty-dom/index.md +++ b/blog-website/blog/2019-03-24-template-tricks-for-dainty-dom/index.md @@ -4,6 +4,7 @@ title: 'Template Tricks for a Dainty DOM' authors: johnnyreilly tags: [Materialized] hide_table_of_contents: false +description: 'Wrapping data in HTML templates can help with performance. This trick kept rendering server-side but only rendered content when necessary.' --- I'm somewhat into code golf. Placing restrictions on what you're "allowed" to do in code and seeing what the happens as a result. I'd like to share with you something that came out of some recent dabblings. diff --git a/blog-website/blog/2019-04-27-react-select-with-less-typing-lag/index.md b/blog-website/blog/2019-04-27-react-select-with-less-typing-lag/index.md index 95ad7c0cb78..7765ae66995 100644 --- a/blog-website/blog/2019-04-27-react-select-with-less-typing-lag/index.md +++ b/blog-website/blog/2019-04-27-react-select-with-less-typing-lag/index.md @@ -4,6 +4,7 @@ title: 'react-select with less typing lag' authors: johnnyreilly tags: [react-select] hide_table_of_contents: false +description: 'Fix lagging in `react-select`. Change `filterOption` to `ignoreAccents: false` for faster typing experience with 1000+ items.' --- This is going out to all those people using [`react-select`](https://react-select.com) with 1000+ items to render. To those people typing into the select and saying out loud "it's _so_ laggy.... This can't be... It's 2019... I mean, right?" To the people who read this [GitHub issue](https://github.com/JedWatson/react-select/issues/3128) top to bottom 30 times and still came back unsure of what to do. This is for you. diff --git a/blog-website/blog/2019-05-23-typescript-and-high-cpu-usage-watch/index.md b/blog-website/blog/2019-05-23-typescript-and-high-cpu-usage-watch/index.md index 2f907c77452..7933361eeb8 100644 --- a/blog-website/blog/2019-05-23-typescript-and-high-cpu-usage-watch/index.md +++ b/blog-website/blog/2019-05-23-typescript-and-high-cpu-usage-watch/index.md @@ -4,6 +4,7 @@ title: "TypeScript and high CPU usage - watch don't stare!" authors: johnnyreilly tags: [typescript, fork-ts-checker-webpack-plugin, webpack] hide_table_of_contents: false +description: 'High CPU usage in watch mode on idle due to TypeScripts fs.watchFile. fs.watch recommended instead. Env variable controls file watching.' --- I'm one of the maintainers of the [fork-ts-checker-webpack-plugin](https://github.com/Realytics/fork-ts-checker-webpack-plugin). Hi there! @@ -33,49 +34,5 @@ John also found that there are other file watching behaviours offered by TypeScr John did some rough benchmarking of the performance of the different options that be set on his PC running linux 64 bit. Here's how it came out: -| Value | CPU usage on idle | -| ------------------------------------- | ----------------- | -| TS default _(TSC_WATCHFILE not set)_ | **7\.4%** | -| UseFsEventsWithFallbackDynamicPolling | 0\.2% | -| UseFsEventsOnParentDirectory | 0\.2% | -| PriorityPollingInterval | **6\.2%** | -| DynamicPriorityPolling | 0\.5% | -| UseFsEvents | 0\.2% | - -As you can see, the default performs poorly. On the other hand, an option like `UseFsEventsWithFallbackDynamicPolling` is comparative greasy lightning. - -## workaround! - -To get this better experience into your world now, you could just set an environment variable on your machine. However, that doesn't scale; let's instead look at introducing the environment variable into your project explicitly. - -We're going to do this in a cross platform way using [`cross-env`](https://github.com/kentcdodds/cross-env). This is a mighty useful utility by Kent C Dodds which allows you to set environment variables in a way that will work on Windows, Mac and Linux. Imagine it as the jQuery of the environment variables world :-) - -Let's add it as a `devDependency`: - -``` -yarn add -D cross-env -``` - -Then take a look at your `package.json`. You've probably got a `start` script that looks something like this: - -``` -"start": "webpack-dev-server --progress --color --mode development --config webpack.config.development.js", -``` - -Or if you're a create-react-app user maybe this: - -``` -"start": "react-scripts start", -``` - -Prefix your `start` script with `cross-env TSC_WATCHFILE=UseFsEventsWithFallbackDynamicPolling`. This will, when run, initialise an environment variable called `TSC_WATCHFILE` with the value `UseFsEventsWithFallbackDynamicPolling`. Then it will start your development server as it did before. When TypeScript is fired up by webpack it will see this environment variable and use it to configure the file watching behaviour to one of the more performant options. - -So, in the case of a `create-react-app` user, your finished `start` script would look like this: - -``` -"start": "cross-env TSC_WATCHFILE=UseFsEventsWithFallbackDynamicPolling react-scripts start", -``` - -## The Future - -There's a possibility that the default watch behaviour may change in TypeScript in future. It's currently under discussion, you can read more [here](https://github.com/microsoft/TypeScript/issues/31048). +| Value | CPU usage on idle | +| diff --git a/blog-website/blog/2019-06-07-typescript-webpack-you-down-with-pnp/index.md b/blog-website/blog/2019-06-07-typescript-webpack-you-down-with-pnp/index.md index b80bbfaa313..f650f2d2bd2 100644 --- a/blog-website/blog/2019-06-07-typescript-webpack-you-down-with-pnp/index.md +++ b/blog-website/blog/2019-06-07-typescript-webpack-you-down-with-pnp/index.md @@ -4,6 +4,7 @@ title: 'TypeScript / webpack - you down with PnP? Yarn, you know me!' authors: johnnyreilly tags: [typescript, yarn, webpack, PnP] hide_table_of_contents: false +description: 'Yarn PnP speeds up module installation and eliminates node_modules. Converting to it is easy but some rough edges exist.' --- Yarn PnP is an innovation by the Yarn team designed to speed up module resolution by node. To quote the [(excellent) docs](https://yarnpkg.com/en/docs/pnp): diff --git a/blog-website/blog/2019-07-13-typescript-and-eslint-meet-fork-ts-checker-webpack-plugin/index.md b/blog-website/blog/2019-07-13-typescript-and-eslint-meet-fork-ts-checker-webpack-plugin/index.md index cb4e66d80b7..baf3720813a 100644 --- a/blog-website/blog/2019-07-13-typescript-and-eslint-meet-fork-ts-checker-webpack-plugin/index.md +++ b/blog-website/blog/2019-07-13-typescript-and-eslint-meet-fork-ts-checker-webpack-plugin/index.md @@ -4,6 +4,7 @@ title: 'Using TypeScript and ESLint with webpack (fork-ts-checker-webpack-plugin authors: johnnyreilly tags: [eslint, typescript, fork-ts-checker-webpack-plugin, webpack] hide_table_of_contents: false +description: 'The `fork-ts-checker-webpack-plugin` adds support for ESLint. Replace TSLint with related packages in `package.json` and configure with `.eslintrc.js`.' --- The `fork-ts-checker-webpack-plugin` has, since its inception, performed two classes of checking: diff --git a/blog-website/blog/2019-08-02-asp-net-authentication-hard-coding-claims/index.md b/blog-website/blog/2019-08-02-asp-net-authentication-hard-coding-claims/index.md index 3173819d2fb..a0f2f60fbf8 100644 --- a/blog-website/blog/2019-08-02-asp-net-authentication-hard-coding-claims/index.md +++ b/blog-website/blog/2019-08-02-asp-net-authentication-hard-coding-claims/index.md @@ -4,6 +4,7 @@ title: 'ASP.NET Core authentication: hard-coding a claim in development' authors: johnnyreilly tags: [ASP.Net Core, Authentication] hide_table_of_contents: false +description: 'The DevelopmentModeAuthenticationHandler allows ASP.NET Core developers to hard code user authentication claims during development, easing testing.' --- This post demonstrates how you can hard code user authentication claims in ASP.NET Core; a useful technique to facilate testing during development. diff --git a/blog-website/blog/2019-08-17-symbiotic-definitely-typed/index.md b/blog-website/blog/2019-08-17-symbiotic-definitely-typed/index.md index 187c6b97cd3..a8540876811 100644 --- a/blog-website/blog/2019-08-17-symbiotic-definitely-typed/index.md +++ b/blog-website/blog/2019-08-17-symbiotic-definitely-typed/index.md @@ -4,6 +4,7 @@ title: 'Symbiotic Definitely Typed' authors: johnnyreilly tags: [typescript, react-testing-library, Definitely Typed] hide_table_of_contents: false +description: 'New approach by `react-testing-library` improves TypeScript experience. Type definitions are maintained separately for `@testing-library/react`.' --- I did ponder calling this post "how to enable a good TypeScript developer experience for npm modules that aren't written in TypeScript"... Not exactly pithy though. diff --git a/blog-website/blog/2019-09-14-coming-soon-definitely-typed/index.md b/blog-website/blog/2019-09-14-coming-soon-definitely-typed/index.md index 2e21ded47b0..e03088a6c90 100644 --- a/blog-website/blog/2019-09-14-coming-soon-definitely-typed/index.md +++ b/blog-website/blog/2019-09-14-coming-soon-definitely-typed/index.md @@ -4,6 +4,7 @@ title: 'Coming Soon: Definitely Typed' authors: johnnyreilly tags: [typescript, Definitely Typed] hide_table_of_contents: false +description: 'The story of Definitely Typed, a project aiming to provide type definitions for JavaScript libraries, from creation to top 10 in GitHub in 2018.' --- A long time ago (well, 2012) in a galaxy far, far away (okay; Plovdiv, Bulgaria).... diff --git a/blog-website/blog/2019-09-30-start-me-up-ts-loader-meet-tsbuildinfo/index.md b/blog-website/blog/2019-09-30-start-me-up-ts-loader-meet-tsbuildinfo/index.md index aa0f2e75a24..4ddc6f3b4f8 100644 --- a/blog-website/blog/2019-09-30-start-me-up-ts-loader-meet-tsbuildinfo/index.md +++ b/blog-website/blog/2019-09-30-start-me-up-ts-loader-meet-tsbuildinfo/index.md @@ -4,6 +4,7 @@ title: 'Start Me Up: ts-loader meet .tsbuildinfo' authors: johnnyreilly tags: [ts-loader, typescript] hide_table_of_contents: false +description: 'TypeScript 3.4 introduced `.tsbuildinfo` files for faster compilations. With TypeScript 3.6, APIs landed to enable third party tool integration.' --- With TypeScript 3.4, [a new behaviour landed and a magical new file type appeared; `.tsbuildinfo`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html) @@ -16,25 +17,4 @@ With TypeScript 3.4, [a new behaviour landed and a magical new file type appeare > > These `.tsbuildinfo` files can be safely deleted and don’t have any impact on our code at runtime - they’re purely used to make compilations faster. -This was all very exciting, but until the release of TypeScript 3.6 there were no APIs available to allow third party tools like `ts-loader` to hook into them. The wait is over! Because with TypeScript 3.6 the APIs landed: [https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html#apis-to-support---build-and---incremental](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html#apis-to-support---build-and---incremental) - -This was the handiwork of the very excellent [@sheetalkamat](https://twitter.com/sheetalkamat) of the TypeScript team - you can see her PR here: [https://github.com/microsoft/TypeScript/pull/31432](https://github.com/microsoft/TypeScript/pull/31432) - -What's more, Sheetal took the PR for a test drive using `ts-loader`, and her hard work has just shipped with [`v6.2.0`](https://github.com/TypeStrong/ts-loader/releases/tag/v6.2.0): - -- [https://github.com/TypeStrong/ts-loader/pull/1012](https://github.com/TypeStrong/ts-loader/pull/1012) -- [https://github.com/TypeStrong/ts-loader/pull/1017](https://github.com/TypeStrong/ts-loader/pull/1017) - -If you're a `ts-loader` user, and you're using TypeScript 3.6+ then you can get the benefit of this now. That is, if you make use of the `experimentalWatchApi: true` option. With this set: - -1. ts-loader will both emit and consume the `.tsbuildinfo` artefact. - -2. This applies both when a project has `tsconfig.json` options `composite` or `incremental` set to `true`. - -3. The net result of people using this should be faster cold starts in build time where a previous compilation has taken place. - -## `ts-loader v7.0.0` - -We would love for you to take this new functionality for a spin. Partly because we think it will make your life better. And partly because we're planning to make using the watch API the default behaviour of `ts-loader` when we come to ship `v7.0.0`. - -If you can take this for a spin before we make that change we'd be so grateful. Thanks so much to Sheetal for persevering away on this feature. It's amazing work and so very appreciated. +This was all very exciting, but until the release of TypeScript 3.6 there were no APIs available to allow third party tools like `ts-loader` to hook into them. The wait is over! Because with TypeScript 3.6 the APIs landed: [https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html#apis-to-support diff --git a/blog-website/blog/2019-12-18-teams-notification-webhooks/index.md b/blog-website/blog/2019-12-18-teams-notification-webhooks/index.md index 424c08f4b13..5c1dafe7337 100644 --- a/blog-website/blog/2019-12-18-teams-notification-webhooks/index.md +++ b/blog-website/blog/2019-12-18-teams-notification-webhooks/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [Microsoft Teams, webhook] image: ./teams-notification.gif hide_table_of_contents: false +description: 'Learn how to automate notifications using Microsoft Teams and Markdown webhooks, and discover how to use ASP.Net Core to send notifications.' --- Teams notifications are mighty useful. You can send them using Markdown via a webhook. diff --git a/blog-website/blog/2020-01-02-ef-core-31-breaks-left-join-with-no-navigation-property/index.md b/blog-website/blog/2020-01-02-ef-core-31-breaks-left-join-with-no-navigation-property/index.md index 66f1011f3bd..13ce1c5a535 100644 --- a/blog-website/blog/2020-01-02-ef-core-31-breaks-left-join-with-no-navigation-property/index.md +++ b/blog-website/blog/2020-01-02-ef-core-31-breaks-left-join-with-no-navigation-property/index.md @@ -4,6 +4,7 @@ title: 'EF Core 3.1 breaks left join with no navigation property' authors: johnnyreilly tags: [Entity Framework] hide_table_of_contents: false +description: 'When upgrading from .NET Core 2.2 to 3.1, an invalid LEFT JOIN error was encountered. The issue was resolved by adding Navigation property.' --- Just recently my team took on the challenge of upgrading our codebase from .NET Core 2.2 to .NET Core 3.1. Along the way we encountered a quirky issue which caused us much befuddlement. Should you be befuddled too, then maybe this can help you. diff --git a/blog-website/blog/2020-01-21-license-to-kill-your-pwa/index.md b/blog-website/blog/2020-01-21-license-to-kill-your-pwa/index.md index ad572e89eb9..726dcde0a93 100644 --- a/blog-website/blog/2020-01-21-license-to-kill-your-pwa/index.md +++ b/blog-website/blog/2020-01-21-license-to-kill-your-pwa/index.md @@ -4,6 +4,7 @@ title: 'LICENSE to kill your PWA' authors: johnnyreilly tags: [] hide_table_of_contents: false +description: 'Creating `.LICENSE` files caused issues for a PWA. The `terser-webpack-plugin` was changed to make `.LICENSE.txt` files instead.' --- ## Update: 26/01/2020 - LICENSE to kill revoked! diff --git a/blog-website/blog/2020-01-31-from-create-react-app-to-pwa/index.md b/blog-website/blog/2020-01-31-from-create-react-app-to-pwa/index.md index 68ffe8041af..49cbb90d53a 100644 --- a/blog-website/blog/2020-01-31-from-create-react-app-to-pwa/index.md +++ b/blog-website/blog/2020-01-31-from-create-react-app-to-pwa/index.md @@ -4,6 +4,7 @@ title: 'From create-react-app to PWA' authors: johnnyreilly tags: [create-react-app, PWA] hide_table_of_contents: false +description: 'Learn how to build a basic Progressive Web App with React and TypeScript, as well as how to add features like code splitting and deployment.' --- Progressive Web Apps are a (terribly named) wonderful idea. You can build an app _once_ using web technologies which serves all devices and form factors. It can be accessible over the web, but also surface on the home screen of your Android / iOS device. That app can work offline, have a splash screen when it launches and have notifications too. diff --git a/blog-website/blog/2020-02-21-web-workers-comlink-typescript-and-react/index.md b/blog-website/blog/2020-02-21-web-workers-comlink-typescript-and-react/index.md index 9859dbf2b13..3b617bbfd42 100644 --- a/blog-website/blog/2020-02-21-web-workers-comlink-typescript-and-react/index.md +++ b/blog-website/blog/2020-02-21-web-workers-comlink-typescript-and-react/index.md @@ -4,6 +4,7 @@ title: 'Web Workers, comlink, TypeScript and React' authors: johnnyreilly tags: [typescript, React] hide_table_of_contents: false +description: 'Learn how to use Web Workers in a React app using Googles comlink library. Offload long-running calculations to a separate thread.' --- JavaScript is famously single threaded. However, if you're developing for the web, you may well know that this is not quite accurate. There are [`Web Workers`](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers): @@ -343,7 +344,7 @@ import { useEffect, useState, useMemo } from 'react'; */ export function useTakeALongTimeToAddTwoNumbers( number1: number, - number2: number + number2: number, ) { // We'll want to expose a wrapping object so we know when a calculation is in progress const [data, setData] = useState({ diff --git a/blog-website/blog/2020-03-29-offline-storage-in-pwa/index.md b/blog-website/blog/2020-03-29-offline-storage-in-pwa/index.md index 9b716cd4f6b..a77b0cd50b4 100644 --- a/blog-website/blog/2020-03-29-offline-storage-in-pwa/index.md +++ b/blog-website/blog/2020-03-29-offline-storage-in-pwa/index.md @@ -4,6 +4,7 @@ title: 'Offline storage in a PWA' authors: johnnyreilly tags: [PWA] hide_table_of_contents: false +description: 'Learn how to use IndexedDB for offline storage in your web app or PWA with the IDB-Keyval library and a React custom hook.' --- When you are building any kind of application it's typical to want to store information which persists beyond a single user session. Sometimes that will be information that you'll want to live in some kind of centralised database, but not always. @@ -82,7 +83,7 @@ async function testIDBKeyval() { await set('hello', 'world'); const whatDoWeHave = await get('hello'); console.log( - `When we queried idb-keyval for 'hello', we found: ${whatDoWeHave}` + `When we queried idb-keyval for 'hello', we found: ${whatDoWeHave}`, ); } @@ -179,7 +180,7 @@ function App() { useEffect(() => { get('darkModeOn').then((value) => // If a value is retrieved then use it; otherwise default to true - setDarkModeOn(value ?? true) + setDarkModeOn(value ?? true), ); }, [setDarkModeOn]); @@ -249,14 +250,14 @@ import { set, get } from 'idb-keyval'; export function usePersistedState( keyToPersistWith: string, - defaultState: TState + defaultState: TState, ) { const [state, setState] = useState(undefined); useEffect(() => { get(keyToPersistWith).then((retrievedState) => // If a value is retrieved then use it; otherwise default to defaultValue - setState(retrievedState ?? defaultState) + setState(retrievedState ?? defaultState), ); }, [keyToPersistWith, setState, defaultState]); @@ -265,7 +266,7 @@ export function usePersistedState( setState(newValue); set(keyToPersistWith, newValue); }, - [keyToPersistWith, setState] + [keyToPersistWith, setState], ); return [state, setPersistedValue] as const; @@ -290,7 +291,7 @@ const sharedStyles = { function App() { const [darkModeOn, setDarkModeOn] = usePersistedState( 'darkModeOn', - true + true, ); const handleOnChange = ({ target }: React.ChangeEvent) => diff --git a/blog-website/blog/2020-04-04-up-to-clouds/index.md b/blog-website/blog/2020-04-04-up-to-clouds/index.md index 1199444b45a..00bcd33921d 100644 --- a/blog-website/blog/2020-04-04-up-to-clouds/index.md +++ b/blog-website/blog/2020-04-04-up-to-clouds/index.md @@ -4,6 +4,7 @@ title: 'Up to the clouds!' authors: johnnyreilly tags: [docker, kubernetes, asp net core] hide_table_of_contents: false +description: 'Migrating ASP.NET Core app from on-prem to cloud with Kubernetes, Docker, Jenkins, Vault & Azure AD Single Sign-On for greater efficiency.' --- This last four months has been quite the departure for me. Most typically I find myself building applications; for this last period of time I've been taking the platform that I work on, and been migrating it from running on our on premise servers to running in the cloud. diff --git a/blog-website/blog/2020-05-21-autofac-webapplicationfactory-integration-tests/index.md b/blog-website/blog/2020-05-21-autofac-webapplicationfactory-integration-tests/index.md index c02fc8d6093..47ac110822b 100644 --- a/blog-website/blog/2020-05-21-autofac-webapplicationfactory-integration-tests/index.md +++ b/blog-website/blog/2020-05-21-autofac-webapplicationfactory-integration-tests/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [autofac, ASP.Net Core, Integration Testing] image: ./autofac-webapplicationfactory-tests.webp hide_table_of_contents: false +description: 'A bug in ASP.NET Core v3.0 thwarts swapping in Autofac as an IOC container in WebApplicationFactory tests. A workaround exists.' --- **Updated 2nd Oct 2020:** _for an approach that works with Autofac 6 and `ConfigureTestContainer` see [this post](../2020-10-02-autofac-6-integration-tests-and-generic-hosting/index.md)._ diff --git a/blog-website/blog/2020-08-09-devcontainers-aka-performance-in-secure/index.md b/blog-website/blog/2020-08-09-devcontainers-aka-performance-in-secure/index.md index 15ebb680070..bafb7428cfb 100644 --- a/blog-website/blog/2020-08-09-devcontainers-aka-performance-in-secure/index.md +++ b/blog-website/blog/2020-08-09-devcontainers-aka-performance-in-secure/index.md @@ -4,6 +4,7 @@ title: 'Devcontainers AKA performance in a secure sandbox' authors: johnnyreilly tags: [devcontainer] hide_table_of_contents: false +description: 'Speedy ASP.NET Core and JavaScript development is made possible by devcontainers, which isolate tools and code to improve productivity.' --- Many corporate machines arrive in engineers hands with a preponderance of pre-installed background tools; from virus checkers to backup utilities to port blockers; the list is long. @@ -104,113 +105,5 @@ services: The devcontainer will be built with the `Dockerfile.devcontainer` in the root of our repo. It relies upon your SSH keys and a `.env` file being available to be copied in: ``` -#----------------------------------------------------------------------------------------------------------- -# Based upon: https://github.com/microsoft/vscode-dev-containers/tree/master/containers/dotnetcore -#----------------------------------------------------------------------------------------------------------- -ARG VARIANT="3.1-bionic" -FROM mcr.microsoft.com/dotnet/core/sdk:${VARIANT} - -# Because MITM certificates -COPY ./docker/certs/. /usr/local/share/ca-certificates/ -ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/mitm.pem -RUN update-ca-certificates - -# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" -# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs -# will be updated to match your local UID/GID (when using the dockerFile property). -# See https://aka.ms/vscode-remote/containers/non-root-user for details. -ARG USERNAME=vscode -ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -# Options for common package install script -ARG INSTALL_ZSH="true" -ARG UPGRADE_PACKAGES="true" -ARG COMMON_SCRIPT_SOURCE="https://raw.githubusercontent.com/microsoft/vscode-dev-containers/master/script-library/common-debian.sh" -ARG COMMON_SCRIPT_SHA="dev-mode" - -# Settings for installing Node.js. -ARG INSTALL_NODE="true" -ARG NODE_SCRIPT_SOURCE="https://raw.githubusercontent.com/microsoft/vscode-dev-containers/master/script-library/node-debian.sh" -ARG NODE_SCRIPT_SHA="dev-mode" - -# ARG NODE_VERSION="lts/*" -ARG NODE_VERSION="14" -ENV NVM_DIR=/usr/local/share/nvm - -# Have nvm create a "current" symlink and add to path to work around https://github.com/microsoft/vscode-remote-release/issues/3224 -ENV NVM_SYMLINK_CURRENT=true -ENV PATH=${NVM_DIR}/current/bin:${PATH} - -# Configure apt and install packages -RUN apt-get update \ - && export DEBIAN_FRONTEND=noninteractive \ - # - # Verify git, common tools / libs installed, add/modify non-root user, optionally install zsh - && apt-get -y install --no-install-recommends curl ca-certificates 2>&1 \ - && curl -sSL ${COMMON_SCRIPT_SOURCE} -o /tmp/common-setup.sh \ - && ([ "${COMMON_SCRIPT_SHA}" = "dev-mode" ] || (echo "${COMMON_SCRIPT_SHA} */tmp/common-setup.sh" | sha256sum -c -)) \ - && /bin/bash /tmp/common-setup.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \ - # - # Install Node.js - && curl -sSL ${NODE_SCRIPT_SOURCE} -o /tmp/node-setup.sh \ - && ([ "${NODE_SCRIPT_SHA}" = "dev-mode" ] || (echo "${COMMON_SCRIPT_SHA} */tmp/node-setup.sh" | sha256sum -c -)) \ - && /bin/bash /tmp/node-setup.sh "${NVM_DIR}" "${NODE_VERSION}" "${USERNAME}" \ - # - # Clean up - && apt-get autoremove -y \ - && apt-get clean -y \ - && rm -f /tmp/common-setup.sh /tmp/node-setup.sh \ - && rm -rf /var/lib/apt/lists/* \ - # - # Workspace - && mkdir workspace \ - && chown -R ${NONROOT_USER}:root workspace - - -# Install Vim -RUN apt-get update && apt-get install -y \ - vim \ - && rm -rf /var/lib/apt/lists/* - -# Set up a timezone in the devcontainer - necessary for anything timezone dependent -ENV TZ=Europe/London -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \ - && apt-get update \ - && apt-get install --no-install-recommends -y \ - apt-utils \ - tzdata \ - && apt-get autoremove -y \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* - -ENV DOTNET_RUNNING_IN_CONTAINER=true - -# Copy across SSH keys so you can git clone -RUN mkdir /root/.ssh -RUN chmod 700 /root/.ssh - -COPY .ssh/id_rsa /root/.ssh -RUN chmod 600 /root/.ssh/id_rsa - -COPY .ssh/id_rsa.pub /root/.ssh -RUN chmod 644 /root/.ssh/id_rsa.pub - -COPY .ssh/known_hosts /root/.ssh -RUN chmod 644 /root/.ssh/known_hosts - -# Disable initial git clone prompt -RUN echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config - -# Copy across .env file so you can customise environment variables -# This will be copied into the root of the repo post git clone -COPY .env /.env -RUN chmod 644 /.env - -# Install dotnet entity framework tools -RUN dotnet tool install dotnet-ef --tool-path /usr/local/bin --version 3.1.2 +# ``` - -With this devcontainer you're good to go for an ASP.NET Core / JavaScript developer setup that is blazing fast! Remember to fire up Docker and give it goodly access to the resources of your host machine. All the CPUs, lots of memory and all the performance that there ought to be. - -_\* "virus checkers" is a euphemism here for all the background tools that may be running. It was that or calling them "we are legion"_ diff --git a/blog-website/blog/2020-09-04-why-your-team-needs-newsfeed/index.md b/blog-website/blog/2020-09-04-why-your-team-needs-newsfeed/index.md index b6ad2e45c69..a11c4ae8db8 100644 --- a/blog-website/blog/2020-09-04-why-your-team-needs-newsfeed/index.md +++ b/blog-website/blog/2020-09-04-why-your-team-needs-newsfeed/index.md @@ -3,6 +3,7 @@ slug: why-your-team-needs-newsfeed title: 'Why your team needs a newsfeed' authors: johnnyreilly hide_table_of_contents: false +description: 'A newsfeed was built to narrow the gap between an online platform team and their users. It generates real-time stories in Markdown with links.' --- I'm part of a team that builds an online platform. I'm often preoccupied by how to narrow the gap between our users and "us" - the people that build the platform. It's important we understand how people use and interact with what we've built. If we don't then we're liable to waste our time and energy building the wrong things. Or the wrong amount of the right things. diff --git a/blog-website/blog/2020-10-19-safari-empty-download-content-type/index.md b/blog-website/blog/2020-10-19-safari-empty-download-content-type/index.md index fce5dcd0257..c7d9d88fce5 100644 --- a/blog-website/blog/2020-10-19-safari-empty-download-content-type/index.md +++ b/blog-website/blog/2020-10-19-safari-empty-download-content-type/index.md @@ -4,6 +4,7 @@ title: 'Safari: The Mysterious Case of the Empty Download' authors: johnnyreilly tags: [Safari] hide_table_of_contents: false +description: 'Safari requires a `Content-Type` header in responses to avoid empty downloads. Providing a `Content-Type` header resolved an authentication issue.' --- Safari wants a `Content-Type` header in responses. Even if the response is `Content-Length: 0`. Without this, Safari can attempt to trigger an empty download. Don't argue; just go with it; some browsers are strange. diff --git a/blog-website/blog/2020-10-31-azure-devops-node-api-git-api-getrefs-wiki-api/index.md b/blog-website/blog/2020-10-31-azure-devops-node-api-git-api-getrefs-wiki-api/index.md index 0c3462ea5cb..e800c5c5a8f 100644 --- a/blog-website/blog/2020-10-31-azure-devops-node-api-git-api-getrefs-wiki-api/index.md +++ b/blog-website/blog/2020-10-31-azure-devops-node-api-git-api-getrefs-wiki-api/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [azure devops, Node.js] image: ./title-image.png hide_table_of_contents: false +description: 'The Azure DevOps Node.js client library has limitations and missing features. Workarounds are possible for using Azure DevOps REST API directly.' --- The Azure DevOps Client library for Node.js has limitations and missing features, `IGitApi.getRefs` is missing pagination and `IWikiApi` is missing page create or update. This post details some of these issues and illustrates a workaround using the Azure DevOps REST API. @@ -239,7 +240,7 @@ export async function getRefs({ 'Failed to load refs', err?.message, err?.response?.status, - err?.response?.data + err?.response?.data, ); throw new Error('Failed to load refs'); } diff --git a/blog-website/blog/2021-01-14-azure-easy-auth-and-roles-with-dotnet-and-core/index.md b/blog-website/blog/2021-01-14-azure-easy-auth-and-roles-with-dotnet-and-core/index.md index 6d751250768..ca39219149f 100644 --- a/blog-website/blog/2021-01-14-azure-easy-auth-and-roles-with-dotnet-and-core/index.md +++ b/blog-website/blog/2021-01-14-azure-easy-auth-and-roles-with-dotnet-and-core/index.md @@ -4,6 +4,7 @@ title: 'Azure App Service, Easy Auth and Roles with .NET' authors: johnnyreilly tags: [Azure, authorization, authentication, easy auth] hide_table_of_contents: false +description: '"Easy Auth" in Azure App Service doesnt currently work with .NET Core and .NET due to discrepancies. Open-source middleware can help solve the issue.' --- Azure App Service has a feature which is intended to allow Authentication and Authorization to be applied outside of your application code. It's called ["Easy Auth"](https://docs.microsoft.com/en-us/azure/app-service/overview-authentication-authorization). Unfortunately, in the context of App Services it doesn't work with .NET Core and .NET. Perhaps it would be better to say: of the various .NETs, it supports .NET Framework. [To quote the docs](https://docs.microsoft.com/en-us/azure/app-service/overview-authentication-authorization#userapplication-claims): diff --git a/blog-website/blog/2021-01-17-azure-easy-auth-and-roles-with-net-and-microsoft-identity-web/index.md b/blog-website/blog/2021-01-17-azure-easy-auth-and-roles-with-net-and-microsoft-identity-web/index.md index 4cbeb95c0b5..7a971e28de6 100644 --- a/blog-website/blog/2021-01-17-azure-easy-auth-and-roles-with-net-and-microsoft-identity-web/index.md +++ b/blog-website/blog/2021-01-17-azure-easy-auth-and-roles-with-net-and-microsoft-identity-web/index.md @@ -4,6 +4,7 @@ title: 'Azure App Service, Easy Auth and Roles with .NET and Microsoft.Identity. authors: johnnyreilly tags: [Azure, easy auth, ASP.NET, authorization] hide_table_of_contents: false +description: 'The `Microsoft.Identity.Web` library has authorization issues with roles. A `IClaimsTransformation` can map claims to fix the problem.' --- [I wrote recently about how to get Azure App Service Easy Auth to work with roles](../2021-01-14-azure-easy-auth-and-roles-with-dotnet-and-core/index.md). This involved borrowing the approach used by [MaximeRouiller.Azure.AppService.EasyAuth](https://github.com/MaximRouiller/MaximeRouiller.Azure.AppService.EasyAuth). diff --git a/open-ai-description/summarizer.ts b/open-ai-description/summarizer.ts index 653eef6a05c..3a767542e9e 100644 --- a/open-ai-description/summarizer.ts +++ b/open-ai-description/summarizer.ts @@ -46,7 +46,7 @@ export async function produceSummary(article: string): Promise { const messages = [ { role: 'system', - content: `You are a summarizer. You will be given the text of an article and will produce a summary / meta description which summarizes the article. The summary / meta descriptions you produce must be between ${minChars} and ${maxChars} characters long. If they are longer or shorter than that they cannot be used. Avoid using the \`'\` character as it is not supported by the blog website - you may use the \`’\` character instead.`, + content: `You are a summarizer. You will be given the text of an article and will produce a summary / meta description which summarizes the article. The summary / meta descriptions you produce must be between ${minChars} and ${maxChars} characters long. If they are longer or shorter than that they cannot be used. Avoid using the \`'\` character as it is not supported by the blog website - you may use the \`’\` character instead. Do not use the expression "the author" or "the writer" in your summary.`, }, { role: 'user', From c4df5d34ed8b7387dcadac17740d3c92eba3d812 Mon Sep 17 00:00:00 2001 From: johnnyreilly Date: Mon, 28 Aug 2023 17:33:42 +0100 Subject: [PATCH 09/14] feat: more descriptions --- .../index.md | 17 ++++++------- .../index.md | 3 ++- .../index.md | 1 + .../index.md | 1 + .../index.md | 24 +++++++++++-------- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 3 ++- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../2016-10-05-react-component-curry/index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 3 ++- .../index.md | 12 ++++++---- .../index.md | 1 + .../index.md | 3 ++- .../blog/2017-02-01-hands-free-https/index.md | 1 + .../index.md | 1 + .../index.md | 6 ++++- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 5 ++-- .../index.md | 1 + .../index.md | 5 ++-- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + 40 files changed, 80 insertions(+), 31 deletions(-) diff --git a/blog-website/blog/2015-09-23-authoring-npm-modules-with-typescript/index.md b/blog-website/blog/2015-09-23-authoring-npm-modules-with-typescript/index.md index d886fde42bd..20e0437214a 100644 --- a/blog-website/blog/2015-09-23-authoring-npm-modules-with-typescript/index.md +++ b/blog-website/blog/2015-09-23-authoring-npm-modules-with-typescript/index.md @@ -4,6 +4,7 @@ title: "Definitely Typed Shouldn't Exist" authors: johnnyreilly tags: [npm, DefinitelyTyped, typescript] hide_table_of_contents: false +description: 'Using TypeScript definition files with npm packages can produce accurate typing information. Making npm a first class citizen may replace Definitely Typed.' --- OK - the title's total clickbait but stay with me; there's a point here. @@ -166,7 +167,7 @@ function determineRequiredCldrData(globalizeOptions) { globalizeOptions, _populateDependencyCurrier('json', function (json) { return json.dependency; - }) + }), ); } @@ -177,8 +178,8 @@ function determineRequiredCldrGlobalizeFiles(globalizeOptions) { 'cldrGlobalizeFiles', function (cldrGlobalizeFile) { return cldrGlobalizeFile; - } - ) + }, + ), ); } @@ -401,7 +402,7 @@ function determineRequiredCldrData(globalizeOptions) { globalizeOptions, _populateDependencyCurrier('json', function (json) { return json.dependency; - }) + }), ); } exports.determineRequiredCldrData = determineRequiredCldrData; @@ -417,8 +418,8 @@ function determineRequiredCldrGlobalizeFiles(globalizeOptions) { 'cldrGlobalizeFiles', function (cldrGlobalizeFile) { return cldrGlobalizeFile; - } - ) + }, + ), ); } exports.determineRequiredCldrGlobalizeFiles = @@ -444,7 +445,7 @@ export interface Options { * @param options The globalize modules being used. */ export declare function determineRequiredCldrData( - globalizeOptions: Options + globalizeOptions: Options, ): string[]; /** * The string array returned will contain a list of the required cldr / globalize files you need, listed in the order they are required. @@ -452,7 +453,7 @@ export declare function determineRequiredCldrData( * @param options The globalize modules being used. */ export declare function determineRequiredCldrGlobalizeFiles( - globalizeOptions: Options + globalizeOptions: Options, ): string[]; ``` diff --git a/blog-website/blog/2015-10-05-jquery-validation-globalize-hits-10/index.md b/blog-website/blog/2015-10-05-jquery-validation-globalize-hits-10/index.md index f70a2e4f21d..306b951d840 100644 --- a/blog-website/blog/2015-10-05-jquery-validation-globalize-hits-10/index.md +++ b/blog-website/blog/2015-10-05-jquery-validation-globalize-hits-10/index.md @@ -4,6 +4,7 @@ title: 'jQuery Validation Globalize hits 1.0' authors: johnnyreilly tags: [Globalize, jQuery Validation] hide_table_of_contents: false +description: 'jQuery Validation Globalize plugin now supports Globalize 1.x, with minor code changes. Users can customize date parsing format.' --- This is just a quick post - the tl;dr is this: jQuery Validation Globalize has been ported to Globalize 1.x. Yay! In one of those twists of fate I'm not actually using this plugin in my day job anymore but I thought it might be useful to other people. So here you go. You can read more about this plugin in an [older post](../2012-09-06-globalize-and-jquery-validate/index.md) and you can see a demo of it in action [here](http://johnnyreilly.github.io/jQuery.Validation.Unobtrusive.Native/AdvancedDemo/Globalize.html). @@ -82,7 +83,7 @@ To this: $.validator.methods.date = function (value, element) { var val = Globalize.parseDate( value, - $.validator.methods.dateGlobalizeOptions.dateParseFormat + $.validator.methods.dateGlobalizeOptions.dateParseFormat, ); return this.optional(element) || val instanceof Date; }; diff --git a/blog-website/blog/2015-10-23-the-names-have-been-changed/index.md b/blog-website/blog/2015-10-23-the-names-have-been-changed/index.md index 7158ceb3973..348f1285a27 100644 --- a/blog-website/blog/2015-10-23-the-names-have-been-changed/index.md +++ b/blog-website/blog/2015-10-23-the-names-have-been-changed/index.md @@ -4,6 +4,7 @@ title: 'The Names Have Been Changed...' authors: johnnyreilly tags: [] hide_table_of_contents: false +description: 'John changes the domain name of his blog from .io to .com to save money and has set up a redirect from old site to new one.' --- ...to protect my wallet. diff --git a/blog-website/blog/2015-11-30-iqueryable-ienumerable-hmmm/index.md b/blog-website/blog/2015-11-30-iqueryable-ienumerable-hmmm/index.md index a1648164d19..012907015b1 100644 --- a/blog-website/blog/2015-11-30-iqueryable-ienumerable-hmmm/index.md +++ b/blog-website/blog/2015-11-30-iqueryable-ienumerable-hmmm/index.md @@ -4,6 +4,7 @@ title: 'IQueryable... IEnumerable... Hmmm...' authors: johnnyreilly tags: [LINQ] hide_table_of_contents: false +description: 'The debate surrounding passing IQueryable as IEnumerable is discussed. Changing the method signature is proposed as a solution.' --- So there I was, tip-tapping away at my keyboard when I became aware of the slowly loudening noise of a debate. It wasn't about poverty, war, civil rights or anything like that. No; this was far more contentious. It was about the behaviour of `IQueryable<T>` when mixed with `IEnumerable<T>`. I know, right, how could I not get involved? diff --git a/blog-website/blog/2015-12-16-es6-typescript-babel-react-flux-karma/index.md b/blog-website/blog/2015-12-16-es6-typescript-babel-react-flux-karma/index.md index f32892dd936..d250cb137ce 100644 --- a/blog-website/blog/2015-12-16-es6-typescript-babel-react-flux-karma/index.md +++ b/blog-website/blog/2015-12-16-es6-typescript-babel-react-flux-karma/index.md @@ -4,6 +4,7 @@ title: 'ES6 + TypeScript + Babel + React + Flux + Karma: The Secret Recipe' authors: johnnyreilly tags: [ES6, Karma, React, ts-loader, webpack] hide_table_of_contents: false +description: 'Learn how to set up a powerful TypeScript-React workflow with Webpack, gulp, Karma, and inject in this comprehensive article.' --- I wrote [a while ago](../2015-09-10-things-done-changed/index.md) about how I was using some different tools in a current project: @@ -65,7 +66,7 @@ gulp.task( webpack.build().then(function () { done(); }); - } + }, ); gulp.task( @@ -73,7 +74,7 @@ gulp.task( ['delete-dist', 'build-process.env.NODE_ENV'], function () { staticFiles.build(); - } + }, ); gulp.task('build', ['build-js', 'build-other', 'lint'], function () { @@ -92,7 +93,7 @@ gulp.task('watch', ['delete-dist'], function () { ]) .then(function () { gutil.log( - 'Now that initial assets (js and css) are generated inject will start...' + 'Now that initial assets (js and css) are generated inject will start...', ); inject.watch(postInjectCb); }) @@ -165,7 +166,7 @@ function buildProduction(done) { filename: 'vendor.[hash].js', }), new webpack.optimize.DedupePlugin(), - new webpack.optimize.UglifyJsPlugin() + new webpack.optimize.UglifyJsPlugin(), ); // run webpack @@ -177,7 +178,7 @@ function buildProduction(done) { '[webpack:build]', stats.toString({ colors: true, - }) + }), ); if (done) { @@ -198,7 +199,10 @@ function createDevCompiler() { name: 'vendor', filename: 'vendor.js', }), - new WebpackNotifierPlugin({ title: 'Webpack build', excludeWarnings: true }) + new WebpackNotifierPlugin({ + title: 'Webpack build', + excludeWarnings: true, + }), ); // create a single instance of the compiler to allow caching @@ -216,7 +220,7 @@ function buildDevelopment(done, devCompiler) { stats.toString({ chunks: false, // dial down the output from webpack (it can be noisy) colors: true, - }) + }), ); if (done) { @@ -327,7 +331,7 @@ module.exports = { Your compiled output needs to be referenced from some kind of HTML page. So we've got this: ```html - + @@ -377,7 +381,7 @@ function injectIndex(options) { './dist/scripts/vendor*.js', './dist/scripts/main*.js', ], - { read: false } + { read: false }, ); return target @@ -397,7 +401,7 @@ function injectIndex(options) { ignorePath: '/dist/', addRootSlash: false, removeTags: true, - }) + }), ) .pipe(gulp.dest('./dist')); } diff --git a/blog-website/blog/2015-12-20-live-reload-considered-harmful/index.md b/blog-website/blog/2015-12-20-live-reload-considered-harmful/index.md index 5332b077d22..855d46f85c2 100644 --- a/blog-website/blog/2015-12-20-live-reload-considered-harmful/index.md +++ b/blog-website/blog/2015-12-20-live-reload-considered-harmful/index.md @@ -4,6 +4,7 @@ title: 'Live Reload Considered Harmful' authors: johnnyreilly tags: [] hide_table_of_contents: false +description: 'Live Reload is a popular development tool that refreshes web pages automatically, however, its usefulness is questionable. It can disrupt app design.' --- I've seen it go by many names; [live reload](http://livereload.com/), hot reload, [browser sync](https://browsersync.io/)... the list goes on. It's been the subject of a million demos. It's the focus of a thousand npm packages. Someone tweaks a file and... wait for it... _doesn't have to refresh their browser to see the changes_... The future is now! diff --git a/blog-website/blog/2016-01-01-usestaticfiles-for-aspnet-vold/index.md b/blog-website/blog/2016-01-01-usestaticfiles-for-aspnet-vold/index.md index df3d33a77cd..d0134b64c47 100644 --- a/blog-website/blog/2016-01-01-usestaticfiles-for-aspnet-vold/index.md +++ b/blog-website/blog/2016-01-01-usestaticfiles-for-aspnet-vold/index.md @@ -4,6 +4,7 @@ title: 'UseStaticFiles for ASP.Net Framework' authors: johnnyreilly tags: [ASP.NET] hide_table_of_contents: false +description: 'Learn how to prevent exposing static files to the public when working with ASP.Net Framework. Discover how to implement an allowlist approach.' --- This is a guide on how _not_ to expose all your static files to the world at large when working with the ASP.Net Framework. How to move from a blocklisting approach to a allowlisting approach. diff --git a/blog-website/blog/2016-01-14-coded-ui-and-curse-of-docking-station/index.md b/blog-website/blog/2016-01-14-coded-ui-and-curse-of-docking-station/index.md index 855538241d0..7b9804324c7 100644 --- a/blog-website/blog/2016-01-14-coded-ui-and-curse-of-docking-station/index.md +++ b/blog-website/blog/2016-01-14-coded-ui-and-curse-of-docking-station/index.md @@ -4,6 +4,7 @@ title: 'Coded UI and the Curse of the Docking Station' authors: johnnyreilly tags: [Surface Pro 3, Coded UI] hide_table_of_contents: false +description: 'Coded UI tests struggle with docking stations due to resolution changes, causing the mouse to miss the element its aiming for.' --- I’ve a love / hate relationship with Coded UI. Well hate / hate might be more accurate. Hate perhaps married with a very grudging respect still underpinned by a wary bitterness. Yes, that’s about the size of it. Why? Well, when Coded UI works, it’s fab. But it’s flaky as anything. Anybody who’s used the technology is presently nodding sagely and holding back the tears. It’s all a bit... tough. diff --git a/blog-website/blog/2016-02-01-tfs-2012-net-45-and-c-6/index.md b/blog-website/blog/2016-02-01-tfs-2012-net-45-and-c-6/index.md index c89b2b9e391..9f3e5ff6514 100644 --- a/blog-website/blog/2016-02-01-tfs-2012-net-45-and-c-6/index.md +++ b/blog-website/blog/2016-02-01-tfs-2012-net-45-and-c-6/index.md @@ -4,6 +4,7 @@ title: 'TFS 2012, .NET 4.5 and C# 6' authors: johnnyreilly tags: [C#, .NET Framework, TFS] hide_table_of_contents: false +description: 'Use C# 6 features on .NET 4.5 with Visual Studio 2015, set MSBuild Arguments and install Microsoft.Net.Compilers on the old build server.' --- So, you want to use C# 6 language features and you’re working on an older project that’s still rocking .NET 4.5. Well, with [some caveats](http://stackoverflow.com/a/28921749/761388), you can. diff --git a/blog-website/blog/2016-02-19-visual-studio-tsconfigjson-and-external/index.md b/blog-website/blog/2016-02-19-visual-studio-tsconfigjson-and-external/index.md index 7eef7b60f51..138df7399fe 100644 --- a/blog-website/blog/2016-02-19-visual-studio-tsconfigjson-and-external/index.md +++ b/blog-website/blog/2016-02-19-visual-studio-tsconfigjson-and-external/index.md @@ -4,6 +4,7 @@ title: 'Visual Studio, tsconfig.json and external TypeScript compilation' authors: johnnyreilly tags: [TFS, Visual Studio, tsconfig.json, typescript, Webpack] hide_table_of_contents: false +description: 'Visual Studio will not gain support for tsconfig.json until TypeScript 1.8, so using external compilation may be preferable.' --- TypeScript first gained support for [`tsconfig.json`](https://github.com/Microsoft/TypeScript/wiki/tsconfig.json) back with the [1\.5 release](https://blogs.msdn.microsoft.com/typescript/2015/07/20/announcing-typescript-1-5/). However, to my lasting regret and surprise Visual Studio will not be gaining meaningful support for it until [TypeScript 1.8](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#improved-support-for-tsconfigjson-in-visual-studio-2015) ships. However, if you want it now, it's already available to use in [beta](https://blogs.msdn.microsoft.com/typescript/2016/01/28/announcing-typescript-1-8-beta/). diff --git a/blog-website/blog/2016-02-29-creating-angular-ui-routes-in-controller/index.md b/blog-website/blog/2016-02-29-creating-angular-ui-routes-in-controller/index.md index 0e1d2dfea25..6a5e51a57e9 100644 --- a/blog-website/blog/2016-02-29-creating-angular-ui-routes-in-controller/index.md +++ b/blog-website/blog/2016-02-29-creating-angular-ui-routes-in-controller/index.md @@ -4,6 +4,7 @@ title: 'Creating Angular UI Routes in the Controller' authors: johnnyreilly tags: [AngularJS] hide_table_of_contents: false +description: 'Dont let your Angular UI Router link get too big - move the URL generation to the controller! Use the $state.href() method instead of ui-sref.' --- So you're creating a link with the Angular UI Router. You're passing more than a few parameters and it's getting kinda big. Something like this: diff --git a/blog-website/blog/2016-03-04-tfs-2012-meet-powershell-karma-and-buildnumber/index.md b/blog-website/blog/2016-03-04-tfs-2012-meet-powershell-karma-and-buildnumber/index.md index fa7fe081461..a038e6fb7c3 100644 --- a/blog-website/blog/2016-03-04-tfs-2012-meet-powershell-karma-and-buildnumber/index.md +++ b/blog-website/blog/2016-03-04-tfs-2012-meet-powershell-karma-and-buildnumber/index.md @@ -4,6 +4,7 @@ title: 'TFS 2012 meet PowerShell, Karma and BuildNumber' authors: johnnyreilly tags: [TFS] hide_table_of_contents: false +description: 'This guide explains how to run PowerShell scripts for TFS 2012 build processes and how to publish Karma test results in the platform.' --- To my lasting regret, TFS 2012 has no direct support for PowerShell. Such a shame as PowerShell scripts can do a lot of heavy lifting in a build process. Well, here we're going to brute force TFS 2012 into running PowerShell scripts. And along the way we'll also get Karma test results publishing into TFS 2012 as an example usage. Nice huh? Let's go! @@ -44,7 +45,7 @@ And _replace_ it with this: '/p:SkipInvalidConfigurations=true /p:BuildNumber={1} /p:BuildDefinitionName={2} {0}', MSBuildArguments, BuildDetail.BuildNumber, - BuildDetail.BuildDefinition.Name + BuildDetail.BuildDefinition.Name, ), ]; ``` diff --git a/blog-website/blog/2016-03-17-atom-recovering-from-corrupted-packages/index.md b/blog-website/blog/2016-03-17-atom-recovering-from-corrupted-packages/index.md index 45b787cd5ef..22885ba02c6 100644 --- a/blog-website/blog/2016-03-17-atom-recovering-from-corrupted-packages/index.md +++ b/blog-website/blog/2016-03-17-atom-recovering-from-corrupted-packages/index.md @@ -4,6 +4,7 @@ title: 'Atom - Recovering from Corrupted Packages' authors: johnnyreilly tags: [Atom] hide_table_of_contents: false +description: 'Atom packages corrupt? Locate `.apm` folder at `[Your name]/.atom`, then delete. On reopening Atom, packages will be restored.' --- Every now and then when I try and update my packages in [Atom](https://atom.io/) I find this glaring back at me: diff --git a/blog-website/blog/2016-03-22-concatting-ienumerables-in-csharp/index.md b/blog-website/blog/2016-03-22-concatting-ienumerables-in-csharp/index.md index e73d6437418..54cf8488a4b 100644 --- a/blog-website/blog/2016-03-22-concatting-ienumerables-in-csharp/index.md +++ b/blog-website/blog/2016-03-22-concatting-ienumerables-in-csharp/index.md @@ -4,6 +4,7 @@ title: 'Concatting IEnumerables in C#' authors: johnnyreilly tags: [C#] hide_table_of_contents: false +description: 'Author proposes clean alternatives to `IEnumerable`s concatenation which entail creating custom extensions & using nulls for null-conditional operator.' --- I hate LINQ's [`Enumerable.Concat`](https://msdn.microsoft.com/en-us/library/bb302894%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396) when bringing together `IEnumerable`s. Not the behaviour (I love that!) but rather how code ends up looking when you use it. Consider this: diff --git a/blog-website/blog/2016-04-25-instant-stubs-with-jsonnet/index.md b/blog-website/blog/2016-04-25-instant-stubs-with-jsonnet/index.md index e8d2b8d3604..9336f33ba0b 100644 --- a/blog-website/blog/2016-04-25-instant-stubs-with-jsonnet/index.md +++ b/blog-website/blog/2016-04-25-instant-stubs-with-jsonnet/index.md @@ -4,6 +4,7 @@ title: 'Instant Stubs with JSON.Net (just add hot water)' authors: johnnyreilly tags: [unit testing, json.net] hide_table_of_contents: false +description: 'A utility class can create stubs to test an untested system with complex I/O. Serializing complex data to JSON files eases the process.' --- I'd like you to close your eyes and imagine a scenario. You're handed a prototype system. You're told it works. It has no documentation. It has 0 unit tests. The hope is that you can take it on, refactor it, make it better and (crucially) not break it. Oh, and you don't really understand what the code does or why it does it either; information on that front is, alas, sorely lacking. diff --git a/blog-website/blog/2016-05-13-inlining-angular-templates-with-webpack/index.md b/blog-website/blog/2016-05-13-inlining-angular-templates-with-webpack/index.md index 796a6dfb649..deb543f1104 100644 --- a/blog-website/blog/2016-05-13-inlining-angular-templates-with-webpack/index.md +++ b/blog-website/blog/2016-05-13-inlining-angular-templates-with-webpack/index.md @@ -4,6 +4,7 @@ title: 'Inlining Angular Templates with WebPack and TypeScript' authors: johnnyreilly tags: [AngularJS, webpack] hide_table_of_contents: false +description: '`raw-loader` package in Webpack configuration for Angular 1.x projects preloads templates and enables compile-time error checking.' --- This technique actually applies to pretty much any web stack where you have to supply templates; it just so happens that I'm using Angular 1.x in this case. Also I have an extra technique which is useful to handle the [ng-include](https://docs.angularjs.org/api/ng/directive/ngInclude) scenario. diff --git a/blog-website/blog/2016-05-24-the-mysterious-case-of-webpack-angular-and-jquery/index.md b/blog-website/blog/2016-05-24-the-mysterious-case-of-webpack-angular-and-jquery/index.md index b82aac80ff8..c2235a4c0f3 100644 --- a/blog-website/blog/2016-05-24-the-mysterious-case-of-webpack-angular-and-jquery/index.md +++ b/blog-website/blog/2016-05-24-the-mysterious-case-of-webpack-angular-and-jquery/index.md @@ -4,6 +4,7 @@ title: 'The Mysterious Case of webpack, AngularJS and jQuery' authors: johnnyreilly tags: [jquery, AngularJS, webpack] hide_table_of_contents: false +description: 'Angular can use jQuery instead of jQLite, but this becomes complicated when using webpack. We need to use the ProvidePlugin function in webpack.config.js.' --- You may know that [Angular ships with a cutdown version of jQuery called jQLite](https://docs.angularjs.org/api/ng/function/angular.element). It's still possible to use the full-fat jQuery; to quote the docs: diff --git a/blog-website/blog/2016-06-02-create-es2015-map-from-array-in-typescript/index.md b/blog-website/blog/2016-06-02-create-es2015-map-from-array-in-typescript/index.md index 562fe6d97f5..630b1ef016d 100644 --- a/blog-website/blog/2016-06-02-create-es2015-map-from-array-in-typescript/index.md +++ b/blog-website/blog/2016-06-02-create-es2015-map-from-array-in-typescript/index.md @@ -4,6 +4,7 @@ title: 'Creating an ES2015 Map from an Array in TypeScript' authors: johnnyreilly tags: [typescript, ES6, ES2015] hide_table_of_contents: false +description: 'TypeScript `Map` initialization from an `Array` is discussed with a workaround using a type assertion of ` as [string, string]`.' --- I'm a great lover of ES2015's [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). However, just recently I tumbled over something I find a touch inconvenient about how you initialise a new `Map` from the contents of an `Array` in TypeScript. diff --git a/blog-website/blog/2016-08-19-the-ternary-operator-meets-destructuring/index.md b/blog-website/blog/2016-08-19-the-ternary-operator-meets-destructuring/index.md index 12892f22d27..ee4f0490745 100644 --- a/blog-website/blog/2016-08-19-the-ternary-operator-meets-destructuring/index.md +++ b/blog-website/blog/2016-08-19-the-ternary-operator-meets-destructuring/index.md @@ -4,6 +4,7 @@ title: 'The Ternary Operator <3 Destructuring' authors: johnnyreilly tags: [typescript, ES2015] hide_table_of_contents: false +description: 'ES2015 destructuring allows setting multiple variables using the ternary operator. Change the return type of each branch to an object for this to work.' --- I'm addicted to the [ternary operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator). For reasons I can't explain, I cannot get enough of: diff --git a/blog-website/blog/2016-09-22-typescript-20-es2016-and-babel/index.md b/blog-website/blog/2016-09-22-typescript-20-es2016-and-babel/index.md index 16a2d7c63b6..6d7571d75ee 100644 --- a/blog-website/blog/2016-09-22-typescript-20-es2016-and-babel/index.md +++ b/blog-website/blog/2016-09-22-typescript-20-es2016-and-babel/index.md @@ -4,6 +4,7 @@ title: 'TypeScript 2.0, ES2016 and Babel' authors: johnnyreilly tags: [typescript, Babel, ES2016] hide_table_of_contents: false +description: 'Upgrading from ES2015 to ES2016 using TypeScript compiler and Babel can be done in a few steps, including a change to tsconfig.json.' --- [TypeScript 2.0 has shipped!](https://blogs.msdn.microsoft.com/typescript/2016/09/22/announcing-typescript-2-0/) Naturally I'm excited. For some time I've been using TypeScript to emit ES2015 code which I pass onto Babel to transpile to ES "old school". You can see how [here](../2015-12-16-es6-typescript-babel-react-flux-karma/index.md). diff --git a/blog-website/blog/2016-10-05-react-component-curry/index.md b/blog-website/blog/2016-10-05-react-component-curry/index.md index 04732edfa52..d0f06ea1469 100644 --- a/blog-website/blog/2016-10-05-react-component-curry/index.md +++ b/blog-website/blog/2016-10-05-react-component-curry/index.md @@ -4,6 +4,7 @@ title: 'React Component Curry' authors: johnnyreilly tags: [jsx, React, stateless functional components] hide_table_of_contents: false +description: 'React 0.14 introduces stateless functional components to reduce code for components where state isnt required, while also allowing for currying.' --- Everyone loves curry don't they? I don't know about you but I'm going for one on Friday. diff --git a/blog-website/blog/2016-11-01-but-you-cant-die-i-love-you-ts-loader/index.md b/blog-website/blog/2016-11-01-but-you-cant-die-i-love-you-ts-loader/index.md index 4dd1baa1ca3..aea1060fe32 100644 --- a/blog-website/blog/2016-11-01-but-you-cant-die-i-love-you-ts-loader/index.md +++ b/blog-website/blog/2016-11-01-but-you-cant-die-i-love-you-ts-loader/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [open source, typescript, ts-loader] image: ./title-image.png hide_table_of_contents: false +description: 'How John Reilly becomes main caretaker of ts-loader, fixing bugs and actively maintaining the project to encourage communal contributions.' --- That's how I was feeling on the morning of October 6th 2016. I'd been feeling that way for some time. The target of my concern? [ts-loader](https://github.com/TypeStrong/ts-loader). ts-loader is a loader for [webpack; the module bundler](https://webpack.github.io/). ts-loader allows you use TypeScript with webpack. I'd been a merry user of it for at least a year or so. But, at that point, all was not well in the land of ts-loader. Come with me and I'll tell you a story... diff --git a/blog-website/blog/2016-11-12-my-subconscious-is-better-developer/index.md b/blog-website/blog/2016-11-12-my-subconscious-is-better-developer/index.md index be0b9936e87..179338baea4 100644 --- a/blog-website/blog/2016-11-12-my-subconscious-is-better-developer/index.md +++ b/blog-website/blog/2016-11-12-my-subconscious-is-better-developer/index.md @@ -4,6 +4,7 @@ title: 'My Subconscious is a Better Developer Than I Am' authors: johnnyreilly tags: [] hide_table_of_contents: false +description: 'In which I wonder if my subconscious is a better developer than I am, as solutions seem to come to mind, bypassing the work I consciously put in.' --- Occasionally I flatter myself that I'm alright at this development lark. Such egotistical talk is foolish. What makes me pause even more when I consider the proposition is this: my subconscious is a better developer than I am. diff --git a/blog-website/blog/2016-12-11-webpack-syncing-enhanced-resolve/index.md b/blog-website/blog/2016-12-11-webpack-syncing-enhanced-resolve/index.md index 8e13fa9b845..4090885bc83 100644 --- a/blog-website/blog/2016-12-11-webpack-syncing-enhanced-resolve/index.md +++ b/blog-website/blog/2016-12-11-webpack-syncing-enhanced-resolve/index.md @@ -4,6 +4,7 @@ title: 'webpack: syncing the enhanced-resolve' authors: johnnyreilly tags: [webpack] hide_table_of_contents: false +description: 'How to create a sync webpack resolver instead of the default async resolver using `enhanced-resolve`.' --- Like Captain Ahab I resolve to sync the white whale that is webpack's [`enhanced-resolve`](https://github.com/webpack/enhanced-resolve)... English you say? Let me start again: @@ -48,7 +49,7 @@ Put it all together and what have you got? const resolvedFileName = resolveSync( undefined, 'C:source\ts-loader.test\babel-issue92', - './submodule/submodule' + './submodule/submodule', ); // resolvedFileName: C:\source\ts-loader\.test\babel-issue92\submodule\submodule.tsx diff --git a/blog-website/blog/2016-12-19-using-ts-loader-with-webpack-2/index.md b/blog-website/blog/2016-12-19-using-ts-loader-with-webpack-2/index.md index 87fe2dc5a8a..15ab67ff0d1 100644 --- a/blog-website/blog/2016-12-19-using-ts-loader-with-webpack-2/index.md +++ b/blog-website/blog/2016-12-19-using-ts-loader-with-webpack-2/index.md @@ -4,6 +4,7 @@ title: 'Using ts-loader with webpack 2' authors: johnnyreilly tags: [ts-loader, webpack] hide_table_of_contents: false +description: 'TypeScript loader ts-loader has made its loader compatible with webpack 2. The update allows greater compatibility between the two applications.' --- Hands up, despite being one of the maintainers of [ts-loader](https://github.com/TypeStrong/ts-loader) (a TypeScript loader for webpack) I have not been tracking webpack v2. My reasons? Well, I'm keen on cutting edge but bleeding edge is often not a ton of fun as dealing with regularly breaking changes is frustrating. I'm generally happy to wait for things to settle down a bit before leaping aboard. However, [webpack 2 RC'd last week](https://github.com/webpack/webpack/releases/tag/v2.2.0-rc.0) and so it's time to take a look! @@ -179,7 +180,7 @@ function buildProduction(done) { debug: false, }), - failPlugin + failPlugin, ); // ..... @@ -204,7 +205,7 @@ function createDevCompiler() { new webpack.LoaderOptionsPlugin({ debug: true, options: myDevConfig, - }) + }), // it ends here - there wasn't much really.... ); @@ -232,7 +233,7 @@ function buildProduction(done) { }, }), - failPlugin + failPlugin, ); // ..... @@ -247,7 +248,10 @@ function createDevCompiler() { name: 'vendor', filename: 'vendor.js', }), - new WebpackNotifierPlugin({ title: 'Webpack build', excludeWarnings: true }) + new WebpackNotifierPlugin({ + title: 'Webpack build', + excludeWarnings: true, + }), ); // create a single instance of the compiler to allow caching diff --git a/blog-website/blog/2017-01-01-webpack-configuring-loader-with-query/index.md b/blog-website/blog/2017-01-01-webpack-configuring-loader-with-query/index.md index ba7f046623f..c801ed6d50f 100644 --- a/blog-website/blog/2017-01-01-webpack-configuring-loader-with-query/index.md +++ b/blog-website/blog/2017-01-01-webpack-configuring-loader-with-query/index.md @@ -4,6 +4,7 @@ title: 'webpack: configuring a loader with query / options' authors: johnnyreilly tags: [webpack] hide_table_of_contents: false +description: 'webpack 2 now enforces a strict schema for `webpack.config.js`. Loaders should be configured using `query` or `options`.' --- [webpack 2 is on it's way](https://medium.com/webpack/webpack-2-2-the-release-candidate-2e614d05d75f#.ntniu44u6). As one of the maintainers of [ts-loader](https://github.com/TypeStrong/ts-loader/) I've been checking out that ts-loader works with webpack 2. It does: phew! diff --git a/blog-website/blog/2017-01-06-webpack-resolveloader-alias-with-query/index.md b/blog-website/blog/2017-01-06-webpack-resolveloader-alias-with-query/index.md index 3328b626e2b..44a4dc16d48 100644 --- a/blog-website/blog/2017-01-06-webpack-resolveloader-alias-with-query/index.md +++ b/blog-website/blog/2017-01-06-webpack-resolveloader-alias-with-query/index.md @@ -4,6 +4,7 @@ title: 'webpack: resolveLoader / alias with query / options' authors: johnnyreilly tags: [webpack] hide_table_of_contents: false +description: 'Webpacks enhanced-resolve has a bug with aliased loaders. A workaround involves suffixing the aliased path with query options.' --- Sometimes you write a post for the ages. Sometimes you write one you hope is out of date before you hit "publish". This is one of those. @@ -57,7 +58,7 @@ rules.forEach(function (rule) { var options = rule.query || rule.options; rule.loader = rule.loader.replace( 'ts-loader', - loaderAliasPath + (options ? '?' + JSON.stringify(options) : '') + loaderAliasPath + (options ? '?' + JSON.stringify(options) : ''), ); }); ``` diff --git a/blog-website/blog/2017-02-01-hands-free-https/index.md b/blog-website/blog/2017-02-01-hands-free-https/index.md index 4d41bc271d1..2d1b5c551b7 100644 --- a/blog-website/blog/2017-02-01-hands-free-https/index.md +++ b/blog-website/blog/2017-02-01-hands-free-https/index.md @@ -4,6 +4,7 @@ title: 'Hands-free HTTPS' authors: johnnyreilly tags: [TLS, HTTPS, cloudflare] hide_table_of_contents: false +description: 'CloudFlare provides free HTTPS certificates. As HTTPS becomes the web default, it is essential for search engine ranking and service workers.' --- I have had a \***great**\* week. You? Take a look at this blog. Can you see what I can see? Here's a clue: diff --git a/blog-website/blog/2017-10-19-working-with-extrahop-on-webpack-and-ts/index.md b/blog-website/blog/2017-10-19-working-with-extrahop-on-webpack-and-ts/index.md index ba93d59eb2d..7b9541ba413 100644 --- a/blog-website/blog/2017-10-19-working-with-extrahop-on-webpack-and-ts/index.md +++ b/blog-website/blog/2017-10-19-working-with-extrahop-on-webpack-and-ts/index.md @@ -4,6 +4,7 @@ title: 'Working with Extrahop on webpack and ts-loader' authors: johnnyreilly tags: [ts-loader, webpack] hide_table_of_contents: false +description: 'The author shares their excitement for working with talented individuals on open source software - its everywhere and a privilege to work on.' --- I'm quite proud of this: [https://www.extrahop.com/company/blog/2017/extrahop-webpack-accelerating-build-times/](https://www.extrahop.com/company/blog/2017/extrahop-webpack-accelerating-build-times/) diff --git a/blog-website/blog/2018-01-14-auth0-typescript-and-aspnet-core/index.md b/blog-website/blog/2018-01-14-auth0-typescript-and-aspnet-core/index.md index 34db0ee1169..cedd55c7518 100644 --- a/blog-website/blog/2018-01-14-auth0-typescript-and-aspnet-core/index.md +++ b/blog-website/blog/2018-01-14-auth0-typescript-and-aspnet-core/index.md @@ -4,6 +4,7 @@ title: 'Auth0, TypeScript and ASP.NET Core' authors: johnnyreilly tags: [ASP.Net Core, Auth0, typescript, OAuth, React] hide_table_of_contents: false +description: 'Auth0 makes authentication and authorization simple. They offer Auth-As-A-Service, quick start and easy customization of settings.' --- Most applications I write have some need for authentication and perhaps authorisation too. In fact, most apps most people write fall into that bracket. Here's the thing: Auth done well is a \*big\* chunk of work. And the minute you start thinking about that you almost invariably lose focus on the thing you actually want to build and ship. @@ -93,7 +94,10 @@ export class AuthStore { @observable.ref userProfile: Auth0UserProfile; @observable.ref token: IStorageToken; - constructor(config: IAuth0Config, private storage: StorageFacade) { + constructor( + config: IAuth0Config, + private storage: StorageFacade, + ) { this.auth0 = new WebAuth({ domain: config.domain, clientID: config.clientId, diff --git a/blog-website/blog/2018-04-28-using-reflection-to-identify-unwanted-dependencies/index.md b/blog-website/blog/2018-04-28-using-reflection-to-identify-unwanted-dependencies/index.md index c44186c09b8..bae95269e80 100644 --- a/blog-website/blog/2018-04-28-using-reflection-to-identify-unwanted-dependencies/index.md +++ b/blog-website/blog/2018-04-28-using-reflection-to-identify-unwanted-dependencies/index.md @@ -4,6 +4,7 @@ title: 'Using Reflection to Identify Unwanted Dependencies' authors: johnnyreilly tags: [.NET] hide_table_of_contents: false +description: 'Learn how to identify unwelcome dependencies in complex web apps by walking a dependency tree using reflection-based tests.' --- I having a web app which is fairly complex. It's made up of services, controllers and all sorts of things. So far, so unremarkable. However, I needed to ensure that the controllers did not attempt to access the database via any of their dependencies. Or their dependencies, dependencies. Or their dependencies. You get my point. diff --git a/blog-website/blog/2018-06-24-vsts-and-ef-core-migrations/index.md b/blog-website/blog/2018-06-24-vsts-and-ef-core-migrations/index.md index ee9b4dcd088..fa93ff2d500 100644 --- a/blog-website/blog/2018-06-24-vsts-and-ef-core-migrations/index.md +++ b/blog-website/blog/2018-06-24-vsts-and-ef-core-migrations/index.md @@ -4,6 +4,7 @@ title: 'VSTS and EF Core Migrations' authors: johnnyreilly tags: [vsts, Entity Framework] hide_table_of_contents: false +description: 'Learn how to migrate Entity Framework database migrations during an ASP.NET Core project deployment with a console app using VSTS and Azure.' --- Let me start by telling you a dirty secret. I have an ASP.Net Core project that I build with VSTS. It is deployed to Azure through a CI / CD setup in VSTS. That part I'm happy with. Proud of even. Now to the sordid hiddenness: try as I might, I've never found a nice way to deploy Entity Framework database migrations as part of the deployment flow. So I have [blushes with embarrassment] been using the `Startup` of my ASP.Net core app to run the migrations on my database. There. I said it. You all know. Absolutely filthy. Don't judge me. diff --git a/blog-website/blog/2018-09-15-semantic-versioning-and-definitely-typed/index.md b/blog-website/blog/2018-09-15-semantic-versioning-and-definitely-typed/index.md index 8b5a0301fbd..95f488969b3 100644 --- a/blog-website/blog/2018-09-15-semantic-versioning-and-definitely-typed/index.md +++ b/blog-website/blog/2018-09-15-semantic-versioning-and-definitely-typed/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [Definitely Typed, typescript] image: ./i-must-break-you.webp hide_table_of_contents: false +description: 'Definitely Typed lacks semantic versioning, causing build failures. Use specific package versions, and breaking changes can be positive.' --- This a tale of things that are and things that aren't. It's a tale of semantic versioning, the lack thereof and heartbreak. It's a story of terror and failing builds. But it has a bittersweet ending wherein our heroes learn a lesson and understand the need for compromise. We all come out better and wiser people. Hopefully there's something for everybody; let's start with an exciting opener and see where it goes... diff --git a/blog-website/blog/2019-02-22-aspnet-core-allowlist-proxying-http-requests/index.md b/blog-website/blog/2019-02-22-aspnet-core-allowlist-proxying-http-requests/index.md index 5cc161bf660..c54c7cde47d 100644 --- a/blog-website/blog/2019-02-22-aspnet-core-allowlist-proxying-http-requests/index.md +++ b/blog-website/blog/2019-02-22-aspnet-core-allowlist-proxying-http-requests/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [.NET] image: ./hang-on-lads-ive-got-a-great-idea.webp hide_table_of_contents: false +description: 'ASP.NET Core can proxy HTTP requests selectively, allowing only acceptable traffic via a middleware for specified paths.' --- This post demonstrates a mechanism for proxying HTTP requests in ASP.NET Core. It doesn't proxy all requests; it only proxies requests that match entries on an "allowlist" - so we only proxy the traffic that we've actively decided is acceptable as determined by taking the form of an expected URL and HTTP verb (GET / POST etc). diff --git a/blog-website/blog/2019-10-08-definitely-typed-the-movie/index.md b/blog-website/blog/2019-10-08-definitely-typed-the-movie/index.md index f030fd235d1..725704a4d06 100644 --- a/blog-website/blog/2019-10-08-definitely-typed-the-movie/index.md +++ b/blog-website/blog/2019-10-08-definitely-typed-the-movie/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [typescript, Definitely Typed] image: ./title-image.png hide_table_of_contents: false +description: 'The history of the TypeScript GitHub project Definitely Typed. And some TypeScript history as well' --- I'd like to tell you a story. It's the tale of the ecosystem that grew up around a language: TypeScript. TypeScript is, for want of a better description, JavaScript after a trip to Saville Row. Essentially the same language, but a little more together, a little less wild west. JS with a decent haircut and a new suit. These days, the world seems to be written in TypeScript. And when you pause to consider just how young the language is, well, that's kind of amazing. @@ -103,7 +104,7 @@ So, imagine your definition looked like this: ```ts declare function turnANumberIntoAString( - numberToMakeStringOutOf: number + numberToMakeStringOutOf: number, ): string; ``` @@ -165,7 +166,7 @@ On December 28th 2013 Basarat decided that a regular contributor to Definitely T ![A photograph of John Reilly](johnny_reilly.webp) -That's me. Or [johnny_reilly on Twitter](https://twitter.com/johnny_reilly), [johnny_reilly on Fosstodon](https://fosstodon.org/@johnny_reilly) and [johnnyreilly on GitHub](https://github.com/johnnyreilly). Relatively few people call me Johnny. I'm named that online because back when I applied for an email address, someone had already bagsied `johnreilly@hotmail.com`. (Hotmail was what everyone used back when I came online - I am *that* old.) So rather than sully my handle with a number or a middle name I settled for `johnny_reilly` and went with the underscore as someone already had the email address without one. I haven't looked back and have generally tried to keep that nom de plume wherever I lay my hat online. I have learned to my chagrin that GitHub doesn't support the `_` character in usernames. This bothers me more than is reasonable. +That's me. Or [johnny_reilly on Twitter](https://twitter.com/johnny_reilly), [johnny_reilly on Fosstodon](https://fosstodon.org/@johnny_reilly) and [johnnyreilly on GitHub](https://github.com/johnnyreilly). Relatively few people call me Johnny. I'm named that online because back when I applied for an email address, someone had already bagsied `johnreilly@hotmail.com`. (Hotmail was what everyone used back when I came online - I am _that_ old.) So rather than sully my handle with a number or a middle name I settled for `johnny_reilly` and went with the underscore as someone already had the email address without one. I haven't looked back and have generally tried to keep that nom de plume wherever I lay my hat online. I have learned to my chagrin that GitHub doesn't support the `_` character in usernames. This bothers me more than is reasonable. In contrast to others I was a relatively late starter to TypeScript. I was intrigued right from the initial announcement, but held off from properly getting my hands dirty until generics was added to the language in 0.9. (This predisposition towards generics in a language perhaps explains why I didn't get too far with Golang.) diff --git a/blog-website/blog/2020-03-22-dual-boot-authentication-with-aspnetcore/index.md b/blog-website/blog/2020-03-22-dual-boot-authentication-with-aspnetcore/index.md index 6dd733e8140..f06e0dc2626 100644 --- a/blog-website/blog/2020-03-22-dual-boot-authentication-with-aspnetcore/index.md +++ b/blog-website/blog/2020-03-22-dual-boot-authentication-with-aspnetcore/index.md @@ -4,6 +4,7 @@ title: 'Dual boot authentication with ASP.NET' authors: johnnyreilly tags: [Authentication, Azure AD, ASP.NET] hide_table_of_contents: false +description: 'The article explains how to have different authentication methods for two classes of users accessing an app. Code snippets are provided.' --- This is a post about having two kinds of authentication working at the same time in ASP.Net Core. But choosing which authentication method to use dynamically at runtime; based upon the criteria of your choice. diff --git a/blog-website/blog/2020-05-10-from-react-window-to-react-virtual/index.md b/blog-website/blog/2020-05-10-from-react-window-to-react-virtual/index.md index cb7b337c35e..e41381c9763 100644 --- a/blog-website/blog/2020-05-10-from-react-window-to-react-virtual/index.md +++ b/blog-website/blog/2020-05-10-from-react-window-to-react-virtual/index.md @@ -4,6 +4,7 @@ title: 'From react-window to react-virtual' authors: johnnyreilly tags: [react-virtual, react-window, React] hide_table_of_contents: false +description: 'Switch from `react-window` to `react-virtual` for simpler code, TypeScript support and improved perceived performance.' --- The tremendous [Tanner Linsley](https://twitter.com/tannerlinsley) recently released [`react-virtual`](https://github.com/tannerlinsley/react-virtual). `react-virtual` provides "hooks for virtualizing scrollable elements in React". @@ -73,7 +74,7 @@ const ImportantDataList: React.FC = React.memo( > {RenderRow} - ) + ), ); type ListItemProps = { @@ -149,7 +150,7 @@ const ImportantDataList: React.FC = React.memo( ); - } + }, ); ``` diff --git a/blog-website/blog/2020-06-21-taskwhenall-select-is-footgun/index.md b/blog-website/blog/2020-06-21-taskwhenall-select-is-footgun/index.md index 1992d23f69c..d5550fe6902 100644 --- a/blog-website/blog/2020-06-21-taskwhenall-select-is-footgun/index.md +++ b/blog-website/blog/2020-06-21-taskwhenall-select-is-footgun/index.md @@ -4,6 +4,7 @@ title: 'Task.WhenAll / Select is a footgun 👟🔫' authors: johnnyreilly tags: [C#, LINQ] hide_table_of_contents: false +description: 'The writer warns against LINQ code that causes concurrent API requests and shares plans for better metrics and a development container.' --- This post differs from my typical fayre. Most often I write "here's how to do a thing". This is not that. It's more "don't do this thing I did". And maybe also, "how can we avoid a situation like this happening again in future?". On this topic I very much don't have all the answers - but by putting my thoughts down maybe I'll learn and maybe others will educate me. I would love that! diff --git a/blog-website/blog/2020-07-11-devcontainers-and-ssl-interception/index.md b/blog-website/blog/2020-07-11-devcontainers-and-ssl-interception/index.md index 0f0dc689892..861b31e7982 100644 --- a/blog-website/blog/2020-07-11-devcontainers-and-ssl-interception/index.md +++ b/blog-website/blog/2020-07-11-devcontainers-and-ssl-interception/index.md @@ -4,6 +4,7 @@ title: 'Devcontainers and SSL interception' authors: johnnyreilly tags: [devcontainer, ssl interception] hide_table_of_contents: false +description: 'Developers may need to overcome MITM certificate issues to use devcontainers, which can optimize productivity for new starters when developing software.' --- [Devcontainers](https://code.visualstudio.com/docs/remote/containers) are cool. They are the infrastructure as code equivalent for developing software. diff --git a/blog-website/blog/2020-10-02-autofac-6-integration-tests-and-generic-hosting/index.md b/blog-website/blog/2020-10-02-autofac-6-integration-tests-and-generic-hosting/index.md index 036249cc1d2..470d4e0dff9 100644 --- a/blog-website/blog/2020-10-02-autofac-6-integration-tests-and-generic-hosting/index.md +++ b/blog-website/blog/2020-10-02-autofac-6-integration-tests-and-generic-hosting/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [autofac, asp.net, Integration Testing] image: ./autofac-integration-tests.webp hide_table_of_contents: false +description: 'Integration tests using Autofac have been affected by a long-standing issue in .NET Core 3.0. Alternative approaches may not last long.' --- I [blogged a little while ago around to support integration tests using Autofac](../2020-05-21-autofac-webapplicationfactory-integration-tests/index.md). This was specific to Autofac but documented a workaround for a [long standing issue with `ConfigureTestContainer` that was introduced into .NET core 3.0](https://github.com/dotnet/aspnetcore/issues/14907) which affects [all third-party containers](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#default-service-container-replacement) that use `ConfigureTestContainer` in their tests. From 736130b3f59af832e4e4a86af35d406885f4fb0a Mon Sep 17 00:00:00 2001 From: johnnyreilly Date: Mon, 28 Aug 2023 17:48:00 +0100 Subject: [PATCH 10/14] feat: more descriptions --- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 1 + .../blog/2012-02-23-joy-of-json/index.md | 1 + .../index.md | 1 + .../index.md | 106 +----- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 329 +----------------- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 98 +----- .../index.md | 1 + .../index.md | 1 + .../index.md | 7 +- .../2012-10-22-mvc-3-meet-dictionary/index.md | 1 + .../index.md | 200 +---------- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../2013-05-04-how-im-using-cassette/index.md | 5 +- .../index.md | 3 +- .../index.md | 3 +- .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 3 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 27 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 13 +- .../index.md | 1 + .../index.md | 13 +- .../index.md | 1 + .../index.md | 15 +- .../index.md | 25 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 49 +-- .../index.md | 1 + .../blog/2014-12-05-whats-in-a-name/index.md | 1 + .../index.md | 7 +- .../index.md | 15 +- .../index.md | 100 +----- .../index.md | 1 + .../index.md | 1 + .../index.md | 15 +- .../index.md | 1 + .../2015-03-20-partialview-tostring/index.md | 1 + .../index.md | 1 + .../index.md | 11 +- .../index.md | 1 + .../index.md | 11 +- .../index.md | 1 + .../index.md | 1 + .../index.md | 1 + .../index.md | 13 +- .../index.md | 13 +- .../2015-09-10-things-done-changed/index.md | 1 + .../index.md | 1 + .../index.md | 2 +- .../index.md | 2 +- .../index.md | 1 + 98 files changed, 220 insertions(+), 950 deletions(-) diff --git a/blog-website/blog/2012-01-07-standing-on-shoulders-of-giants/index.md b/blog-website/blog/2012-01-07-standing-on-shoulders-of-giants/index.md index 7516f316d5f..8ae84574ff3 100644 --- a/blog-website/blog/2012-01-07-standing-on-shoulders-of-giants/index.md +++ b/blog-website/blog/2012-01-07-standing-on-shoulders-of-giants/index.md @@ -3,6 +3,7 @@ slug: standing-on-shoulders-of-giants title: 'Standing on the Shoulders of Giants...' authors: johnnyreilly hide_table_of_contents: false +description: 'John starts a blog inspired by Scott Hanselman to share useful tools and techniques for web development.' --- It started with Scott Hanselman. I had no particular plans to start a blog at all. However, I was reading Scott Hanselman's turn of the year [post](http://www.hanselman.com/blog/YourBlogIsTheEngineOfCommunity.aspx) and I was struck with an idea. diff --git a/blog-website/blog/2012-01-14-jqgrid-its-just-far-better-grid/index.md b/blog-website/blog/2012-01-14-jqgrid-its-just-far-better-grid/index.md index 0c330e757c9..207c63dfbf9 100644 --- a/blog-website/blog/2012-01-14-jqgrid-its-just-far-better-grid/index.md +++ b/blog-website/blog/2012-01-14-jqgrid-its-just-far-better-grid/index.md @@ -4,6 +4,7 @@ title: "jqGrid - it's just a far better grid" authors: johnnyreilly tags: [jqgrid, ajax, jquery] hide_table_of_contents: false +description: 'jqGrid is a sleek & efficient grid component for ASP.NET projects. Its minimal data exchange between client and server impressed John no end.' --- The year was 2010 (not really that long ago I know) and the project that I was working on was sorely in need of a new grid component. It was an [ASP.NET WebForms](http://www.asp.net/web-forms) project and for some time we'd been using what was essentially a glorified [datagrid](http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.datagrid.aspx) which had a few extra features implemented to allow us to change column order / columns displayed / copy contents to clipboard etc. Our grid worked perfectly fine - it gave us the functionality we needed. However, it looked pretty terrible, and had some "quirky" approaches in place for supporting IE and Firefox side by side. Also, at the time we were attempting to make our app seem new and exciting again for the users. The surprising truth is that users seem to be more impressed with a visual revamp than with new or amended functionality. So I was looking for something which would make them sit up and say "oooh - isn't it pretty!". Unfortunately the nature of the organisation I was working for was not one that lended itself to paying for components. They were occasionally willing to do that but the hoops that would have to be jumped through first, the forms that would need to be signed in triplicate by people that had nearly nothing to do with the project made that an unattractive prospect. So I began my search initially looking at the various open source offerings that were around. As a minimum I was looking for something that would do what our home-grown component did already (change column order / columns displayed / copy contents to clipboard etc) but hopefully in a "nicer" way. Also, I had long been unhappy with the fact that to get our current grid to render results we did a \***full postback**\* to the server and re-rendered the whole page. Pointless! Why should you need to do all this each time when you only wanted to refresh the data? Instead I was thinking about using an [Ajax](http://en.wikipedia.org/wiki/Ajax_%28programming%29) approach; a grid that could just get the data that it needed and render it to the client. This seemed to me a vastly "cleaner" solution - why update a whole screen when you only want to update a small part of it? Why not save yourself the trouble of having to ensure that all other screen controls are persisted just as you'd like them after the postback? I also thought it was probably something that would scale better as it would massively reduce the amount of data moving backwards and forwards between client and server. No need for a full page life cycle on the server each time the grid refreshes. Just simple data travelling down the pipes of web. With the above criteria in mind I set out on my Google quest for a grid. Quite soon I found that there was a component out there which seemed to do all that I wanted and far more besides. It was called [jqGrid](http://www.trirand.com/blog/): diff --git a/blog-website/blog/2012-01-24-what-on-earth-is-jquery-and-why-should/index.md b/blog-website/blog/2012-01-24-what-on-earth-is-jquery-and-why-should/index.md index 2a85e328f0b..e5db399341f 100644 --- a/blog-website/blog/2012-01-24-what-on-earth-is-jquery-and-why-should/index.md +++ b/blog-website/blog/2012-01-24-what-on-earth-is-jquery-and-why-should/index.md @@ -4,6 +4,7 @@ title: 'What on earth is jQuery? And why should I care?' authors: johnnyreilly tags: [jqgrid, ajax, jquery] hide_table_of_contents: false +description: 'What is jQuery? Discover the truth about the JavaScript library thats taking the web development world by storm - its simply brilliant!' --- What on earth is jQuery? What's a jQuery plugin? diff --git a/blog-website/blog/2012-01-30-javascript-getting-to-know-beast/index.md b/blog-website/blog/2012-01-30-javascript-getting-to-know-beast/index.md index e2e7c110ece..eb5292a5e2c 100644 --- a/blog-website/blog/2012-01-30-javascript-getting-to-know-beast/index.md +++ b/blog-website/blog/2012-01-30-javascript-getting-to-know-beast/index.md @@ -4,9 +4,10 @@ title: 'JavaScript - getting to know the beast...' authors: johnnyreilly tags: [javascript, c#] hide_table_of_contents: false +description: 'Discovering jQuery and advice from Elijah Manor and Douglas Crockford changes Johns initial dislike of JavaScripts quirks.' --- -So it's 2010 and I've started using jQuery. jQuery is a JavaScript library. This means that I'm writing JavaScript... Gulp! I should say that at this point in time I \***hated**\* JavaScript (I have mentioned this previously). But what I know now is that I barely understood the language at all. All the JavaScript I knew was the result of copying and pasting after I'd hit "view source". I don't feel too bad about this - not because my ignorance was laudable but because I certainly wasn't alone in this. It seems that up until recently hardly anyone knew anything about JavaScript. It puzzles me now that I thought this was okay. I suppose like many people I didn't think JavaScript was capable of much and hence felt time spent researching it would be wasted. Just to illustrate where I was then, here is 2009 John's idea of some pretty "advanced" JavaScript: +So it's 2010 and I've started using jQuery. jQuery is a JavaScript library. This means that I'm writing JavaScript... Gulp! I should say that at this point in time I **hated** JavaScript (I have mentioned this previously). But what I know now is that I barely understood the language at all. All the JavaScript I knew was the result of copying and pasting after I'd hit "view source". I don't feel too bad about this - not because my ignorance was laudable but because I certainly wasn't alone in this. It seems that up until recently hardly anyone knew anything about JavaScript. It puzzles me now that I thought this was okay. I suppose like many people I didn't think JavaScript was capable of much and hence felt time spent researching it would be wasted. Just to illustrate where I was then, here is 2009 John's idea of some pretty "advanced" JavaScript: diff --git a/blog-website/blog/2012-02-05-potted-history-of-using-ajax-on/index.md b/blog-website/blog/2012-02-05-potted-history-of-using-ajax-on/index.md index fe096a85ffa..589e5198e4b 100644 --- a/blog-website/blog/2012-02-05-potted-history-of-using-ajax-on/index.md +++ b/blog-website/blog/2012-02-05-potted-history-of-using-ajax-on/index.md @@ -4,6 +4,7 @@ title: 'A Potted History of using Ajax (on the Microsoft Stack of Love)' authors: johnnyreilly tags: [ajax, jquery, json, microsoft] hide_table_of_contents: false +description: 'Discovering Ajax and JSON transformed Johns approach to programming by lifting limitations and improving performance.' --- This post originally started out as an explanation of JSON. However as I wrote this quickly got abandoned in favour of writing about how I came to use JSON in the first place - which was through the use of Ajax. Having written a goodly amount I've now decided to move the actual JSON stuff into another post since I think Ajax is probably worth thinking about by itself rather than as an aside. So let me start at the beginning and explain how I came to use Ajax in the first place (this may take some time so please bear with me). In late 2004 I first started working on a project which I was to remain involved with (on and off) for a very long time indeed. The project was part financial reporting system and part sales incentivisation tool; it was used internally in the investment bank in which I was working. The project had been in existence for a number of years and had a web front end which at that point would been built in a combination of HTML, JavaScript, classic ASP and with a Visual Basic 6.0 back end. One of the reasons I had been brought on to the project was to help ".Net-ify" the thing and migrate it to ASP.NET and C#. I digress. The interesting thing about this app was that there were actually some quite advanced things being done with it (despite the classic ASP / VB). The users could enter trades into the system which represented actual trades that had been entered into a trading system elsewhere in the organisation. These trades would be assigned a reporting value which would be based on their various attributes. (Stay with me people this will get more interesting I \***promise**\*.) The calculation of the reporting value was quite an in depth process and needed to be performed server-side. However, the users had decreed that it wasn't acceptable to do a full postback to the server to perform this calculation; they wanted it done "on-the-fly". Now if you asked me at the time I'd have said "can't be done". Fortunately the other people working on the project then weren't nearly so defeatist. Instead they went away and found Microsoft's [webservice.htc](http://msdn.microsoft.com/en-us/library/ie/ms531033%28v=vs.85%29.aspx) library. For those of you that don't know this was a JavaScript library that Microsoft came up with to enable the access of Web Services on the client. Given that it was designed to work with IE 5 I suspect it was created between 1999-2001 (but I'm not certain about that). Now it came as a revelation to me but this was a JavaScript library that talked to our web services through the medium of XML. In short it was my first encounter with anything remotely [Ajax]()\-y. It was exciting! However, the possibilities of what we could do didn't actually become apparent to me for some years. It's worth saying that the way we were using webservice.htc was exceedingly simplistic and rather than investigating further I took the limited ways we were using it as indications of the limitations of Ajax and / or webservice.htc. So for a long time I thought the following: - The only way to pass multiple arguments to a web service was to package up arguments into a single string with delimiters which you could [split]() and unpackage as your first step on the server. diff --git a/blog-website/blog/2012-02-15-wcf-transport-windows-authentication/index.md b/blog-website/blog/2012-02-15-wcf-transport-windows-authentication/index.md index 10948b34712..f2356fa1798 100644 --- a/blog-website/blog/2012-02-15-wcf-transport-windows-authentication/index.md +++ b/blog-website/blog/2012-02-15-wcf-transport-windows-authentication/index.md @@ -4,6 +4,7 @@ title: 'WCF Transport Windows authentication using NetTcpBinding in an Intranet authors: johnnyreilly tags: [WCF, Authentication] hide_table_of_contents: false +description: 'John explains authentication issues when migrating from .NET Remoting to WCF. The post highlights a security feature and suggests solutions.' --- ## Update diff --git a/blog-website/blog/2012-02-23-joy-of-json/index.md b/blog-website/blog/2012-02-23-joy-of-json/index.md index e7b9c86b181..3497be29184 100644 --- a/blog-website/blog/2012-02-23-joy-of-json/index.md +++ b/blog-website/blog/2012-02-23-joy-of-json/index.md @@ -4,6 +4,7 @@ title: 'The Joy of JSON' authors: johnnyreilly tags: [json] hide_table_of_contents: false +description: 'JSON is a lightweight data format that helps create and read JavaScript objects. This article traces its discovery and explains its usefulness.' --- So back to JSON. For those of you that don't know JSON stands for JavaScript Object Notation and is lightweight text based data interchange format. Rather than quote other people verbatim you can find thorough explanations of JSON here: - [Introducing JSON](http://www.json.org/) diff --git a/blog-website/blog/2012-03-03-jquery-unobtrusive-remote-validation/index.md b/blog-website/blog/2012-03-03-jquery-unobtrusive-remote-validation/index.md index d7488e5d6a5..e3f589d1023 100644 --- a/blog-website/blog/2012-03-03-jquery-unobtrusive-remote-validation/index.md +++ b/blog-website/blog/2012-03-03-jquery-unobtrusive-remote-validation/index.md @@ -4,6 +4,7 @@ title: 'jQuery Unobtrusive Remote Validation' authors: johnnyreilly tags: [jquery, jquery validation, jquery unobtrusive validation] hide_table_of_contents: false +description: 'Learn how to do remote validation with unobtrusive data attributes in ASP.NET MVC. Block validation and re-evaluate it with this hacky solution.' --- Just recently I have been particularly needing to make use of remote / server-side validation in my ASP.NET MVC application and found that the unobtrusive way of using this seemed to be rather inadequately documented (of course it's possible that it's well documented and I just didn't find the resources). Anyway I've rambled on much longer than I intended to in this post so here's the TL;DR: diff --git a/blog-website/blog/2012-03-12-striving-for-javascript-convention/index.md b/blog-website/blog/2012-03-12-striving-for-javascript-convention/index.md index 3dfe2e48fd1..b93866e4e84 100644 --- a/blog-website/blog/2012-03-12-striving-for-javascript-convention/index.md +++ b/blog-website/blog/2012-03-12-striving-for-javascript-convention/index.md @@ -4,113 +4,11 @@ title: 'Striving for (JavaScript) Convention' authors: johnnyreilly tags: [javascript] hide_table_of_contents: false +description: 'Visual Studio 11 beta resolved issues. John has moved away from Hungarian Notation but retained using "$" as a prefix for jQuery objects.' --- ## Update -The speed of change makes fools of us all. Since I originally wrote this post all of 3 weeks ago Visual Studio 11 beta has been released and the issues I was seeking to solve have pretty much been resolved by the new innovations found therein. It's nicely detailed in [@carlbergenhem](http://www.twitter.com/carlbergenhem)'s blog post: [My Top 5 Visual Studio 11 Designer Improvements for ASP.NET 4.5 Development](https://blogs.telerik.com/blogs/posts/12-03-26/my-top-5-visual-studio-11-designer-improvements-for-asp-net-4-5-development.aspx). I've left the post in place below but much of what I said (particularly with regard to Hungarian Notation) I've now moved away from. That was originally my intention anyway so that's no bad thing. The one HN artefact that I've held onto is using "$" as a prefix for jQuery objects. I think that still makes sense. I would have written my first line of JavaScript in probably 2000. It probably looked something like this: `alert('hello world')`. I know. Classy. As I've mentioned before it was around 2010 before I took JavaScript in any way seriously. Certainly it was then when I started to actively learn the language. Because up until this point I'd been studiously avoiding writing any JavaScript at all I'd never really given thought to forms and conventions. When I wrote any JavaScript I just used the same style and approaches as I used in my main development language (of C#). By and large I have been following the .net naming conventions which are ably explained by Pete Brown [here](http://10rem.net/articles/net-naming-conventions-and-programming-standards---best-practices). Over time I have started to move away from this approach. Without a deliberate intention to do so I have found myself adopting a different style for my JavaScript code as compared with anything else I write. I wouldn't go so far as to say I'm completely happy with the style I'm currently using. But I find it more helpful than not and thought it might be worth talking about. It was really 2 things that started me down the road of "rolling my own" convention: dynamic typing and the lack of safety nets. Let's take each in turn.... ### 1\. Dynamic typing - -Having grown up (in a development sense) using compiled and strongly-typed languages I was used to the IDE making it pretty clear what was what through friendly tooltips and the like: - -![](IDE.png) - -JavaScript is loosely / dynamically typed ([occasionally called "untyped" but let's not go there](http://stackoverflow.com/questions/9154388/does-untyped-also-mean-dynamically-typed-in-the-academic-cs-world)). This means that the IDE can't easily determine what's what. So no tooltips for you sunshine. ### 2\. The lack of safety nets / running with scissors - -Now I've come to love it but what I realised pretty quickly when getting into JavaScript was this: you are running with scissors. If you're not careful and you don't take precautions it can bloody quickly. If I'm writing C# I have a lot of safety nets. Not the least of which is "does it compile"? If I declare an integer and then subsequently try to assign a string value to it it won't let me - -. But JavaScript is forgiving. Some would say too forgiving. Let's do something mad: - -```js -var iAmANumber = 77; - -console.log(iAmANumber); //Logs a number - -iAmANumber = "It's a string"; - -console.log(iAmANumber); //Logs a string - -iAmANumber = { - description: 'I am an object', -}; - -console.log(iAmANumber); //Logs an object - -iAmANumber = function (myVariable) { - console.log(myVariable); -}; - -console.log(iAmANumber); //Logs a function -iAmANumber('I am not a number, I am a free man!'); //Calls a function which performs a log -``` - -Now if I were to attempt something similar in C# fuggedaboudit but JavaScript; no I'm romping home free: - -![](Mad-Stuff.webp) - -Now I'm not saying that you should ever do the above, and thinking about it I can't think of a situation where you'd want to (suggestions on a postcard). But the point is it's possible. And because it's possible to do this deliberately, it's doubly possible to do this accidentally. My point is this: it's easy to make bugs in JavaScript. ## What ~~Katy~~ Johnny Did Next - -I'd started making more and more extensive use of JavaScript. I was beginning to move in the direction of using the [single-page application](http://en.wikipedia.org/wiki/Single-page_application) approach (_although more in the sense of giving application style complexity to individual pages rather than ensuring that entire applications ended up in a single page_). This meant that whereas in the past I'd had the occasional 2 lines of JavaScript I now had a multitude of functions which were all interacting in response to user input. All these functions would contain a number of different variables. As well as this I was making use of jQuery for both Ajax purposes and to smooth out the DOM inconsistencies between various browsers. This only added to the mix as variables in one of my functions could be any one of the following: - a number - -- a string -- a boolean -- a date -- an object -- an array -- a function -- a jQuery object - not strictly a distinct JavaScript type obviously but treated pretty much as one in the sense that it has a particular functions / properties etc associated with it - -As I started doing this sort of work I made no changes to my coding style. Wherever possible I did \***exactly**\* what I would have been doing in C# in JavaScript. And it worked fine. Until.... Okay there is no "until" as such, it did work fine. But what I found was that I would do a piece of work, check it into source control, get users to test it, release the work into Production and promptly move onto the next thing. However, a little way down the line there would be a request to add a new feature or perhaps a bug was reported and I'd find myself back looking at the code. And, as is often the case, despite the comments I would realise that it wasn't particularly clear why something worked in the way it did. (Happily it's not just me that has this experience, paranoia has lead me to ask many a fellow developer and they have confessed to similar) When it came to bug hunting in particular I found myself cursing the lack of friendly tooltips and the like. Each time I wanted to look at a variable I'd find myself tracking back through the function, looking for the initial use of the variable to determine the type. Then I'd be tracking forward through the function for each subsequent use to ensure that it conformed. Distressingly, I would find examples of where it looked like I'd forgotten the type of the variable towards the end of a function (for which I can only, regrettably, blame myself). Most commonly I would have a situation like this: - -```js -var tableCell = $('#ItIsMostDefinitelyATableCell'); //I jest ;-) - -/* ...THERE WOULD BE SOME CODE DOING SOMETHING HERE... */ - -tableCell.className = 'makeMeProminent'; //Oh dear - not good. -``` - -You see what happened above? I forgot I had a jQuery object and instead treated it like it was a standard DOM element. Oh dear. ## Spinning my own safety net; Hungarian style - -After I'd experienced a few of the situations described above I decided that steps needed to be taken to minimise the risk of this. In this case, I decided that "steps" meant [Hungarian notation](http://en.wikipedia.org/wiki/Hungarian_notation). I know. I bet you're wincing right now. For those of you that don't remember HN was pretty much the standard way of coding at one point (although at the point that I started coding professionally it had already started to decline). It was adopted in simpler times long before the modern IDE's that tell you what each variable is became the norm. Back when you couldn't be sure of the types you were dealing with. In short, kind of like my situation with JavaScript right now. There's not much to it. By and large HN simply means having a lowercase prefix of 1-3 characters on all your variables indicating type. It doesn't solve all your problems. It doesn't guarantee to stop bugs. But because each instance of the variables use implicitly indicates it's type it makes bugs more glaringly obvious. This means when writing code I'm less likely to misuse a variable (eg `iNum = "JIKJ"`) because part of my brain would be bellowing: "that just looks wrong... pay better attention lad!". Likewise, if I'm scanning through some JavaScript and searching for a bug then this can make it more obvious. Here's some examples of different types of variables declared using the style I have adopted: - -```js -var iInteger = 4; -var dDecimal = 10.5; -var sString = 'I am a string'; -var bBoolean = true; -var dteDate = new Date(); -var oObject = { - description: 'I am an object', -}; -var aArray = [34, 77]; -var fnFunction = function () { - //Do something -}; -var $jQueryObject = $('#ItIsMostDefinitelyATableCell'); -``` - -Some of you have read this and thought "hold on a minute... JavaScript doesn't have integers / decimals etc". You're quite right. My style is not specifically stating the type of a variable. More it is seeking to provide a guide on how a variable should be used. JavaScript does not have integers. But oftentimes I'll be using a number variable which i will only ever want to treat as an integer. And so I'll name it accordingly. ## Spinning a better safety net; DOJO style - -I would be the first to say that alternative approaches are available. And here's one I recently happened upon that I rather like the look of: look 2/3rds down at the parameters section of [the DOJO styleguide](http://dojotoolkit.org/community/styleGuide) Essentially they advise specifying parameter types through the use of prefixed comments. See the examples below: - -```js -function(/*String*/ foo, /*int*/ bar)... -``` - -or - -```js -function(/_String?_/ foo, /_int_/ bar, /_String[]?_/ baz)... -``` - -I really rather like this approach and I'm thinking about starting to adopt it. It's not possible in Hungarian Notation to be so clear about the purpose of a variable. At least not without starting to adopt all kinds of kooky conventions that take in all the possible permutations of variable types. And if you did that you'd really be defeating yourself anyway as it would simply reduce the clarity of your code and make bugs more likely. ## Spinning a better safety net; unit tests - -Despite being quite used to writing unit tests for all my server-side code I have not yet fully embraced unit testing on the client. Partly I've been holding back because of the variety of JavaScript testing frameworks available. I wasn't sure which to start with. But given that it is so easy to introduce bugs into JavaScript I have come to the conclusion that it's better to have some tests in place rather than none. Time to embrace the new. ## Conclusion - -I've found using Hungarian Notation useful whilst working in JavaScript. Not everyone will feel the same and I think that's fair enough; within reason I think it's generally a good idea to go with what you find useful. However, I am giving genuine consideration to moving to the DOJO style and moving back to my more standard camel-cased variable names instead of Hungarian Notation. Particularly since I strive to keep my functions short with the view that ideally each should 1 thing well. Keep it simple etc... And so in a perfect world the situation of forgetting a variables purpose shouldn't really arise... I think once I've got up and running with JavaScript unit tests I may make that move. Hungarian Notation may have proved to be just a stop-gap measure until better techniques were employed... - -``` - -``` +The speed of change makes fools of us all. Since I originally wrote this post all of 3 weeks ago Visual Studio 11 beta has been released and the issues I was seeking to solve have pretty much been resolved by the new innovations found therein. It's nicely detailed in [@carlbergenhem](http://www.twitter.com/carlbergenhem)'s blog post: [My Top 5 Visual Studio 11 Designer Improvements for ASP.NET 4.5 Development](https://blogs.telerik.com/blogs/posts/12-03-26/my-top-5-visual-studio-11-designer-improvements-for-asp-net-4-5-development.aspx). I've left the post in place below but much of what I said (particularly with regard to Hungarian Notation) I've now moved away from. That was originally my intention anyway so that's no bad thing. The one HN artefact that I've held onto is using "$" as a prefix for jQuery objects. I think that still makes sense. I would have written my first line of JavaScript in probably 2000. It probably looked something like this: `alert('hello world')`. I know. Classy. As I've mentioned before it was around 2010 before I took JavaScript in any way seriously. Certainly it was then when I started to actively learn the language. Because up until this point I'd been studiously avoiding writing any JavaScript at all I'd never really given thought to forms and conventions. When I wrote any JavaScript I just used the same style and approaches as I used in my main development language (of C#). By and large I have been following the .net naming conventions which are ably explained by Pete Brown [here](http://10rem.net/articles/net-naming-conventions-and-programming-standards diff --git a/blog-website/blog/2012-03-17-using-pubsub-observer-pattern-to/index.md b/blog-website/blog/2012-03-17-using-pubsub-observer-pattern-to/index.md index 3c39dc361bb..0370c37a737 100644 --- a/blog-website/blog/2012-03-17-using-pubsub-observer-pattern-to/index.md +++ b/blog-website/blog/2012-03-17-using-pubsub-observer-pattern-to/index.md @@ -4,6 +4,7 @@ title: 'Using the PubSub / Observer pattern to emulate constructor chaining with authors: johnnyreilly tags: [javascript] hide_table_of_contents: false +description: 'Pass objects between JavaScript files using PubSub interface to achieve code reusability without global scope pollution. No prototypes needed.' --- Yes the title of this post is \***painfully**\* verbose. Sorry about that. Couple of questions for you: - Have you ever liked the way you can have base classes in C# which can then be inherited and subclassed in a different file / class diff --git a/blog-website/blog/2012-03-22-wcf-moving-from-config-to-code-simple/index.md b/blog-website/blog/2012-03-22-wcf-moving-from-config-to-code-simple/index.md index fc7974c439c..696fc9da953 100644 --- a/blog-website/blog/2012-03-22-wcf-moving-from-config-to-code-simple/index.md +++ b/blog-website/blog/2012-03-22-wcf-moving-from-config-to-code-simple/index.md @@ -4,6 +4,7 @@ title: 'WCF - moving from Config to Code, a simple WCF service harness (plus imp authors: johnnyreilly tags: [WCF, authorization] hide_table_of_contents: false +description: 'John describes his approach to developing a Windows Service-hosted WCF service/client harness, including locking down WCF authorization.' --- Last time I wrote about WCF I was getting up and running with [WCF Transport Windows authentication using NetTcpBinding in an Intranet environment](../2012-02-15-wcf-transport-windows-authentication/index.md). I ended up with a WCF service hosted in a Windows Service which did pretty much what the previous post name implies. diff --git a/blog-website/blog/2012-04-05-making-pdfs-from-html-in-c-using/index.md b/blog-website/blog/2012-04-05-making-pdfs-from-html-in-c-using/index.md index abae5886184..4c4580581f9 100644 --- a/blog-website/blog/2012-04-05-making-pdfs-from-html-in-c-using/index.md +++ b/blog-website/blog/2012-04-05-making-pdfs-from-html-in-c-using/index.md @@ -4,6 +4,7 @@ title: 'Making PDFs from HTML in C# using WKHTMLtoPDF' authors: johnnyreilly tags: [wkhtmltopdf, c#, pdf] hide_table_of_contents: false +description: 'Create PDF reports from HTML with WKHTMLtoPDF, an open source tool that renders web pages to PDF, using a simple wrapper class.' --- ## Updated 03/01/2013 diff --git a/blog-website/blog/2012-04-16-simple-technique-for-initialising/index.md b/blog-website/blog/2012-04-16-simple-technique-for-initialising/index.md index ac2a93e803d..5ae8a5aa984 100644 --- a/blog-website/blog/2012-04-16-simple-technique-for-initialising/index.md +++ b/blog-website/blog/2012-04-16-simple-technique-for-initialising/index.md @@ -4,6 +4,7 @@ title: 'A Simple Technique for Initialising Properties with Internal Setters for authors: johnnyreilly tags: [unit testing, MOQ] hide_table_of_contents: false +description: 'Refactoring a legacy app includes adding unit tests, but properties with internal setters pose a problem. John explores various approaches.' --- I was recently working with my colleagues on refactoring a legacy application. We didn't have an immense amount of time available for this but the plan was to try and improve what was there as much as possible. In its initial state the application had no unit tests in place at all and so the plan was to refactor the code base in such a way as to make testing it a realistic proposition. To that end the [domain layer](http://en.wikipedia.org/wiki/Domain_layer) was being heavily adjusted and the GUI was being migrated from WebForms to MVC 3. The intention was to build up a pretty solid collection of unit tests. However, as we were working on this we realised we had a problem with properties on our models with [`internal`]() setters... diff --git a/blog-website/blog/2012-04-23-jshint-customising-your-hurt-feelings/index.md b/blog-website/blog/2012-04-23-jshint-customising-your-hurt-feelings/index.md index e6910e6a463..564979b5a94 100644 --- a/blog-website/blog/2012-04-23-jshint-customising-your-hurt-feelings/index.md +++ b/blog-website/blog/2012-04-23-jshint-customising-your-hurt-feelings/index.md @@ -4,6 +4,7 @@ title: 'JSHint - Customising your hurt feelings' authors: johnnyreilly tags: [jslint, jshint, eslint] hide_table_of_contents: false +description: 'JSHint is a tool for evaluating JavaScript code quality. Its configurable, has an extension for Visual Studio and is better than JSLint.' --- As I've started making greater use of JavaScript to give a richer GUI experience the amount of JS in my ASP.NET apps has unsurprisingly ballooned. If I'm honest, I hadn't given much consideration to the code quality of my JavaScript in the past. However, if I was going to make increasing use of it (and given the way the web is going at the moment I'd say that's a given) I didn't think this was tenable position to maintain. A friend of mine works for [Coverity](http://www.coverity.com/) which is a company that provides tools for analysing code quality. I understand, from conversations with him, that their tools provide static analysis for compiled languages such as C++ / C# / Java etc. I was looking for something similar for JavaScript. Like many, I have read and loved [Douglas Crockford's "JavaScript: The Good Parts"](http://www.amazon.com/JavaScript-Good-Parts-Douglas-Crockford/dp/0596517742); it is by some margin the most useful and interesting software related book I have read.So I was aware that Crockford had come up with his own JavaScript code quality tool called [JSLint](http://www.jslint.com/). JSLint is quite striking when you first encounter it: diff --git a/blog-website/blog/2012-04-28-beg-steal-or-borrow-decent-javascript/index.md b/blog-website/blog/2012-04-28-beg-steal-or-borrow-decent-javascript/index.md index 1419fcfc0fe..88382e9af03 100644 --- a/blog-website/blog/2012-04-28-beg-steal-or-borrow-decent-javascript/index.md +++ b/blog-website/blog/2012-04-28-beg-steal-or-borrow-decent-javascript/index.md @@ -4,6 +4,7 @@ title: 'Beg, Steal or Borrow a Decent JavaScript DateTime Converter' authors: johnnyreilly tags: [javascript, Serialization, .NET] hide_table_of_contents: false +description: 'ASP.NETs JavaScriptSerializer class is improved through ISO 8601 and a custom converter for better DateTime serialisation.' --- I've so named this blog post because it shamelessly borrows from the fine work of others: Sebastian Markbåge and Nathan Vonnahme. Sebastian wrote a blog post documenting a good solution to the ASP.NET JavaScriptSerializer DateTime problem at the tail end of last year. However, his solution didn't get me 100% of the way there when I tried to use it because of a need to support IE 8 which lead me to use Nathan Vonnahme's ISO 8601 JavaScript Date parser. I thought it was worth documenting this, hence this post, but just so I'm clear; the hard work here was done by Sebastian Markbåge and Nathan Vonnahme and not me. Consider me just a curator in this case. The original blog posts that I am drawing upon can be found here: 1. [http://blog.calyptus.eu/seb/2011/12/custom-datetime-json-serialization/](http://blog.calyptus.eu/seb/2011/12/custom-datetime-json-serialization/) and here: 2. [http://n8v.enteuxis.org/2010/12/parsing-iso-8601-dates-in-javascript/](http://n8v.enteuxis.org/2010/12/parsing-iso-8601-dates-in-javascript/) diff --git a/blog-website/blog/2012-05-07-globalizejs-number-and-date/index.md b/blog-website/blog/2012-05-07-globalizejs-number-and-date/index.md index a788eb305e4..9dce0f60ee9 100644 --- a/blog-website/blog/2012-05-07-globalizejs-number-and-date/index.md +++ b/blog-website/blog/2012-05-07-globalizejs-number-and-date/index.md @@ -4,6 +4,7 @@ title: 'Globalize.js - number and date localisation made easy' authors: johnnyreilly tags: [javascript, ASP.NET AJAX, Globalization] hide_table_of_contents: false +description: 'Globalize.js is a JavaScript library allowing developers to format and parse numbers and dates in a culture specific fashion for better user experience.' --- I wanted to write about a JavaScript library which seems to have had very little attention so far. And that surprises me as it's diff --git a/blog-website/blog/2012-05-30-dad-didnt-buy-any-games/index.md b/blog-website/blog/2012-05-30-dad-didnt-buy-any-games/index.md index 12382913b3c..9a8d33f8aa6 100644 --- a/blog-website/blog/2012-05-30-dad-didnt-buy-any-games/index.md +++ b/blog-website/blog/2012-05-30-dad-didnt-buy-any-games/index.md @@ -4,6 +4,7 @@ title: "Dad Didn't Buy Any Games" authors: johnnyreilly tags: [] hide_table_of_contents: false +description: 'Growing up in the 80s, John didnt have a computer until his father gave him one but with no games, encouraging him to learn programming.' --- Inspired by [Hanselmans post on how he got started in programming](http://www.hanselman.com/blog/SheLetMeTakeTheComputerHomeHowDidYouGetStartedInComputersAndProgramming.aspx) I thought I'd shared my own tale about how it all began... I grew up the 80's just outside London. For those of you of a different vintage let me paint a picture. These were the days when "Personal Computers", as they were then styled, were taking the world by storm. Every house would be equipped with either a ZX Spectrum, a Commodore 64 or an Amstrad CPC. These were 8 bit computers which were generally plugged into the family television and spent a good portion of their time loading games like [Target: Renegade](http://en.wikipedia.org/wiki/Target:_Renegade) from an audio cassette. But not in our house; we didn't have a computer. I remember mournfully pedalling home from friends houses on a number of occasions, glum as I compared my lot with theirs. Whereas my friends would be spending their evenings gleefully battering their keyboards as they thrashed the life out of various end-of-level bosses I was reduced to \***wasting**\* my time reading. That's right Enid Blyton - you were second best in my head. Then one happy day (and it may have been a Christmas present although I'm not certain) our family became the proud possessors of an [Amstrad CPC 6128](http://en.wikipedia.org/wiki/Amstrad_CPC): diff --git a/blog-website/blog/2012-06-04-reasons-to-be-cheerful-why-now-is-good/index.md b/blog-website/blog/2012-06-04-reasons-to-be-cheerful-why-now-is-good/index.md index 7d48d8dea65..0ea3f469235 100644 --- a/blog-website/blog/2012-06-04-reasons-to-be-cheerful-why-now-is-good/index.md +++ b/blog-website/blog/2012-06-04-reasons-to-be-cheerful-why-now-is-good/index.md @@ -4,6 +4,7 @@ title: 'Reasons to be Cheerful (why now is a good time to be a dev)' authors: johnnyreilly tags: [] hide_table_of_contents: false +description: 'Easy access to information via Google and the rise of JavaScript, as well as blogs, screencasts, and podcasts have made learning coding easier.' --- I've been a working as a developer in some way, shape or form for just over 10 years now. And it occurred to me the other day that I can't think of a better time to be a software developer than right now diff --git a/blog-website/blog/2012-07-01-how-im-structuring-my-javascript-in-web/index.md b/blog-website/blog/2012-07-01-how-im-structuring-my-javascript-in-web/index.md index 2de8ce17e61..d35b1743a73 100644 --- a/blog-website/blog/2012-07-01-how-im-structuring-my-javascript-in-web/index.md +++ b/blog-website/blog/2012-07-01-how-im-structuring-my-javascript-in-web/index.md @@ -4,6 +4,7 @@ title: 'Optimally Serving Up JavaScript' authors: johnnyreilly tags: [asp.net mvc, javascript, cassette] hide_table_of_contents: false +description: 'John describes his "JS second" approach to structuring JavaScript and using HtmlHelper to control the order of scripts in web apps for internal use.' --- I have occasionally done some server-side JavaScript with Rhino and Node.js but this is the exception rather than the rule. Like most folk at the moment, almost all the JavaScript I write is in a web context. @@ -82,333 +83,5 @@ namespace DummyMvc.Helpers /// http://stackoverflow.com/questions/5598167/mvc3-need-help-with-html-helper /// /// [Usage] - /// --- From each view / partial view --- - /// @Html.AddClientScript("~/Scripts/jquery.js") - /// @Html.AddClientScript("~/Scripts/jquery-ui.js", dependancies: new string[] {"~/Scripts/jquery.js"}) - /// @Html.AddClientScript("~/Scripts/site.js", dependancies: new string[] {"~/Scripts/jquery.js"}) - /// @Html.AddClientScript("~/Scripts/views/myview.js", dependancies: new string[] {"~/Scripts/jquery.js"}) /// - /// --- From the main Master/View (just before last Body tag so all "registered" scripts are included) --- - /// @Html.ClientScripts() - /// - public static class ScriptExtensions - { - #region Public - - /// - /// Render a Script element given a URL - /// - /// - /// URL of script to render - /// - public static MvcHtmlString Script( - this HtmlHelper helper, - string url) - { - return MvcHtmlString.Create(MakeScriptTag(helper, url)); - } - - /// - /// Add client script files to list of script files that will eventually be added to the view - /// - /// path to script file - /// OPTIONAL: a string array of any script dependancies - /// always MvcHtmlString.Empty - public static MvcHtmlString AddClientScript(this HtmlHelper helper, string scriptPath, string[] dependancies = null) - { - //If script list does not already exist then initialise - if (!helper.ViewContext.HttpContext.Items.Contains("client-script-list")) - helper.ViewContext.HttpContext.Items["client-script-list"] = new Dictionary>(); - - var scripts = helper.ViewContext.HttpContext.Items["client-script-list"] as Dictionary>; - - //Ensure scripts are not added twice - var scriptFilePath = helper.ViewContext.HttpContext.Server.MapPath(DetermineScriptToRender(helper, scriptPath)); - if (!scripts.ContainsKey(scriptFilePath)) - scripts.Add(scriptFilePath, new KeyValuePair(scriptPath, dependancies)); - - return MvcHtmlString.Empty; - } - - /// - /// Add a script tag for each "registered" script associated with the current view. - /// Output script tags with order depending on script dependancies - /// - /// MvcHtmlString - public static MvcHtmlString ClientScripts(this HtmlHelper helper) - { - var url = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection); - var scripts = helper.ViewContext.HttpContext.Items["client-script-list"] as Dictionary> ?? new Dictionary>(); - - //Build script tag block - var scriptList = new List(); - if (scripts.Count > 0) - { - //Check all script dependancies exist and throw an exception if any do not - var distinctDependancies = scripts.Where(s => s.Value.Value != null) - .SelectMany(s => s.Value.Value) - .Distinct() - .Select(s => GetScriptFilePath(helper, s)) //Exception will be thrown here if file does not exist - .ToList(); - - var missingDependancies = distinctDependancies.Except(scripts.Select(s => s.Key)).ToList(); - if (missingDependancies.Count > 0) - { - throw new KeyNotFoundException("The following dependancies are missing: " + Environment.NewLine + - Environment.NewLine + - string.Join(Environment.NewLine, missingDependancies)); - } - - //Serve scripts without dependancies first - scriptList.AddRange(scripts.Where(s => s.Value.Value == null).Select(s => s.Value.Key)); - - //Get all scripts which have dependancies - var scriptsAndDependancies = scripts.Where(s => s.Value.Value != null) - .OrderBy(s => s.Value.Value.Length) - .Select(s => s.Value) - .ToList(); - - //Loop round adding scripts to the scriptList until all are done - do - { - //Loop backwards through list so items can be removed mid loop - for (var i = scriptsAndDependancies.Count - 1; i >= 0; i--) - { - var script = scriptsAndDependancies[i].Key; - var dependancies = scriptsAndDependancies[i].Value; - - //Check if all the dependancies have been added to scriptList yet - bool currentScriptDependanciesAdded = !dependancies.Except(scriptList).Any(); - if (currentScriptDependanciesAdded) - { - //Move script to scriptList - scriptList.Add(script); - scriptsAndDependancies.RemoveAt(i); - } - } - } while (scriptsAndDependancies.Count > 0); - } - - //Generate a script tag for each script - var scriptsToRender = scriptList.Select(s => MakeScriptTag(helper, s)).ToList(); -#if DEBUG - scriptsToRender.Insert(0, ""); - scriptsToRender.Insert(scriptsToRender.Count, ""); -#endif - - //Output script tag block at point in view where method is called (by returning an MvcHtmlString) - return (scriptsToRender.Count > 0 - ? MvcHtmlString.Create(string.Join(Environment.NewLine, scriptsToRender)) - : MvcHtmlString.Empty); - } - - /// - /// Add client script block to list of script blocks that will eventually be added to the view - /// - /// unique identifier for script block - /// script block - /// OPTIONAL: a string array of any script block dependancies - /// always MvcHtmlString.Empty - public static MvcHtmlString AddClientScriptBlock(this HtmlHelper helper, string key, string scriptBlock, string[] dependancies = null) - { - //If script list does not already exist then initialise - if (!helper.ViewContext.HttpContext.Items.Contains("client-script-block-list")) - helper.ViewContext.HttpContext.Items["client-script-block-list"] = new Dictionary>(); - - var scriptBlocks = helper.ViewContext.HttpContext.Items["client-script-block-list"] as Dictionary>; - - //Prevent duplication - if (scriptBlocks.ContainsKey(key)) return MvcHtmlString.Empty; - - scriptBlocks.Add(key, new KeyValuePair(scriptBlock, dependancies)); - - return MvcHtmlString.Empty; - } - - /// - /// Output all "registered" script blocks associated with the current view. - /// Output script tags with order depending on script dependancies - /// - /// MvcHtmlString - public static MvcHtmlString ClientScriptBlocks(this HtmlHelper helper) - { - var scriptBlocks = helper.ViewContext.HttpContext.Items["client-script-block-list"] as Dictionary> ?? new Dictionary>(); - - //Build script tag block - var scriptBlockList = new List(); - if (scriptBlocks.Count > 0) - { - //Check all script dependancies exist and throw an exception if any do not - var distinctDependancies = scriptBlocks.Where(s => s.Value.Value != null) - .SelectMany(s => s.Value.Value) - .Distinct() - .ToList(); - - var missingDependancies = distinctDependancies.Except(scriptBlocks.Select(s => s.Key)).ToList(); - if (missingDependancies.Count > 0) - { - throw new KeyNotFoundException("The following dependancies are missing: " + Environment.NewLine + - Environment.NewLine + - string.Join(Environment.NewLine, missingDependancies)); - } - - //Serve script blocks without dependancies first - scriptBlockList.AddRange(scriptBlocks.Where(s => s.Value.Value == null).Select(s => s.Value.Key)); - - //Get all script blocks which have dependancies - var scriptBlocksAndDependancies = scriptBlocks.Where(s => s.Value.Value != null) - .OrderBy(s => s.Value.Value.Length) - .Select(s => s.Value) - .ToList(); - - //Loop round adding scripts to the scriptList until all are done - do - { - //Loop backwards through list so items can be removed mid loop - for (var i = scriptBlocksAndDependancies.Count - 1; i >= 0; i--) - { - var scriptBlock = scriptBlocksAndDependancies[i].Key; - var dependancies = scriptBlocksAndDependancies[i].Value; - - //Check if all the dependancies have been added to scriptList yet - bool currentScriptBlockDependanciesAdded = !dependancies.Except(scriptBlockList).Any(); - if (currentScriptBlockDependanciesAdded) - { - //Move script to scriptList - scriptBlockList.Add(scriptBlock); - scriptBlocksAndDependancies.RemoveAt(i); - } - } - } while (scriptBlocksAndDependancies.Count > 0); - } - - //Generate a script tag for each script - var scriptBlocksToRender = scriptBlockList.Select(s => string.Format("", Environment.NewLine, s)).ToList(); -#if DEBUG - scriptBlocksToRender.Insert(0, ""); - scriptBlocksToRender.Insert(scriptBlocksToRender.Count, ""); -#endif - - //Output script tag block at point in view where method is called (by returning an MvcHtmlString) - return (scriptBlocksToRender.Count > 0 - ? MvcHtmlString.Create(string.Join(Environment.NewLine, scriptBlocksToRender)) - : MvcHtmlString.Empty); - } - - #endregion - - #region Private - - /// - /// Take a URL, resolve it and a version suffix. In Debug this will be based on DateTime.Now to prevent caching - /// on a development machine. In Production this will be based on the version number of the appplication. - /// This means when the version number is incremented in subsequent releases script files should be recached automatically. - /// - /// - /// URL to resolve and add suffix to - /// - private static string ResolveUrlWithVersion(HtmlHelper helper, string url) - { -#if DEBUG - var suffix = DateTime.Now.Ticks.ToString(); -#else - var suffix = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); -#endif - - var urlWithVersionSuffix = string.Format("{0}?v={1}", url, suffix); - var urlResolved = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection).Content(urlWithVersionSuffix); - - return urlResolved; - } - - /// - /// Create the string that represents a script tag - /// - /// - /// - /// - private static string MakeScriptTag(HtmlHelper helper, string url) - { - var scriptToRender = DetermineScriptToRender(helper, url); - - //Render script tag - var scriptTag = new TagBuilder("script"); - scriptTag.Attributes["type"] = "text/javascript"; //This isn't really required with HTML 5 as this is the default. As it does no real harm so I have left it for now. http://stackoverflow.com/a/9659074/761388 - scriptTag.Attributes["src"] = SharedExtensions.ResolveUrlWithVersion(helper, scriptToRender); - - var scriptTagString = scriptTag.ToString(); - return scriptTagString; - } - - /// - /// Author : John Reilly - /// Description : Get the script that should be served to the user - throw an exception if it doesn't exist and minify if in release mode - /// - /// - /// - /// OPTIONAL - this allows you to directly specify the minified suffix if it differs from the standard - /// of "min.js" - unlikely this will ever be used but possible - /// - private static string DetermineScriptToRender(HtmlHelper helper, string url, string minifiedSuffix = "min.js") - { - //Initialise a list that will contain potential scripts to render - var possibleScriptsToRender = new List() { url }; - -#if DEBUG - //Don't add minified scripts in debug mode -#else - //Add minified path of script to list - possibleScriptsToRender.Insert(0, Path.ChangeExtension(url, minifiedSuffix)); -#endif - - var validScriptsToRender = possibleScriptsToRender.Where(s => File.Exists(helper.ViewContext.HttpContext.Server.MapPath(s))); - if (!validScriptsToRender.Any()) - throw new FileNotFoundException("Unable to render " + url + " as none of the following scripts exist:" + - string.Join(Environment.NewLine, possibleScriptsToRender)); - else - return validScriptsToRender.First(); //Return first existing file in list (minified file in release mode) - } - - #endregion - } -} ``` - -## Minification - I want to serve you less... - -Another tweak I made to the script helper meant that when compiling either the debug or production (minified) versions of common JS files will be included if available. This means in a production environment the users get minified JS files so faster loading. And in a development environment we get the full JS files which make debugging more straightforward. - -What I haven't started doing is minifying my own JS files as yet. I know I'm being somewhat inconsistent here by sometimes serving minified files and sometimes not. I'm not proud. Part of my rationale for this that since most of my users use my apps on a daily basis they will for the most part be using cached JS files. Obviously there'll be slightly slower load times the first time they go to a page but nothing that significant I hope. - -I have thought of starting to do my own minification as a build step but have held off for now. Again this is something being baked into .NET 4.5; another reason why I have held off doing this a different way for now. - -Update - -It now looks like this Microsofts optimisations have become [this Nuget package](http://nuget.org/packages/Microsoft.AspNet.Web.Optimization). It's early days (well it was released on 15th August 2012 and I'm writing this on the 16th) but I think this looks not to be tied to MVC 4 or .NET 4.5 in which case I could use it in my current MVC 3 projects. I hope so... - -By the way there's a [nice rundown of how to use this by K. Scott Allen of Pluralsight](http://www.pluralsight.com/training/Courses/TableOfContents/mvc4#mvc4-m3-optimization). It's fantastic. Recommended. - -Update 2 - -Having done a little asking around I now understand that this \***can**\* be used with MVC 3 / .NET 4.0. Excellent! - -One rather nice alternative script serving mechanism I've seen (but not yet used) is Andrew Davey's [Cassette](http://getcassette.net) which I mean to take for a test drive soon. This looks fantastic (and is available as a [Nuget package](http://nuget.org/packages/Cassette) \- 10 points!). - -## CDNs (they want to serve you) - -I've never professionally made use of CDNs at all. There are [clearly good reasons why you should](http://encosia.com/3-reasons-why-you-should-let-google-host-jquery-for-you/) but most of those good reasons relate most to public facing web apps. - -As I've said, the applications I tend to work on sit behind firewalls and it's not always guaranteed what my users can see from the grand old world of web beyond. (Indeed what they see can change on hour by hour basis sometimes...) Combined with that, because my apps are only accessible by a select few I don't face the pressure to reduce load on the server that public web apps can face. - -So while CDN's are clearly a good thing. I don't use them at present. And that's unlikely to change in the short term. - -## TL:DR - -1. I don't use CDNs - they're clearly useful but they don't suit my particular needs -2. I serve each JavaScript file individually just before the body tag. I don't bundle. -3. I don't minify my own scripts (though clearly it wouldn't be hard) but I do serve the minified versions of 3rd party libraries (eg jQuery) in a Production environment. -4. I don't use async script loaders at present. I may in future; we shall see. - -I expect some of the above may change (well, possibly not point #1) but this general approach is working well for me at present. - -I haven't touched at all on how I'm structuring my JavaScript code itself. Perhaps next time. diff --git a/blog-website/blog/2012-07-16-rendering-partial-view-to-string/index.md b/blog-website/blog/2012-07-16-rendering-partial-view-to-string/index.md index 2a499400331..ca093fffd0f 100644 --- a/blog-website/blog/2012-07-16-rendering-partial-view-to-string/index.md +++ b/blog-website/blog/2012-07-16-rendering-partial-view-to-string/index.md @@ -4,6 +4,7 @@ title: 'Rendering Partial View to a String' authors: johnnyreilly tags: [asp.net mvc] hide_table_of_contents: false +description: 'John solves a problem with Partial Views in ASP.NET MVC, allowing simplified code and multiple view nesting.' --- ## Well done that man! diff --git a/blog-website/blog/2012-08-06-jquery-unobtrusive-validation/index.md b/blog-website/blog/2012-08-06-jquery-unobtrusive-validation/index.md index 0d5e57876e0..a7e6d092094 100644 --- a/blog-website/blog/2012-08-06-jquery-unobtrusive-validation/index.md +++ b/blog-website/blog/2012-08-06-jquery-unobtrusive-validation/index.md @@ -4,6 +4,7 @@ title: 'jQuery Unobtrusive Validation (+ associated gotchas)' authors: johnnyreilly tags: [jquery unobtrusive validation] hide_table_of_contents: false +description: 'Implement unobtrusive jQuery validation in your MVC application using HTML 5 data attributes to simplify code maintenance and reduce mistakes.' --- I was recently working on a project which had client side validation manually set up which essentially duplicated the same logic on the server. Like many things this had started out small and grown and grown until it became arduos and tedious to maintain. diff --git a/blog-website/blog/2012-08-16-closedxml-real-sdk-for-excel/index.md b/blog-website/blog/2012-08-16-closedxml-real-sdk-for-excel/index.md index e24627988b0..f447063462e 100644 --- a/blog-website/blog/2012-08-16-closedxml-real-sdk-for-excel/index.md +++ b/blog-website/blog/2012-08-16-closedxml-real-sdk-for-excel/index.md @@ -4,6 +4,7 @@ title: 'ClosedXML - the real SDK for Excel' authors: johnnyreilly tags: [Open XML, Excel, ClosedXML] hide_table_of_contents: false +description: 'Closed XML simplifies Excel document creation for developers with its straightforward API, sitting on top of Open XML. A frustration-solver for many!' --- Simplicity appeals to me. It always has. Something that is simple is straightforward to comprehend and is consequently easy to use. It's clarity. diff --git a/blog-website/blog/2012-08-24-how-to-attribute-encode-partialview-in/index.md b/blog-website/blog/2012-08-24-how-to-attribute-encode-partialview-in/index.md index 1c714e1b789..f1c5945e5dc 100644 --- a/blog-website/blog/2012-08-24-how-to-attribute-encode-partialview-in/index.md +++ b/blog-website/blog/2012-08-24-how-to-attribute-encode-partialview-in/index.md @@ -4,6 +4,7 @@ title: 'How to attribute encode a PartialView in MVC (Razor)' authors: johnnyreilly tags: [asp.net mvc, razor] hide_table_of_contents: false +description: 'Find out how to attribute encode PartialView HTML in Razor/ASP.Net MVC with the HTML helper method `PartialAttributeEncoded`.' --- This post is plagiarism. But I'm plagiarising myself so I don't feel too bad. diff --git a/blog-website/blog/2012-09-06-globalize-and-jquery-validate/index.md b/blog-website/blog/2012-09-06-globalize-and-jquery-validate/index.md index d395396f29d..bea1e18f57e 100644 --- a/blog-website/blog/2012-09-06-globalize-and-jquery-validate/index.md +++ b/blog-website/blog/2012-09-06-globalize-and-jquery-validate/index.md @@ -4,6 +4,7 @@ title: 'Globalize and jQuery Validation' authors: johnnyreilly tags: [asp.net mvc, Globalize, jQuery Validation] hide_table_of_contents: false +description: 'A jQuery plugin has been replaced by Globalize and makes locale specific number and date formatting easy with Javascript; a tutorial on how to use it.' --- ## Updated 05/10/2015 @@ -111,100 +112,5 @@ And in my `web.config` I have following setting set: - - - + - /// Static class that is a store for commonly used filenames - /// (so if the files are updated they only need to be amended in a single place) - /// - public static class GlobalizeUrls - { - - /// - /// URL for Globalize: https://github.com/jquery/globalize - /// - public static string Globalize { get { return "~/Scripts/globalize.js"; } } - - /// - /// URL for the specific Globalize culture - /// - public static string GlobalizeCulture - { - get - { - //Determine culture - GUI culture for preference, user selected culture as fallback - var currentCulture = CultureInfo.CurrentCulture; - var filePattern = "~/scripts/globalize/globalize.culture.{0}.js"; - var regionalisedFileToUse = string.Format(filePattern, "en-GB"); //Default localisation to use - - //Try to pick a more appropriate regionalisation - if (File.Exists(HostingEnvironment.MapPath(string.Format(filePattern, currentCulture.Name)))) //First try for a globalize.culture.en-GB.js style file - regionalisedFileToUse = string.Format(filePattern, currentCulture.Name); - else if (File.Exists(HostingEnvironment.MapPath(string.Format(filePattern, currentCulture.TwoLetterISOLanguageName)))) //That failed; now try for a globalize.culture.en.js style file - regionalisedFileToUse = string.Format(filePattern, currentCulture.TwoLetterISOLanguageName); - - return regionalisedFileToUse; - } - } - } -} -``` - -## Putting it all together - -To make use of all of this together you'll need to have the `html lang` attribute set as described earlier and some scripts output in your layout page like this: - -```html - - - - - - -@* Only serve the following script if you need it: *@ - -``` - -Which will render something like this: - -```html - - - - - - -``` - -This will load up jQuery, Globalize, your Globalize culture, jQuery Validate, jQuery Validates unobtrusive extensions (which you don't need if you're not using them) and the jQuery Validate Globalize script which will set up culture aware validation. - -Finally and just to re-iterate, it's highly worthwhile to give [Scott Hanselman's original article a look](http://www.hanselman.com/blog/GlobalizationInternationalizationAndLocalizationInASPNETMVC3JavaScriptAndJQueryPart1.aspx). Most all the ideas in here were taken wholesale from him! diff --git a/blog-website/blog/2012-09-24-giving-odata-to-crm-40/index.md b/blog-website/blog/2012-09-24-giving-odata-to-crm-40/index.md index 59aecad4e6c..33e86c7898f 100644 --- a/blog-website/blog/2012-09-24-giving-odata-to-crm-40/index.md +++ b/blog-website/blog/2012-09-24-giving-odata-to-crm-40/index.md @@ -4,6 +4,7 @@ title: 'Giving OData to CRM 4.0' authors: johnnyreilly tags: [OData, WCF, CRM, LINQ] hide_table_of_contents: false +description: 'The article explains how to create an OData service to access Dynamics CRM 4.0 by using LINQ to CRM provider and WCF Data Services.' --- Just recently I was tasked with seeing if we could provide a way to access our Dynamics CRM instance via OData. My initial investigations made it seem like there was nothing for me to do; [CRM 2011 provides OData support out of the box](http://msdn.microsoft.com/en-us/library/gg309461.aspx). Small problem. We were running CRM 4.0. diff --git a/blog-website/blog/2012-10-03-unit-testing-and-entity-framework-filth/index.md b/blog-website/blog/2012-10-03-unit-testing-and-entity-framework-filth/index.md index 9816726475d..d83e082dc6a 100644 --- a/blog-website/blog/2012-10-03-unit-testing-and-entity-framework-filth/index.md +++ b/blog-website/blog/2012-10-03-unit-testing-and-entity-framework-filth/index.md @@ -4,6 +4,7 @@ title: 'Unit Testing and Entity Framework: The Filth and the Fury' authors: johnnyreilly tags: [unit testing, Entity Framework, MOQ] hide_table_of_contents: false +description: 'Controversy arises over Unit Testing with Entity Framework & MOQ. A simple class could be used to wrap all Entity Framework code.' --- Just recently I've noticed that there appears to be something of a controversy around Unit Testing and Entity Framework. I first came across it as I was Googling around for useful posts on using MOQ in conjunction with EF. I've started to notice the topic more and more and as I have mixed feelings on the subject (that is to say I don't have a settled opinion) I thought I'd write about this and see if I came to any kind of conclusion... diff --git a/blog-website/blog/2012-10-05-using-web-optimization-with-mvc-3/index.md b/blog-website/blog/2012-10-05-using-web-optimization-with-mvc-3/index.md index 08a0f7e6d27..d85017057e5 100644 --- a/blog-website/blog/2012-10-05-using-web-optimization-with-mvc-3/index.md +++ b/blog-website/blog/2012-10-05-using-web-optimization-with-mvc-3/index.md @@ -4,6 +4,7 @@ title: 'Using Web Optimization with MVC 3' authors: johnnyreilly tags: [asp.net] hide_table_of_contents: false +description: 'Optimize JavaScript/CSS in MVC 3 through Microsofts NuGet package, bundling jQuery, jQuery UI, jQuery Validate and Modernizr.' --- A while ago I [wrote](http://icanmakethiswork.blogspot.com/2012/06/how-im-structuring-my-javascript-in-web.html#WebOptimization) about optimally serving up JavaScript in web applications. I mentioned that Microsoft had come up with a NuGet package called [Microsoft ASP.NET Web Optimization](http://nuget.org/packages/Microsoft.AspNet.Web.Optimization) which could help with that by minifying and bundling CSS and JavaScript. At the time I was wondering if I would be able to to use this package with pre-existing MVC 3 projects (given that the package had been released together with MVC 4). Happily it turns out you can. But it's not quite as straightforward as I might have liked so I've documented how to get going with this here... @@ -131,7 +132,7 @@ Once you've done this you're ready to start using Web Optimization in your MVC 3 With a "vanilla" MVC 3 app the only use of CSS and JavaScript files is found in `_Layout.cshtml`. To switch over to using Web Optimization you should replace the existing `_Layout.cshtml` with this: (you'll see that the few differences that there are between the 2 are solely around the replacement of link / script tags with references to `Scripts` and `Styles` instead) ```html - + @ViewBag.Title @@ -168,7 +169,7 @@ Do note that in the above `Scripts.Render` call we're rendering out 3 bundles; j In your root web.config file make sure that the following tag is in place: `<compilation debug="true" targetFramework="4.0">`. Then run, the generated HTML should look something like this: ```html - + Home Page @@ -245,7 +246,7 @@ This demonstrates that when the application has debug set to true you see the fu Now go back to your root `web.config` file and chance the debug tag to false: `<compilation debug="false" targetFramework="4.0">`. This time when you run, the generated HTML should look something like this: ```html - + Home Page diff --git a/blog-website/blog/2012-10-22-mvc-3-meet-dictionary/index.md b/blog-website/blog/2012-10-22-mvc-3-meet-dictionary/index.md index 0717253c954..4bedbcecbc5 100644 --- a/blog-website/blog/2012-10-22-mvc-3-meet-dictionary/index.md +++ b/blog-website/blog/2012-10-22-mvc-3-meet-dictionary/index.md @@ -4,6 +4,7 @@ title: 'MVC 3 meet Dictionary' authors: johnnyreilly tags: [.NET Framework] hide_table_of_contents: false +description: 'MVC 3 has a Dictionary deserialization bug resolved in MVC 4. Workaround includes using JSON stringify and manual deserialization.' --- ## Documenting a JsonValueProviderFactory Gotcha diff --git a/blog-website/blog/2012-11-02-xsdxml-schema-generator-xsdexe-taking/index.md b/blog-website/blog/2012-11-02-xsdxml-schema-generator-xsdexe-taking/index.md index 37f7b8461d1..fcf2651013d 100644 --- a/blog-website/blog/2012-11-02-xsdxml-schema-generator-xsdexe-taking/index.md +++ b/blog-website/blog/2012-11-02-xsdxml-schema-generator-xsdexe-taking/index.md @@ -4,6 +4,7 @@ title: 'XSD/XML Schema Generator + Xsd.exe:Taking the pain out of manual XML' authors: johnnyreilly tags: [XSD, LINQ to XML] hide_table_of_contents: false +description: 'Discover how to use XSD for validating XML and generating C# classes from XSD files, including an online tool to simplify the task.' --- ## Is it 2003 again?!? @@ -71,204 +72,5 @@ Excited? Thought not. But what this means is we can hurl our XSD at this tool an And you're left with the lovely Contact.cs class: ```cs -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.239 // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -// -// This source code was auto-generated by xsd, Version=4.0.30319.1. -// -namespace MyNameSpace { - using System.Xml.Serialization; - - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)] - [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)] - public partial class contact { - - private string firstNameField; - - private string lastNameField; - - private sbyte heightInInchesField; - - private string typeField; - - /// - public string firstName { - get { - return this.firstNameField; - } - set { - this.firstNameField = value; - } - } - - /// - public string lastName { - get { - return this.lastNameField; - } - set { - this.lastNameField = value; - } - } - - /// - public sbyte heightInInches { - get { - return this.heightInInchesField; - } - set { - this.heightInInchesField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - } -} ``` - -## Justify Your Actions - -But why is this good stuff? Indeed why is this more interesting than the newer, and hence obviously cooler, LINQ to XML? Well for my money it's the following reasons that are important: - -1. Intellisense - I have always loved this. Call me lazy but I think intellisense frees up the mind to think about what problem you're actually trying to solve. Xsd.exe's generated classes give me that; I don't need to hold the whole data structure in my head as I code. -2. Terse code - I'm passionate about less code. I think that a noble aim in software development is to write as little code as possible in order to achieve your aims. I say this as generally I have found that writing a minimal amount of code expresses the intention of the code in a far clearer fashion. In service of that aim Xsd.exe's generated classes allow me to write less code than would be required with LINQ to XML. -3. To quote Scott Hanselman "[successful compilation is just the first unit test](http://www.hanselman.com/blog/NuGetPackageOfTheWeek6DynamicMalleableEnjoyableExpandoObjectsWithClay.aspx)". That it is but it's a doozy. If I'm making changes to the code and I've been using LINQ to XML I'm not going to see the benefits of strong typing that I would with Xsd.exe's generated classes. I like learning if I've broken the build sooner rather than later; strong typing gives me that safety net. - -## Serialization / Deserialization Helper - -As you read this you're no doubt thinking "but wait he's shown us how to create XSDs from XML and classes from XSDs but how do we take XML and turn it into objects? And how do we turn those objects back into XML?" - -See how I read your mind just there? It's a gift. Well, I've written a little static helper class for the very purpose: - -```cs -using System.IO; -using System.Linq; -using System.Text; -using System.Xml.Serialization; - -namespace My.Helpers -{ - public static class XmlConverter - { - private static XmlSerializer _serializer = null; - - #region Static Constructor - - /// - /// Static constructor that initialises the serializer for this type - /// - static XmlConverter() - { - _serializer = new XmlSerializer(typeof(T)); - } - - #endregion - - #region Public - - /// - /// Deserialize the supplied XML into an object - /// - /// - /// - public static T ToObject(string xml) - { - return (T)_serializer.Deserialize(new StringReader(xml)); - } - - /// - /// Serialize the supplied object into XML - /// - /// - /// - public static string ToXML(T obj) - { - using (var memoryStream = new MemoryStream()) - { - _serializer.Serialize(memoryStream, obj); - - return Encoding.UTF8.GetString(memoryStream.ToArray()); - } - } - - #endregion - } -} -``` - -And here's an example of how to use it: - -```cs -using MyNameSpace; - -//Make a new contact -contact myContact = new contact(); - -//Serialize the contact to XML -string myContactXML = XmlConverter.ToXML(myContact); - -//Deserialize the XML back into an object -contact myContactAgain = XmlConverter.ToObject(myContactXML); -``` - -I was tempted to name my methods in tribute to Crockford's JSON (namely `ToXML` becoming `stringify` and `ToObject` becoming `parse`). Maybe later. - -And that's us done. Whilst it's no doubt unfashionable I think that this is a very useful approach indeed and I commend it to the interweb! - -## Updated - using Xsd.exe to generate XSD from XML - -I was chatting to a friend about this blog post and he mentioned that you can actually use Xsd.exe to generate XSD files from XML as well. He's quite right - this feature does exist. To go back to our example from earlier we can execute the following command: - -`xsd.exe "C:\\Contact.xml" /out:"C:\\"` - -And this will generate the following file: - -```xsd - - - - - - - - - - - - - - - - - - - - -``` - -However, the XSD generated above is very much a "Microsoft XSD"; it's an XSD which features MS properties and so on. It's fine but I think that generally I prefer my XSDs to be as vanilla as possible. To that end I'm likely to stick to using the XSD/XML Schema Generator as it doesn't appear to be possible to get Xsd.exe to generate "vanilla XSD". - -Thanks to Ajay for bringing it to my attention though. diff --git a/blog-website/blog/2012-11-13-a-nicer-net-api-for-bloombergs-open-api/index.md b/blog-website/blog/2012-11-13-a-nicer-net-api-for-bloombergs-open-api/index.md index 8125b360859..7f35913fe60 100644 --- a/blog-website/blog/2012-11-13-a-nicer-net-api-for-bloombergs-open-api/index.md +++ b/blog-website/blog/2012-11-13-a-nicer-net-api-for-bloombergs-open-api/index.md @@ -4,6 +4,7 @@ title: "Getting up to speed with Bloomberg's Open API..." authors: johnnyreilly tags: [.NET, c#, Bloomberg, Open API] hide_table_of_contents: false +description: 'John documents his experience investigating Bloombergs Open API. He includes a simple C# console application wrapper for the API.' --- A good portion of any devs life is usually spent playing with APIs. If you need to integrate some other system into the system you're working on (and it's rare to come upon a situation where this doesn't happen at some point) then it's API time. diff --git a/blog-website/blog/2013-01-03-html-to-pdf-using-wcf-service/index.md b/blog-website/blog/2013-01-03-html-to-pdf-using-wcf-service/index.md index 61765acda0a..c86847342bd 100644 --- a/blog-website/blog/2013-01-03-html-to-pdf-using-wcf-service/index.md +++ b/blog-website/blog/2013-01-03-html-to-pdf-using-wcf-service/index.md @@ -4,6 +4,7 @@ title: 'HTML to PDF using a WCF Service' authors: johnnyreilly tags: [wkhtmltopdf, WCF, pdf] hide_table_of_contents: false +description: 'This ASP.NET WCF service creates PDFs from HTML and is remotely fired with wkhtmltopdf, using `webHttpBinding` for simple service calls.' --- ## TL; DR - "Talk is cheap. Show me the code." diff --git a/blog-website/blog/2013-01-09-twitterbootstrapmvc4-meet-bootstrap/index.md b/blog-website/blog/2013-01-09-twitterbootstrapmvc4-meet-bootstrap/index.md index 6edf8f2739a..614ff538e13 100644 --- a/blog-website/blog/2013-01-09-twitterbootstrapmvc4-meet-bootstrap/index.md +++ b/blog-website/blog/2013-01-09-twitterbootstrapmvc4-meet-bootstrap/index.md @@ -4,6 +4,7 @@ title: 'Twitter.Bootstrap.MVC4 meet Bootstrap Datepicker' authors: johnnyreilly tags: [asp.net mvc, Bootstrap] hide_table_of_contents: false +description: 'Learn about responsive web design and how to incorporate Twitter Bootstrap and Bootstrap Datepicker into ASP.Net MVC projects in this beginner’s guide.' --- ## Updated 14/01/2013 diff --git a/blog-website/blog/2013-01-14-twitterbootstrapmvc4-meet-bootstrap_14/index.md b/blog-website/blog/2013-01-14-twitterbootstrapmvc4-meet-bootstrap_14/index.md index 8a71eb8482a..96ccf32fa0f 100644 --- a/blog-website/blog/2013-01-14-twitterbootstrapmvc4-meet-bootstrap_14/index.md +++ b/blog-website/blog/2013-01-14-twitterbootstrapmvc4-meet-bootstrap_14/index.md @@ -4,6 +4,7 @@ title: 'Twitter.Bootstrap.MVC4 meet Bootstrap Datepicker *and* get your Internat authors: johnnyreilly tags: [Globalization, Bootstrap] hide_table_of_contents: false +description: 'Learn how to internationalize ASP.NET web apps using Globalize and Bootstrap Datepicker in this developers comprehensive step by step guide.' --- [Last time](../2013-01-09-twitterbootstrapmvc4-meet-bootstrap/index.md) I wrote about marrying up Twitter.Bootstrap.MVC4 and Bootstrap Datepicker. It came together quite nicely but when I took a more in depth look at what I'd done I discovered a problem. The brief work on regionalisation / internationalisation / localisation / globalisation / whatever it's called this week... wasn't really working. We had problems with the validation. @@ -158,7 +159,7 @@ The code above creates a script bundle for each culture when the application sta `_BootstrapLayout.basic.cshtml` has been amended to make use of the new bundles and also to include a meta tag that will used to drive regionalisation: ```html - + diff --git a/blog-website/blog/2013-02-13-using-expressions-with-constructors/index.md b/blog-website/blog/2013-02-13-using-expressions-with-constructors/index.md index a888056edbb..493f06932b8 100644 --- a/blog-website/blog/2013-02-13-using-expressions-with-constructors/index.md +++ b/blog-website/blog/2013-02-13-using-expressions-with-constructors/index.md @@ -4,6 +4,7 @@ title: 'Using Expressions with Constructors' authors: johnnyreilly tags: [.NET] hide_table_of_contents: false +description: 'This article explains how John used LINQs expression to extend a validation class and automatically change the property name.' --- Every now and then you think "x should be easy" - and it isn't. I had one of those situations this morning. Something I thought would take 5 minutes had me still pondering 30 minutes later. I finally cracked it (with the help of a colleague - thanks Marc!) and I wanted to note down what I did since I'm sure to forget this. diff --git a/blog-website/blog/2013-02-18-unit-testing-mvc-controllers-mocking/index.md b/blog-website/blog/2013-02-18-unit-testing-mvc-controllers-mocking/index.md index 18db3947ec0..6f5433ed4a2 100644 --- a/blog-website/blog/2013-02-18-unit-testing-mvc-controllers-mocking/index.md +++ b/blog-website/blog/2013-02-18-unit-testing-mvc-controllers-mocking/index.md @@ -4,6 +4,7 @@ title: 'Unit testing MVC controllers / Mocking UrlHelper' authors: johnnyreilly tags: [asp.net] hide_table_of_contents: false +description: 'This article presents a solution for testing ASP.net MVC controllers, including how to test controllers using UrlHelper.' --- ## I have put a name to my pain... diff --git a/blog-website/blog/2013-03-03-unit-testing-modelstate/index.md b/blog-website/blog/2013-03-03-unit-testing-modelstate/index.md index 4519d122060..ea0171bf1b0 100644 --- a/blog-website/blog/2013-03-03-unit-testing-modelstate/index.md +++ b/blog-website/blog/2013-03-03-unit-testing-modelstate/index.md @@ -4,6 +4,7 @@ title: 'Unit testing ModelState' authors: johnnyreilly tags: [asp.net mvc, unit testing] hide_table_of_contents: false +description: 'Testing Model validation in ASP.NET MVC can be accomplished by making use of ModelStateTestController class which simulates the functional tests.' --- - Me: "It can't be done" diff --git a/blog-website/blog/2013-03-11-decimalmodelbinder-for-nullable-decimals/index.md b/blog-website/blog/2013-03-11-decimalmodelbinder-for-nullable-decimals/index.md index 26f21c4a28d..9e208124459 100644 --- a/blog-website/blog/2013-03-11-decimalmodelbinder-for-nullable-decimals/index.md +++ b/blog-website/blog/2013-03-11-decimalmodelbinder-for-nullable-decimals/index.md @@ -4,6 +4,7 @@ title: 'DecimalModelBinder for nullable Decimals' authors: johnnyreilly tags: [Globalization, .NET] hide_table_of_contents: false +description: 'John forgot that MVCs ModelBinding doesnt handle regionalised numbers well. Provides solution found on Phil Haacks post.' --- My memory appears to be a sieve. Twice in the last year I've forgotten that MVCs ModelBinding doesn't handle regionalised numbers terribly well. Each time I've thought "hmmmm.... best Google that" and lo and behold come upon this post on the issue by the fantastic Phil Haack: diff --git a/blog-website/blog/2013-04-01-death-to-compatibility-mode/index.md b/blog-website/blog/2013-04-01-death-to-compatibility-mode/index.md index 1de72f6a86f..5bc39fca75c 100644 --- a/blog-website/blog/2013-04-01-death-to-compatibility-mode/index.md +++ b/blog-website/blog/2013-04-01-death-to-compatibility-mode/index.md @@ -4,6 +4,7 @@ title: 'Death to compatibility mode' authors: johnnyreilly tags: [internet explorer] hide_table_of_contents: false +description: 'John discusses compatibility mode in Internet Explorer and suggests using custom HTTP headers or meta tags to prevent rendering and CSS issues.' --- For just over 10 years my bread and butter has been the development and maintenance of line of business apps. More particularly, web apps built on the Microsoft stack of love ([© Scott Hanselman](https://channel9.msdn.com/Events/MIX/MIX11/FRM02)). These sort of apps are typically accessed via the company intranet and since "bring your own device" is still a relatively new innovation these apps are invariably built for everyones favourite browser: Internet Explorer. As we all know, enterprises are generally not that speedy when it comes to upgrades. So we're basically talking IE 9 at best, but more often than not, IE 8. @@ -74,7 +75,7 @@ Obviously there's a whole raft of ways you could get this in, using `Application The final approach uses meta tags. And, in my experience it is the most quirky approach - it doesn't always seem to work. First up, what do we do? Well, in each page served we include the following meta tag like this: ```html - + diff --git a/blog-website/blog/2013-04-09-making-ie-10s-clear-field-x-button-and/index.md b/blog-website/blog/2013-04-09-making-ie-10s-clear-field-x-button-and/index.md index 58ee43b75fe..91528d703c8 100644 --- a/blog-website/blog/2013-04-09-making-ie-10s-clear-field-x-button-and/index.md +++ b/blog-website/blog/2013-04-09-making-ie-10s-clear-field-x-button-and/index.md @@ -4,6 +4,7 @@ title: "Making IE 10's clear field (X) button and jQuery UI autocomplete play ni authors: johnnyreilly tags: [jQuery UI] hide_table_of_contents: false +description: 'IE 10 installed w/o notice on Johns machine, causing issues with jQuery UI auto-complete loading gifs which have been resolved with a CSS fix.' --- This morning when I logged on I was surprised to discover IE 10 had been installed onto my machine. I hadn't taken any action to trigger this myself and so I’m assuming that this was part of the general Windows Update mechanism. I know [Microsoft had planned to push IE 10 out through this mechanism](http://technet.microsoft.com/en-us/ie/jj898508.aspx). diff --git a/blog-website/blog/2013-04-17-ie-10-install-torches-javascript/index.md b/blog-website/blog/2013-04-17-ie-10-install-torches-javascript/index.md index f1d3b03cc0d..f867b3f2904 100644 --- a/blog-website/blog/2013-04-17-ie-10-install-torches-javascript/index.md +++ b/blog-website/blog/2013-04-17-ie-10-install-torches-javascript/index.md @@ -4,6 +4,7 @@ title: 'IE 10 Install Torches JavaScript Debugging in Visual Studio 2012 Through authors: johnnyreilly tags: [Visual Studio, javascript, IE 10] hide_table_of_contents: false +description: 'Learn how to fix missing Script Documents when debugging JavaScript in Visual Studio 2012, likely caused by auto-updating from IE9 to IE10.' --- OK the title of this post is a little verbose. I've just wasted a morning of my life trying to discover what happened to my ability to debug JavaScript in Visual Studio 2012. If you don't want to experience the same pain then read on... diff --git a/blog-website/blog/2013-04-26-a-navigation-animation-for-your-users/index.md b/blog-website/blog/2013-04-26-a-navigation-animation-for-your-users/index.md index 7c381d98229..97d6380609b 100644 --- a/blog-website/blog/2013-04-26-a-navigation-animation-for-your-users/index.md +++ b/blog-website/blog/2013-04-26-a-navigation-animation-for-your-users/index.md @@ -4,6 +4,7 @@ title: 'A navigation animation (for your users delectation)' authors: johnnyreilly tags: [CSS] hide_table_of_contents: false +description: 'Adding a CSS animation or GIF can help users navigating an app in an iframe get visual feedback despite the lack of browser feedback tics.' --- ## The Vexation diff --git a/blog-website/blog/2013-05-04-how-im-using-cassette/index.md b/blog-website/blog/2013-05-04-how-im-using-cassette/index.md index 96b41951125..9858f2eae1a 100644 --- a/blog-website/blog/2013-05-04-how-im-using-cassette/index.md +++ b/blog-website/blog/2013-05-04-how-im-using-cassette/index.md @@ -4,6 +4,7 @@ title: "How I'm Using Cassette part 1:Getting Up and Running" authors: johnnyreilly tags: [asp.net mvc, cassette] hide_table_of_contents: false +description: 'Learn how to serve JavaScript assets efficiently in ASP.Net MVC with Cassette to avoid duplicate scripts and ensure speedy loading.' --- ## Backing into the light @@ -137,7 +138,7 @@ If you're more familiar with the workings of Web Optimization than Cassette then Now we've created our bundles let's get the project serving up CSS and JavaScript using Cassette. First the layout file. Take the `_Layout.cshtml` file from this: ```html - + @@ -191,7 +192,7 @@ To this: ```html @{ Bundles.Reference("~/bundles/css"); Bundles.Reference("~/bundles/head"); Bundles.Reference("~/bundles/core"); } - + diff --git a/blog-website/blog/2013-06-06-how-im-using-cassette-part-2/index.md b/blog-website/blog/2013-06-06-how-im-using-cassette-part-2/index.md index 6369ca3eb57..3833ea9ae3a 100644 --- a/blog-website/blog/2013-06-06-how-im-using-cassette-part-2/index.md +++ b/blog-website/blog/2013-06-06-how-im-using-cassette-part-2/index.md @@ -4,6 +4,7 @@ title: "How I'm Using Cassette part 2:Get Cassette to Serve Scripts in Dependenc authors: johnnyreilly tags: [RequireJS, cassette] hide_table_of_contents: false +description: 'Cassettes script dependency order feature is the most useful, managing script order manually is tedious. Use server-side or JavaScript asset references.' --- [Last time](../2013-05-04-how-im-using-cassette/index.md) I wrote about Cassette I was talking about how to generally get up and running. How to use Cassette within an ASP.Net MVC project. What I want to write about now is (in my eyes) the most useful feature of Cassette by a country mile. This is Cassettes ability to ensure scripts are served in dependency order. @@ -90,7 +91,7 @@ $(document).ready(function () { $body .html( '
' + - 'I made it all go away...
' + 'I made it all go away...', ) .fadeIn(); }); diff --git a/blog-website/blog/2013-06-26-jquery-validate-native-unobtrusive-validation/index.md b/blog-website/blog/2013-06-26-jquery-validate-native-unobtrusive-validation/index.md index 70d539e17e1..650b5330ed1 100644 --- a/blog-website/blog/2013-06-26-jquery-validate-native-unobtrusive-validation/index.md +++ b/blog-website/blog/2013-06-26-jquery-validate-native-unobtrusive-validation/index.md @@ -4,6 +4,7 @@ title: 'jQuery Validation - Native Unobtrusive Validation Support!' authors: johnnyreilly tags: [jQuery Validation] hide_table_of_contents: false +description: 'Use HTML5 data attributes with jQuery Validation to simplify code and achieve validation unobtrusively. Ideal for dynamically added DOM elements.' --- Did you know that jQuery Validation natively supports the use of [HTML 5 data attributes](http://ejohn.org/blog/html-5-data-attributes/) to drive validation unobtrusively? Neither did I - I haven't seen any documentation for it. However, I was reading the [jQuery Validation test suite](https://github.com/jzaefferer/jquery-validation/blob/master/test/index.html) and that's what I spotted being used in some of the tests. @@ -19,7 +20,7 @@ So when I realised that there was native alternative available I was delighted. Not particularly exciting? Not noticably different to any other jQuery Validate demo you've ever seen? Fair enough. Now look at the source: ```html - + diff --git a/blog-website/blog/2013-08-08-announcing-jquery-validation/index.md b/blog-website/blog/2013-08-08-announcing-jquery-validation/index.md index ab75461aab9..5a67916d7a6 100644 --- a/blog-website/blog/2013-08-08-announcing-jquery-validation/index.md +++ b/blog-website/blog/2013-08-08-announcing-jquery-validation/index.md @@ -4,6 +4,7 @@ title: 'Announcing jQuery Validation Unobtrusive Native...' authors: johnnyreilly tags: [] hide_table_of_contents: false +description: 'jQuery Validation Unobtrusive Native bridges data attributes and jQuery Validations native support. The ASP.Net MVC HTML extension is available on GitHub.' --- I've been busy working on an open source project called **[jQuery Validation Unobtrusive Native](https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native)**. [To see it in action take a look here](https://johnnyreilly.github.io/jQuery.Validation.Unobtrusive.Native/). diff --git a/blog-website/blog/2013-08-17-using-bootstrap-tooltips-to-display/index.md b/blog-website/blog/2013-08-17-using-bootstrap-tooltips-to-display/index.md index e544d522397..80d6491b347 100644 --- a/blog-website/blog/2013-08-17-using-bootstrap-tooltips-to-display/index.md +++ b/blog-website/blog/2013-08-17-using-bootstrap-tooltips-to-display/index.md @@ -4,6 +4,7 @@ title: 'Using Bootstrap Tooltips to display jQuery Validation error messages' authors: johnnyreilly tags: [Tooltip, Bootstrap, jQuery Validation] hide_table_of_contents: false +description: 'Using tooltips can be a better approach than displaying validation messages next to the element being validated in jQuery Validation.' --- I love jQuery Validation. I was recently putting together a screen which had a lot of different bits of validation going on. And the default jQuery Validation approach of displaying the validation messages next to the element being validated wasn't working for me. That is to say, because of the amount of elements on the form, the appearance of validation messages was really making a mess of the presentation. So what to do? @@ -21,7 +22,7 @@ After a certain amount of fiddling I came up with a fairly solid mechanism for g Beautiful isn't it? Now look at the source: ```html - + diff --git a/blog-website/blog/2013-10-04-migrating-from-jquery.validate.unobtrusive.js-to-jQuery.Validation.Unobtrusive.Native/index.md b/blog-website/blog/2013-10-04-migrating-from-jquery.validate.unobtrusive.js-to-jQuery.Validation.Unobtrusive.Native/index.md index 52982ee39e2..68911ebf68e 100644 --- a/blog-website/blog/2013-10-04-migrating-from-jquery.validate.unobtrusive.js-to-jQuery.Validation.Unobtrusive.Native/index.md +++ b/blog-website/blog/2013-10-04-migrating-from-jquery.validate.unobtrusive.js-to-jQuery.Validation.Unobtrusive.Native/index.md @@ -4,6 +4,7 @@ title: 'Migrating from jquery.validate.unobtrusive.js to jQuery.Validation.Unobt authors: johnnyreilly tags: [jQuery Validation] hide_table_of_contents: false +description: 'Migrating from jquery.validation.unobtrusive.js to jQuery.Validation.Unobtrusive.Native is easy, with only minor tweaks to HTML and JS needed.' --- So, you're looking at [jQuery.Validation.Unobtrusive.Native](https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native). You're thinking to yourself "Yeah, I'd really like to use the native unobtrusive support in jQuery Validation. But I've already got this app which is using [jquery.validate.unobtrusive.js](https://www.nuget.org/packages/jQuery.Validation.Unobtrusive/) \- actually how easy is switching over?" Well I'm here to tell you that it's pretty straightforward - here's a walkthrough of how it might be done. diff --git a/blog-website/blog/2013-10-30-getting-typescript-compile-on-save-and-continous-integration-to-play-nice/index.md b/blog-website/blog/2013-10-30-getting-typescript-compile-on-save-and-continous-integration-to-play-nice/index.md index 8149b1480a9..4ab16466cdf 100644 --- a/blog-website/blog/2013-10-30-getting-typescript-compile-on-save-and-continous-integration-to-play-nice/index.md +++ b/blog-website/blog/2013-10-30-getting-typescript-compile-on-save-and-continous-integration-to-play-nice/index.md @@ -4,6 +4,7 @@ title: 'Getting TypeScript Compile-on-Save and Continuous Integration to play ni authors: johnnyreilly tags: [TFS, typescript] hide_table_of_contents: false +description: 'Learn how to compile TypeScript in Visual Studio without making TypeScript compilation part of the build process on the server.' --- Well sort of... Perhaps this post should more accurately called "How to get CI to ignore your TypeScript whilst Visual Studio still compiles it..." diff --git a/blog-website/blog/2013-11-04-typescript-dont-forget-build-action-for-implicit-referencing/index.md b/blog-website/blog/2013-11-04-typescript-dont-forget-build-action-for-implicit-referencing/index.md index 9cae44b7e94..f95532c24cd 100644 --- a/blog-website/blog/2013-11-04-typescript-dont-forget-build-action-for-implicit-referencing/index.md +++ b/blog-website/blog/2013-11-04-typescript-dont-forget-build-action-for-implicit-referencing/index.md @@ -4,6 +4,7 @@ title: "TypeScript: Don't forget Build Action for Implicit Referencing..." authors: johnnyreilly tags: [typescript, Definitely Typed, typescript, nuget] hide_table_of_contents: false +description: 'TypeScript files in Visual Studio now implicitly reference each other. This caused problems for some projects and its important to check file settings.' --- As part of the [known breaking changes between 0.9 and 0.9.1](https://typescript.codeplex.com/wikipage?title=Known%20breaking%20changes%20between%200.8%20and%200.9&referringTitle=Documentation) there was this subtle but significant switch: diff --git a/blog-website/blog/2013-11-26-rolling-your-own-confirm-mechanism/index.md b/blog-website/blog/2013-11-26-rolling-your-own-confirm-mechanism/index.md index cf266660bdc..812c3697236 100644 --- a/blog-website/blog/2013-11-26-rolling-your-own-confirm-mechanism/index.md +++ b/blog-website/blog/2013-11-26-rolling-your-own-confirm-mechanism/index.md @@ -4,6 +4,7 @@ title: 'Rolling your own confirm mechanism using Promises and jQuery UI' authors: johnnyreilly tags: [jQuery UI] hide_table_of_contents: false +description: 'Learn how to create a custom confirm dialog using jQuery UI’s dialog and promises. The custom dialog is more configurable than the default `window.confirm`.' --- We're here to talk about the [confirm](https://developer.mozilla.org/en-US/docs/Web/API/Window.confirm) dialog. Or, more specifically, how we can make our own confirm dialog. diff --git a/blog-website/blog/2013-12-04-simple-fading-in-and-out-using-css-transitions/index.md b/blog-website/blog/2013-12-04-simple-fading-in-and-out-using-css-transitions/index.md index 4b0ca962726..f921c0d6fec 100644 --- a/blog-website/blog/2013-12-04-simple-fading-in-and-out-using-css-transitions/index.md +++ b/blog-website/blog/2013-12-04-simple-fading-in-and-out-using-css-transitions/index.md @@ -4,6 +4,7 @@ title: 'Simple fading in and out using CSS transitions and classes' authors: johnnyreilly tags: [CSS] hide_table_of_contents: false +description: 'Learn to create a fade effect with CSS transitions for improved animation and battery life. Warning: display: none behaves differently than jQuery.' --- Caveat emptor folks... Let me start off by putting my hands up and saying I am no expert on CSS. And furthermore let me say that this blog post is essentially the distillation of a heady session of googling on the topic of CSS transitions. The credit for the technique detailed here belongs to many others, I'm just documenting it for my own benefit (and for anyone who stumbles upon this). @@ -84,7 +85,7 @@ $(document).on( if ($faded.hasClass('fadedOut')) { $faded.css('display', 'none'); } - } + }, ); ``` diff --git a/blog-website/blog/2013-12-13-nuget-and-webmatrix-how-to-install/index.md b/blog-website/blog/2013-12-13-nuget-and-webmatrix-how-to-install/index.md index c13a91c9aed..0301be95345 100644 --- a/blog-website/blog/2013-12-13-nuget-and-webmatrix-how-to-install/index.md +++ b/blog-website/blog/2013-12-13-nuget-and-webmatrix-how-to-install/index.md @@ -4,6 +4,7 @@ title: 'NuGet and WebMatrix: How to install a specific version of a package' authors: johnnyreilly tags: [jquery, NuGet] hide_table_of_contents: false +description: 'WebMatrix lacks NuGet command line, but users can still install a specific version manually by following the necessary steps - a bit of a challenge.' --- I've recently been experimenting with WebMatrix. If you haven't heard of it, WebMatrix is Microsoft's _["free, lightweight, cloud-connected web development tool"](http://www.microsoft.com/web/webmatrix/)_. All marketing aside, it's pretty cool. You can whip up a site in next to no time, it has source control, publishing abilities, intellisense. Much good stuff. And one thing it has, that I genuinely hadn't expected is [NuGet](https://www.nuget.org/). Brilliant! diff --git a/blog-website/blog/2014-01-09-upgrading-to-typescript-095-personal/index.md b/blog-website/blog/2014-01-09-upgrading-to-typescript-095-personal/index.md index baabbaa186b..cfd5ee13332 100644 --- a/blog-website/blog/2014-01-09-upgrading-to-typescript-095-personal/index.md +++ b/blog-website/blog/2014-01-09-upgrading-to-typescript-095-personal/index.md @@ -4,6 +4,7 @@ title: 'Upgrading to TypeScript 0.9.5 - A Personal Memoir' authors: johnnyreilly tags: [typescript] hide_table_of_contents: false +description: 'Upgrade to TypeScript 0.9.5 worth it despite Visual Studio issues. Declaration merging glitches resolved by interface-driven approach.' --- I recently made the step to upgrade from TypeScript 0.9.1.1 to 0.9.5. To my surprise this process was rather painful and certainly not an unalloyed pleasure. Since I'm now on the other side, so to speak, I thought I'd share my experience and cast back a rope bridge to those about to journey over the abyss. diff --git a/blog-website/blog/2014-01-24-integration-testing-with-entity/index.md b/blog-website/blog/2014-01-24-integration-testing-with-entity/index.md index c6c0f91d3df..bf35d44fb21 100644 --- a/blog-website/blog/2014-01-24-integration-testing-with-entity/index.md +++ b/blog-website/blog/2014-01-24-integration-testing-with-entity/index.md @@ -4,6 +4,7 @@ title: 'Integration Testing with Entity Framework and Snapshot Backups' authors: johnnyreilly tags: [Database Snapshots, Integration Testing, SQL Server] hide_table_of_contents: false +description: 'The article shows how to use SQL Servers snapshot backups for creating effective integration tests that dont affect production data.' --- I've written before about how unit testing [Entity Framework is a contentious and sometimes pointless activity](../2012-10-03-unit-testing-and-entity-framework-filth/index.md). The TL;DR is that LINQ-to-Objects != Linq-to-Entities and so if you want some useful tests around your data tier then integration tests that actually hit a database are what you want. diff --git a/blog-website/blog/2014-02-12-wpf-and-mystic-meg-or-playing/index.md b/blog-website/blog/2014-02-12-wpf-and-mystic-meg-or-playing/index.md index ec4a460032c..cfffda83a5b 100644 --- a/blog-website/blog/2014-02-12-wpf-and-mystic-meg-or-playing/index.md +++ b/blog-website/blog/2014-02-12-wpf-and-mystic-meg-or-playing/index.md @@ -4,6 +4,7 @@ title: 'WPF and Mystic Meg or Playing Futurologist' authors: johnnyreilly tags: [SPA] hide_table_of_contents: false +description: 'Native client apps will eventually be replaced by rich web apps/SPAs. WPF will become more niche, but wont die, predicts John.' --- Time for an unusual post. Most of what gets put down here is technical "how-to's". It's usually prompted by something I've been working on and serves, as much as anything else, as an aide-memoire. Not this time. diff --git a/blog-website/blog/2014-02-27-typescript-and-requirejs-keep-it-simple/index.md b/blog-website/blog/2014-02-27-typescript-and-requirejs-keep-it-simple/index.md index bb282071116..98b2ab6996b 100644 --- a/blog-website/blog/2014-02-27-typescript-and-requirejs-keep-it-simple/index.md +++ b/blog-website/blog/2014-02-27-typescript-and-requirejs-keep-it-simple/index.md @@ -4,6 +4,7 @@ title: 'TypeScript and RequireJS (Keep It Simple)' authors: johnnyreilly tags: [typescript] hide_table_of_contents: false +description: 'This article explains how to mix TypeScript and RequireJS, gives examples of the code changes needed, and shows how to create a demo.' --- I'm not the first to take a look at mixing TypeScript and RequireJS but I wanted to get it clear in my head. Also, I've always felt the best way to learn is to do. So here we go. I'm going to create a TypeScript and RequireJS demo based on [John Papa's "Keep It Simple RequireJS Demo"](https://github.com/johnpapa/kis-requirejs-demo/). @@ -15,7 +16,7 @@ So let's fire up Visual Studio 2013 and create a new ASP.NET Web Application cal Add a new HTML file to the root called “index.html” and base it on “index3.html” from [John Papa’s demo](https://github.com/johnpapa/kis-requirejs-demo/blob/master/ModularDemo/index3.html): ```html - + TypeScript with RequireJS diff --git a/blog-website/blog/2014-03-05-caching-and-cache-busting-with-requirejs/index.md b/blog-website/blog/2014-03-05-caching-and-cache-busting-with-requirejs/index.md index ed09e97230d..ce2a7a1ea85 100644 --- a/blog-website/blog/2014-03-05-caching-and-cache-busting-with-requirejs/index.md +++ b/blog-website/blog/2014-03-05-caching-and-cache-busting-with-requirejs/index.md @@ -4,6 +4,7 @@ title: 'Caching and cache-busting with RequireJS' authors: johnnyreilly tags: [asp.net, RequireJS, cache, caching] hide_table_of_contents: false +description: 'Learn how to use "urlArgs" in RequireJS to manage caching and offer a reusable solution for both development and production environments.' --- Having put together a demo of using TypeScript with RequireJS my attention turned quickly to caching. Or rather, IE forced me to think about caching. diff --git a/blog-website/blog/2014-03-11-knockout-globalize-valuenumber-binding/index.md b/blog-website/blog/2014-03-11-knockout-globalize-valuenumber-binding/index.md index f85d48159e4..85b0368bffc 100644 --- a/blog-website/blog/2014-03-11-knockout-globalize-valuenumber-binding/index.md +++ b/blog-website/blog/2014-03-11-knockout-globalize-valuenumber-binding/index.md @@ -4,6 +4,7 @@ title: 'Knockout + Globalize = valueNumber Binding Handler' authors: johnnyreilly tags: [Globalize, Knockout] hide_table_of_contents: false +description: 'Learn how to use Globalize and Knockout to create a "valueNumber" binding handler that makes numeric validation and localization easy.' --- I’ve long used [Globalize](https://github.com/jquery/globalize/) for my JavaScript number formatting / parsing needs. In a current project I’m using Knockout for the UI. When it came to data-binding numeric values none of the default binding handlers seemed appropriate. What I wanted was a binding handler that: @@ -24,7 +25,7 @@ ko.bindingHandlers.valueNumber = { valueAccessor, allBindingsAccessor, viewModel, - bindingContext + bindingContext, ) { /** * Adapted from the KO hasfocus handleElementFocusChange function diff --git a/blog-website/blog/2014-03-17-the-surprisingly-happy-tale-of-visual/index.md b/blog-website/blog/2014-03-17-the-surprisingly-happy-tale-of-visual/index.md index 30a45023673..26f1f124cf7 100644 --- a/blog-website/blog/2014-03-17-the-surprisingly-happy-tale-of-visual/index.md +++ b/blog-website/blog/2014-03-17-the-surprisingly-happy-tale-of-visual/index.md @@ -4,6 +4,7 @@ title: 'The Surprisingly Happy Tale of Visual Studio Online, Continous Integrati authors: johnnyreilly tags: [Jasmine, TFS, unit testing, javascript, Continuous Integration] hide_table_of_contents: false +description: 'John recounts his experience with JavaScript unit testing using Jasmine and Chutzpah for integration with Visual Studio and Team Foundation Service.' --- ## Going off piste diff --git a/blog-website/blog/2014-04-01-typescript-instance-methods/index.md b/blog-website/blog/2014-04-01-typescript-instance-methods/index.md index d62406b6d2d..92d4e1c3cf7 100644 --- a/blog-website/blog/2014-04-01-typescript-instance-methods/index.md +++ b/blog-website/blog/2014-04-01-typescript-instance-methods/index.md @@ -4,6 +4,7 @@ title: 'TypeScript this is what I want! (the unfortunate neglect of Instance Met authors: johnnyreilly tags: [typescript] hide_table_of_contents: false +description: 'TypeScripts "Instance Methods" feature solves the `this` keyword issues in classes, unlike prototype methods. It suggests using a combination of the two.' --- I was recently reading [Jeff Walker's blog post "Why TypeScript Isn't the Answer"](http://www.walkercoderanger.com/blog/2014/02/typescript-isnt-the-answer/). This is part of series in which Jeff goes through various compile-to-JavaScript technologies including TypeScript, CoffeeScript and Dart and explains his view of why he feels they don't quite hit the mark. diff --git a/blog-website/blog/2014-05-05-typescript-jsdoc-and-intellisense/index.md b/blog-website/blog/2014-05-05-typescript-jsdoc-and-intellisense/index.md index da46e69ba08..348aa11fffd 100644 --- a/blog-website/blog/2014-05-05-typescript-jsdoc-and-intellisense/index.md +++ b/blog-website/blog/2014-05-05-typescript-jsdoc-and-intellisense/index.md @@ -4,6 +4,7 @@ title: 'TypeScript, JSDoc and Intellisense' authors: johnnyreilly tags: [jquery, JSDoc, typescript] hide_table_of_contents: false +description: 'Transforming API documentation into JSDoc for TypeScript: author explains how he enriched popular `jquery.d.ts` file with comments.' --- ## Days of Yore diff --git a/blog-website/blog/2014-05-15-team-foundation-server-continuous-integration-and-javascript-unit-tests-in-unit-test-project/index.md b/blog-website/blog/2014-05-15-team-foundation-server-continuous-integration-and-javascript-unit-tests-in-unit-test-project/index.md index 16fa63fa2f8..b34476c8588 100644 --- a/blog-website/blog/2014-05-15-team-foundation-server-continuous-integration-and-javascript-unit-tests-in-unit-test-project/index.md +++ b/blog-website/blog/2014-05-15-team-foundation-server-continuous-integration-and-javascript-unit-tests-in-unit-test-project/index.md @@ -11,6 +11,7 @@ tags: Chutzpah, ] hide_table_of_contents: false +description: 'Learn how to run JavaScript tests on TFS/VSO by creating a separate unit test project to house tests, and installing Chutzpah on TFS/VSO.' --- Do you like to separate out your unit tests from the project you are testing? I imagine so. My own practice when creating a new project in Visual Studio is to create a separate unit test project alongside whose responsibility is to house unit tests for that new project. diff --git a/blog-website/blog/2014-06-01-migrating-from-angularjs-to-angularts/index.md b/blog-website/blog/2014-06-01-migrating-from-angularjs-to-angularts/index.md index c8e1f8cb298..c15e1bb4645 100644 --- a/blog-website/blog/2014-06-01-migrating-from-angularjs-to-angularts/index.md +++ b/blog-website/blog/2014-06-01-migrating-from-angularjs-to-angularts/index.md @@ -4,6 +4,7 @@ title: 'Migrating from AngularJS to AngularTS - a walkthrough' authors: johnnyreilly tags: [Jasmine, typescript, Unit tests, AngularJS] hide_table_of_contents: false +description: 'Learn how to migrate an AngularJS app from JavaScript to TypeScript in this walkthrough on a simple website/app for sending prayer requests.' --- It started with nuns. Don't all good stories start that way? One of my (many) aunts is a Poor Clare nun. At some point in the distant past I was cajoled into putting together a simple website for her convent. This post is a walkthrough of how to migrate from AngularJS using JavaScript to AngularJS using TypeScript. It just so happens that the AngularJS app in question is the one that belongs to my mother's sister's convent. @@ -169,7 +170,7 @@ angular.module('poorClaresApp.services').factory( determineSiteSection: determineSiteSection, }; }, - ] + ], ); ``` @@ -189,7 +190,7 @@ As with `siteSectionService` we need to create an interface to define what `pray interface IPrayerRequestService { sendPrayerRequest: ( email: string, - prayFor: string + prayFor: string, ) => ng.IPromise<{ success: boolean; text: string; @@ -219,7 +220,7 @@ angular.module('poorClaresApp.services').factory( sendPrayerRequest: sendPrayerRequest, }; }, - ] + ], ); ``` @@ -264,7 +265,7 @@ angular.module('poorClaresApp.controllers').controller( }); }; }, - ] + ], ); ``` @@ -282,7 +283,7 @@ module poorClaresApp.controllers { static $inject = ['$scope', 'prayerRequestService']; constructor( private $scope: ng.IScope, - private prayerRequestService: IPrayerRequestService + private prayerRequestService: IPrayerRequestService, ) {} message: { success: boolean; text: string }; @@ -345,7 +346,7 @@ angular.module('poorClaresApp.controllers').controller( 'siteSectionService', function ( $scope: INavControllerScope, - siteSectionService: ISiteSectionService + siteSectionService: ISiteSectionService, ) { $scope.isCollapsed = true; $scope.siteSection = siteSectionService.getSiteSection(); @@ -354,10 +355,10 @@ angular.module('poorClaresApp.controllers').controller( siteSectionService.getSiteSection, function (newValue, oldValue) { $scope.siteSection = newValue; - } + }, ); }, - ] + ], ); ``` @@ -378,7 +379,7 @@ module poorClaresApp.controllers { static $inject = ['$scope', 'siteSectionService']; constructor( private $scope: INavControllerScope, - private siteSectionService: ISiteSectionService + private siteSectionService: ISiteSectionService, ) { $scope.isCollapsed = true; $scope.siteSection = siteSectionService.getSiteSection(); @@ -387,7 +388,7 @@ module poorClaresApp.controllers { siteSectionService.getSiteSection, function (newValue, oldValue) { $scope.siteSection = newValue; - } + }, ); } } @@ -413,11 +414,11 @@ angular.module('poorClaresApp.controllers').controller( 'siteSectionService', function ( $location: ng.ILocationService, - siteSectionService: ISiteSectionService + siteSectionService: ISiteSectionService, ) { siteSectionService.determineSiteSection($location.path()); }, - ] + ], ); ``` @@ -431,7 +432,7 @@ module poorClaresApp.controllers { static $inject = ['$location', 'siteSectionService']; constructor( private $location: ng.ILocationService, - private siteSectionService: ISiteSectionService + private siteSectionService: ISiteSectionService, ) { siteSectionService.determineSiteSection($location.path()); } diff --git a/blog-website/blog/2014-06-20-dates-DataAnnotations-and-data-impedance-mismatch/index.md b/blog-website/blog/2014-06-20-dates-DataAnnotations-and-data-impedance-mismatch/index.md index 8404dda6527..0e3aae2754e 100644 --- a/blog-website/blog/2014-06-20-dates-DataAnnotations-and-data-impedance-mismatch/index.md +++ b/blog-website/blog/2014-06-20-dates-DataAnnotations-and-data-impedance-mismatch/index.md @@ -4,6 +4,7 @@ title: 'A folk story wherein we shall find dates, DataAnnotations & data impedan authors: johnnyreilly tags: [Date] hide_table_of_contents: false +description: 'This article offers developers an attribute-based solution to prevent datetime errors, ensuring that DateTime properties only include dates.' --- If you ever take a step back from what you're doing it can sometimes seem pretty abstract. Here's an example. I was looking at an issue in an app that I was supporting. The problem concerned a field which was to store a date value. Let's call it, for the sake of argument, `valuation_date`. (Clearly in reality the field name was entirely different... Probably.) This field was supposed to represent a specific date, like June 15th 2012 or 19th August 2014. To be clear, a date and \***not**\* in any way, a time. diff --git a/blog-website/blog/2014-07-03-hottowel-angular-meet-typescript/index.md b/blog-website/blog/2014-07-03-hottowel-angular-meet-typescript/index.md index a99a5612be4..9e7f266a6eb 100644 --- a/blog-website/blog/2014-07-03-hottowel-angular-meet-typescript/index.md +++ b/blog-website/blog/2014-07-03-hottowel-angular-meet-typescript/index.md @@ -4,6 +4,7 @@ title: 'HotTowel-Angular meet TypeScript' authors: johnnyreilly tags: [typescript, AngularJS] hide_table_of_contents: false +description: 'Johnny Reilly creates a bare-bones port of the Hot Towel Angular SPA Template to TypeScript in order to demonstrate the ease of transition.' --- I've recently ported John Papa's popular [Hot Towel Angular SPA Template](https://github.com/johnpapa/HotTowel-Angular) to TypeScript. Why? [Because it was there.](http://en.wikipedia.org/wiki/George_Mallory) diff --git a/blog-website/blog/2014-08-01-angularjs-meet-aspnet-server-validation/index.md b/blog-website/blog/2014-08-01-angularjs-meet-aspnet-server-validation/index.md index 91e06efd25d..edf57ae723c 100644 --- a/blog-website/blog/2014-08-01-angularjs-meet-aspnet-server-validation/index.md +++ b/blog-website/blog/2014-08-01-angularjs-meet-aspnet-server-validation/index.md @@ -4,6 +4,7 @@ title: 'AngularJS meet ASP.Net Server Validation' authors: johnnyreilly tags: [asp.net, typescript, AngularJS] hide_table_of_contents: false +description: 'Learn how to perform server-side validation in your AngularJS and ASP.Net project using a `serverError` directive and server response error messages.' --- So. You're using AngularJS to build your front end with ASP.Net running on the server side. You're a trustworthy dev - you know that validation on the client will only get you so far. You need to validate on the server. @@ -129,7 +130,7 @@ app.directive('serverError', [ safeWatch(function () { return ngModelController.$error.server; }), - showHideValidation + showHideValidation, ); function showHideValidation(serverError) { @@ -141,7 +142,7 @@ app.directive('serverError', [ var errorKey = scope.name; errorHtml = template.replace( /%error%/, - errorDictionary[errorKey] || 'Unknown error occurred...' + errorDictionary[errorKey] || 'Unknown error occurred...', ); } decorator.html(errorHtml); @@ -274,7 +275,7 @@ module controllers { private $routeParams: sageEditRouteParams, private $scope: sageEditScope, private common: common, - private datacontext: datacontext + private datacontext: datacontext, ) { this.errors = {}; this.log = common.logger.getLogFn(controllerId); @@ -315,7 +316,7 @@ module controllers { if (response.success) { this.sage = response.entity; this.logSuccess( - 'Saved ' + this.sage.name + ' [' + this.sage.id + ']' + 'Saved ' + this.sage.name + ' [' + this.sage.id + ']', ); this.$location.path('/sages/detail/' + this.sage.id); } else { @@ -324,7 +325,7 @@ module controllers { angular.forEach(response.errors, (errors, field) => { (this.$scope.form[field]).$setValidity( 'server', - false + false, ); this.errors[field] = errors.join(','); }); @@ -404,7 +405,7 @@ var controllers; if (response.success) { _this.sage = response.entity; _this.logSuccess( - 'Saved ' + _this.sage.name + ' [' + _this.sage.id + ']' + 'Saved ' + _this.sage.name + ' [' + _this.sage.id + ']', ); _this.$location.path('/sages/detail/' + _this.sage.id); diff --git a/blog-website/blog/2014-08-08-getting-more-RESTful-with-Web-API/index.md b/blog-website/blog/2014-08-08-getting-more-RESTful-with-Web-API/index.md index 94a2c3957c0..a7dbfc9fb3c 100644 --- a/blog-website/blog/2014-08-08-getting-more-RESTful-with-Web-API/index.md +++ b/blog-website/blog/2014-08-08-getting-more-RESTful-with-Web-API/index.md @@ -4,6 +4,7 @@ title: 'Getting more RESTful with Web API and IHttpActionResult' authors: johnnyreilly tags: [ASP.NET] hide_table_of_contents: false +description: 'Learn how to use HTTP status codes in Web API methods to return successful or failed requests without wrapping the outcomes.' --- Up until, well yesterday really, I tended to have my Web API action methods all returning [200](http://en.wikipedia.org/wiki/HTTP_200#2xx_Success)'s no matter what. Successful request? 200 for you sir! Some validation error in the model? 200 for you too ma'am - but I'll wrap up the validation errors and send them back too. Database error? 200 and and an error message. diff --git a/blog-website/blog/2014-08-12-my-unrequited-love-for-isolate-scope/index.md b/blog-website/blog/2014-08-12-my-unrequited-love-for-isolate-scope/index.md index a47455f2bc2..7f4a9d46a6d 100644 --- a/blog-website/blog/2014-08-12-my-unrequited-love-for-isolate-scope/index.md +++ b/blog-website/blog/2014-08-12-my-unrequited-love-for-isolate-scope/index.md @@ -4,6 +4,7 @@ title: 'My Unrequited Love for Isolate Scope' authors: johnnyreilly tags: [typescript, javascript, Bootstrap, AngularJS] hide_table_of_contents: false +description: 'A new version of the serverError directive is presented without isolated scope after discovering directives can only create one isolated scope.' --- [I wrote a little while ago about creating a directive to present server errors on the screen in an Angular application](../2014-08-01-angularjs-meet-aspnet-server-validation/index.md). In my own (not so humble opinion), it was really quite nice. I was particularly proud of my usage of isolate scope. However, pride comes before a fall. @@ -50,7 +51,7 @@ So ladies and gentlemen, let me present serverError 2.0 – this time without is scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, - ngModelController: ng.INgModelController + ngModelController: ng.INgModelController, ) { // Extract values from attributes (deliberately not using isolated scope) var errorKey: string = attrs['name']; // eg "sage.name" @@ -67,7 +68,7 @@ So ladies and gentlemen, let me present serverError 2.0 – this time without is // Watch ngModelController.$error.server & show/hide validation accordingly scope.$watch( safeWatch(() => ngModelController.$error.server), - showHideValidation + showHideValidation, ); function showHideValidation(serverError: boolean) { @@ -75,11 +76,11 @@ So ladies and gentlemen, let me present serverError 2.0 – this time without is var errorHtml = ''; if (serverError) { var errorDictionary: { [field: string]: string } = scope.$eval( - errorDictionaryExpression + errorDictionaryExpression, ); errorHtml = template.replace( /%error%/, - errorDictionary[errorKey] || 'Unknown error occurred...' + errorDictionary[errorKey] || 'Unknown error occurred...', ); } decorator.html(errorHtml); @@ -147,7 +148,7 @@ So ladies and gentlemen, let me present serverError 2.0 – this time without is safeWatch(function () { return ngModelController.$error.server; }), - showHideValidation + showHideValidation, ); function showHideValidation(serverError) { @@ -157,7 +158,7 @@ So ladies and gentlemen, let me present serverError 2.0 – this time without is var errorDictionary = scope.$eval(errorDictionaryExpression); errorHtml = template.replace( /%error%/, - errorDictionary[errorKey] || 'Unknown error occurred...' + errorDictionary[errorKey] || 'Unknown error occurred...', ); } decorator.html(errorHtml); diff --git a/blog-website/blog/2014-09-06-running-javascript-unit-tests-in-appveyor/index.md b/blog-website/blog/2014-09-06-running-javascript-unit-tests-in-appveyor/index.md index e6fedf175ba..2a123847e06 100644 --- a/blog-website/blog/2014-09-06-running-javascript-unit-tests-in-appveyor/index.md +++ b/blog-website/blog/2014-09-06-running-javascript-unit-tests-in-appveyor/index.md @@ -5,6 +5,7 @@ authors: johnnyreilly tags: [Jasmine, javascript, Unit tests, Continuous Integration, AppVeyor, Chutzpah] hide_table_of_contents: false +description: 'AppVeyor and Chutzpah were integrated to run C# and JavaScript unit tests in a single PowerShell script for CI purposes.' --- ## With a little help from Chutzpah... diff --git a/blog-website/blog/2014-09-10-unit-testing-angular-controller-with/index.md b/blog-website/blog/2014-09-10-unit-testing-angular-controller-with/index.md index 485c43f7920..c3a40f21368 100644 --- a/blog-website/blog/2014-09-10-unit-testing-angular-controller-with/index.md +++ b/blog-website/blog/2014-09-10-unit-testing-angular-controller-with/index.md @@ -4,6 +4,7 @@ title: 'Unit Testing an Angular Controller with Jasmine' authors: johnnyreilly tags: [Jasmine, unit tests, AngularJS] hide_table_of_contents: false +description: 'John shares how they wrote unit tests for an Angular controller in Proverb using Jasmine 2.0, with heavily annotated JavaScript tests.' --- Anyone who reads my blog will know that I have been long in the habit of writing unit tests for my C# code. I'm cool like that. However, it took me a while to get up and running writing unit tests for my JavaScript code. I finally [got there](../2014-03-17-the-surprisingly-happy-tale-of-visual/index.md) using a combination of Jasmine 2.0 and Chutzpah. (Jasmine being my test framework and Chutzpah being my test runner.) @@ -55,7 +56,7 @@ module controllers { private $location: ng.ILocationService, private $routeParams: sageDetailRouteParams, private common: common, - private datacontext: datacontext + private datacontext: datacontext, ) { this.sage = undefined; this.title = 'Sage Details'; @@ -179,7 +180,7 @@ describe('Proverb.Web -> app-> controllers ->', function () { _$q_, _$location_, _common_, - _datacontext_ + _datacontext_, ) { // Note how each parameter is prefixed and suffixed with "_" - this an Angular nicety // which allows you to have variables in your tests with the original reference name. @@ -204,7 +205,7 @@ describe('Proverb.Web -> app-> controllers ->', function () { // this allows us to #1 detect that getById has been called // and #2 resolve / reject our promise as our test requires using getById_deferred spyOn(datacontext.sage, 'getById').and.returnValue( - getById_deferred.promise + getById_deferred.promise, ); // set up a spy on common.activateController and set it to call through @@ -214,7 +215,7 @@ describe('Proverb.Web -> app-> controllers ->', function () { // set up spys on common.logger.getLogFn and $location.path so we can detect they have been called spyOn(common.logger, 'getLogFn').and.returnValue( - jasmine.createSpy('log') + jasmine.createSpy('log'), ); spyOn($location, 'path').and.returnValue(jasmine.createSpy('path')); @@ -281,10 +282,10 @@ describe('Proverb.Web -> app-> controllers ->', function () { // this.log("Activated Sage Details View"); // this.title = "Sage Details: " + this.sage.name; expect(sageDetailController.log).toHaveBeenCalledWith( - 'Activated Sage Details View' + 'Activated Sage Details View', ); expect(sageDetailController.title).toBe( - 'Sage Details: ' + sage_stub.name + 'Sage Details: ' + sage_stub.name, ); }); }); @@ -309,7 +310,7 @@ describe('Proverb.Web -> app-> controllers ->', function () { // tests this code has executed: // this.$location.path("/sages/edit/" + this.sage.id); expect($location.path).toHaveBeenCalledWith( - '/sages/edit/' + sage_stub.id + '/sages/edit/' + sage_stub.id, ); }); }); diff --git a/blog-website/blog/2014-09-13-migrating-jasmine-tests-to-typescript/index.md b/blog-website/blog/2014-09-13-migrating-jasmine-tests-to-typescript/index.md index b7731347e89..938e15b61d0 100644 --- a/blog-website/blog/2014-09-13-migrating-jasmine-tests-to-typescript/index.md +++ b/blog-website/blog/2014-09-13-migrating-jasmine-tests-to-typescript/index.md @@ -4,6 +4,7 @@ title: 'Journalling the Migration of Jasmine Tests to TypeScript' authors: johnnyreilly tags: [Jasmine, typescript, javascript] hide_table_of_contents: false +description: 'Author describes issues migrating Jasmine tests from JS to TypeScript, including tooling, typings, and missing dependencies.' --- I previously attempted to migrate my Jasmine tests from JavaScript to TypeScript. The last time I tried it didn't go so well and I bailed. Thank the Lord for source control. But feeling I shouldn't be deterred I decided to have another crack at it. @@ -37,7 +38,7 @@ describe('Proverb.Web -> app-> controllers ->', function () { _$q_, _$location_, _common_, - _datacontext_ + _datacontext_, ) { $rootScope = _$rootScope_; $q = _$q_; @@ -50,11 +51,11 @@ describe('Proverb.Web -> app-> controllers ->', function () { getById_deferred = $q.defer(); spyOn(datacontext.sage, 'getById').and.returnValue( - getById_deferred.promise + getById_deferred.promise, ); spyOn(common, 'activateController').and.callThrough(); spyOn(common.logger, 'getLogFn').and.returnValue( - jasmine.createSpy('log') + jasmine.createSpy('log'), ); spyOn($location, 'path').and.returnValue(jasmine.createSpy('path')); @@ -98,10 +99,10 @@ describe('Proverb.Web -> app-> controllers ->', function () { $rootScope.$digest(); // So Angular processes the resolved promise expect(sageDetailController.log).toHaveBeenCalledWith( - 'Activated Sage Details View' + 'Activated Sage Details View', ); expect(sageDetailController.title).toBe( - 'Sage Details: ' + sage_stub.name + 'Sage Details: ' + sage_stub.name, ); }); }); @@ -119,7 +120,7 @@ describe('Proverb.Web -> app-> controllers ->', function () { sageDetailController.gotoEdit(); expect($location.path).toHaveBeenCalledWith( - '/sages/edit/' + sage_stub.id + '/sages/edit/' + sage_stub.id, ); }); }); @@ -223,7 +224,7 @@ describe('Proverb.Web -> app-> controllers ->', function () { _$q_: ng.IQService, _$location_: ng.ILocationService, _common_: common, - _datacontext_: datacontext + _datacontext_: datacontext, ) { $rootScope = _$rootScope_; var $q = _$q_; @@ -236,11 +237,11 @@ describe('Proverb.Web -> app-> controllers ->', function () { getById_deferred = $q.defer(); spyOn(datacontext.sage, 'getById').and.returnValue( - getById_deferred.promise + getById_deferred.promise, ); spyOn(common, 'activateController').and.callThrough(); spyOn(common.logger, 'getLogFn').and.returnValue( - jasmine.createSpy('log') + jasmine.createSpy('log'), ); spyOn($location, 'path').and.returnValue(jasmine.createSpy('path')); @@ -290,10 +291,10 @@ describe('Proverb.Web -> app-> controllers ->', function () { $rootScope.$digest(); // So Angular processes the resolved promise expect(sageDetailController.log).toHaveBeenCalledWith( - 'Activated Sage Details View' + 'Activated Sage Details View', ); expect(sageDetailController.title).toBe( - 'Sage Details: ' + sage_stub.name + 'Sage Details: ' + sage_stub.name, ); }); }); @@ -317,7 +318,7 @@ describe('Proverb.Web -> app-> controllers ->', function () { sageDetailController.gotoEdit(); expect($location.path).toHaveBeenCalledWith( - '/sages/edit/' + sage_stub.id + '/sages/edit/' + sage_stub.id, ); }); }); diff --git a/blog-website/blog/2014-10-03-he-tasks-me-he-heaps-me-i-will-wreak/index.md b/blog-website/blog/2014-10-03-he-tasks-me-he-heaps-me-i-will-wreak/index.md index e2700e9d746..fdc31a6f0af 100644 --- a/blog-website/blog/2014-10-03-he-tasks-me-he-heaps-me-i-will-wreak/index.md +++ b/blog-website/blog/2014-10-03-he-tasks-me-he-heaps-me-i-will-wreak/index.md @@ -4,6 +4,7 @@ title: 'He tasks me; he heaps me.... I will wreak that MOQ upon him.' authors: johnnyreilly tags: [unit testing, MOQ] hide_table_of_contents: false +description: 'Use Moq to simplify async testing, with ReturnAsync method. For testing a class that consumes async API, mock it using Task.Delay with Moqs Returns.' --- Enough with the horrific misquotes - this is about Moq and async (that's my slight justification for robbing Herman Melville). diff --git a/blog-website/blog/2014-10-06-caching-and-cache-busting-in-angularjs-with-http-interceptors/index.md b/blog-website/blog/2014-10-06-caching-and-cache-busting-in-angularjs-with-http-interceptors/index.md index 91af5898342..cff954787ac 100644 --- a/blog-website/blog/2014-10-06-caching-and-cache-busting-in-angularjs-with-http-interceptors/index.md +++ b/blog-website/blog/2014-10-06-caching-and-cache-busting-in-angularjs-with-http-interceptors/index.md @@ -4,6 +4,7 @@ title: 'Caching and Cache-Busting in AngularJS with HTTP interceptors' authors: johnnyreilly tags: [typescript, AngularJS] hide_table_of_contents: false +description: 'Learn how to modify GET request URLs for static resources and AngularJS views with HTTP interceptors using version numbers and unique querystrings.' --- ## Loading On-Demand and Caching diff --git a/blog-website/blog/2014-11-04-using-gulp-in-visual-studio-instead-of-web-optimization/index.md b/blog-website/blog/2014-11-04-using-gulp-in-visual-studio-instead-of-web-optimization/index.md index 843c00cc51d..a3410ca2211 100644 --- a/blog-website/blog/2014-11-04-using-gulp-in-visual-studio-instead-of-web-optimization/index.md +++ b/blog-website/blog/2014-11-04-using-gulp-in-visual-studio-instead-of-web-optimization/index.md @@ -4,6 +4,7 @@ title: 'Using Gulp in Visual Studio instead of Web Optimization' authors: johnnyreilly tags: [Task Runner Explorer, Visual Studio, typescript, javascript, gulpjs] hide_table_of_contents: false +description: 'The ASP.NET team may replace Web Optimization with Grunt or Gulp. John Reilly tried out Gulp, which concatenates, minimises & version-numbers files.' --- ### Updated 17/02/2015: I've taken the approach discussed in this post a little further - you can see [here](../2012-10-05-using-web-optimization-with-mvc-3/index.md) @@ -244,7 +245,7 @@ function getManifest( manifestName, bundleName, includeRelativePath, - pathPrepend + pathPrepend, ) { // Determine filename ("./build/manifest-debug.json" or "./build/manifest-release.json" var manifestFile = @@ -298,7 +299,7 @@ gulp.task( //.pipe(ignore.exclude("**/*.{ts,js.map}")) // Exclude ts and js.map files from the manifest (as they won't become script tags) .pipe(getManifest(filesAndFolders.debug, bundleNames.styles, true)) ); - } + }, ); // Concatenate & Minify JS for release into a single file @@ -335,7 +336,7 @@ gulp.task('styles-release', ['clean'], function () { .pipe(minifyCss()) // Make the file titchy tiny small .pipe(rev()) // Suffix a version number to it .pipe( - gulp.dest(filesAndFolders.releaseFolder + '/' + filesAndFolders.css) + gulp.dest(filesAndFolders.releaseFolder + '/' + filesAndFolders.css), ) ); // Write single versioned file to build/release folder }); @@ -352,10 +353,10 @@ gulp.task( filesAndFolders.release, bundleNames.styles, false, - filesAndFolders.css + '/' - ) + filesAndFolders.css + '/', + ), ); - } + }, ); // Copy across all fonts in filesAndFolders.fonts to both release and debug locations @@ -482,7 +483,7 @@ That would be pretty simple - and for what it's worth \*\*simple is good Before I make all the changes let's review where we were. I had a single MVC view which, in terms of bundles, CSS and JavaScript pretty much looked like this: ```html - + @@ -494,23 +495,23 @@ Before I make all the changes let's review where we were. I had a single MVC vie @Scripts.Render("~/angularApp") @@ -522,7 +523,7 @@ This is already more a complicated example than most peoples use cases. Essentia After reading [an article about script loading by the magnificently funny Jake Archibald](http://www.html5rocks.com/en/tutorials/speed/script-loading/) I felt ready. I cast my MVC view to the four winds and created myself a straight HTML file: ```html - + diff --git a/blog-website/blog/2014-11-26-Coded-UI-IE-11-and-the-runas-problem/index.md b/blog-website/blog/2014-11-26-Coded-UI-IE-11-and-the-runas-problem/index.md index 55786979906..73aeaa252d2 100644 --- a/blog-website/blog/2014-11-26-Coded-UI-IE-11-and-the-runas-problem/index.md +++ b/blog-website/blog/2014-11-26-Coded-UI-IE-11-and-the-runas-problem/index.md @@ -4,6 +4,7 @@ title: "Pretending to be someone you're not and the dark pit of despair" authors: johnnyreilly tags: [Coded UI, internet explorer] hide_table_of_contents: false +description: 'Workaround for issues with Coded UI impersonation feature. Tests can be unreliable, but the fix works well.' --- ## Coded UI, IE 11 and the "runas" problem diff --git a/blog-website/blog/2014-12-05-whats-in-a-name/index.md b/blog-website/blog/2014-12-05-whats-in-a-name/index.md index c74f4e46aea..418ba3a2b54 100644 --- a/blog-website/blog/2014-12-05-whats-in-a-name/index.md +++ b/blog-website/blog/2014-12-05-whats-in-a-name/index.md @@ -3,6 +3,7 @@ slug: whats-in-a-name title: "What's in a (Domain) Name?" authors: johnnyreilly hide_table_of_contents: false +description: '"icanmakethiswork" blog has a new domain due to Johns concern about potential changes in Google hosting, now "blog.icanmakethiswork.io".' --- The observant amongst you may have noticed that this blog has a brand new and shiny domain name! That's right, after happily trading under "icanmakethiswork.blogspot.com" for the longest time it's now "blog.icanmakethiswork.io". Trumpets and fanfare! diff --git a/blog-website/blog/2014-12-12-gulp-npm-long-paths-and-visual-studio-fight/index.md b/blog-website/blog/2014-12-12-gulp-npm-long-paths-and-visual-studio-fight/index.md index bc056b00248..879f5944887 100644 --- a/blog-website/blog/2014-12-12-gulp-npm-long-paths-and-visual-studio-fight/index.md +++ b/blog-website/blog/2014-12-12-gulp-npm-long-paths-and-visual-studio-fight/index.md @@ -4,16 +4,17 @@ title: 'Gulp, npm, long paths and Visual Studio.... Fight!' authors: johnnyreilly tags: [npm, Visual Studio] hide_table_of_contents: false +description: 'Installing gulp-angular-templatecache plugin caused issues with Visual Studio. A temporary solution is to install lodash.bind at root level.' --- -## How I managed to gulp-angular-templatecache working inside Visual Studio - - +## How I managed to gulp-angular-templatecache working inside Visual Studio Every now and then something bites you unexpectedly. After a certain amount of pain, the answer comes to you and you know you want to save others from falling into the same deathtrap. There I was minding my own business and having a play with a Gulp plugin called [gulp-angular-templatecache](https://www.npmjs.com/package/gulp-angular-templatecache). If you're not aware of it, it "Concatenates and registers AngularJS templates in the $templateCache". I was planning to use it so that all the views in an [Angular app of mine](https://github.com/johnnyreilly/proverb-offline) were loaded up-front rather than on demand. (It's a first step in making an "offline-first" version of that particular app.) + + I digress already. No sooner had I tapped in: ```ps diff --git a/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md b/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md index 5d69c7a3d5b..97219028219 100644 --- a/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md +++ b/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md @@ -4,6 +4,7 @@ title: 'Deploying from ASP.Net MVC to GitHub Pages using AppVeyor part 1' authors: johnnyreilly tags: [powershell, github pages, AppVeyor] hide_table_of_contents: false +description: 'The creator of jQuery Validation Unobtrusive Native found a way to use GitHub Pages and automate deployment by creating a static version of the app.' --- There's a small open source project I'm responsible for called [jQuery Validation Unobtrusive Native](https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native). (A catchy name is a must for any good open source project. Alas I'm not quite meeting my own exacting standards on this particular point... I should have gone with my gut and called it "Livingstone" instead. Too late now...) @@ -145,18 +146,4 @@ Contents of C:\projects\static-site Mode LastWriteTime Length Name ----- ------------- ------ ---- -d---- 12/29/2014 7:50 AM AdvancedDemo -d---- 12/29/2014 7:50 AM Content -d---- 12/29/2014 7:50 AM Demo -d---- 12/29/2014 7:50 AM Home -d---- 12/29/2014 7:50 AM Scripts --a--- 12/29/2014 7:50 AM 5967 AdvancedDemo.html --a--- 12/29/2014 7:50 AM 6802 Demo.html --a--- 12/29/2014 7:47 AM 12862 favicon.ico --a--- 12/29/2014 7:50 AM 8069 index.html ``` - -And that's it for part 1 my friends! You now have a static version of the ASP.Net MVC site to dazzle the world with. I should say for the purposes of full disclosure that there are 2 pages in the site which are not entirely "static" friendly. For these 2 pages I've put messages in that are displayed when the page is served up in a static format explaining the limitations. Their full glory can still be experienced by cloning the project and running locally. - -[Next time](../2015-01-07-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2/index.md) we'll take the mechanism detailed above and plug it into AppVeyor for some Continuous Integration happiness. diff --git a/blog-website/blog/2015-01-07-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2/index.md b/blog-website/blog/2015-01-07-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2/index.md index 5b7ade54319..358d74dfaa7 100644 --- a/blog-website/blog/2015-01-07-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2/index.md +++ b/blog-website/blog/2015-01-07-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2/index.md @@ -11,6 +11,7 @@ tags: AppVeyor, ] hide_table_of_contents: false +description: 'To save time, automating open source projects is key. Using AppVeyor and creating static sites with tools like Wget can help update documentation.' --- "Automation, automation, automation." Those were and are Tony Blair's priorities for keeping open source projects well maintained. @@ -40,104 +41,5 @@ The token I'm using for my project has the following scopes selected: With our token in hand we turn our attention to AppVeyor build configuration. This is possible using a file called [`appveyor.yml`](http://www.appveyor.com/docs/build-configuration) stored in the root of your repo. You can also use the AppVeyor web UI to do this. However, for the purposes of ease of demonstration I'm using the file approach. The [jQuery Validation Unobtrusive Native `appveyor.yml`](https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native/blob/master/appveyor.yml) looks like this: ```yml ---- -#---------------------------------# -# general configuration # -#---------------------------------# - -# version format -version: 1.0.{build} - -#---------------------------------# -# environment configuration # -#---------------------------------# - -# environment variables -environment: - GithubEmail: johnny_reilly@hotmail.com - GithubUsername: johnnyreilly - GithubPersonalAccessToken: - secure: T4M/N+e/baksVoeWoYKPWIpfahOsiSFw/+Zc81VuThZmWEqmrRtgEHUyin0vCWhl - -branches: - only: - - master - -install: - - ps: choco install wget - -build: - verbosity: minimal - -after_test: - - ps: ./makeStatic.ps1 $env:APPVEYOR_BUILD_FOLDER - - ps: ./pushStatic.ps1 $env:APPVEYOR_BUILD_FOLDER $env:GithubEmail $env:GithubUsername $env:GithubPersonalAccessToken -``` - -There's a number of things you should notice from the yml file: - -- We create 3 environment variables: GithubEmail, GithubUsername and GithubPersonalAccessToken (more on this in a moment). -- We only build the master branch. -- We use [Chocolatey](https://chocolatey.org/packages/Wget) to install Wget which is used by the `makeStatic.ps1` Powershell script. -- After the tests have completed we run 2 Powershell scripts. First [`makeStatic.ps1`](https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native/blob/master/makeStatic.ps1) which builds the static version of our site. This is the exact same script we discussed in the previous post - we're just passing it the build folder this time (one of AppVeyor's environment variables). Second, we run [`pushStatic.ps1`](https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native/blob/master/pushStatic.ps1) which publishes the static site to GitHub Pages. - -We pass 4 arguments to `pushStatic.ps1`: the build folder, my email address, my username and my personal access token. For the sake of security the GithubPersonalAccessToken has been encrypted as indicated by the `secure` keyword. This is a capability available in AppVeyor [here](https://ci.appveyor.com/tools/encrypt). -![](AppVeyor-encrypt.webp) - -This allows me to mask my personal access token rather than have it available as free text for anyone to grab. - -## `pushStatic.ps1` - -Finally we can turn our attention to how our Powershell script `pushStatic.ps1` goes about pushing our changes up to GitHub Pages: - -```ps -param([string]$buildFolder, [string]$email, [string]$username, [string]$personalAccessToken) - -Write-Host "- Set config settings...." -git config --global user.email $email -git config --global user.name $username -git config --global push.default matching - -Write-Host "- Clone gh-pages branch...." -cd "$($buildFolder)\..\" -mkdir gh-pages -git clone --quiet --branch=gh-pages https://$($username):$($personalAccessToken)@github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native.git .\gh-pages\ -cd gh-pages -git status - -Write-Host "- Clean gh-pages folder...." -Get-ChildItem -Attributes !r | Remove-Item -Recurse -Force - -Write-Host "- Copy contents of static-site folder into gh-pages folder...." -copy-item -path ..\static-site\* -Destination $pwd.Path -Recurse - -git status -$thereAreChanges = git status | select-string -pattern "Changes not staged for commit:","Untracked files:" -simplematch -if ($thereAreChanges -ne $null) { - Write-host "- Committing changes to documentation..." - git add --all - git status - git commit -m "skip ci - static site regeneration" - git status - Write-Host "- Push it...." - git push --quiet - Write-Host "- Pushed it good!" -} -else { - write-host "- No changes to documentation to commit" -} ``` - -So what's happening here? Let's break it down: - -- Git is configured with the passed in username and email address. -- A folder is created that sits alongside the build folder called "gh-pages". -- We clone the "gh-pages" branch of jQuery Validation Unobtrusive Native into our "gh-pages" directory. You'll notice that we are using our GitHub personal access token in the clone URL itself. -- We delete the contents of the "gh-pages" directory leaving it empty. -- We copy across the contents of the "static-site" folder (created by `makeStatic.ps1`) into the "gh-pages". -- We use `git status` to check if there are any changes. (This method is completely effective but a little crude to my mind - there's probably better approaches to this.... shout me in the comments if you have a suggestion.) -- If we have no changes then we do nothing. -- If we have changes then we stage them, commit them and push them to GitHub Pages. Then we sign off with an allusion to [80's East Coast hip-hop]()... 'Cos that's how we roll. - -With this in place, any changes to the docs will be automatically published out to our "gh-pages" branch. Our documentation will always be up to date thanks to the goodness of AppVeyor's Continuous Integration service. diff --git a/blog-website/blog/2015-01-20-typescript-using-functions-with-union-types/index.md b/blog-website/blog/2015-01-20-typescript-using-functions-with-union-types/index.md index 4518f536c0e..b0802858ce3 100644 --- a/blog-website/blog/2015-01-20-typescript-using-functions-with-union-types/index.md +++ b/blog-website/blog/2015-01-20-typescript-using-functions-with-union-types/index.md @@ -4,6 +4,7 @@ title: 'TypeScript: In Praise of Union Types' authors: johnnyreilly tags: [typescript, Union Types] hide_table_of_contents: false +description: 'TypeScript 1.4s Union Types offer a way to specify a value that is of one of many different types and results in a much terser definition file.' --- ## (& How to Express Functions in UTs) diff --git a/blog-website/blog/2015-02-11-the-convent-with-continuous-delivery/index.md b/blog-website/blog/2015-02-11-the-convent-with-continuous-delivery/index.md index ceb6737cc83..6258e93dc0a 100644 --- a/blog-website/blog/2015-02-11-the-convent-with-continuous-delivery/index.md +++ b/blog-website/blog/2015-02-11-the-convent-with-continuous-delivery/index.md @@ -4,6 +4,7 @@ title: 'The Convent with Continuous Delivery' authors: johnnyreilly tags: [Continuous Delivery, AppVeyor] hide_table_of_contents: false +description: 'Programmer has open-sourced the Poor Clares Arundel website, making tweaks and site updating easier, with continuous delivery and collaboration.' --- I've done it. I've open sourced the [website that I maintain for my aunt what is a nun](http://www.poorclaresarundel.org/). Because I think we can all agree that nuns need open source and continuous integration about as much as anyone else. diff --git a/blog-website/blog/2015-02-17-using-gulp-in-asp-net-instead-of-web-optimization/index.md b/blog-website/blog/2015-02-17-using-gulp-in-asp-net-instead-of-web-optimization/index.md index 068532b345f..6a8a311e052 100644 --- a/blog-website/blog/2015-02-17-using-gulp-in-asp-net-instead-of-web-optimization/index.md +++ b/blog-website/blog/2015-02-17-using-gulp-in-asp-net-instead-of-web-optimization/index.md @@ -4,6 +4,7 @@ title: 'Using Gulp to inject scripts and styles tags directly into your HTML' authors: johnnyreilly tags: [asp.net, Web Optimization, gulpjs] hide_table_of_contents: false +description: 'Learn how to use Gulp to directly inject scripts and styles into your HTML, which speeds up app times and makes the setup simpler.' --- This is very probably the dullest title for a blog post I've ever come up with. Read on though folks - it's definitely going to pick up... @@ -33,7 +34,7 @@ gulp-inject (as the name suggests) is used to inject `script` and `link` tags in So, let's get the launch page (`index.html`) ready for gulp-inject: ```html - + @@ -213,11 +214,11 @@ function getScriptsOrStyles(jsOrCss) { var bowerScriptsRelative = bowerScriptsAbsolute.map( function makePathRelativeToCwd(file) { return path.relative('', file); - } + }, ); var appScripts = bowerScriptsRelative.concat( - jsOrCss === 'js' ? config.scripts : config.styles + jsOrCss === 'js' ? config.scripts : config.styles, ); return appScripts; @@ -290,10 +291,10 @@ gulp.task('inject-debug', ['styles-debug', 'scripts-debug'], function () { config.debugFolder + '**/*.{js,css}', '!build\\debug\\bower_components\\spin.js', // Exclude weird spin js path ], - { read: false } + { read: false }, ) - .pipe(order(scriptsAndStyles)) - ) + .pipe(order(scriptsAndStyles)), + ), ) .pipe(gulp.dest(config.buildDir)); }); @@ -304,7 +305,7 @@ gulp.task('inject-release', ['styles-release', 'scripts-release'], function () { return gulp .src(config.bootFile) .pipe( - inject(gulp.src(config.releaseFolder + '**/*.{js,css}', { read: false })) + inject(gulp.src(config.releaseFolder + '**/*.{js,css}', { read: false })), ) .pipe(gulp.dest(config.buildDir)); }); diff --git a/blog-website/blog/2015-02-27-hey-tsconfigjson-where-have-you-been/index.md b/blog-website/blog/2015-02-27-hey-tsconfigjson-where-have-you-been/index.md index 70516eedcb9..ddcc274c06d 100644 --- a/blog-website/blog/2015-02-27-hey-tsconfigjson-where-have-you-been/index.md +++ b/blog-website/blog/2015-02-27-hey-tsconfigjson-where-have-you-been/index.md @@ -4,6 +4,7 @@ title: 'Hey tsconfig.json, where have you been all my life?' authors: johnnyreilly tags: [tsconfig.json, typescript] hide_table_of_contents: false +description: 'The creation of a "tsconfig.json" file will eliminate the need for "reference" comments when using TypeScript, reducing barriers between IDEs.' --- Sometimes, you just miss things. Something seismic happens and you had no idea. So it was with `tsconfig.json`. diff --git a/blog-website/blog/2015-03-20-partialview-tostring/index.md b/blog-website/blog/2015-03-20-partialview-tostring/index.md index 46ff967f9f6..0f9dd864626 100644 --- a/blog-website/blog/2015-03-20-partialview-tostring/index.md +++ b/blog-website/blog/2015-03-20-partialview-tostring/index.md @@ -4,6 +4,7 @@ title: 'PartialView.ToString()' authors: johnnyreilly tags: [asp.net mvc] hide_table_of_contents: false +description: 'Learn three ways to turn a `PartialViewResult` into a `string` to reuse the result returned by a controller in a JSON payload.' --- In the name of [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) I found myself puzzling how one could take a `PartialViewResult` and render it as a `string`. Simple, right? diff --git a/blog-website/blog/2015-04-17-how-to-activate-your-emoji-keyboard-on-android/index.md b/blog-website/blog/2015-04-17-how-to-activate-your-emoji-keyboard-on-android/index.md index e77e2c758d2..8350f3bc9e5 100644 --- a/blog-website/blog/2015-04-17-how-to-activate-your-emoji-keyboard-on-android/index.md +++ b/blog-website/blog/2015-04-17-how-to-activate-your-emoji-keyboard-on-android/index.md @@ -4,6 +4,7 @@ title: 'How to activate your emoji keyboard on Android 5.0 (Lollipop)' authors: johnnyreilly tags: [android] hide_table_of_contents: false +description: 'Learn how to get emoji on your Android phone by activating the "iWnn IME Japanese" keyboard and selecting the "Emoji" option.' --- A departure from from my normal content - I need to tell you about [emoji](http://en.wikipedia.org/wiki/Emoji)! You'll probably already know about them - just imagine a emoticon but about 300,000 times better. They really add spice to to textual content. Oh and they're Japanese - which is also way cool. diff --git a/blog-website/blog/2015-04-24-tonight-ill-start-open-source-project/index.md b/blog-website/blog/2015-04-24-tonight-ill-start-open-source-project/index.md index 09812306c3d..db928460c6b 100644 --- a/blog-website/blog/2015-04-24-tonight-ill-start-open-source-project/index.md +++ b/blog-website/blog/2015-04-24-tonight-ill-start-open-source-project/index.md @@ -4,6 +4,7 @@ title: "Tonight I'll Start an Open Source Project..." authors: johnnyreilly tags: [asp.net mvc, validation, AngularJS] hide_table_of_contents: false +description: 'A new AngularJS validation mechanism aims to propagate data annotations on ASP.NET MVC server models into ng-* directive attributes in HTML.' --- ### Further posts on this topic @@ -125,8 +126,14 @@ I could tweak it to push in the validation directive attributes like this:
diff --git a/blog-website/blog/2015-05-05-a-tale-of-angular-html5mode-aspnet-mvc/index.md b/blog-website/blog/2015-05-05-a-tale-of-angular-html5mode-aspnet-mvc/index.md index e2b848bacc4..15d0cc23f88 100644 --- a/blog-website/blog/2015-05-05-a-tale-of-angular-html5mode-aspnet-mvc/index.md +++ b/blog-website/blog/2015-05-05-a-tale-of-angular-html5mode-aspnet-mvc/index.md @@ -4,6 +4,7 @@ title: 'A tale of Angular, html5mode, ASP.Net MVC and ASP.Net Web API' authors: johnnyreilly tags: [asp.net, AngularJS] hide_table_of_contents: false +description: 'This article offers tips on how to preserve specific routes while redirecting non-specified URLs to the root angular app page for ASP.Net MVC and Web API.' --- So. You want to kick hash based routing to the kerb. You want _real_ URLs. You've read the HTML5 mode section of the [Angular $location docs](https://docs.angularjs.org/guide/$location) and you're good to go. It's just a matter of dropping `$locationProvider.html5Mode(true)` into your app initialisation right? diff --git a/blog-website/blog/2015-05-11-ngvalidationfor-baby-steps/index.md b/blog-website/blog/2015-05-11-ngvalidationfor-baby-steps/index.md index d19fa2ee088..9eb164166b4 100644 --- a/blog-website/blog/2015-05-11-ngvalidationfor-baby-steps/index.md +++ b/blog-website/blog/2015-05-11-ngvalidationfor-baby-steps/index.md @@ -4,6 +4,7 @@ title: 'NgValidationFor Baby Steps' authors: johnnyreilly tags: [asp.net mvc, AngularJS] hide_table_of_contents: false +description: 'The NgValidationFor project translates data annotations to Angular validation directive attributes while minimising dependencies.' --- I thought as I start the [NgValidationFor project](../2015-04-24-tonight-ill-start-open-source-project/index.md) I'd journal my progress. I'm writing this with someone particular in mind: me. Specifically, me in 2 years who will no doubt wonder why I made some of the choices I did. Everyone else, move along now - nothing to see. Unless the inner workings of someone else's mind are interesting to you... In which case: welcome! @@ -48,8 +49,14 @@ When used in an MVC View for which `RequiredDemoModel` is the Model, NgValiditio ```html @using NgValidationFor.Core @using NgValidationFor.Documentation.Models @model -RequiredDemoModel Model.RequiredField)) > +RequiredDemoModel + +Model.RequiredField)) > ``` Which results in this HTML: diff --git a/blog-website/blog/2015-05-23-angular-ui-bootstrap-datepicker-weirdness/index.md b/blog-website/blog/2015-05-23-angular-ui-bootstrap-datepicker-weirdness/index.md index d65b0928eb9..0a4de36ebd6 100644 --- a/blog-website/blog/2015-05-23-angular-ui-bootstrap-datepicker-weirdness/index.md +++ b/blog-website/blog/2015-05-23-angular-ui-bootstrap-datepicker-weirdness/index.md @@ -4,6 +4,7 @@ title: 'Angular UI Bootstrap Datepicker Weirdness' authors: johnnyreilly tags: [Bootstrap, AngularJS] hide_table_of_contents: false +description: 'Add a calendar glyph to your Angular UI Bootstrap Datepicker popup by passing along $event and calling stopPropagation() to avoid an issue.' --- The [Angular UI Bootstrap Datepicker](https://angular-ui.github.io/bootstrap/#/datepicker) is fan-dabby-dozy. But it has a ... pecularity. You can use the picker like this: diff --git a/blog-website/blog/2015-06-19-Back-to-the-Future-with-Code-First-Migrations/index.md b/blog-website/blog/2015-06-19-Back-to-the-Future-with-Code-First-Migrations/index.md index b01d380000e..6c0b2d29a3d 100644 --- a/blog-website/blog/2015-06-19-Back-to-the-Future-with-Code-First-Migrations/index.md +++ b/blog-website/blog/2015-06-19-Back-to-the-Future-with-Code-First-Migrations/index.md @@ -4,6 +4,7 @@ title: 'Back to the Future with Code First Migrations' authors: johnnyreilly tags: [Entity Framework] hide_table_of_contents: false +description: 'Code First Migrations order is determined by file name, not renaming, and requires changing the IMigrationMetadata.Id property to match.' --- Code First Migrations. They look a little like this in Visual Studio: diff --git a/blog-website/blog/2015-06-29-npm-please-stop-hurting-visual-studio/index.md b/blog-website/blog/2015-06-29-npm-please-stop-hurting-visual-studio/index.md index 1b2949ebaad..9c16f8a8987 100644 --- a/blog-website/blog/2015-06-29-npm-please-stop-hurting-visual-studio/index.md +++ b/blog-website/blog/2015-06-29-npm-please-stop-hurting-visual-studio/index.md @@ -4,6 +4,7 @@ title: 'npm please stop hurting Visual Studio' authors: johnnyreilly tags: [npm, Windows] hide_table_of_contents: false +description: 'Windows handling of long paths can be problematic when using Visual Studio with npm; using rimraf for deletions can help until npm 3.0 comes out.' --- I don't know about you but I personally feel that the following sentence may well be the saddest in the English language: diff --git a/blog-website/blog/2015-07-30-upgrading-to-globalize-1x-for-dummies/index.md b/blog-website/blog/2015-07-30-upgrading-to-globalize-1x-for-dummies/index.md index d7b2c1a2333..7abd94f366b 100644 --- a/blog-website/blog/2015-07-30-upgrading-to-globalize-1x-for-dummies/index.md +++ b/blog-website/blog/2015-07-30-upgrading-to-globalize-1x-for-dummies/index.md @@ -4,6 +4,7 @@ title: 'Upgrading to Globalize 1.x for Dummies' authors: johnnyreilly tags: [Globalize] hide_table_of_contents: false +description: 'Migrating to Globalize 1.0, which modularized the code, requires a significant amount of work, as shown by John Reilly’s examples.' --- Globalize has hit 1.0. Anyone who reads my blog will likely be aware that I'm a long time user of [Globalize 0.1.x](../2012-05-07-globalizejs-number-and-date/index.md). I've been a little daunted by the leap that the move from 0.1.x to 1.x represents. It appears to be the very definition of "breaking changes". :-) But hey, this is Semantic Versioning being used correctly so how could I complain? Either way, I've decided to write up the migration here as I'm not expecting this to be easy. @@ -47,11 +48,11 @@ To kick things off I've set up a very [simple repo](https://github.com/johnnyrei document.querySelector('#date').innerText = date; document.querySelector('#numberFormatted').innerText = Globalize.format( number, - 'n2' + 'n2', ); document.querySelector('#dateFormatted').innerText = Globalize.format( date, - 'd' + 'd', ); @@ -259,10 +260,10 @@ To do this I'm going to lean heavily upon [an example put together by Rafael](ht // Date fetch( - 'bower_components/cldr-data/main/' + locale + '/ca-gregorian.json' + 'bower_components/cldr-data/main/' + locale + '/ca-gregorian.json', ), fetch( - 'bower_components/cldr-data/main/' + locale + '/timeZoneNames.json' + 'bower_components/cldr-data/main/' + locale + '/timeZoneNames.json', ), fetch('bower_components/cldr-data/supplemental/timeData.json'), fetch('bower_components/cldr-data/supplemental/weekData.json'), @@ -275,7 +276,7 @@ To do this I'm going to lean heavily upon [an example put together by Rafael](ht return Promise.all( responses.map(function (response) { return response.json(); - }) + }), ); }) .then(Globalize.load) @@ -346,7 +347,7 @@ gulp.task('make-globalize-culture-de-js', function () { } else { console.log('The file was created!'); } - } + }, ); }); ``` diff --git a/blog-website/blog/2015-08-13-top-one-nice-one-get-sorted/index.md b/blog-website/blog/2015-08-13-top-one-nice-one-get-sorted/index.md index 504cc76ae38..e24ffb5055f 100644 --- a/blog-website/blog/2015-08-13-top-one-nice-one-get-sorted/index.md +++ b/blog-website/blog/2015-08-13-top-one-nice-one-get-sorted/index.md @@ -4,6 +4,7 @@ title: '(Top One, Nice One) Get Sorted' authors: johnnyreilly tags: [javascript, LINQ] hide_table_of_contents: false +description: 'John creates a way to use .NETs LINQ feature to sort JavaScript arrays. The tools allow sorting by one or more criteria.' --- I was recently reading [a post by Jaime González García](http://www.barbarianmeetscoding.com/blog/2015/07/09/mastering-the-arcane-art-of-javascript-mancy-for-c-sharp-developers-chapter-7-using-linq-in-javascript/) which featured the following mind-bending proposition: @@ -108,7 +109,7 @@ If we use the `numberComparer` on our original array it looks like this: ```js const foodInTheHouseSorted = foodInTheHouse.sort( - numberComparer((x) => x.daysSincePurchase) + numberComparer((x) => x.daysSincePurchase), ); // foodInTheHouseSorted: [ @@ -147,7 +148,7 @@ Which is more optimal and even simpler as it just swaps the values supplied to t ```js const foodInTheHouseSorted = foodInTheHouse.sort( - reverse(stringComparer((x) => x.what)) + reverse(stringComparer((x) => x.what)), ); // foodInTheHouseSorted: [ @@ -181,8 +182,8 @@ This fine function takes any number of comparers that have been supplied to it. const foodInTheHouseSorted = foodInTheHouse.sort( composeComparers( stringComparer((x) => x.what), - numberComparer((x) => x.daysSincePurchase) - ) + numberComparer((x) => x.daysSincePurchase), + ), ); // foodInTheHouseSorted: [ @@ -280,7 +281,7 @@ You want to do this with TypeScript? Use this: type Comparer = (obj1: TObject, obj2: TObject) => number; export function stringComparer( - propLambda: (obj: TObject) => string + propLambda: (obj: TObject) => string, ): Comparer { return (obj1: TObject, obj2: TObject) => { const obj1Val = propLambda(obj1) || ''; @@ -290,7 +291,7 @@ export function stringComparer( } export function numberComparer( - propLambda: (obj: TObject) => number + propLambda: (obj: TObject) => number, ): Comparer { return (obj1: TObject, obj2: TObject) => { const obj1Val = propLambda(obj1); diff --git a/blog-website/blog/2015-09-10-things-done-changed/index.md b/blog-website/blog/2015-09-10-things-done-changed/index.md index f8f7642d73b..5e54ee5173b 100644 --- a/blog-website/blog/2015-09-10-things-done-changed/index.md +++ b/blog-website/blog/2015-09-10-things-done-changed/index.md @@ -4,6 +4,7 @@ title: 'Things Done Changed' authors: johnnyreilly tags: [ES6, Atom, Babel, React, WebSockets] hide_table_of_contents: false +description: 'Embracing change is key to being a developer; John discusses some of the tools that have taken his fancy, including React and ES6.' --- Some people fear change. Most people actually. I'm not immune to that myself, but not in the key area of technology. Any developer that fears change when it comes to the tools and languages that he / she is using is in the _wrong_ business. Because what you're using to cut code today will not last. The language will evolve, the tools and frameworks that you love will die out and be replaced by new ones that are different and strange. In time, the language you feel you write as a native will fall out of favour, replaced by a new upstart. diff --git a/blog-website/blog/2016-09-12-integration-tests-with-sql-server/index.md b/blog-website/blog/2016-09-12-integration-tests-with-sql-server/index.md index 22c3e484370..8c2b5428b3a 100644 --- a/blog-website/blog/2016-09-12-integration-tests-with-sql-server/index.md +++ b/blog-website/blog/2016-09-12-integration-tests-with-sql-server/index.md @@ -4,6 +4,7 @@ title: 'Integration Tests with SQL Server Database Snapshots' authors: johnnyreilly tags: [Database Snapshots, Integration Testing, SQL Server] hide_table_of_contents: false +description: 'Discover the benefits of using database snapshots for integration tests to reduce complexity & errors in this informative article.' --- ## Once More With Feeling diff --git a/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md b/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md index 6b002d57d96..e2054ec0c20 100644 --- a/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md +++ b/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md @@ -4,7 +4,7 @@ title: 'Debugging ASP.Net Core in VS or Code' authors: johnnyreilly tags: [VS Code, ASP.Net Core, Visual Studio] hide_table_of_contents: false -description: 'Learn how the author became a fan of VS Code for TypeScript and how they managed to debug ASP.Net Core using the extension for C#.' +description: 'Learn how John became a fan of VS Code for TypeScript and how they managed to debug ASP.Net Core using the extension for C#.' --- I've been using Visual Studio for a long time. Very good it is too. However, it is heavyweight; it does far more than I need. What I really want when I'm working is a fast snappy editor, with intellisense and debugging. What I've basically described is [VS Code](https://code.visualstudio.com/). It rocks and has long become my go-to editor for TypeScript. diff --git a/blog-website/blog/2017-10-19-working-with-extrahop-on-webpack-and-ts/index.md b/blog-website/blog/2017-10-19-working-with-extrahop-on-webpack-and-ts/index.md index 7b9541ba413..6a2847dc021 100644 --- a/blog-website/blog/2017-10-19-working-with-extrahop-on-webpack-and-ts/index.md +++ b/blog-website/blog/2017-10-19-working-with-extrahop-on-webpack-and-ts/index.md @@ -4,7 +4,7 @@ title: 'Working with Extrahop on webpack and ts-loader' authors: johnnyreilly tags: [ts-loader, webpack] hide_table_of_contents: false -description: 'The author shares their excitement for working with talented individuals on open source software - its everywhere and a privilege to work on.' +description: 'John shares his excitement for working with talented individuals on open source software - its everywhere and a privilege to work on.' --- I'm quite proud of this: [https://www.extrahop.com/company/blog/2017/extrahop-webpack-accelerating-build-times/](https://www.extrahop.com/company/blog/2017/extrahop-webpack-accelerating-build-times/) diff --git a/blog-website/blog/2018-03-25-uploading-images-to-cloudinary-with-fetch/index.md b/blog-website/blog/2018-03-25-uploading-images-to-cloudinary-with-fetch/index.md index 642bb3ad7da..f1b0bfbb82e 100644 --- a/blog-website/blog/2018-03-25-uploading-images-to-cloudinary-with-fetch/index.md +++ b/blog-website/blog/2018-03-25-uploading-images-to-cloudinary-with-fetch/index.md @@ -4,6 +4,7 @@ title: 'Uploading Images to Cloudinary with the Fetch API' authors: johnnyreilly tags: [React, Cloudinary] hide_table_of_contents: false +description: 'Learn how to handle image uploads to Cloudinary using Fetch instead of SuperAgent with a sample code demonstrating the replacement of FormData.' --- I was recently checking out a [very good post](https://css-tricks.com/image-upload-manipulation-react/) which explained how to upload images using [React Dropzone](https://github.com/react-dropzone/react-dropzone) and [SuperAgent](https://github.com/visionmedia/superagent) to [Cloudinary](https://cloudinary.com/). From cf895d7dfc287a84cd07308a8dc315c9d35e1a21 Mon Sep 17 00:00:00 2001 From: johnnyreilly Date: Mon, 28 Aug 2023 18:01:31 +0100 Subject: [PATCH 11/14] feat: more descriptions --- .../index.md | 103 +++++- .../index.md | 328 ++++++++++++++++++ .../index.md | 97 +++++- .../index.md | 199 +++++++++++ .../index.md | 2 +- .../index.md | 2 +- 6 files changed, 727 insertions(+), 4 deletions(-) diff --git a/blog-website/blog/2012-03-12-striving-for-javascript-convention/index.md b/blog-website/blog/2012-03-12-striving-for-javascript-convention/index.md index b93866e4e84..919fb49c588 100644 --- a/blog-website/blog/2012-03-12-striving-for-javascript-convention/index.md +++ b/blog-website/blog/2012-03-12-striving-for-javascript-convention/index.md @@ -9,6 +9,107 @@ description: 'Visual Studio 11 beta resolved issues. John has moved away from Hu ## Update +The speed of change makes fools of us all. Since I originally wrote this post all of 3 weeks ago Visual Studio 11 beta has been released and the issues I was seeking to solve have pretty much been resolved by the new innovations found therein. It's nicely detailed in [@carlbergenhem](http://www.twitter.com/carlbergenhem)'s blog post: [My Top 5 Visual Studio 11 Designer Improvements for ASP.NET 4.5 Development](https://blogs.telerik.com/blogs/posts/12-03-26/my-top-5-visual-studio-11-designer-improvements-for-asp-net-4-5-development.aspx). I've left the post in place below but much of what I said (particularly with regard to Hungarian Notation) I've now moved away from. That was originally my intention anyway so that's no bad thing. The one HN artefact that I've held onto is using "$" as a prefix for jQuery objects. I think that still makes sense. I would have written my first line of JavaScript in probably 2000. It probably looked something like this: `alert('hello world')`. I know. Classy. As I've mentioned before it was around 2010 before I took JavaScript in any way seriously. Certainly it was then when I started to actively learn the language. Because up until this point I'd been studiously avoiding writing any JavaScript at all I'd never really given thought to forms and conventions. When I wrote any JavaScript I just used the same style and approaches as I used in my main development language (of C#). By and large I have been following the .net naming conventions which are ably explained by Pete Brown [here](http://10rem.net/articles/net-naming-conventions-and-programming-standards---best-practices). Over time I have started to move away from this approach. Without a deliberate intention to do so I have found myself adopting a different style for my JavaScript code as compared with anything else I write. I wouldn't go so far as to say I'm completely happy with the style I'm currently using. But I find it more helpful than not and thought it might be worth talking about. It was really 2 things that started me down the road of "rolling my own" convention: dynamic typing and the lack of safety nets. Let's take each in turn.... + -The speed of change makes fools of us all. Since I originally wrote this post all of 3 weeks ago Visual Studio 11 beta has been released and the issues I was seeking to solve have pretty much been resolved by the new innovations found therein. It's nicely detailed in [@carlbergenhem](http://www.twitter.com/carlbergenhem)'s blog post: [My Top 5 Visual Studio 11 Designer Improvements for ASP.NET 4.5 Development](https://blogs.telerik.com/blogs/posts/12-03-26/my-top-5-visual-studio-11-designer-improvements-for-asp-net-4-5-development.aspx). I've left the post in place below but much of what I said (particularly with regard to Hungarian Notation) I've now moved away from. That was originally my intention anyway so that's no bad thing. The one HN artefact that I've held onto is using "$" as a prefix for jQuery objects. I think that still makes sense. I would have written my first line of JavaScript in probably 2000. It probably looked something like this: `alert('hello world')`. I know. Classy. As I've mentioned before it was around 2010 before I took JavaScript in any way seriously. Certainly it was then when I started to actively learn the language. Because up until this point I'd been studiously avoiding writing any JavaScript at all I'd never really given thought to forms and conventions. When I wrote any JavaScript I just used the same style and approaches as I used in my main development language (of C#). By and large I have been following the .net naming conventions which are ably explained by Pete Brown [here](http://10rem.net/articles/net-naming-conventions-and-programming-standards +### 1\. Dynamic typing + +Having grown up (in a development sense) using compiled and strongly-typed languages I was used to the IDE making it pretty clear what was what through friendly tooltips and the like: + +![](IDE.png) + +JavaScript is loosely / dynamically typed ([occasionally called "untyped" but let's not go there](http://stackoverflow.com/questions/9154388/does-untyped-also-mean-dynamically-typed-in-the-academic-cs-world)). This means that the IDE can't easily determine what's what. So no tooltips for you sunshine. ### 2\. The lack of safety nets / running with scissors + +Now I've come to love it but what I realised pretty quickly when getting into JavaScript was this: you are running with scissors. If you're not careful and you don't take precautions it can bloody quickly. If I'm writing C# I have a lot of safety nets. Not the least of which is "does it compile"? If I declare an integer and then subsequently try to assign a string value to it it won't let me + +. But JavaScript is forgiving. Some would say too forgiving. Let's do something mad: + +```js +var iAmANumber = 77; + +console.log(iAmANumber); //Logs a number + +iAmANumber = "It's a string"; + +console.log(iAmANumber); //Logs a string + +iAmANumber = { + description: 'I am an object', +}; + +console.log(iAmANumber); //Logs an object + +iAmANumber = function (myVariable) { + console.log(myVariable); +}; + +console.log(iAmANumber); //Logs a function +iAmANumber('I am not a number, I am a free man!'); //Calls a function which performs a log +``` + +Now if I were to attempt something similar in C# fuggedaboudit but JavaScript; no I'm romping home free: + +![](Mad-Stuff.webp) + +Now I'm not saying that you should ever do the above, and thinking about it I can't think of a situation where you'd want to (suggestions on a postcard). But the point is it's possible. And because it's possible to do this deliberately, it's doubly possible to do this accidentally. My point is this: it's easy to make bugs in JavaScript. ## What ~~Katy~~ Johnny Did Next + +I'd started making more and more extensive use of JavaScript. I was beginning to move in the direction of using the [single-page application](http://en.wikipedia.org/wiki/Single-page_application) approach (_although more in the sense of giving application style complexity to individual pages rather than ensuring that entire applications ended up in a single page_). This meant that whereas in the past I'd had the occasional 2 lines of JavaScript I now had a multitude of functions which were all interacting in response to user input. All these functions would contain a number of different variables. As well as this I was making use of jQuery for both Ajax purposes and to smooth out the DOM inconsistencies between various browsers. This only added to the mix as variables in one of my functions could be any one of the following: - a number + +- a string +- a boolean +- a date +- an object +- an array +- a function +- a jQuery object - not strictly a distinct JavaScript type obviously but treated pretty much as one in the sense that it has a particular functions / properties etc associated with it + +As I started doing this sort of work I made no changes to my coding style. Wherever possible I did \***exactly**\* what I would have been doing in C# in JavaScript. And it worked fine. Until.... Okay there is no "until" as such, it did work fine. But what I found was that I would do a piece of work, check it into source control, get users to test it, release the work into Production and promptly move onto the next thing. However, a little way down the line there would be a request to add a new feature or perhaps a bug was reported and I'd find myself back looking at the code. And, as is often the case, despite the comments I would realise that it wasn't particularly clear why something worked in the way it did. (Happily it's not just me that has this experience, paranoia has lead me to ask many a fellow developer and they have confessed to similar) When it came to bug hunting in particular I found myself cursing the lack of friendly tooltips and the like. Each time I wanted to look at a variable I'd find myself tracking back through the function, looking for the initial use of the variable to determine the type. Then I'd be tracking forward through the function for each subsequent use to ensure that it conformed. Distressingly, I would find examples of where it looked like I'd forgotten the type of the variable towards the end of a function (for which I can only, regrettably, blame myself). Most commonly I would have a situation like this: + +```js +var tableCell = $('#ItIsMostDefinitelyATableCell'); //I jest ;-) + +/* ...THERE WOULD BE SOME CODE DOING SOMETHING HERE... */ + +tableCell.className = 'makeMeProminent'; //Oh dear - not good. +``` + +You see what happened above? I forgot I had a jQuery object and instead treated it like it was a standard DOM element. Oh dear. ## Spinning my own safety net; Hungarian style + +After I'd experienced a few of the situations described above I decided that steps needed to be taken to minimise the risk of this. In this case, I decided that "steps" meant [Hungarian notation](http://en.wikipedia.org/wiki/Hungarian_notation). I know. I bet you're wincing right now. For those of you that don't remember HN was pretty much the standard way of coding at one point (although at the point that I started coding professionally it had already started to decline). It was adopted in simpler times long before the modern IDE's that tell you what each variable is became the norm. Back when you couldn't be sure of the types you were dealing with. In short, kind of like my situation with JavaScript right now. There's not much to it. By and large HN simply means having a lowercase prefix of 1-3 characters on all your variables indicating type. It doesn't solve all your problems. It doesn't guarantee to stop bugs. But because each instance of the variables use implicitly indicates it's type it makes bugs more glaringly obvious. This means when writing code I'm less likely to misuse a variable (eg `iNum = "JIKJ"`) because part of my brain would be bellowing: "that just looks wrong... pay better attention lad!". Likewise, if I'm scanning through some JavaScript and searching for a bug then this can make it more obvious. Here's some examples of different types of variables declared using the style I have adopted: + +```js +var iInteger = 4; +var dDecimal = 10.5; +var sString = 'I am a string'; +var bBoolean = true; +var dteDate = new Date(); +var oObject = { + description: 'I am an object', +}; +var aArray = [34, 77]; +var fnFunction = function () { + //Do something +}; +var $jQueryObject = $('#ItIsMostDefinitelyATableCell'); +``` + +Some of you have read this and thought "hold on a minute... JavaScript doesn't have integers / decimals etc". You're quite right. My style is not specifically stating the type of a variable. More it is seeking to provide a guide on how a variable should be used. JavaScript does not have integers. But oftentimes I'll be using a number variable which i will only ever want to treat as an integer. And so I'll name it accordingly. ## Spinning a better safety net; DOJO style + +I would be the first to say that alternative approaches are available. And here's one I recently happened upon that I rather like the look of: look 2/3rds down at the parameters section of [the DOJO styleguide](http://dojotoolkit.org/community/styleGuide) Essentially they advise specifying parameter types through the use of prefixed comments. See the examples below: + +```js +function(/*String*/ foo, /*int*/ bar)... +``` + +or + +```js +function(/_String?_/ foo, /_int_/ bar, /_String[]?_/ baz)... +``` + +I really rather like this approach and I'm thinking about starting to adopt it. It's not possible in Hungarian Notation to be so clear about the purpose of a variable. At least not without starting to adopt all kinds of kooky conventions that take in all the possible permutations of variable types. And if you did that you'd really be defeating yourself anyway as it would simply reduce the clarity of your code and make bugs more likely. ## Spinning a better safety net; unit tests + +Despite being quite used to writing unit tests for all my server-side code I have not yet fully embraced unit testing on the client. Partly I've been holding back because of the variety of JavaScript testing frameworks available. I wasn't sure which to start with. But given that it is so easy to introduce bugs into JavaScript I have come to the conclusion that it's better to have some tests in place rather than none. Time to embrace the new. ## Conclusion + +I've found using Hungarian Notation useful whilst working in JavaScript. Not everyone will feel the same and I think that's fair enough; within reason I think it's generally a good idea to go with what you find useful. However, I am giving genuine consideration to moving to the DOJO style and moving back to my more standard camel-cased variable names instead of Hungarian Notation. Particularly since I strive to keep my functions short with the view that ideally each should 1 thing well. Keep it simple etc... And so in a perfect world the situation of forgetting a variables purpose shouldn't really arise... I think once I've got up and running with JavaScript unit tests I may make that move. Hungarian Notation may have proved to be just a stop-gap measure until better techniques were employed... diff --git a/blog-website/blog/2012-07-01-how-im-structuring-my-javascript-in-web/index.md b/blog-website/blog/2012-07-01-how-im-structuring-my-javascript-in-web/index.md index d35b1743a73..76e277f63a5 100644 --- a/blog-website/blog/2012-07-01-how-im-structuring-my-javascript-in-web/index.md +++ b/blog-website/blog/2012-07-01-how-im-structuring-my-javascript-in-web/index.md @@ -83,5 +83,333 @@ namespace DummyMvc.Helpers /// http://stackoverflow.com/questions/5598167/mvc3-need-help-with-html-helper /// /// [Usage] + /// --- From each view / partial view --- + /// @Html.AddClientScript("~/Scripts/jquery.js") + /// @Html.AddClientScript("~/Scripts/jquery-ui.js", dependancies: new string[] {"~/Scripts/jquery.js"}) + /// @Html.AddClientScript("~/Scripts/site.js", dependancies: new string[] {"~/Scripts/jquery.js"}) + /// @Html.AddClientScript("~/Scripts/views/myview.js", dependancies: new string[] {"~/Scripts/jquery.js"}) /// + /// --- From the main Master/View (just before last Body tag so all "registered" scripts are included) --- + /// @Html.ClientScripts() + /// + public static class ScriptExtensions + { + #region Public + + /// + /// Render a Script element given a URL + /// + /// + /// URL of script to render + /// + public static MvcHtmlString Script( + this HtmlHelper helper, + string url) + { + return MvcHtmlString.Create(MakeScriptTag(helper, url)); + } + + /// + /// Add client script files to list of script files that will eventually be added to the view + /// + /// path to script file + /// OPTIONAL: a string array of any script dependancies + /// always MvcHtmlString.Empty + public static MvcHtmlString AddClientScript(this HtmlHelper helper, string scriptPath, string[] dependancies = null) + { + //If script list does not already exist then initialise + if (!helper.ViewContext.HttpContext.Items.Contains("client-script-list")) + helper.ViewContext.HttpContext.Items["client-script-list"] = new Dictionary>(); + + var scripts = helper.ViewContext.HttpContext.Items["client-script-list"] as Dictionary>; + + //Ensure scripts are not added twice + var scriptFilePath = helper.ViewContext.HttpContext.Server.MapPath(DetermineScriptToRender(helper, scriptPath)); + if (!scripts.ContainsKey(scriptFilePath)) + scripts.Add(scriptFilePath, new KeyValuePair(scriptPath, dependancies)); + + return MvcHtmlString.Empty; + } + + /// + /// Add a script tag for each "registered" script associated with the current view. + /// Output script tags with order depending on script dependancies + /// + /// MvcHtmlString + public static MvcHtmlString ClientScripts(this HtmlHelper helper) + { + var url = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection); + var scripts = helper.ViewContext.HttpContext.Items["client-script-list"] as Dictionary> ?? new Dictionary>(); + + //Build script tag block + var scriptList = new List(); + if (scripts.Count > 0) + { + //Check all script dependancies exist and throw an exception if any do not + var distinctDependancies = scripts.Where(s => s.Value.Value != null) + .SelectMany(s => s.Value.Value) + .Distinct() + .Select(s => GetScriptFilePath(helper, s)) //Exception will be thrown here if file does not exist + .ToList(); + + var missingDependancies = distinctDependancies.Except(scripts.Select(s => s.Key)).ToList(); + if (missingDependancies.Count > 0) + { + throw new KeyNotFoundException("The following dependancies are missing: " + Environment.NewLine + + Environment.NewLine + + string.Join(Environment.NewLine, missingDependancies)); + } + + //Serve scripts without dependancies first + scriptList.AddRange(scripts.Where(s => s.Value.Value == null).Select(s => s.Value.Key)); + + //Get all scripts which have dependancies + var scriptsAndDependancies = scripts.Where(s => s.Value.Value != null) + .OrderBy(s => s.Value.Value.Length) + .Select(s => s.Value) + .ToList(); + + //Loop round adding scripts to the scriptList until all are done + do + { + //Loop backwards through list so items can be removed mid loop + for (var i = scriptsAndDependancies.Count - 1; i >= 0; i--) + { + var script = scriptsAndDependancies[i].Key; + var dependancies = scriptsAndDependancies[i].Value; + + //Check if all the dependancies have been added to scriptList yet + bool currentScriptDependanciesAdded = !dependancies.Except(scriptList).Any(); + if (currentScriptDependanciesAdded) + { + //Move script to scriptList + scriptList.Add(script); + scriptsAndDependancies.RemoveAt(i); + } + } + } while (scriptsAndDependancies.Count > 0); + } + + //Generate a script tag for each script + var scriptsToRender = scriptList.Select(s => MakeScriptTag(helper, s)).ToList(); +#if DEBUG + scriptsToRender.Insert(0, ""); + scriptsToRender.Insert(scriptsToRender.Count, ""); +#endif + + //Output script tag block at point in view where method is called (by returning an MvcHtmlString) + return (scriptsToRender.Count > 0 + ? MvcHtmlString.Create(string.Join(Environment.NewLine, scriptsToRender)) + : MvcHtmlString.Empty); + } + + /// + /// Add client script block to list of script blocks that will eventually be added to the view + /// + /// unique identifier for script block + /// script block + /// OPTIONAL: a string array of any script block dependancies + /// always MvcHtmlString.Empty + public static MvcHtmlString AddClientScriptBlock(this HtmlHelper helper, string key, string scriptBlock, string[] dependancies = null) + { + //If script list does not already exist then initialise + if (!helper.ViewContext.HttpContext.Items.Contains("client-script-block-list")) + helper.ViewContext.HttpContext.Items["client-script-block-list"] = new Dictionary>(); + + var scriptBlocks = helper.ViewContext.HttpContext.Items["client-script-block-list"] as Dictionary>; + + //Prevent duplication + if (scriptBlocks.ContainsKey(key)) return MvcHtmlString.Empty; + + scriptBlocks.Add(key, new KeyValuePair(scriptBlock, dependancies)); + + return MvcHtmlString.Empty; + } + + /// + /// Output all "registered" script blocks associated with the current view. + /// Output script tags with order depending on script dependancies + /// + /// MvcHtmlString + public static MvcHtmlString ClientScriptBlocks(this HtmlHelper helper) + { + var scriptBlocks = helper.ViewContext.HttpContext.Items["client-script-block-list"] as Dictionary> ?? new Dictionary>(); + + //Build script tag block + var scriptBlockList = new List(); + if (scriptBlocks.Count > 0) + { + //Check all script dependancies exist and throw an exception if any do not + var distinctDependancies = scriptBlocks.Where(s => s.Value.Value != null) + .SelectMany(s => s.Value.Value) + .Distinct() + .ToList(); + + var missingDependancies = distinctDependancies.Except(scriptBlocks.Select(s => s.Key)).ToList(); + if (missingDependancies.Count > 0) + { + throw new KeyNotFoundException("The following dependancies are missing: " + Environment.NewLine + + Environment.NewLine + + string.Join(Environment.NewLine, missingDependancies)); + } + + //Serve script blocks without dependancies first + scriptBlockList.AddRange(scriptBlocks.Where(s => s.Value.Value == null).Select(s => s.Value.Key)); + + //Get all script blocks which have dependancies + var scriptBlocksAndDependancies = scriptBlocks.Where(s => s.Value.Value != null) + .OrderBy(s => s.Value.Value.Length) + .Select(s => s.Value) + .ToList(); + + //Loop round adding scripts to the scriptList until all are done + do + { + //Loop backwards through list so items can be removed mid loop + for (var i = scriptBlocksAndDependancies.Count - 1; i >= 0; i--) + { + var scriptBlock = scriptBlocksAndDependancies[i].Key; + var dependancies = scriptBlocksAndDependancies[i].Value; + + //Check if all the dependancies have been added to scriptList yet + bool currentScriptBlockDependanciesAdded = !dependancies.Except(scriptBlockList).Any(); + if (currentScriptBlockDependanciesAdded) + { + //Move script to scriptList + scriptBlockList.Add(scriptBlock); + scriptBlocksAndDependancies.RemoveAt(i); + } + } + } while (scriptBlocksAndDependancies.Count > 0); + } + + //Generate a script tag for each script + var scriptBlocksToRender = scriptBlockList.Select(s => string.Format("", Environment.NewLine, s)).ToList(); +#if DEBUG + scriptBlocksToRender.Insert(0, ""); + scriptBlocksToRender.Insert(scriptBlocksToRender.Count, ""); +#endif + + //Output script tag block at point in view where method is called (by returning an MvcHtmlString) + return (scriptBlocksToRender.Count > 0 + ? MvcHtmlString.Create(string.Join(Environment.NewLine, scriptBlocksToRender)) + : MvcHtmlString.Empty); + } + + #endregion + + #region Private + + /// + /// Take a URL, resolve it and a version suffix. In Debug this will be based on DateTime.Now to prevent caching + /// on a development machine. In Production this will be based on the version number of the appplication. + /// This means when the version number is incremented in subsequent releases script files should be recached automatically. + /// + /// + /// URL to resolve and add suffix to + /// + private static string ResolveUrlWithVersion(HtmlHelper helper, string url) + { +#if DEBUG + var suffix = DateTime.Now.Ticks.ToString(); +#else + var suffix = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); +#endif + + var urlWithVersionSuffix = string.Format("{0}?v={1}", url, suffix); + var urlResolved = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection).Content(urlWithVersionSuffix); + + return urlResolved; + } + + /// + /// Create the string that represents a script tag + /// + /// + /// + /// + private static string MakeScriptTag(HtmlHelper helper, string url) + { + var scriptToRender = DetermineScriptToRender(helper, url); + + //Render script tag + var scriptTag = new TagBuilder("script"); + scriptTag.Attributes["type"] = "text/javascript"; //This isn't really required with HTML 5 as this is the default. As it does no real harm so I have left it for now. http://stackoverflow.com/a/9659074/761388 + scriptTag.Attributes["src"] = SharedExtensions.ResolveUrlWithVersion(helper, scriptToRender); + + var scriptTagString = scriptTag.ToString(); + return scriptTagString; + } + + /// + /// Author : John Reilly + /// Description : Get the script that should be served to the user - throw an exception if it doesn't exist and minify if in release mode + /// + /// + /// + /// OPTIONAL - this allows you to directly specify the minified suffix if it differs from the standard + /// of "min.js" - unlikely this will ever be used but possible + /// + private static string DetermineScriptToRender(HtmlHelper helper, string url, string minifiedSuffix = "min.js") + { + //Initialise a list that will contain potential scripts to render + var possibleScriptsToRender = new List() { url }; + +#if DEBUG + //Don't add minified scripts in debug mode +#else + //Add minified path of script to list + possibleScriptsToRender.Insert(0, Path.ChangeExtension(url, minifiedSuffix)); +#endif + + var validScriptsToRender = possibleScriptsToRender.Where(s => File.Exists(helper.ViewContext.HttpContext.Server.MapPath(s))); + if (!validScriptsToRender.Any()) + throw new FileNotFoundException("Unable to render " + url + " as none of the following scripts exist:" + + string.Join(Environment.NewLine, possibleScriptsToRender)); + else + return validScriptsToRender.First(); //Return first existing file in list (minified file in release mode) + } + + #endregion + } +} ``` + +## Minification - I want to serve you less... + +Another tweak I made to the script helper meant that when compiling either the debug or production (minified) versions of common JS files will be included if available. This means in a production environment the users get minified JS files so faster loading. And in a development environment we get the full JS files which make debugging more straightforward. + +What I haven't started doing is minifying my own JS files as yet. I know I'm being somewhat inconsistent here by sometimes serving minified files and sometimes not. I'm not proud. Part of my rationale for this that since most of my users use my apps on a daily basis they will for the most part be using cached JS files. Obviously there'll be slightly slower load times the first time they go to a page but nothing that significant I hope. + +I have thought of starting to do my own minification as a build step but have held off for now. Again this is something being baked into .NET 4.5; another reason why I have held off doing this a different way for now. + +Update + +It now looks like this Microsofts optimisations have become [this Nuget package](http://nuget.org/packages/Microsoft.AspNet.Web.Optimization). It's early days (well it was released on 15th August 2012 and I'm writing this on the 16th) but I think this looks not to be tied to MVC 4 or .NET 4.5 in which case I could use it in my current MVC 3 projects. I hope so... + +By the way there's a [nice rundown of how to use this by K. Scott Allen of Pluralsight](http://www.pluralsight.com/training/Courses/TableOfContents/mvc4#mvc4-m3-optimization). It's fantastic. Recommended. + +Update 2 + +Having done a little asking around I now understand that this \***can**\* be used with MVC 3 / .NET 4.0. Excellent! + +One rather nice alternative script serving mechanism I've seen (but not yet used) is Andrew Davey's [Cassette](http://getcassette.net) which I mean to take for a test drive soon. This looks fantastic (and is available as a [Nuget package](http://nuget.org/packages/Cassette) \- 10 points!). + +## CDNs (they want to serve you) + +I've never professionally made use of CDNs at all. There are [clearly good reasons why you should](http://encosia.com/3-reasons-why-you-should-let-google-host-jquery-for-you/) but most of those good reasons relate most to public facing web apps. + +As I've said, the applications I tend to work on sit behind firewalls and it's not always guaranteed what my users can see from the grand old world of web beyond. (Indeed what they see can change on hour by hour basis sometimes...) Combined with that, because my apps are only accessible by a select few I don't face the pressure to reduce load on the server that public web apps can face. + +So while CDN's are clearly a good thing. I don't use them at present. And that's unlikely to change in the short term. + +## TL:DR + +1. I don't use CDNs - they're clearly useful but they don't suit my particular needs +2. I serve each JavaScript file individually just before the body tag. I don't bundle. +3. I don't minify my own scripts (though clearly it wouldn't be hard) but I do serve the minified versions of 3rd party libraries (eg jQuery) in a Production environment. +4. I don't use async script loaders at present. I may in future; we shall see. + +I expect some of the above may change (well, possibly not point #1) but this general approach is working well for me at present. + +I haven't touched at all on how I'm structuring my JavaScript code itself. Perhaps next time. diff --git a/blog-website/blog/2012-09-06-globalize-and-jquery-validate/index.md b/blog-website/blog/2012-09-06-globalize-and-jquery-validate/index.md index bea1e18f57e..35372e4157c 100644 --- a/blog-website/blog/2012-09-06-globalize-and-jquery-validate/index.md +++ b/blog-website/blog/2012-09-06-globalize-and-jquery-validate/index.md @@ -112,5 +112,100 @@ And in my `web.config` I have following setting set: - + + ``` + +With both of these set this means I get `<html lang="de-DE">` or `<html lang="en-GB">` etc. depending on a users culture. + +## Serving up the right Globalize culture files + +In order that I send the correct Globalize culture to the client I've come up with this static class which provides the user with the relevant culture URL (falling back to the en-GB culture if it can't find one based your culture): + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Hosting; +using System.IO; +using System.Globalization; + +namespace My.Helpers +{ + /// + /// Static class that is a store for commonly used filenames + /// (so if the files are updated they only need to be amended in a single place) + /// + public static class GlobalizeUrls + { + + /// + /// URL for Globalize: https://github.com/jquery/globalize + /// + public static string Globalize { get { return "~/Scripts/globalize.js"; } } + + /// + /// URL for the specific Globalize culture + /// + public static string GlobalizeCulture + { + get + { + //Determine culture - GUI culture for preference, user selected culture as fallback + var currentCulture = CultureInfo.CurrentCulture; + var filePattern = "~/scripts/globalize/globalize.culture.{0}.js"; + var regionalisedFileToUse = string.Format(filePattern, "en-GB"); //Default localisation to use + + //Try to pick a more appropriate regionalisation + if (File.Exists(HostingEnvironment.MapPath(string.Format(filePattern, currentCulture.Name)))) //First try for a globalize.culture.en-GB.js style file + regionalisedFileToUse = string.Format(filePattern, currentCulture.Name); + else if (File.Exists(HostingEnvironment.MapPath(string.Format(filePattern, currentCulture.TwoLetterISOLanguageName)))) //That failed; now try for a globalize.culture.en.js style file + regionalisedFileToUse = string.Format(filePattern, currentCulture.TwoLetterISOLanguageName); + + return regionalisedFileToUse; + } + } + } +} +``` + +## Putting it all together + +To make use of all of this together you'll need to have the `html lang` attribute set as described earlier and some scripts output in your layout page like this: + +```html + + + + + + +@* Only serve the following script if you need it: *@ + +``` + +Which will render something like this: + +```html + + + + + + +``` + +This will load up jQuery, Globalize, your Globalize culture, jQuery Validate, jQuery Validates unobtrusive extensions (which you don't need if you're not using them) and the jQuery Validate Globalize script which will set up culture aware validation. + +Finally and just to re-iterate, it's highly worthwhile to give [Scott Hanselman's original article a look](http://www.hanselman.com/blog/GlobalizationInternationalizationAndLocalizationInASPNETMVC3JavaScriptAndJQueryPart1.aspx). Most all the ideas in here were taken wholesale from him! diff --git a/blog-website/blog/2012-11-02-xsdxml-schema-generator-xsdexe-taking/index.md b/blog-website/blog/2012-11-02-xsdxml-schema-generator-xsdexe-taking/index.md index fcf2651013d..3969bf19733 100644 --- a/blog-website/blog/2012-11-02-xsdxml-schema-generator-xsdexe-taking/index.md +++ b/blog-website/blog/2012-11-02-xsdxml-schema-generator-xsdexe-taking/index.md @@ -72,5 +72,204 @@ Excited? Thought not. But what this means is we can hurl our XSD at this tool an And you're left with the lovely Contact.cs class: ```cs +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.239 // +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +// +// This source code was auto-generated by xsd, Version=4.0.30319.1. +// +namespace MyNameSpace { + using System.Xml.Serialization; + + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)] + [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)] + public partial class contact { + + private string firstNameField; + + private string lastNameField; + + private sbyte heightInInchesField; + + private string typeField; + + /// + public string firstName { + get { + return this.firstNameField; + } + set { + this.firstNameField = value; + } + } + + /// + public string lastName { + get { + return this.lastNameField; + } + set { + this.lastNameField = value; + } + } + + /// + public sbyte heightInInches { + get { + return this.heightInInchesField; + } + set { + this.heightInInchesField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string type { + get { + return this.typeField; + } + set { + this.typeField = value; + } + } + } +} ``` + +## Justify Your Actions + +But why is this good stuff? Indeed why is this more interesting than the newer, and hence obviously cooler, LINQ to XML? Well for my money it's the following reasons that are important: + +1. Intellisense - I have always loved this. Call me lazy but I think intellisense frees up the mind to think about what problem you're actually trying to solve. Xsd.exe's generated classes give me that; I don't need to hold the whole data structure in my head as I code. +2. Terse code - I'm passionate about less code. I think that a noble aim in software development is to write as little code as possible in order to achieve your aims. I say this as generally I have found that writing a minimal amount of code expresses the intention of the code in a far clearer fashion. In service of that aim Xsd.exe's generated classes allow me to write less code than would be required with LINQ to XML. +3. To quote Scott Hanselman "[successful compilation is just the first unit test](http://www.hanselman.com/blog/NuGetPackageOfTheWeek6DynamicMalleableEnjoyableExpandoObjectsWithClay.aspx)". That it is but it's a doozy. If I'm making changes to the code and I've been using LINQ to XML I'm not going to see the benefits of strong typing that I would with Xsd.exe's generated classes. I like learning if I've broken the build sooner rather than later; strong typing gives me that safety net. + +## Serialization / Deserialization Helper + +As you read this you're no doubt thinking "but wait he's shown us how to create XSDs from XML and classes from XSDs but how do we take XML and turn it into objects? And how do we turn those objects back into XML?" + +See how I read your mind just there? It's a gift. Well, I've written a little static helper class for the very purpose: + +```cs +using System.IO; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace My.Helpers +{ + public static class XmlConverter + { + private static XmlSerializer _serializer = null; + + #region Static Constructor + + /// + /// Static constructor that initialises the serializer for this type + /// + static XmlConverter() + { + _serializer = new XmlSerializer(typeof(T)); + } + + #endregion + + #region Public + + /// + /// Deserialize the supplied XML into an object + /// + /// + /// + public static T ToObject(string xml) + { + return (T)_serializer.Deserialize(new StringReader(xml)); + } + + /// + /// Serialize the supplied object into XML + /// + /// + /// + public static string ToXML(T obj) + { + using (var memoryStream = new MemoryStream()) + { + _serializer.Serialize(memoryStream, obj); + + return Encoding.UTF8.GetString(memoryStream.ToArray()); + } + } + + #endregion + } +} +``` + +And here's an example of how to use it: + +```cs +using MyNameSpace; + +//Make a new contact +contact myContact = new contact(); + +//Serialize the contact to XML +string myContactXML = XmlConverter.ToXML(myContact); + +//Deserialize the XML back into an object +contact myContactAgain = XmlConverter.ToObject(myContactXML); +``` + +I was tempted to name my methods in tribute to Crockford's JSON (namely `ToXML` becoming `stringify` and `ToObject` becoming `parse`). Maybe later. + +And that's us done. Whilst it's no doubt unfashionable I think that this is a very useful approach indeed and I commend it to the interweb! + +## Updated - using Xsd.exe to generate XSD from XML + +I was chatting to a friend about this blog post and he mentioned that you can actually use Xsd.exe to generate XSD files from XML as well. He's quite right - this feature does exist. To go back to our example from earlier we can execute the following command: + +`xsd.exe "C:\\Contact.xml" /out:"C:\\"` + +And this will generate the following file: + +```xsd + + + + + + + + + + + + + + + + + + + + +``` + +However, the XSD generated above is very much a "Microsoft XSD"; it's an XSD which features MS properties and so on. It's fine but I think that generally I prefer my XSDs to be as vanilla as possible. To that end I'm likely to stick to using the XSD/XML Schema Generator as it doesn't appear to be possible to get Xsd.exe to generate "vanilla XSD". + +Thanks to Ajay for bringing it to my attention though. diff --git a/blog-website/blog/2014-09-13-migrating-jasmine-tests-to-typescript/index.md b/blog-website/blog/2014-09-13-migrating-jasmine-tests-to-typescript/index.md index 938e15b61d0..9ae44d8d323 100644 --- a/blog-website/blog/2014-09-13-migrating-jasmine-tests-to-typescript/index.md +++ b/blog-website/blog/2014-09-13-migrating-jasmine-tests-to-typescript/index.md @@ -4,7 +4,7 @@ title: 'Journalling the Migration of Jasmine Tests to TypeScript' authors: johnnyreilly tags: [Jasmine, typescript, javascript] hide_table_of_contents: false -description: 'Author describes issues migrating Jasmine tests from JS to TypeScript, including tooling, typings, and missing dependencies.' +description: 'John describes issues migrating Jasmine tests from JS to TypeScript, including tooling, typings, and missing dependencies.' --- I previously attempted to migrate my Jasmine tests from JavaScript to TypeScript. The last time I tried it didn't go so well and I bailed. Thank the Lord for source control. But feeling I shouldn't be deterred I decided to have another crack at it. diff --git a/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md b/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md index 97219028219..d9375015b37 100644 --- a/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md +++ b/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md @@ -4,7 +4,7 @@ title: 'Deploying from ASP.Net MVC to GitHub Pages using AppVeyor part 1' authors: johnnyreilly tags: [powershell, github pages, AppVeyor] hide_table_of_contents: false -description: 'The creator of jQuery Validation Unobtrusive Native found a way to use GitHub Pages and automate deployment by creating a static version of the app.' +description: 'John (creator of jQuery Validation Unobtrusive Native) found a way to use GitHub Pages and automate deployment by creating a static version of the app.' --- There's a small open source project I'm responsible for called [jQuery Validation Unobtrusive Native](https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native). (A catchy name is a must for any good open source project. Alas I'm not quite meeting my own exacting standards on this particular point... I should have gone with my gut and called it "Livingstone" instead. Too late now...) From 0355018f5c3e1a5ad38681f6c8962a4901276fdf Mon Sep 17 00:00:00 2001 From: johnnyreilly Date: Mon, 28 Aug 2023 18:16:33 +0100 Subject: [PATCH 12/14] feat: more descriptions --- .../index.md | 14 ++ .../index.md | 99 ++++++++++++++ .../index.md | 41 ++++++ .../index.md | 30 +++++ .../index.md | 50 ++++++- .../index.md | 23 +++- .../index.md | 124 ++++++++++++++++-- 7 files changed, 370 insertions(+), 11 deletions(-) diff --git a/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md b/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md index d9375015b37..8583da5dab7 100644 --- a/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md +++ b/blog-website/blog/2014-12-29-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-1/index.md @@ -146,4 +146,18 @@ Contents of C:\projects\static-site Mode LastWriteTime Length Name +---- ------------- ------ ---- +d---- 12/29/2014 7:50 AM AdvancedDemo +d---- 12/29/2014 7:50 AM Content +d---- 12/29/2014 7:50 AM Demo +d---- 12/29/2014 7:50 AM Home +d---- 12/29/2014 7:50 AM Scripts +-a--- 12/29/2014 7:50 AM 5967 AdvancedDemo.html +-a--- 12/29/2014 7:50 AM 6802 Demo.html +-a--- 12/29/2014 7:47 AM 12862 favicon.ico +-a--- 12/29/2014 7:50 AM 8069 index.html ``` + +And that's it for part 1 my friends! You now have a static version of the ASP.Net MVC site to dazzle the world with. I should say for the purposes of full disclosure that there are 2 pages in the site which are not entirely "static" friendly. For these 2 pages I've put messages in that are displayed when the page is served up in a static format explaining the limitations. Their full glory can still be experienced by cloning the project and running locally. + +[Next time](../2015-01-07-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2/index.md) we'll take the mechanism detailed above and plug it into AppVeyor for some Continuous Integration happiness. diff --git a/blog-website/blog/2015-01-07-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2/index.md b/blog-website/blog/2015-01-07-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2/index.md index 358d74dfaa7..03ee7244e23 100644 --- a/blog-website/blog/2015-01-07-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2/index.md +++ b/blog-website/blog/2015-01-07-deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2/index.md @@ -41,5 +41,104 @@ The token I'm using for my project has the following scopes selected: With our token in hand we turn our attention to AppVeyor build configuration. This is possible using a file called [`appveyor.yml`](http://www.appveyor.com/docs/build-configuration) stored in the root of your repo. You can also use the AppVeyor web UI to do this. However, for the purposes of ease of demonstration I'm using the file approach. The [jQuery Validation Unobtrusive Native `appveyor.yml`](https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native/blob/master/appveyor.yml) looks like this: ```yml +--- +#---------------------------------# +# general configuration # +#---------------------------------# + +# version format +version: 1.0.{build} + +#---------------------------------# +# environment configuration # +#---------------------------------# + +# environment variables +environment: + GithubEmail: johnny_reilly@hotmail.com + GithubUsername: johnnyreilly + GithubPersonalAccessToken: + secure: T4M/N+e/baksVoeWoYKPWIpfahOsiSFw/+Zc81VuThZmWEqmrRtgEHUyin0vCWhl + +branches: + only: + - master + +install: + - ps: choco install wget + +build: + verbosity: minimal + +after_test: + - ps: ./makeStatic.ps1 $env:APPVEYOR_BUILD_FOLDER + - ps: ./pushStatic.ps1 $env:APPVEYOR_BUILD_FOLDER $env:GithubEmail $env:GithubUsername $env:GithubPersonalAccessToken +``` + +There's a number of things you should notice from the yml file: + +- We create 3 environment variables: GithubEmail, GithubUsername and GithubPersonalAccessToken (more on this in a moment). +- We only build the master branch. +- We use [Chocolatey](https://chocolatey.org/packages/Wget) to install Wget which is used by the `makeStatic.ps1` Powershell script. +- After the tests have completed we run 2 Powershell scripts. First [`makeStatic.ps1`](https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native/blob/master/makeStatic.ps1) which builds the static version of our site. This is the exact same script we discussed in the previous post - we're just passing it the build folder this time (one of AppVeyor's environment variables). Second, we run [`pushStatic.ps1`](https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native/blob/master/pushStatic.ps1) which publishes the static site to GitHub Pages. + +We pass 4 arguments to `pushStatic.ps1`: the build folder, my email address, my username and my personal access token. For the sake of security the GithubPersonalAccessToken has been encrypted as indicated by the `secure` keyword. This is a capability available in AppVeyor [here](https://ci.appveyor.com/tools/encrypt). +![](AppVeyor-encrypt.webp) + +This allows me to mask my personal access token rather than have it available as free text for anyone to grab. + +## `pushStatic.ps1` + +Finally we can turn our attention to how our Powershell script `pushStatic.ps1` goes about pushing our changes up to GitHub Pages: + +```ps +param([string]$buildFolder, [string]$email, [string]$username, [string]$personalAccessToken) + +Write-Host "- Set config settings...." +git config --global user.email $email +git config --global user.name $username +git config --global push.default matching + +Write-Host "- Clone gh-pages branch...." +cd "$($buildFolder)\..\" +mkdir gh-pages +git clone --quiet --branch=gh-pages https://$($username):$($personalAccessToken)@github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native.git .\gh-pages\ +cd gh-pages +git status + +Write-Host "- Clean gh-pages folder...." +Get-ChildItem -Attributes !r | Remove-Item -Recurse -Force + +Write-Host "- Copy contents of static-site folder into gh-pages folder...." +copy-item -path ..\static-site\* -Destination $pwd.Path -Recurse + +git status +$thereAreChanges = git status | select-string -pattern "Changes not staged for commit:","Untracked files:" -simplematch +if ($thereAreChanges -ne $null) { + Write-host "- Committing changes to documentation..." + git add --all + git status + git commit -m "skip ci - static site regeneration" + git status + Write-Host "- Push it...." + git push --quiet + Write-Host "- Pushed it good!" +} +else { + write-host "- No changes to documentation to commit" +} ``` + +So what's happening here? Let's break it down: + +- Git is configured with the passed in username and email address. +- A folder is created that sits alongside the build folder called "gh-pages". +- We clone the "gh-pages" branch of jQuery Validation Unobtrusive Native into our "gh-pages" directory. You'll notice that we are using our GitHub personal access token in the clone URL itself. +- We delete the contents of the "gh-pages" directory leaving it empty. +- We copy across the contents of the "static-site" folder (created by `makeStatic.ps1`) into the "gh-pages". +- We use `git status` to check if there are any changes. (This method is completely effective but a little crude to my mind - there's probably better approaches to this.... shout me in the comments if you have a suggestion.) +- If we have no changes then we do nothing. +- If we have changes then we stage them, commit them and push them to GitHub Pages. Then we sign off with an allusion to [80's East Coast hip-hop]()... 'Cos that's how we roll. + +With this in place, any changes to the docs will be automatically published out to our "gh-pages" branch. Our documentation will always be up to date thanks to the goodness of AppVeyor's Continuous Integration service. diff --git a/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md b/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md index e2054ec0c20..685c3c93ce3 100644 --- a/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md +++ b/blog-website/blog/2017-03-28-debugging-aspnet-core-in-vs-or-code/index.md @@ -40,4 +40,45 @@ So it wants me to `dotnet restore`. It's even offering to do that for me! Have a ```ts Welcome to .NET Core! +--------------------- +Learn more about .NET Core @ https://aka.ms/dotnet-docs. Use dotnet --help to see available commands or go to https://aka.ms/dotnet-cli-docs. + +Telemetry +-------------- +The .NET Core tools collect usage data in order to improve your experience. The data is anonymous and does not include command-line arguments. The data is collected by Microsoft and shared with the community. +You can opt out of telemetry by setting a DOTNET_CLI_TELEMETRY_OPTOUT environment variable to 1 using your favorite shell. +You can read more about .NET Core tools telemetry @ https://aka.ms/dotnet-cli-telemetry. + +Configuring... +------------------- +A command is running to initially populate your local package cache, to improve restore speed and enable offline access. This command will take up to a minute to complete and will only happen once. +Decompressing Decompressing 100% 4026 ms +Expanding 100% 34814 ms + Restoring packages for c:\Source\Debugging\WebApplication1\WebApplication1\WebApplication1.csproj... + Restoring packages for c:\Source\Debugging\WebApplication1\WebApplication1\WebApplication1.csproj... + Restore completed in 734.05 ms for c:\Source\Debugging\WebApplication1\WebApplication1\WebApplication1.csproj. + Generating MSBuild file c:\Source\Debugging\WebApplication1\WebApplication1\obj\WebApplication1.csproj.nuget.g.props. + Writing lock file to disk. Path: c:\Source\Debugging\WebApplication1\WebApplication1\obj\project.assets.json + Restore completed in 1.26 sec for c:\Source\Debugging\WebApplication1\WebApplication1\WebApplication1.csproj. + + NuGet Config files used: + C:\Users\johnr\AppData\Roaming\NuGet\NuGet.Config + C:\Program Files (x86)\NuGet\Config\Microsoft.VisualStudio.Offline.config + + Feeds used: + https://api.nuget.org/v3/index.json + C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\ +Done: 0. ``` + +The other prompt says `"Required assets to build and debug are missing from 'WebApplication1'. Add them?"`. This also sounds very promising and I give it the nod. This creates a `.vscode` directory and 2 enclosed files; `launch.json` and `tasks.json`. + +So lets try that F5 thing again... http://localhost:5000/ is now serving the same app. That looks pretty good. So lets add a breakpoint to the `HomeController` and see if we can hit it: + +![](firstgo.webp) + +Well I can certainly add a breakpoint but all those red squigglies are unnerving me. Let's clean the slate. If you want to simply do that in VS Code hold down `CTRL+SHIFT+P` and then type "reload". Pick "Reload window". A couple of seconds later we're back in and Code is looking much happier. Can we hit our breakpoint? + +![](secondgo.webp) + +Yes we can! So you're free to develop in either Code or VS; the choice is yours. I think that's pretty awesome - and well done to all the peeople behind Code who've made this a pretty seamless experience! diff --git a/blog-website/blog/2018-11-17-snapshot-testing-for-c/index.md b/blog-website/blog/2018-11-17-snapshot-testing-for-c/index.md index 83c80157aaf..ee6cad29633 100644 --- a/blog-website/blog/2018-11-17-snapshot-testing-for-c/index.md +++ b/blog-website/blog/2018-11-17-snapshot-testing-for-c/index.md @@ -168,5 +168,35 @@ Someone decides that the implementation of `GetTheLeopards` needs to change. Def If we make that change we'd ideally expect our trusty test to fail. Let's see what happens: ``` +----- Test Execution Summary ----- +Leopard.Tests.Services.LeopardServiceTests.GetTheLeopards_should_return_expected_Leopards: + Outcome: Failed + Error Message: + Expected item[1].Spots to be 90, but found 900. ``` + +Boom! We are protected! + +Since this is a change we're completely happy with we want to update our `leopardsSnapshot.json` file. We could make our test pass by manually updating the JSON. That'd be fine. But why work when you don't have to? Let's uncomment our `Snapshot.Make...` line and run the test the once. + +```json +[ + { + "name": "Nimoy", + "spots": 42 + }, + { + "name": "Dotty", + "spots": 90 + } +] +``` + +That's right, we have an updated snapshot! Minimal effort. + +## Next Steps + +This is a basic approach to getting the goodness of snapshot testing in C#. It could be refined further. To my mind the uncommenting / commenting of code is not the most elegant way to approach this and so there's some work that could be done around this area. + +Happy snapshotting! diff --git a/blog-website/blog/2019-05-23-typescript-and-high-cpu-usage-watch/index.md b/blog-website/blog/2019-05-23-typescript-and-high-cpu-usage-watch/index.md index 7933361eeb8..b26e08cf163 100644 --- a/blog-website/blog/2019-05-23-typescript-and-high-cpu-usage-watch/index.md +++ b/blog-website/blog/2019-05-23-typescript-and-high-cpu-usage-watch/index.md @@ -34,5 +34,51 @@ John also found that there are other file watching behaviours offered by TypeScr John did some rough benchmarking of the performance of the different options that be set on his PC running linux 64 bit. Here's how it came out: -| Value | CPU usage on idle | -| +``` +| Value | CPU usage on idle | +| ------------------------------------- | ----------------- | +| TS default _(TSC_WATCHFILE not set)_ | **7\.4%** | +| UseFsEventsWithFallbackDynamicPolling | 0\.2% | +| UseFsEventsOnParentDirectory | 0\.2% | +| PriorityPollingInterval | **6\.2%** | +| DynamicPriorityPolling | 0\.5% | +| UseFsEvents | 0\.2% | +``` + +As you can see, the default performs poorly. On the other hand, an option like `UseFsEventsWithFallbackDynamicPolling` is comparative greasy lightning. + +## workaround! + +To get this better experience into your world now, you could just set an environment variable on your machine. However, that doesn't scale; let's instead look at introducing the environment variable into your project explicitly. + +We're going to do this in a cross platform way using [`cross-env`](https://github.com/kentcdodds/cross-env). This is a mighty useful utility by Kent C Dodds which allows you to set environment variables in a way that will work on Windows, Mac and Linux. Imagine it as the jQuery of the environment variables world :-) + +Let's add it as a `devDependency`: + +``` +yarn add -D cross-env +``` + +Then take a look at your `package.json`. You've probably got a `start` script that looks something like this: + +``` +"start": "webpack-dev-server --progress --color --mode development --config webpack.config.development.js", +``` + +Or if you're a create-react-app user maybe this: + +``` +"start": "react-scripts start", +``` + +Prefix your `start` script with `cross-env TSC_WATCHFILE=UseFsEventsWithFallbackDynamicPolling`. This will, when run, initialise an environment variable called `TSC_WATCHFILE` with the value `UseFsEventsWithFallbackDynamicPolling`. Then it will start your development server as it did before. When TypeScript is fired up by webpack it will see this environment variable and use it to configure the file watching behaviour to one of the more performant options. + +So, in the case of a `create-react-app` user, your finished `start` script would look like this: + +``` +"start": "cross-env TSC_WATCHFILE=UseFsEventsWithFallbackDynamicPolling react-scripts start", +``` + +## The Future + +There's a possibility that the default watch behaviour may change in TypeScript in future. It's currently under discussion, you can read more [here](https://github.com/microsoft/TypeScript/issues/31048). diff --git a/blog-website/blog/2019-09-30-start-me-up-ts-loader-meet-tsbuildinfo/index.md b/blog-website/blog/2019-09-30-start-me-up-ts-loader-meet-tsbuildinfo/index.md index 4ddc6f3b4f8..4308f49caa0 100644 --- a/blog-website/blog/2019-09-30-start-me-up-ts-loader-meet-tsbuildinfo/index.md +++ b/blog-website/blog/2019-09-30-start-me-up-ts-loader-meet-tsbuildinfo/index.md @@ -17,4 +17,25 @@ With TypeScript 3.4, [a new behaviour landed and a magical new file type appeare > > These `.tsbuildinfo` files can be safely deleted and don’t have any impact on our code at runtime - they’re purely used to make compilations faster. -This was all very exciting, but until the release of TypeScript 3.6 there were no APIs available to allow third party tools like `ts-loader` to hook into them. The wait is over! Because with TypeScript 3.6 the APIs landed: [https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html#apis-to-support +This was all very exciting, but until the release of TypeScript 3.6 there were no APIs available to allow third party tools like `ts-loader` to hook into them. The wait is over! Because with TypeScript 3.6 the APIs landed: [https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html#apis-to-support---build-and---incremental](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html#apis-to-support---build-and---incremental) + +This was the handiwork of the very excellent [@sheetalkamat](https://twitter.com/sheetalkamat) of the TypeScript team - you can see her PR here: [https://github.com/microsoft/TypeScript/pull/31432](https://github.com/microsoft/TypeScript/pull/31432) + +What's more, Sheetal took the PR for a test drive using `ts-loader`, and her hard work has just shipped with [`v6.2.0`](https://github.com/TypeStrong/ts-loader/releases/tag/v6.2.0): + +- [https://github.com/TypeStrong/ts-loader/pull/1012](https://github.com/TypeStrong/ts-loader/pull/1012) +- [https://github.com/TypeStrong/ts-loader/pull/1017](https://github.com/TypeStrong/ts-loader/pull/1017) + +If you're a `ts-loader` user, and you're using TypeScript 3.6+ then you can get the benefit of this now. That is, if you make use of the `experimentalWatchApi: true` option. With this set: + +1. ts-loader will both emit and consume the `.tsbuildinfo` artefact. + +2. This applies both when a project has `tsconfig.json` options `composite` or `incremental` set to `true`. + +3. The net result of people using this should be faster cold starts in build time where a previous compilation has taken place. + +## `ts-loader v7.0.0` + +We would love for you to take this new functionality for a spin. Partly because we think it will make your life better. And partly because we're planning to make using the watch API the default behaviour of `ts-loader` when we come to ship `v7.0.0`. + +If you can take this for a spin before we make that change we'd be so grateful. Thanks so much to Sheetal for persevering away on this feature. It's amazing work and so very appreciated. diff --git a/blog-website/blog/2020-08-09-devcontainers-aka-performance-in-secure/index.md b/blog-website/blog/2020-08-09-devcontainers-aka-performance-in-secure/index.md index bafb7428cfb..32e31a85b13 100644 --- a/blog-website/blog/2020-08-09-devcontainers-aka-performance-in-secure/index.md +++ b/blog-website/blog/2020-08-09-devcontainers-aka-performance-in-secure/index.md @@ -71,8 +71,8 @@ Enough talk... We're going to need a `.devcontainer/devcontainer.json`: Now the `docker-compose.devcontainer.yml` which lives in the root of the project. It provisions a SQL Server container (using the official image) and our devcontainer: -``` -version: "3.7" +```yml +version: '3.7' services: my-devcontainer: image: my-devcontainer @@ -86,8 +86,8 @@ services: # user: vscode ports: - - "5000:5000" - - "8080:8080" + - '5000:5000' + - '8080:8080' environment: - CONNECTIONSTRINGS__MYDATABASECONNECTION depends_on: @@ -98,12 +98,120 @@ services: ports: - 1433:1433 environment: - SA_PASSWORD: "Your_password123" - ACCEPT_EULA: "Y" + SA_PASSWORD: 'Your_password123' + ACCEPT_EULA: 'Y' ``` The devcontainer will be built with the `Dockerfile.devcontainer` in the root of our repo. It relies upon your SSH keys and a `.env` file being available to be copied in: +```Dockerfile +#----------------------------------------------------------------------------------------------------------- +# Based upon: https://github.com/microsoft/vscode-dev-containers/tree/master/containers/dotnetcore +#----------------------------------------------------------------------------------------------------------- +ARG VARIANT="3.1-bionic" +FROM mcr.microsoft.com/dotnet/core/sdk:${VARIANT} + +# Because MITM certificates +COPY ./docker/certs/. /usr/local/share/ca-certificates/ +ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/mitm.pem +RUN update-ca-certificates + +# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" +# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs +# will be updated to match your local UID/GID (when using the dockerFile property). +# See https://aka.ms/vscode-remote/containers/non-root-user for details. +ARG USERNAME=vscode +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +# Options for common package install script +ARG INSTALL_ZSH="true" +ARG UPGRADE_PACKAGES="true" +ARG COMMON_SCRIPT_SOURCE="https://raw.githubusercontent.com/microsoft/vscode-dev-containers/master/script-library/common-debian.sh" +ARG COMMON_SCRIPT_SHA="dev-mode" + +# Settings for installing Node.js. +ARG INSTALL_NODE="true" +ARG NODE_SCRIPT_SOURCE="https://raw.githubusercontent.com/microsoft/vscode-dev-containers/master/script-library/node-debian.sh" +ARG NODE_SCRIPT_SHA="dev-mode" + +# ARG NODE_VERSION="lts/*" +ARG NODE_VERSION="14" +ENV NVM_DIR=/usr/local/share/nvm + +# Have nvm create a "current" symlink and add to path to work around https://github.com/microsoft/vscode-remote-release/issues/3224 +ENV NVM_SYMLINK_CURRENT=true +ENV PATH=${NVM_DIR}/current/bin:${PATH} + +# Configure apt and install packages +RUN apt-get update \ + && export DEBIAN_FRONTEND=noninteractive \ + # + # Verify git, common tools / libs installed, add/modify non-root user, optionally install zsh + && apt-get -y install --no-install-recommends curl ca-certificates 2>&1 \ + && curl -sSL ${COMMON_SCRIPT_SOURCE} -o /tmp/common-setup.sh \ + && ([ "${COMMON_SCRIPT_SHA}" = "dev-mode" ] || (echo "${COMMON_SCRIPT_SHA} */tmp/common-setup.sh" | sha256sum -c -)) \ + && /bin/bash /tmp/common-setup.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \ + # + # Install Node.js + && curl -sSL ${NODE_SCRIPT_SOURCE} -o /tmp/node-setup.sh \ + && ([ "${NODE_SCRIPT_SHA}" = "dev-mode" ] || (echo "${COMMON_SCRIPT_SHA} */tmp/node-setup.sh" | sha256sum -c -)) \ + && /bin/bash /tmp/node-setup.sh "${NVM_DIR}" "${NODE_VERSION}" "${USERNAME}" \ + # + # Clean up + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -f /tmp/common-setup.sh /tmp/node-setup.sh \ + && rm -rf /var/lib/apt/lists/* \ + # + # Workspace + && mkdir workspace \ + && chown -R ${NONROOT_USER}:root workspace + + +# Install Vim +RUN apt-get update && apt-get install -y \ + vim \ + && rm -rf /var/lib/apt/lists/* + +# Set up a timezone in the devcontainer - necessary for anything timezone dependent +ENV TZ=Europe/London +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \ + && apt-get update \ + && apt-get install --no-install-recommends -y \ + apt-utils \ + tzdata \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + +ENV DOTNET_RUNNING_IN_CONTAINER=true + +# Copy across SSH keys so you can git clone +RUN mkdir /root/.ssh +RUN chmod 700 /root/.ssh + +COPY .ssh/id_rsa /root/.ssh +RUN chmod 600 /root/.ssh/id_rsa + +COPY .ssh/id_rsa.pub /root/.ssh +RUN chmod 644 /root/.ssh/id_rsa.pub + +COPY .ssh/known_hosts /root/.ssh +RUN chmod 644 /root/.ssh/known_hosts + +# Disable initial git clone prompt +RUN echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config + +# Copy across .env file so you can customise environment variables +# This will be copied into the root of the repo post git clone +COPY .env /.env +RUN chmod 644 /.env + +# Install dotnet entity framework tools +RUN dotnet tool install dotnet-ef --tool-path /usr/local/bin --version 3.1.2 ``` -# -``` + +With this devcontainer you're good to go for an ASP.NET Core / JavaScript developer setup that is blazing fast! Remember to fire up Docker and give it goodly access to the resources of your host machine. All the CPUs, lots of memory and all the performance that there ought to be. + +_\* "virus checkers" is a euphemism here for all the background tools that may be running. It was that or calling them "we are legion"_ From b7c531371db3a0e244fd108fdfca191e08cf0fb1 Mon Sep 17 00:00:00 2001 From: johnnyreilly Date: Mon, 28 Aug 2023 18:26:27 +0100 Subject: [PATCH 13/14] feat: more descriptions --- .../index.md | 401 +++++++++++++++++- .../index.md | 62 ++- .../index.md | 65 ++- 3 files changed, 515 insertions(+), 13 deletions(-) diff --git a/blog-website/blog/2021-10-31-nswag-generated-c-sharp-client-property-name-clash/index.md b/blog-website/blog/2021-10-31-nswag-generated-c-sharp-client-property-name-clash/index.md index 4350802e188..d8e144d7258 100644 --- a/blog-website/blog/2021-10-31-nswag-generated-c-sharp-client-property-name-clash/index.md +++ b/blog-website/blog/2021-10-31-nswag-generated-c-sharp-client-property-name-clash/index.md @@ -240,5 +240,404 @@ We'll tweak our `NSwag.csproj` file to ensure that the `json` file is included i ```xml - + + + PreserveNewest + + + ``` + +This will give us a console app with a reference to NSwag. Now we'll flesh out the `Program.cs` file thusly: + +```cs +using System; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using NJsonSchema; +using NJsonSchema.Visitors; +using NSwag.CodeGeneration.CSharp; + +namespace NSwag { + class Program { + static async Task Main(string[] args) { + Console.WriteLine("Generating client..."); + await ClientGenerator.GenerateCSharpClient(); + Console.WriteLine("Generated client."); + } + } + + public static class ClientGenerator { + + public async static Task GenerateCSharpClient() => + GenerateClient( + // https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v2.0/json/petstore-simple.json + document: await GetDocumentFromFile("petstore-simple.json"), + generatedLocation: "GeneratedClient.cs", + generateCode: (OpenApiDocument document) => { + var settings = new CSharpClientGeneratorSettings(); + + var generator = new CSharpClientGenerator(document, settings); + var code = generator.GenerateFile(); + return code; + } + ); + + private static void GenerateClient(OpenApiDocument document, string generatedLocation, Func generateCode) { + var root = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + var location = Path.GetFullPath(Path.Join(root, @"../../../", generatedLocation)); + + Console.WriteLine($"Generating {location}..."); + + var code = generateCode(document); + + System.IO.File.WriteAllText(location, code); + } + + private static async Task GetDocumentFromFile(string swaggerJsonFilePath) { + var root = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + var swaggerJson = await File.ReadAllTextAsync(Path.GetFullPath(Path.Join(root, swaggerJsonFilePath))); + var document = await OpenApiDocument.FromJsonAsync(swaggerJson); + + return document; + } + } +} +``` + +If we perform a `dotnet run` we now pump out a `GeneratedClient.cs` file which is a C# client library for the pet store. Fabulous. + +So far so dandy. We're taking an Open API `json` file and generating a C# client library from it. + +## When properties collide + +It's time to break things. We're presently generating a `Pet` class that looks like this: + +```cs +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] +public partial class Pet : NewPet +{ + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + public long Id { get; set; } +} +``` + +We're going to take our `Pet` definition in the `petstore-simple.json` file, and add a new `@id` property alongside the `id` property: + +```json +"Pet": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/NewPet" + }, + { + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "@id": { + "type": "integer", + "format": "int64" + } + } + } + ] +}, +``` + +For why? Whilst this may seem esoteric, this is a scenario that can present. It's not unknown to encounter properties which are identical, save for an `@` prefix. This is often the case for meta-properties. + +What do we get if we run our generator over that? + +```cs +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] +public partial class Pet : NewPet +{ + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + public long Id { get; set; } + + [Newtonsoft.Json.JsonProperty("@id", Required = Newtonsoft.Json.Required.Always)] + public long Id { get; set; } +} +``` + +We get code that doesn't compile. You can't have two properties in a C# class with the same name. You also cannot have `@` as a character in a C# property or variable name. To quote the [docs](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/verbatim): + +> The @ special character serves as a verbatim identifier. + +It so happens that, by default, NSwag purges `@` characters from property names. If there isn't another property which is named the same save for an `@` prefix, this is a fine strategy. If there is, as for us now, you're toast. + +There's a workaround. We'll create a new `HandleAtCSharpPropertyNameGenerator` class: + +```cs +/// +/// Replace characters which will not comply with C# syntax with something that will +/// +public class HandleAtCSharpPropertyNameGenerator : NJsonSchema.CodeGeneration.IPropertyNameGenerator { + /// Generates the property name. + /// The property. + /// The new name. + public virtual string Generate(JsonSchemaProperty property) => + ConversionUtilities.ConvertToUpperCamelCase(property.Name + .Replace("\"", string.Empty) + .Replace("@", "__") // make "@" => "__", so "@type" => "__type" + .Replace("?", string.Empty) + .Replace("$", string.Empty) + .Replace("[", string.Empty) + .Replace("]", string.Empty) + .Replace("(", "_") + .Replace(")", string.Empty) + .Replace(".", "-") + .Replace("=", "-") + .Replace("+", "plus"), true) + .Replace("*", "Star") + .Replace(":", "_") + .Replace("-", "_") + .Replace("#", "_"); +} +``` + +This is a replacement for the `CSharpPropertyNameGenerator` that NSwag ships with. Rather than purging the `@` character, it replaces usage with a double underscore: `__`. + +We'll make use of our new `PropertyNameGenerator`: + +```cs +public async static Task GenerateCSharpClient() => + GenerateClient( + // https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v2.0/json/petstore-simple.json + document: await GetDocumentFromFile("petstore-simple.json"), + generatedLocation: "GeneratedClient.cs", + generateCode: (OpenApiDocument document) => { + var settings = new CSharpClientGeneratorSettings { + CSharpGeneratorSettings = { + PropertyNameGenerator = new HandleAtCSharpPropertyNameGenerator() // @ shouldn't cause us problems + } + }; + + var generator = new CSharpClientGenerator(document, settings); + var code = generator.GenerateFile(); + return code; + } + ); +``` + +With this in place, when we `dotnet run` we create a class that looks like this: + +```cs +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] +public partial class Pet : NewPet +{ + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + public long Id { get; set; } + + [Newtonsoft.Json.JsonProperty("@id", Required = Newtonsoft.Json.Required.Always)] + public long __id { get; set; } +} +``` + +So the newly generated property name is `__id` rather than the clashing `Id`. Rather wonderfully, this works. It resolves the issue we faced. We've chosen to use `__` as our prefix - we could choose something else if that worked better for us. + +Knowing that this hook exists is super useful. + +## Use `decimal` not `double` for floating point numbers + +Another common problem with generated C# clients is the number type used to represent floating point numbers. The default for C# is `double`. + +This is a reasonable choice when you consider the [official format](https://swagger.io/docs/specification/data-models/data-types/#numbers) for highly precise floating point numbers is `double`: + +> OpenAPI has two numeric types, `number` and `integer`, where `number` includes both integer and floating-point numbers. An optional `format` keyword serves as a hint for the tools to use a specific numeric type: +> +> `float` - Floating-point numbers. +> `double` - Floating-point numbers with double precision. + +Let's tweak our pet definition to reflect this: + +```json +"Pet": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/NewPet" + }, + { + "required": [ + "id" + ], + "properties": { + "id": { + "type": "number", + "format": "double" + }, + "@id": { + "type": "number", + "format": "double" + } + } + } + ] +}, +``` + +With this in place, when we `dotnet run` we create a class that looks like this: + +```cs +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] +public partial class Pet : NewPet +{ + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + public double Id { get; set; } + + [Newtonsoft.Json.JsonProperty("@id", Required = Newtonsoft.Json.Required.Always)] + public double __id { get; set; } +} +``` + +C# developers may well rather work with a [`decimal`](https://docs.microsoft.com/en-us/dotnet/api/system.decimal?view=net-5.0) type which can handle "financial calculations that require large numbers of significant integral and fractional digits and no round-off errors". + +There is a way to switch from using `double` to `decimal` in your generated clients. I've been using the approach for some years, and I suspect I first adapted it from [a comment on GitHub](https://github.com/RicoSuter/NSwag/issues/1814#issuecomment-448752684). + +It uses the [visitor pattern](https://en.m.wikipedia.org/wiki/Visitor_pattern) and looks like this: + +```cs +/// +/// By default the C# decimal number type used is double; this makes it decimal +/// +public class DoubleToDecimalVisitor : JsonSchemaVisitorBase { + protected override JsonSchema VisitSchema(JsonSchema schema, string path, string typeNameHint) { + if (schema.Type == JsonObjectType.Number) + schema.Format = JsonFormatStrings.Decimal; + + return schema; + } +} +``` + +The code above, when invoked upon our `OpenApiDocument`, changes the format of all number types to be `decimal`. Which results in code along these lines: + +```cs +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] +public partial class Pet : NewPet +{ + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + public decimal Id { get; set; } + + [Newtonsoft.Json.JsonProperty("@id", Required = Newtonsoft.Json.Required.Always)] + public decimal __id { get; set; } +} +``` + +If we take all the code, and put it together, we end up with this: + +```cs +using System; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using NJsonSchema; +using NJsonSchema.Visitors; +using NSwag.CodeGeneration.CSharp; + +namespace NSwag { + class Program { + static async Task Main(string[] args) { + Console.WriteLine("Generating client..."); + await ClientGenerator.GenerateCSharpClient(); + Console.WriteLine("Generated client."); + } + } + + public static class ClientGenerator { + + public async static Task GenerateCSharpClient() => + GenerateClient( + // https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v2.0/json/petstore-simple.json + document: await GetDocumentFromFile("petstore-simple.json"), + generatedLocation: "GeneratedClient.cs", + generateCode: (OpenApiDocument document) => { + new DoubleToDecimalVisitor().Visit(document); // we want decimals not doubles + + var settings = new CSharpClientGeneratorSettings { + CSharpGeneratorSettings = { + PropertyNameGenerator = new HandleAtCSharpPropertyNameGenerator() // @ shouldn't cause us problems + } + }; + + var generator = new CSharpClientGenerator(document, settings); + var code = generator.GenerateFile(); + return code; + } + ); + + private static void GenerateClient(OpenApiDocument document, string generatedLocation, Func generateCode) { + var root = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + var location = Path.GetFullPath(Path.Join(root, @"../../../", generatedLocation)); + + Console.WriteLine($"Generating {location}..."); + + var code = generateCode(document); + + System.IO.File.WriteAllText(location, code); + } + + private static async Task GetDocumentFromFile(string swaggerJsonFilePath) { + var root = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + var swaggerJson = await File.ReadAllTextAsync(Path.GetFullPath(Path.Join(root, swaggerJsonFilePath))); + var document = await OpenApiDocument.FromJsonAsync(swaggerJson); + + return document; + } + } + + /// + /// By default the C# decimal number type used is double; this makes it decimal + /// + public class DoubleToDecimalVisitor : JsonSchemaVisitorBase { + protected override JsonSchema VisitSchema(JsonSchema schema, string path, string typeNameHint) { + if (schema.Type == JsonObjectType.Number) + schema.Format = JsonFormatStrings.Decimal; + + return schema; + } + } + + /// + /// Replace characters which will not comply with C# syntax with something that will + /// + public class HandleAtCSharpPropertyNameGenerator : NJsonSchema.CodeGeneration.IPropertyNameGenerator { + /// Generates the property name. + /// The property. + /// The new name. + public virtual string Generate(JsonSchemaProperty property) => + ConversionUtilities.ConvertToUpperCamelCase(property.Name + .Replace("\"", string.Empty) + .Replace("@", "__") // make "@" => "__", so "@type" => "__type" + .Replace("?", string.Empty) + .Replace("$", string.Empty) + .Replace("[", string.Empty) + .Replace("]", string.Empty) + .Replace("(", "_") + .Replace(")", string.Empty) + .Replace(".", "-") + .Replace("=", "-") + .Replace("+", "plus"), true) + .Replace("*", "Star") + .Replace(":", "_") + .Replace("-", "_") + .Replace("#", "_"); + } +} +``` + +## Conclusion + +This post takes the tremendous NSwag, and demonstrates a mechanism for using it to create C# clients from an Open API / Swagger documents which: + +- can handle property names with an `@` prefix which might collide with the same property without the prefix +- use `decimal` as the preferred number type for floating point numbers diff --git a/blog-website/blog/2021-11-22-typescript-vs-jsdoc-javascript/index.md b/blog-website/blog/2021-11-22-typescript-vs-jsdoc-javascript/index.md index 3c93add9146..ea36a356892 100644 --- a/blog-website/blog/2021-11-22-typescript-vs-jsdoc-javascript/index.md +++ b/blog-website/blog/2021-11-22-typescript-vs-jsdoc-javascript/index.md @@ -59,4 +59,64 @@ Perhaps you're writing simple node scripts and you'd like a little type safety t That, in fact, was the rationale of the webpack team. A little bit of history: webpack has always been a JavaScript codebase. As the codebase grew and grew, there was often discussion about using static typing. However, having a compilation step wasn't desired. -TypeScript had been quietly adding support for type checking JavaScript with the assistance of JSDoc for some time. Initial support arrived with the `--checkJs` compiler option in [TypeScript 2.3](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#errors-in-js-files-with +TypeScript had been quietly adding support for type checking JavaScript with the assistance of JSDoc for some time. Initial support arrived with the `--checkJs` compiler option in [TypeScript 2.3](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#errors-in-js-files-with---checkjs). + +A community member by the name of [Mohsen Azimi](https://twitter.com/mohsen____) experimentally started out using this approach to type check the webpack codebase. [His PR](https://github.com/webpack/webpack/pull/6862) ended up being a test case that helped improve the type checking of JavaScript by TypeScript. TypeScript v2.9 shipped with a whole host of JSDoc improvements as a consequence of the webpack work. Being such a widely used project this also helped popularise the approach of using JSDoc to type check JavaScript codebases. It demonstrated that this approach could work on a significantly sized codebase. + +These days, JSDoc type checking with TypeScript is extremely powerful. Whilst not quite on par with TypeScript (not all TypeScript syntax is supported in JSDoc) the gap in functionality is pretty small. + +It's a completely legitimate choice to build a JavaScript codebase with all the benefits of static typing. + +## Why use TypeScript? + +So if you were starting a project today, and you'd decided you wanted to make use of static typing, how do you choose? TypeScript or JavaScript with JSDoc? + +Well, unless you've a compelling need to avoid a compilation step, I'm going to suggest that TypeScript may be the better choice for a number of reasons. + +Firstly, the tooling support for using TypeScript directly is better than that for JSDoc JavaScript. At the time of writing, things like refactoring tools etc in your editor work more effectively with TypeScript than with JSDoc JavaScript. (Although these are improving as time goes by.) + +Secondly, working with JSDoc is distinctly "noisier". It requires far more keystrokes to achieve the same level of type safety. Consider the following TypeScript: + +```ts +function stringsStringStrings( + p1: string, + p2?: string, + p3?: string, + p4 = 'test', +): string { + // ... +} +``` + +As compared to the equivalent JSDoc JavaScript: + +```ts +/** + * @param {string} p1 + * @param {string=} p2 + * @param {string} [p3] + * @param {string} [p4="test"] + * @return {string} + */ +function stringsStringStrings(p1, p2, p3, p4) { + // ... +} +``` + +It may be my own familiarity with TypeScript speaking, but I find that the TypeScript is easier to read and comprehend as compared to the JSDoc JavaScript alternative. The fact that all JSDoc annotations live in comments, rather than directly in syntax, makes it harder to follow. (It certainly doesn't help that many VS Code themes present comments in a very faint colour.) + +My final reason for favouring TypeScript comes down to falling into the ["pit of success"](https://blog.codinghorror.com/falling-into-the-pit-of-success/). You're cutting _against_ the grain when it comes to static typing and JavaScript. You can have it, but you have to work that bit harder to ensure that you have statically typed code. On the other hand, you're cutting _with_ the grain when it comes to static typing and TypeScript. You have to work hard to opt out of static typing. The TypeScript defaults tend towards static typing, whilst the JavaScript defaults tend away. + +As someone who very much favours static typing, you can imagine how this is compelling to me! + +## It's your choice! + +So in a way, I don't feel super strongly whether people use JavaScript or TypeScript. But having static typing will likely be a benefit to new projects. Bottom line, I'm keen that people fall into the "pit of success", so my recommendation for a new project would be TypeScript. + +I really like JSDoc myself, and will often use it on small projects. It's a fantastic addition to TypeScript's capabilities. For bigger projects, I'll likely go with TypeScript from the get go. But really, this is a choice - and either is great. + +[This post was originally published on LogRocket.](https://blog.logrocket.com/typescript-vs-jsdoc-javascript/) + + + + diff --git a/blog-website/blog/2021-12-05-azure-static-web-app-deploy-previews-with-azure-devops/index.md b/blog-website/blog/2021-12-05-azure-static-web-app-deploy-previews-with-azure-devops/index.md index a7c6854bca1..08384e7a2c7 100644 --- a/blog-website/blog/2021-12-05-azure-static-web-app-deploy-previews-with-azure-devops/index.md +++ b/blog-website/blog/2021-12-05-azure-static-web-app-deploy-previews-with-azure-devops/index.md @@ -123,7 +123,7 @@ variables: - name: appName value: 'our-static-web-app' - name: location - value: 'westeurope' # at time of writing static sites are available in limited locations such as westeurope + value: 'westeurope' # at time of writing static sites are available in limited locations such as westeurope - name: serviceConnection value: 'azureRMWestEurope' - name: azureResourceGroup # this resource group lives in westeurope @@ -289,7 +289,7 @@ async function run({ if (!pullRequestId) console.log( - 'No pull request id supplied, so will look up latest active PR' + 'No pull request id supplied, so will look up latest active PR', ); const pullRequestIdToUpdate = @@ -300,7 +300,7 @@ async function run({ } console.log( - `Updating ${systemCollectionUri}/${project}/_git/${repository}/pullrequest/${pullRequestIdToUpdate} with a preview URL of ${previewUrl}` + `Updating ${systemCollectionUri}/${project}/_git/${repository}/pullrequest/${pullRequestIdToUpdate} with a preview URL of ${previewUrl}`, ); const pullRequest = await getPullRequest({ @@ -315,12 +315,12 @@ async function run({ pullRequestId: pullRequestIdToUpdate, description: makePreviewDescriptionMarkdown( pullRequest.description!, - previewUrl + previewUrl, ), }); console.log( - `Updated pull request description a preview URL of ${previewUrl}` + `Updated pull request description a preview URL of ${previewUrl}`, ); } @@ -341,11 +341,11 @@ async function getGitApi({ const authHandler = pat ? nodeApi.getPersonalAccessTokenHandler( pat, - /** allowCrossOriginAuthentication */ true + /** allowCrossOriginAuthentication */ true, ) : nodeApi.getHandlerFromToken( sat, - /** allowCrossOriginAuthentication */ true + /** allowCrossOriginAuthentication */ true, ); const webApi = new nodeApi.WebApi(systemCollectionUri, authHandler); @@ -367,7 +367,7 @@ async function getActivePullRequestId({ config.project, undefined, /** skip */ 0, - /** top */ 1 + /** top */ 1, ); return topActivePullRequest.length > 0 @@ -392,7 +392,7 @@ async function getPullRequest({ /** skip */ 0, /** top */ 1, /** includeCommits */ false, - /** includeWorkItemRefs */ false + /** includeWorkItemRefs */ false, ); return pullRequest; } @@ -416,12 +416,55 @@ async function updatePullRequestDescription({ updatePullRequest, config.repository, pullRequestId, - config.project + config.project, ); } function makePreviewDescriptionMarkdown(desc: string, previewUrl: string) { const previewRegex = /(> -*\n> # Preview:\n.*\n>.*\n> -*\n)/; - const makePreview = (previewUrl: string) => `> + const makePreview = (previewUrl: string) => `> --- +> # Preview: +> ${previewUrl} +> +> --- +`; + + const alreadyHasPreview = desc.match(previewRegex); + return alreadyHasPreview + ? desc.replace(previewRegex, makePreview(previewUrl)) + : makePreview(previewUrl) + desc; +} +``` + +The above code does two things: + +1. Looks up the pull request, using the details supplied from the pipeline. It's worth noting that the `System.PullRequest.PullRequestId` variable is [initialized only if the build ran because of a Git PR affected by a branch policy](https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml). If you don't have that set up, the script falls back to using the latest active pull request. This is generally useful when you're getting set up in the first place; you won't want to rely on this behaviour. +2. Updates the pull request description with a prefix piece of markdown that provides the link to the preview URL. This is our "browse the preview": + ![screenshot of rendered markdown with the preview link](screenshot-of-deploy-preview-small.png) + +This script could be refactored into a dedicated Azure Pipelines custom task. + +## Permissions + +The first time you run this you may encounter a permissions error of the form: + ``` +Error: TF401027: You need the Git 'PullRequestContribute' permission to perform this action. +``` + +To remedy this you need to give your build service the relevant permissions to update a pull request. You can do that by going to the security settings of your repo and setting "Contribute to pull requests" to "Allow" for your build service: + +![Screenshot of "Contribute to pull requests" permission in Azure DevOps Git security being set to "Allow" ](screenshot-of-git-repository-security-settings.webp) + +## Enjoy! (and keep Azure tidy) + +When the pipeline is now run you can see that a deployment preview link is now updated onto the PR description: + +![Screenshot of deployment preview on PR](screenshot-of-deploy-preview.webp) + +This will happen whenever a PR is raised which is tremendous. + +A thing to remember, is that there's nothing in this post that tears down the temporary deployment after the pull request has been merged. It will hang around. We happen to be using free resources in this post, but if we weren't there would be cost implications. Either way, you'll want to clean up unused environments as a matter of course. And I'd advise automating that. + +So be tidy and cost aware with this approach. From 3cad478d781bc9d6ec16d0c8fffd2dd19696e1e1 Mon Sep 17 00:00:00 2001 From: johnnyreilly Date: Mon, 28 Aug 2023 18:41:38 +0100 Subject: [PATCH 14/14] fix: capitalisation --- .../index.md | 6 +++--- .../index.md | 2 +- .../index.md | 2 +- .../blog/2016-09-22-typescript-20-es2016-and-babel/index.md | 4 ++-- .../blog/2016-12-19-using-ts-loader-with-webpack-2/index.md | 6 +++--- .../index.md | 6 +++--- .../2018-01-28-webpack-4-ts-loader-fork-ts-checker/index.md | 4 ++-- .../blog/2018-01-29-finding-webpack-4-use-map/index.md | 2 +- .../index.md | 2 +- .../2018-03-07-its-not-dead-webpack-and-dead-code/index.md | 4 ++-- .../index.md | 2 +- .../blog/2021-04-20-ts-loader-goes-webpack-5/index.md | 2 +- 12 files changed, 21 insertions(+), 21 deletions(-) diff --git a/blog-website/blog/2015-12-16-es6-typescript-babel-react-flux-karma/index.md b/blog-website/blog/2015-12-16-es6-typescript-babel-react-flux-karma/index.md index d250cb137ce..c407f6d8b64 100644 --- a/blog-website/blog/2015-12-16-es6-typescript-babel-react-flux-karma/index.md +++ b/blog-website/blog/2015-12-16-es6-typescript-babel-react-flux-karma/index.md @@ -4,7 +4,7 @@ title: 'ES6 + TypeScript + Babel + React + Flux + Karma: The Secret Recipe' authors: johnnyreilly tags: [ES6, Karma, React, ts-loader, webpack] hide_table_of_contents: false -description: 'Learn how to set up a powerful TypeScript-React workflow with Webpack, gulp, Karma, and inject in this comprehensive article.' +description: 'Learn how to set up a powerful TypeScript-React workflow with webpack, gulp, Karma, and inject in this comprehensive article.' --- I wrote [a while ago](../2015-09-10-things-done-changed/index.md) about how I was using some different tools in a current project: @@ -27,7 +27,7 @@ But the pain is over. The dark days are gone. It's possible to have strong typin I decided a couple of months ago what I wanted to have in my setup: 1. I want to be able to write React / JSX in TypeScript. Naturally I couldn't achieve that by myself but handily the TypeScript team decided to add support for JSX with [TypeScript 1.6](https://blogs.msdn.com/b/typescript/archive/2015/09/16/announcing-typescript-1-6.aspx). Ooh yeah. -2. I wanted to be able to write ES6. When I realised [the approach for writing ES6 and having the transpilation handled by TypeScript wasn't clear](https://github.com/Microsoft/TypeScript/issues/3956) I had another idea. I thought ["what if I write ES6 and hand off the transpilation to Babel?"](https://github.com/Microsoft/TypeScript/issues/4765) i.e. Use TypeScript for type checking, not for transpilation. I realised that [James Brantly had my back](http://www.jbrantly.com/es6-modules-with-typescript-and-webpack/#configuringwebpack) here already. Enter [Webpack](https://webpack.github.io/) and [ts-loader](https://github.com/TypeStrong/ts-loader). +2. I wanted to be able to write ES6. When I realised [the approach for writing ES6 and having the transpilation handled by TypeScript wasn't clear](https://github.com/Microsoft/TypeScript/issues/3956) I had another idea. I thought ["what if I write ES6 and hand off the transpilation to Babel?"](https://github.com/Microsoft/TypeScript/issues/4765) i.e. Use TypeScript for type checking, not for transpilation. I realised that [James Brantly had my back](http://www.jbrantly.com/es6-modules-with-typescript-and-webpack/#configuringwebpack) here already. Enter [webpack](https://webpack.github.io/) and [ts-loader](https://github.com/TypeStrong/ts-loader). 3. Debugging. Being able to debug my code is non-negotiable for me. If I can't debug it I'm less productive. (I'm also bitter and twisted inside.) I should say that I wanted to be able to debug my _original_ source code. Thanks to the magic of [sourcemaps](https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?usp=sharing), that mad thing is possible. 4. Karma for unit testing. I've become accustomed to writing my tests in ES6 and running them on a continual basis with [Karma](https://karma-runner.github.io/0.13/index.html). This allows for a rather good debugging story as well. I didn't want to lose this when I moved to TypeScript. I didn't. @@ -200,7 +200,7 @@ function createDevCompiler() { filename: 'vendor.js', }), new WebpackNotifierPlugin({ - title: 'Webpack build', + title: 'webpack build', excludeWarnings: true, }), ); diff --git a/blog-website/blog/2016-02-19-visual-studio-tsconfigjson-and-external/index.md b/blog-website/blog/2016-02-19-visual-studio-tsconfigjson-and-external/index.md index 138df7399fe..2abc937d581 100644 --- a/blog-website/blog/2016-02-19-visual-studio-tsconfigjson-and-external/index.md +++ b/blog-website/blog/2016-02-19-visual-studio-tsconfigjson-and-external/index.md @@ -2,7 +2,7 @@ slug: visual-studio-tsconfigjson-and-external title: 'Visual Studio, tsconfig.json and external TypeScript compilation' authors: johnnyreilly -tags: [TFS, Visual Studio, tsconfig.json, typescript, Webpack] +tags: [TFS, Visual Studio, tsconfig.json, typescript, webpack] hide_table_of_contents: false description: 'Visual Studio will not gain support for tsconfig.json until TypeScript 1.8, so using external compilation may be preferable.' --- diff --git a/blog-website/blog/2016-05-13-inlining-angular-templates-with-webpack/index.md b/blog-website/blog/2016-05-13-inlining-angular-templates-with-webpack/index.md index deb543f1104..a7c30fcc6a0 100644 --- a/blog-website/blog/2016-05-13-inlining-angular-templates-with-webpack/index.md +++ b/blog-website/blog/2016-05-13-inlining-angular-templates-with-webpack/index.md @@ -4,7 +4,7 @@ title: 'Inlining Angular Templates with WebPack and TypeScript' authors: johnnyreilly tags: [AngularJS, webpack] hide_table_of_contents: false -description: '`raw-loader` package in Webpack configuration for Angular 1.x projects preloads templates and enables compile-time error checking.' +description: '`raw-loader` package in webpack configuration for Angular 1.x projects preloads templates and enables compile-time error checking.' --- This technique actually applies to pretty much any web stack where you have to supply templates; it just so happens that I'm using Angular 1.x in this case. Also I have an extra technique which is useful to handle the [ng-include](https://docs.angularjs.org/api/ng/directive/ngInclude) scenario. diff --git a/blog-website/blog/2016-09-22-typescript-20-es2016-and-babel/index.md b/blog-website/blog/2016-09-22-typescript-20-es2016-and-babel/index.md index 6d7571d75ee..8a7593cec27 100644 --- a/blog-website/blog/2016-09-22-typescript-20-es2016-and-babel/index.md +++ b/blog-website/blog/2016-09-22-typescript-20-es2016-and-babel/index.md @@ -38,9 +38,9 @@ Well, there's no `"es2016"` target for TypeScript. You carry on with a target of ## Babel changes -I needed the Babel preset for ES2016; with a quick [`npm install --save-dev babel-preset-es2016`](https://www.npmjs.com/package/babel-preset-es2016) that was sorted. Now just to kick Webpack into gear... +I needed the Babel preset for ES2016; with a quick [`npm install --save-dev babel-preset-es2016`](https://www.npmjs.com/package/babel-preset-es2016) that was sorted. Now just to kick webpack into gear... -## Webpack changes +## webpack changes My webpack config plugs together TypeScript and Babel with the help of [ts-loader](https://www.npmjs.com/package/ts-loader) and [babel-loader](https://www.npmjs.com/package/babel-loader). It allows the transpilation of my (few) JavaScript files so I can write ES2015. However, mainly it allows the transpilation of my (many) TypeScript files so I can write ES2015-flavoured TypeScript. I'll now tweak the `loaders` so they cater for ES2016 as well. diff --git a/blog-website/blog/2016-12-19-using-ts-loader-with-webpack-2/index.md b/blog-website/blog/2016-12-19-using-ts-loader-with-webpack-2/index.md index 15ab67ff0d1..0b7c9500fab 100644 --- a/blog-website/blog/2016-12-19-using-ts-loader-with-webpack-2/index.md +++ b/blog-website/blog/2016-12-19-using-ts-loader-with-webpack-2/index.md @@ -197,11 +197,11 @@ function createDevCompiler() { filename: 'vendor.js', }), new WebpackNotifierPlugin({ - title: 'Webpack build', + title: 'webpack build', excludeWarnings: true, }), - // this is the Webpack 2 hotness! + // this is the webpack 2 hotness! new webpack.LoaderOptionsPlugin({ debug: true, options: myDevConfig, @@ -249,7 +249,7 @@ function createDevCompiler() { filename: 'vendor.js', }), new WebpackNotifierPlugin({ - title: 'Webpack build', + title: 'webpack build', excludeWarnings: true, }), ); diff --git a/blog-website/blog/2017-09-07-typescript-webpack-super-pursuit-mode/index.md b/blog-website/blog/2017-09-07-typescript-webpack-super-pursuit-mode/index.md index 3b791a12c2d..6ffe0f0d3ef 100644 --- a/blog-website/blog/2017-09-07-typescript-webpack-super-pursuit-mode/index.md +++ b/blog-website/blog/2017-09-07-typescript-webpack-super-pursuit-mode/index.md @@ -1,8 +1,8 @@ --- slug: typescript-webpack-super-pursuit-mode -title: 'TypeScript + Webpack: Super Pursuit Mode' +title: 'TypeScript + webpack: Super Pursuit Mode' authors: johnnyreilly -tags: [typescript, fork-ts-checker-webpack-plugin, Webpack] +tags: [typescript, fork-ts-checker-webpack-plugin, webpack] hide_table_of_contents: false description: 'Learn how to improve build speeds with TypeScript and webpack using fork-ts-checker-webpack-plugin, HappyPack, and thread-loader/cache-loader.' --- @@ -27,7 +27,7 @@ Apologies for the image quality above; there appear to be no high quality pictur ["Faster type checking with forked process"](https://github.com/TypeStrong/ts-loader/issues/537) read the enticing name of the issue. It turned out to be [Piotr Oleś](https://github.com/piotr-oles) ([@OlesDev](https://twitter.com/OlesDev)) telling the world about his beautiful creation. He'd put together a mighty fine plugin that can be used alongside ts-loader called the [fork-ts-checker-webpack-plugin](https://github.com/Realytics/fork-ts-checker-webpack-plugin). The name is a bit of a mouthful but the purpose is mouth-watering. To quote the README, it is a: -> Webpack plugin that runs typescript type checker on a separate process. +> webpack plugin that runs typescript type checker on a separate process. What does this mean and how does this fit with ts-loader? Well, ts-loader does 2 jobs: diff --git a/blog-website/blog/2018-01-28-webpack-4-ts-loader-fork-ts-checker/index.md b/blog-website/blog/2018-01-28-webpack-4-ts-loader-fork-ts-checker/index.md index 854fbdf7142..5bf083da8de 100644 --- a/blog-website/blog/2018-01-28-webpack-4-ts-loader-fork-ts-checker/index.md +++ b/blog-website/blog/2018-01-28-webpack-4-ts-loader-fork-ts-checker/index.md @@ -2,9 +2,9 @@ slug: webpack-4-ts-loader-fork-ts-checker title: 'webpack 4 - ts-loader / fork-ts-checker-webpack-plugin betas' authors: johnnyreilly -tags: [fork-ts-checker-webpack-plugin, ts-loader, Webpack] +tags: [fork-ts-checker-webpack-plugin, ts-loader, webpack] hide_table_of_contents: false -description: 'The TypeScript ts-loader beta to work with Webpack 4 is now available, along with the fork-ts-checker-webpack-plugin, which complements ts-loader.' +description: 'The TypeScript ts-loader beta to work with webpack 4 is now available, along with the fork-ts-checker-webpack-plugin, which complements ts-loader.' --- [The first webpack 4 beta dropped on Friday](https://medium.com/webpack/webpack-4-beta-try-it-today-6b1d27d7d7e2). Very exciting! Following hot on the heels of those announcements, I've some news to share too. Can you guess what it is? diff --git a/blog-website/blog/2018-01-29-finding-webpack-4-use-map/index.md b/blog-website/blog/2018-01-29-finding-webpack-4-use-map/index.md index 2940f9f9248..4d000ef1ad2 100644 --- a/blog-website/blog/2018-01-29-finding-webpack-4-use-map/index.md +++ b/blog-website/blog/2018-01-29-finding-webpack-4-use-map/index.md @@ -4,7 +4,7 @@ title: 'Finding webpack 4 (use a Map)' authors: johnnyreilly tags: [webpack] hide_table_of_contents: false -description: 'Webpack 4s new plugin architecture requires migrating from "kebab-case" to "camelCase". A migration guide for plugins and loaders is available.' +description: 'webpack 4s new plugin architecture requires migrating from "kebab-case" to "camelCase". A migration guide for plugins and loaders is available.' --- ## Update: 03/02/2018 diff --git a/blog-website/blog/2018-02-25-ts-loader-400-fork-ts-checker-webpack/index.md b/blog-website/blog/2018-02-25-ts-loader-400-fork-ts-checker-webpack/index.md index f098d1dc7f2..ebd789bdd5f 100644 --- a/blog-website/blog/2018-02-25-ts-loader-400-fork-ts-checker-webpack/index.md +++ b/blog-website/blog/2018-02-25-ts-loader-400-fork-ts-checker-webpack/index.md @@ -4,7 +4,7 @@ title: 'ts-loader 4 / fork-ts-checker-webpack-plugin 0.4' authors: johnnyreilly tags: [webpack, fork-ts-checker-webpack-plugin, ts-loader] hide_table_of_contents: false -description: 'Webpack 4 has been released, along with updates for ts-loader and fork-ts-checker-webpack-plugin. See links for details and examples.' +description: 'webpack 4 has been released, along with updates for ts-loader and fork-ts-checker-webpack-plugin. See links for details and examples.' --- webpack 4 has shipped! diff --git a/blog-website/blog/2018-03-07-its-not-dead-webpack-and-dead-code/index.md b/blog-website/blog/2018-03-07-its-not-dead-webpack-and-dead-code/index.md index 0a13d8dce7f..1cafa81cb83 100644 --- a/blog-website/blog/2018-03-07-its-not-dead-webpack-and-dead-code/index.md +++ b/blog-website/blog/2018-03-07-its-not-dead-webpack-and-dead-code/index.md @@ -4,10 +4,10 @@ title: "It's Not Dead: webpack and dead code elimination limitations" authors: johnnyreilly tags: [webpack] hide_table_of_contents: false -description: 'Webpack eliminates dead code through DefinePlugin. Directly use `process.env.NODE_ENV !== production` for smarter code elimination by UglifyJSPlugin.' +description: 'webpack eliminates dead code through DefinePlugin. Directly use `process.env.NODE_ENV !== production` for smarter code elimination by UglifyJSPlugin.' --- -Webpack has long supported the notion of dead code elimination. webpack facilitates this through use of the `DefinePlugin`. The compile time value of `process.env.NODE_ENV` is set either to `'production'` or something else. If it's set to `'production'` then some dead code hackery can happen. [Libraries like React make use of this to serve up different, and crucially smaller, production builds.](https://reactjs.org/docs/optimizing-performance.html#webpack) +webpack has long supported the notion of dead code elimination. webpack facilitates this through use of the `DefinePlugin`. The compile time value of `process.env.NODE_ENV` is set either to `'production'` or something else. If it's set to `'production'` then some dead code hackery can happen. [Libraries like React make use of this to serve up different, and crucially smaller, production builds.](https://reactjs.org/docs/optimizing-performance.html#webpack) diff --git a/blog-website/blog/2018-09-23-ts-loader-project-references-first-blood/index.md b/blog-website/blog/2018-09-23-ts-loader-project-references-first-blood/index.md index 31b1919c6cf..c9b710364c5 100644 --- a/blog-website/blog/2018-09-23-ts-loader-project-references-first-blood/index.md +++ b/blog-website/blog/2018-09-23-ts-loader-project-references-first-blood/index.md @@ -2,7 +2,7 @@ slug: ts-loader-project-references-first-blood title: 'ts-loader Project References: First Blood' authors: johnnyreilly -tags: [typescript, project references, ts-loader, Webpack] +tags: [typescript, project references, ts-loader, webpack] hide_table_of_contents: false description: 'ts-loader now supports TypeScripts project references. However, composite projects built with `outDir` on Windows cannot be consumed by ts-loader... yet' --- diff --git a/blog-website/blog/2021-04-20-ts-loader-goes-webpack-5/index.md b/blog-website/blog/2021-04-20-ts-loader-goes-webpack-5/index.md index 963cfb73449..4604c92b13c 100644 --- a/blog-website/blog/2021-04-20-ts-loader-goes-webpack-5/index.md +++ b/blog-website/blog/2021-04-20-ts-loader-goes-webpack-5/index.md @@ -5,7 +5,7 @@ authors: johnnyreilly tags: [webpack, ts-loader, typescript] image: ./ts-loader-9.png hide_table_of_contents: false -description: 'TypeScript Webpack loader `ts-loader` has released version 9.0.0, with support for Webpack 5 and a minimum supported Node version of 12.' +description: 'TypeScript webpack loader `ts-loader` has released version 9.0.0, with support for webpack 5 and a minimum supported Node version of 12.' --- `ts-loader` has just released [v9.0.0](https://github.com/TypeStrong/ts-loader/releases/tag/v9.0.0). This post goes through what this release is all about, and what it took to ship this version. For intrigue, it includes a brief scamper into my mental health along the way. Some upgrades go smoothly - this one had some hiccups. But we'll get into that.