From 8fb6136f984b8b6cc667f5377a584ca82fbb5115 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Mon, 7 Nov 2016 22:42:40 -0600 Subject: [PATCH 1/5] feat(errors): overlay build errors during development --- README.md | 24 +- bin/ion-build-active.png | Bin 0 -> 4230 bytes bin/ion-build-error.png | Bin 0 -> 3747 bytes bin/ion-build-success.png | Bin 0 -> 4256 bytes bin/ion-dev.css | 268 ++++++++++++++++++ bin/ion-dev.js | 110 ++++++-- bin/ionic.png | Bin 20491 -> 0 bytes config/watch.config.js | 46 +-- package.json | 2 +- src/build.ts | 209 +++++++++++--- src/dev-server/http-server.ts | 32 +-- src/dev-server/injector.ts | 9 +- src/dev-server/live-reload.ts | 63 ++--- src/dev-server/notification-server.ts | 129 +++++---- src/index.ts | 3 +- src/lint.ts | 15 +- src/ngc.ts | 4 +- src/rollup.ts | 21 +- src/sass.ts | 83 +++--- src/serve.ts | 4 +- src/spec/ion-compiler.spec.ts | 22 +- src/spec/logger.spec.ts | 2 - src/spec/watch.spec.ts | 333 ++++++++++++++++++++++ src/spec/watcher.spec.ts | 80 ------ src/template.ts | 201 +++++-------- src/transpile-worker.ts | 34 +++ src/transpile.ts | 254 +++++++++-------- src/util/events.ts | 11 +- src/util/file-cache.ts | 10 +- src/util/helpers.ts | 51 +--- src/util/interfaces.ts | 20 +- src/util/logger-diagnostics.ts | 390 +++++++------------------- src/util/logger-sass.ts | 17 +- src/util/logger-tslint.ts | 9 +- src/util/logger-typescript.ts | 14 +- src/util/logger.ts | 77 ++--- src/watch.ts | 158 +++++++++-- src/webpack.ts | 13 +- src/webpack/ionic-webpack-factory.ts | 3 +- src/worker-client.ts | 13 +- src/worker-process.ts | 2 +- 41 files changed, 1613 insertions(+), 1123 deletions(-) create mode 100755 bin/ion-build-active.png create mode 100755 bin/ion-build-error.png create mode 100755 bin/ion-build-success.png delete mode 100644 bin/ionic.png create mode 100644 src/spec/watch.spec.ts delete mode 100644 src/spec/watcher.spec.ts create mode 100644 src/transpile-worker.ts diff --git a/README.md b/README.md index 18fe734f..9e724dd4 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,9 @@ In many cases, the defaults which Ionic provides covers most of the scenarios re [Default Config Files](https://github.com/driftyco/ionic-app-scripts/tree/master/config) -### NPM Config +### package.json Config -Within the `package.json` file, NPM also provides a handy [config](https://docs.npmjs.com/misc/config#per-package-config-settings) property. Below is an example of setting a custom config file using the `config` property in a project's `package.json`. +Within the `package.json` file, there's also a handy [config](https://docs.npmjs.com/misc/config#per-package-config-settings) property which can be used. Below is an example of setting a custom config file using the `config` property in a project's `package.json`. ``` "config": { @@ -88,7 +88,7 @@ npm run build --rollup ./config/rollup.config.js ### Overriding Config Files -| Config File | NPM Config Property | Cmd-line Flag | +| Config File | package.json Config | Cmd-line Flag | |-------------|---------------------|-----------------------| | CleanCss | `ionic_cleancss` | `--cleancss` or `-e` | | Copy | `ionic_copy` | `--copy` or `-y` | @@ -103,7 +103,7 @@ npm run build --rollup ./config/rollup.config.js ### Overriding Config Values -| Config Values | NPM Config Property | Cmd-line Flag | Defaults | +| Config Values | package.json Config | Cmd-line Flag | Defaults | |-----------------|---------------------|---------------|-----------------| | bundler | `ionic_bundler` | `--bundler` | `webpack` | | source map type | `ionic_source_map` | `--sourceMap` | `eval` | @@ -113,9 +113,21 @@ npm run build --rollup ./config/rollup.config.js | build directory | `ionic_build_dir` | `--buildDir` | `build` | -### Ionic Environment Variable +### Ionic Environment Variables -The `process.env.IONIC_ENV` environment variable can be used to test whether it is a `prod` or `dev` build, which automatically gets set by any command. By default the `build` task is `prod`, and the `watch` task is `dev`. Note that `ionic serve` uses the `watch` task. Additionally, using the `--dev` command line flag will force the build to use `dev`. +These environment variables are automatically set to [Node's `process.env`](https://nodejs.org/api/process.html#process_process_env) property. These variables can be useful from within custom configuration files, such as custom `webpack.config.js` file. + +| Environment Variable | Description | +|-------------------------|----------------------------------------------------------------------| +| `IONIC_ENV` | Value can be either `prod` or `dev`. | +| `IONIC_ROOT_DIR` | The absolute path to the project's root directory. | +| `IONIC_TMP_DIR` | The absolute path to the project's temporary directory. | +| `IONIC_SRC_DIR` | The absolute path to the app's source directory. | +| `IONIC_WWW_DIR` | The absolute path to the app's public distribution directory. | +| `IONIC_BUILD_DIR` | The absolute path to the app's bundled js and css files. | +| `IONIC_APP_SCRIPTS_DIR` | The absolute path to the `@ionic/app-scripts` node_module directory. | + +The `process.env.IONIC_ENV` environment variable can be used to test whether it is a `prod` or `dev` build, which automatically gets set by any command. By default the `build` task is `prod`, and the `watch` and `serve` tasks are `dev`. Additionally, using the `--dev` command line flag will force the build to use `dev`. Please take a look at the bottom of the [default Rollup config file](https://github.com/driftyco/ionic-app-scripts/blob/master/config/rollup.config.js) to see how the `IONIC_ENV` environment variable is being used to conditionally change config values for production builds. diff --git a/bin/ion-build-active.png b/bin/ion-build-active.png new file mode 100755 index 0000000000000000000000000000000000000000..4550a405b8caaf951629020c3113d2c68a0f12a1 GIT binary patch literal 4230 zcmYLMc{mi%*S<4@(b%%4?205=21UpuYxX5uWbCOhWGmEE3fUD>WEUw#w8%1+Y$HoZ zvW=oJLK(6&bHDNXe&6#w&pGFw^Pcy-_xy4Hx$#yOMqHSE7ytm5iLt&7^Cay!JBk^- zkvZxBAaPb^b_UE6w&}wjb=YwNe$d;oMl0B8xr6i^h3&YVmYLH~ULtsXxa*Oc}!kl7`Gb8yZY%=>V z0m7YfXYfB)G@8Q}6G$?JoyTGK@tvL9Pwwp8sSTTrVXGnR(1zW|V2k0-&P;K#2JFDW zW;57o1lvzR1|2fC;PwjKT7um=aBTu^{D9kk;NKbeXB4&@!hh>zGL$Wh$bAnxZ@OM94Ai=pt_`L{z z3Wp;>aKIl9`oJM~*kK2Mli_?dT&RR2H(;+X?DmE2*6^bN+?a#kp23+iIFk!0_aP-7 zj)cJxf7p5k_SwRZX0Y81u5`iW4{)&+QfuLl7w|_3{E-j8XTi_$aFVGO0(<=7XK&bQ z0eekgH`5H^Tsiz21_xYWwC88z8e(H%3pl0)CD^BpsjHSovdBlyFKoJBOM(LZ=d8`a$Kj)4Y=?( zls$Ps*!=Z4He2c^^NF^Y=%2KU_&Sm6d)C%OpyL#J;_1b)$;dpM%V*a&ZxfDW9I#2p z>Fp|z*_|q$$}MevRSENnm4zjQenDh+sO-DT_j=5}jMH^_>SH!%Csi*y_r__<&CD73 zCQBqF))4|Mb(i7s{y+=*=9ATq@>TTK_OfXfBWz*hXWbT=gC3OM#Hjm|Wvi>Xw!Ivw z+dCC`H~J|#vZ)bH*Y-_mMPt92^1i#7oTC{w-SQz}kCH~{7f=-xc|6ko&*prbWcqMZ z6z!H=mE*rW)Z+YLp!B)*wN|M3<=qwzL6HAftdt_Ps9)?T>Ii&go=mc@hS4Robm=5i zs&R|k+J()6jt}2REUCtmJZtp~rNXuw9FkYagQwUd4HUS&h;wriSB^-{xCZ5;j3;+Z zU!lA0L7yPbs6Ugc7f=vF5@%jz5vVD=Y{UaO_atZ5gbRT2x$iG;y$tt^LG7j|Z=a)` zPD}-bx*=Lk9x<%De+3ubHr&NUITE6(BfejaIQN$qsv1Us6;lBL1%2Ym9;x1VVJW13 z)9-%>eGETk=#|lmb)qt&&{BLx;^uA4ZibpekeXo`7~tIo66|{@)M$Q&{#y{Zl>IwR zxE(mWcz*2cG3wZb%e4>nZ@cj3MSI$eob8{S5mt3^=hz+X)yL({PVnq1L_~woGGuciCnHU5Eyr^fVIr;QUY4vW~erN z02w8>b(b$cNS=51VgEk`N!%2j0jeE1;y+J3{W~`V1SUuy0`^;2kc<755CZbz3p)|d zG_kmY{({fB>JQzOVs)&OLErB#yIRHJx-_oXr}QExWtB_vp#W|)!WFY|z(G?de|~an za?;P$zfxW$-gD1|tBpza*)-@bkG3iavx=MLn2(-DR?*|H_!6@K*Q+MjiQ@Xso~ zyVJc&Id4=(gr$z2o>kf?_A_jlV$xJ3;2n?p7eRY}TBN9Fm>&?jg&N-X7%3c9Q~4rO zr?&}kj4M7eOS7S@2=HiNWcmO4>lQ4KGQNcA8uR&l|HqzR6D;n!kh3MUjC?3>8!t+n zM7fnsAI)lVnAZif#6+^sLCnRdxX!P(YG*7P2IP;tc{DJu1jw8nTZ6J+qqz=gac+YI z9pc$T1`NbYTF_$bF%G4)_BJhyOTX3p9w5`PrA{wuJK508Lcngb;(btW zu^KPZP$V3ZV7>JjWA0pXqhq)d&-3yro8rE}QZmOGP=ECSs^;z9@#ofcZJAPeXneXl zsf@b$XuRpqK^bB^n$#nV%U-w|CMbWsh*j=8!q}Ztd4H(N)Jd7>fEps2!h4oj;FYz; zQ)Rpquv(n_CmX3y?lRJMKx?9abkKEC0ns~=UbuXH|0CMfEN+feogTh?mAz{t%T-T# z>}02bnSyJ4iKyCzMPGZ$%j_UT(_dofaS|NI$w|753;?fz!l5|?NCMK{L;Tg_JW=1`rED}_CE_D3tNPu}3sRcnIW1#V5&j8Y;KDLJruk^_h?|V)oglPrm$}1^|&QH=^8Pf8^C2b&P6n^EZ%){uX zVdSwZ=L(|sijK^c1M!M!uc>|F_yc1dTLiQ?iS4?OjTRS4_pcRyJ920UDUmUF_n3zm zU$(b3aKsa5@uyFLJ|Ii}#B(utWBAHBDV2DD8 zaymb(bL2{(S!_bxd8&_tavMDi@gcE>hXbF1S^QJkbl{YY+#tfc*G1S;R5l_9DE zM9sUiltIzHU|_({mM_QO8_bI$CC7B%M7(_3g}6&5fOmwdL}8HJ9w4Uz!o6pJjToCG z$BBg6Hn1|58;g2?6u^E5lo&r*6|mTCJ`H5ggPMeK5LB|ldzur3-=6FMa)DNBNv}PC z89~y3GnGBhQ<#`R4N1xp1{()#ccVyq+cq&N1waOOP$0G8T zk$z%|#W^T5y>3=8ugINmU@kiJ<~!&fWmz-DXh@O}IR~%BVugZ$;ZZ+9viU}nJOA0+ zF)B_rhmKaAL57Rl-FqUm+wSM*PziVV-Vb+RELOK+FA5$y<&*%&o42}Y)|YO0%4OquazE_RpoC+yOG)m2x03L{ z&Ex4`v3zbfkLo?G$t(u(yVQ|>;*Fhe7FX?`22m2C3=dsG8mM`H&ScpT+t-}sG!jLN z6X4pwUJR8b)|flY-DwpZ<+Ln_?0>U7gRwBatAz}MN})^y{EgNGt-XiORC_t}-&F&5z2z*TSK%sPeV9OCI-D)x`q zhvSBZlXsr@yjU;nVPhtnoHI4Og#ntUM04#Jqw+<6OCGo4(bvgNJbcw32aeGvj2IO8 z>w{Cn&D4MF`+TV~N+eO6mGbAMf$#OV&#fwme^t!dR>wYrQSWQkFfigVImIgc*2$?d zZ;Vqbv13z@O4syrX|#OUJ+q=ASyRi@N*IW3jb9u1>9}Ktnt4?0!Wjok#22XJ(SV zyu3Pb?YjTOz`*qRXv$jR5gJwW!)(wc@HjsOS4({%ZlULU|Q3VPZdh|OhGT9Ko=|qt9d-|-86U`DWd2$u zo#k}k_s3@l+9cynfhG2EHQYB?l;`@~_SJi`mBGnZD=XwO?0$}V-RVxnT>h-ms zLkMxW z?wdRE4%xO(sN^jU45oV_GUUf4*6Ur8y6Xq^h?d6a(i6R=Y>9R`@4vg8*b6@POqg@w z3&%}$W#}wG5&-Q*w@-G$3akbT`LUd-FoixzY zcyujf^Sso&tuZ!htjs9um9Th|wnm0Uf3TAl6|b!Q_~Qm6;|Esay-rPu)^oG=zpC$X z?!{FnI;fA==6}hfiZ}#>&$$PMdY$*NS*jZT z|K&NXa)W*hU0qRqB=Zsa*z#PC%n9>4Jt>Fci`dIO<&l5ih9v3Wqm#&RT3W2x&HKPV zaxu&G+$@Tc9X}%@>}Hdl)yBwi48y~G+Rb17AK9Drdc%tqB}XBK;KTzLL`%yC`sDmQUMcBobPhhb>M?ECTnXl95SSQP=vV6DZ~hOZ^KJP6 literal 0 HcmV?d00001 diff --git a/bin/ion-build-error.png b/bin/ion-build-error.png new file mode 100755 index 0000000000000000000000000000000000000000..2a24b28fc05b371bd0201d4fc039abce24cf4528 GIT binary patch literal 3747 zcmXw6c{CJ!7yiv)491d_t%wkjb?nK6X!07%*hOS7N;QV?YNCuKDU5v?QM5^xY{`g_ zbtp@+MWV$nytd!x?fbrS?(*E{Jolb+?|(POf?&eUDZ&W=;KrXevf9mze;v)T8$FR( z>Hr`vTKr{wYPW>ehW|GMXk!j-EFe(>64n33)&~EAUBNCUV*kWH{QpRcKg_Ne3$6A3 zH2znk@dy833E}@5iMr6*6j~eqJCLXciCWMa2Z^VlwHDmohF?bEx36&HANYF(E-t`d zKj5c6=x7RC>f!5h*xLo|PQdTeaA^^Cw!)hMF!MfiJ_Xm;;HzgaFB2wG;W;blU=HVI z;aN*KIt07hU|}Bgc7bm8@a-GOsD{r9;L9iQ`XzYX3&w>*2Ll**9u5z}s#1766grx~ z)<)=e9(p*!z8+Ze5GLP+cW%MNoA72Z3^)td)*zh*sbm;Nf>(T?uOoCl10SZrtQ1I# zf$nDTIUUA?K+;9{I2)$NKtDU^Z3P{1u>1v#j#!aBVY_>736d3o2+#tK;>#1u6Y1tZ zF+YcEYfMaJhWkEd-VY*Qy42EARM>W>MhyT=Gv4T=b@0$^_VkSp#zHOGI5d8cO~jZg z#bqV(o|v*W8rp)-stZDLEKCnDK~O0nWuEZ3Z*?t7@T>cnUCp9`zPB)a@yH8ralPF1 zt;x#{HdyxWBfNDsKg{ZPwvwjduNb@7I=J$w@v&V(%dvHl%$&b>TEv%^m+S72X@Tx= z%lTaIvJdX|jW?DyrCP4Q<2duZ^?G9Dl{FXrnfP#P5K^=JV^sOt^c1Egem`g*y^$9x zGg8xjC7+!c%2i3?hO39~ikg9xzk{7xCUd_U3sSO$R`{F4zg1bHTtCTm(v)}PHGhC# zN|PcVAVPBk{33oeSpTl*jRLD(=BU%Y?97;!hdLLykOH5x~!;q%K-9| z`K^f#HpZxxA3e#7C5@u_!6fSw7^E(+s)_@xnC7{RJ>y*o*)!7K#$feb5WLcy1=i}X zr@zp?hAeqej1WuPH~u|v>z0~4*bFM43R*^Ptp@UDW0e?9soA8Ae9Qt_#X+Cf&Lg4H z-&B{yx4x=W$}UKxf(8Eo!yp)X$!ard-+kD?tIwRb-e>QCp5Ld2MLeSDMIRU^{gg`a zM77%T48$Ja177pSUn2pYpKj)q9_dg;@uR&6*Op`EMZiz`48aS1D)sJ~v z@w-n$t=x|*Q}`-HFeMj{2$T5K?JDPd!Q(XIkwO9*VK9Y_Jl4{SPsNPqO=<7JnUYnE zmgbWX$ICkSfKitpCcTLeg&BY2$uB-Wd`#Kns!~$%O%5(9SAY4hu7 zb5E$ZA=dGIs2R__PjQ$bC{xNfW} znuF|A5@KblsHphn`tm#E?6y4qeVmaezT8mg;uufqdFh2oebyF(h5*XbQc4MbR`r?$ zhbO_bVm-0F>uh`DIhKz@ZIAoh;@OJ4`%H3qqIgmNu&{r*h-z7?kXx+2ab%Pr#GxFN zW2=`uOiMXI_0pc_QU2@q(EcIPW4*Y*Nf0Lf>%yyW<(>m?4?FR$lIbr%yZ>)J*D+a2 zf=NUMo+;G-@b&s<-J~aXY9i#^!HG9^!u3Io)Sr6?dSwg0lu3XSw8hV4!prOsxo� z8j=z6Q6IJkMidz#W+Q4=xxqT$>}Ez18gK7u-uTfapSR@aOlWstX%$l>(w3%!d)hr4 zpPk6|YP|}@;PTjr>Xo_N~F(Bv!Na zlk)l{cS(OOW6ffsMOQ_Filnt-U6LEm(K+bKU(R1!QN?Bw0=q-vAL*A&MLeHIcM2qJ zSeD@W4kzUaj$gLyyf3Y`Cp{|%72oOJ7J%Gz5d{lSv?(FF z#(cuf^sAa}{pCV7GnkGcrvD|wW?Y!S_C-ybu_f%`=I?W)W~@Ilf{go{*qeT&tEpb0 zN=k(xEfBoa;fp*lruBrk@=OKLtJd^3>Z}g2`aEo)FGlTW6wmoxJe0>#;xe?OGO6PMmfd{VG}Hmv z*|5NF01K?{eRt}-2N-InwLTQlig;-yBn|8+?!Yj$j7^eqEWi$bIS)}>YWR(+#01U@ z4(LoNuplO2Bp8a-GxWAknmKhg>Ufy|g3%%rd^f5G=yotSZlUZeBNLvV27K=$)Mhw2 zm{&2puJeZwsu}?e^Ls%#D-&bT&sEp%Mich9$LPY zi38${O3pV+e4OLiqGeXnkw|+U%Bmeobljb9#Un$Oa!pr^!e=sMaA_efZV*rvmCq(9 zXt%4RwVja#t#Ne>l#dF<))J&5&QfN1kbtN5Ml)*A6J*oD=%gJt%@SmedyBcVgn=V| z2ie9wOK4Mjb{BkZ`x1e>+xmmqM423ygk2E5Gwivx zAK;xqZ}0(c*C+B#{pyq`)QoLCSN=5n&Bb zb=(~dax+@wYu3j+`(V1c4}~ zFeiPALH;pd30g>58^ncEA@|(&{nGYGvH)Bdx=6UIJbUJ`I#LTQ4! zKW`@FARDu(eEO`j01grMy8#m(AQ=1ffJ9{Nk>K{K2+W)e>q0mB?ax32us1+rN9-Sw z_*rpEH(3#kJnpc`!d6xj+1Qsz{@u)(mDKZhqCgTxF@kqSfan=a;n#Wh5fQ|ugpcKL zVHLyTnsit8hfq-VloVVPphWV>4M^*B)_98ca~c#n#7UCw+?TG2TsU!yKnjZ5G~3QF zeh_+$Q#;$^AeOH#2u?-MBz#{I0inKHCU_K| zL&T44Y`cHgc8IASGhQW`iX*3Ez$i71bh>Q(adG>zy_)J=Ig0dIdZOep?a%xY;TA!QmJXxT;GQS7G$~E z!F%WkuA+5A&{|U6ki=rt#xw~R8ay`>&@yWwT4&f1#J(!%NPVSx`KIyrMMaOt6_uo9 zG3r;c4kxsBR!}c!IG9u868%qFAp!_0s;cDXOw3nS9MqoK@2?j#o0Y|j4{@}9xP&R9 zA+wi4*N-HxM$Ij4YQB%O{duUmsJ5lwOJw7bdkPO_LNjdQp{3c|*b?`zvsTEo7rMg1 zM^w?SCwqn{qX`s&mrvE)!CT~3memR%oEj7&2qBm^;~Q4kI>q1m54r_O8Hta-WfK>@ zo+3jr?}=*~d%$gB!J2BDNXkUYM$Sy^ccv6Oxm_Lb(QNiXqmfquZISCrjs_*r#<#z6 z1|g7qU&TB&{4(t{LD+$-(-K+LdQ_ay5Iv`ndL}`oq^AJMvIQbkj7Uq{@)>VE6SMK0&jVDNNa>WRqWT9o;3ga;v|_laA6SYkZ~kpE#4 zYMD7~e}I({bRvews6+GJ7#e5p6n!U=Z{JiB3l^s-5%(D>`K`8PSrf%{Y9pf>4zUhj z)go0?t&If#| zOm=s0j;tY`JTI2Oh3mbh>E~Y5N=rpM_K&_}FL7z}jk3RK6<^@1?DqMabMJMj;JqIH znRrp}O^pb0VolGal-s1=QvVcHG{?WAFqbPR#wqoKkJybW`PzVQ;S&^t_GjvEQb z)&SniPAfD0BR#BD*V-Qt-IctJdB=C+RN}%%LbqMZ!QAT_Z(%F*@Z1CYC^2JVP61dA zz0Iv$_FxTNJ+Fp=^`wEagq-fbMvpU^w!L`^UG&5%eSRu6pFJNY(Bvt<`~Cp@DS}b4 IfosHn09D!tI{*Lx literal 0 HcmV?d00001 diff --git a/bin/ion-build-success.png b/bin/ion-build-success.png new file mode 100755 index 0000000000000000000000000000000000000000..7469634d5a7f4a3b1be7a2bd7e5a765944a6ea14 GIT binary patch literal 4256 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 -1) { - this.consoleLog(taskEvent.data.msg); - } - if (taskEvent.data && taskEvent.data.type === 'failed') { - Notification.requestPermission().then(function(result) { - var options = { - body: taskEvent.data.msg, - icon: IonicDevServerConfig.notificationIconPath + + buildUpdate: function(msg) { + var status = 'success'; + + if (msg.type === 'started') { + status = 'active'; + + var toastEle = document.getElementById('ion-diagnostics-toast'); + if (!toastEle) { + toastEle = document.createElement('div'); + toastEle.id = 'ion-diagnostics-toast'; + var c = [] + c.push('
'); + c.push('
Building...
'); + c.push('
'); + c.push(''); + c.push('
'); + c.push('
'); + toastEle.innerHTML = c.join(''); + document.body.insertBefore(toastEle, document.body.firstChild); + } + IonicDevServer.toastTimerId = setTimeout(function() { + var toastEle = document.getElementById('ion-diagnostics-toast'); + if (toastEle) { + toastEle.classList.add('ion-diagnostics-toast-active'); } - var notification = new Notification(taskEvent.data.scope, options); - setTimeout(notification.close.bind(n), 5000); - }); + }, 50); + + } else { + status = msg.data.diagnosticsHtml ? 'error' : 'success'; + + clearTimeout(IonicDevServer.toastTimerId); + + var toastEle = document.getElementById('ion-diagnostics-toast'); + if (toastEle) { + toastEle.classList.remove('ion-diagnostics-toast-active'); + } + + var diagnosticsEle = document.getElementById('ion-diagnostics'); + if (diagnosticsEle && !msg.data.diagnosticsHtml) { + diagnosticsEle.classList.add('ion-diagnostics-fade-out'); + IonicDevServer.diagnosticsTimerId = setTimeout(function() { + var diagnosticsEle = document.getElementById('ion-diagnostics'); + if (diagnosticsEle) { + diagnosticsEle.parentElement.removeChild(diagnosticsEle); + } + }, 100); + + } else if (msg.data.diagnosticsHtml) { + clearTimeout(IonicDevServer.diagnosticsTimerId); + + if (!diagnosticsEle) { + diagnosticsEle = document.createElement('div'); + diagnosticsEle.id = 'ion-diagnostics'; + diagnosticsEle.className = 'ion-diagnostics-fade-out'; + document.body.insertBefore(diagnosticsEle, document.body.firstChild); + IonicDevServer.diagnosticsTimerId = setTimeout(function() { + var diagnosticsEle = document.getElementById('ion-diagnostics'); + if (diagnosticsEle) { + diagnosticsEle.classList.remove('ion-diagnostics-fade-out'); + } + }, 24); + } + diagnosticsEle.innerHTML = msg.data.diagnosticsHtml; + } } - } -}; + IonicDevServer.buildStatus(status); + }, + + buildStatus: function (status) { + var iconLinks = document.querySelectorAll('link[rel="icon"]'); + for (var i = 0; i < iconLinks.length; i++) { + iconLinks[i].parentElement.removeChild(iconLinks[i]); + } + + var iconLink = document.createElement('link'); + iconLink.rel = 'icon'; + iconLink.type = 'image/png'; + iconLink.href = '__ion-dev-server/ion-build-' + status + '.png?v=' + IonicDevServerConfig.appScriptsVersion; + document.head.appendChild(iconLink); + }, + + domReady: function() { + document.removeEventListener("DOMContentLoaded", IonicDevServer.domReady); + var diagnosticsEle = document.getElementById('ion-diagnostics'); + if (diagnosticsEle) { + IonicDevServer.buildStatus('error'); + } else { + IonicDevServer.buildStatus('success'); + } + } +}; IonicDevServer.start(); diff --git a/bin/ionic.png b/bin/ionic.png deleted file mode 100644 index d82dab01c94f335d54b12aaba7a57c75ae77d59a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20491 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 B6o>!- diff --git a/config/watch.config.js b/config/watch.config.js index bde65851..8ef6ec8f 100644 --- a/config/watch.config.js +++ b/config/watch.config.js @@ -1,9 +1,6 @@ -var fullBuildUpdate = require('../dist/build').fullBuildUpdate; -var buildUpdate = require('../dist/build').buildUpdate; -var templateUpdate = require('../dist/template').templateUpdate; -var copyUpdate = require('../dist/copy').copyUpdate; -var sassUpdate = require('../dist/sass').sassUpdate; -var copyConfig = require('./copy.config').include; +var watch = require('../dist/watch'); +var copy = require('../dist/copy'); +var copyConfig = require('./copy.config'); // https://www.npmjs.com/package/chokidar @@ -14,44 +11,15 @@ module.exports = { { paths: [ - '{{SRC}}/**/*.ts' + '{{SRC}}/**/*.(ts|html|scss)' ], options: { ignored: '{{SRC}}/**/*.spec.ts' }, - eventName: 'all', - callback: buildUpdate + callback: watch.buildUpdate }, { - paths: [ - '{{SRC}}/**/*.html' - ], - eventName: 'all', - callback: templateUpdate - }, - - { - paths: [ - '{{SRC}}/**/*.scss' - ], - eventName: 'all', - callback: sassUpdate - }, - - { - paths: copyConfig.map(f => f.src), - eventName: 'all', - callback: copyUpdate - }, - - { - paths: [ - '{{SRC}}/**/*', - ], - options: { - ignored: `{{SRC}}/assets/**/*` - }, - eventName: 'unlinkDir', - callback: fullBuildUpdate + paths: copyConfig.include.map(f => f.src), + callback: copy.copyUpdate } ] diff --git a/package.json b/package.json index 2817f4e2..d215bd65 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "dependencies": { "autoprefixer": "6.4.1", "chalk": "1.1.3", - "chokidar": "1.6.0", + "chokidar": "1.6.1", "clean-css": "3.4.19", "conventional-changelog-cli": "^1.2.0", "cross-spawn": "4.0.0", diff --git a/src/build.ts b/src/build.ts index cfcb1c17..16690dd7 100644 --- a/src/build.ts +++ b/src/build.ts @@ -1,15 +1,16 @@ -import { BuildContext } from './util/interfaces'; -import { BuildError, IgnorableError, Logger } from './util/logger'; +import { BuildContext, BuildState } from './util/interfaces'; +import { BuildError, Logger } from './util/logger'; import { bundle, bundleUpdate } from './bundle'; import { clean } from './clean'; import { copy } from './copy'; import { emit, EventType } from './util/events'; import { generateContext } from './util/config'; -import { lint } from './lint'; +import { lint, lintUpdate } from './lint'; import { minifyCss, minifyJs } from './minify'; import { ngc } from './ngc'; import { sass, sassUpdate } from './sass'; -import { transpile, transpileUpdate } from './transpile'; +import { templateUpdate } from './template'; +import { transpile, transpileUpdate, transpileDiagnosticsOnly } from './transpile'; export function build(context: BuildContext) { @@ -20,8 +21,6 @@ export function build(context: BuildContext) { return buildWorker(context) .then(() => { // congrats, we did it! (•_•) / ( •_•)>⌐■-■ / (⌐■_■) - context.fullBuildCompleted = true; - emit(EventType.BuildFinished); logger.finish(); }) .catch(err => { @@ -48,7 +47,6 @@ function buildProd(context: BuildContext) { // async tasks // these can happen all while other tasks are running const copyPromise = copy(context); - const lintPromise = lint(context); // kick off ngc to run the Ahead of Time compiler return ngc(context) @@ -72,10 +70,13 @@ function buildProd(context: BuildContext) { ]); }) .then(() => { + // kick off the tslint after everything else + // nothing needs to wait on its completion + lint(context); + // ensure the async tasks have fully completed before resolving return Promise.all([ - copyPromise, - lintPromise + copyPromise ]); }) .catch(err => { @@ -91,7 +92,6 @@ function buildDev(context: BuildContext) { // async tasks // these can happen all while other tasks are running const copyPromise = copy(context); - const lintPromise = lint(context); // just bundle, and if that passes then do the rest at the same time return transpile(context) @@ -101,55 +101,176 @@ function buildDev(context: BuildContext) { .then(() => { return Promise.all([ sass(context), - copyPromise, - lintPromise + copyPromise ]); }) + .then(() => { + // kick off the tslint after everything else + // nothing needs to wait on its completion + lint(context); + return Promise.resolve(); + }) .catch(err => { throw new BuildError(err); }); } -export function fullBuildUpdate(event: string, filePath: string, context: BuildContext) { - return buildUpdateWorker(event, filePath, context, true).then(() => { - context.fullBuildCompleted = true; - }); -} export function buildUpdate(event: string, filePath: string, context: BuildContext) { - return buildUpdateWorker(event, filePath, context, false); + return new Promise(resolve => { + const logger = new Logger('build update'); + + buildUpdateId++; + emit(EventType.BuildUpdateStarted, buildUpdateId); + + function buildTasksDone(resolveValue: BuildTaskResolveValue) { + // all build tasks have been resolved or one of them + // bailed early, stopping all others to not run + + parallelTasksPromise.then(() => { + // all parallel tasks are also done + // so now we're done done + emit(EventType.BuildUpdateCompleted, buildUpdateId); + + if (resolveValue.requiresRefresh) { + // emit that we need to do a full page refresh + emit(EventType.ReloadApp); + + } else { + // just emit that only a certain file changed + // this one is useful when only a sass changed happened + // and the webpack only needs to livereload the css + // but does not need to do a full page refresh + emit(EventType.FileChange, resolveValue.changedFile); + } + + if (filePath.endsWith('.ts')) { + // a ts file changed, so let's lint it too, however + // this task should run as an after thought + lintUpdate(event, filePath, context); + } + + Logger.newLine(); + logger.ready('green', true); + Logger.newLine(); + + // we did it! + resolve(); + }); + } + + // kick off all the build tasks + // and the tasks that can run parallel to all the build tasks + const buildTasksPromise = buildUpdateTasks(event, filePath, context); + const parallelTasksPromise = buildUpdateParallelTasks(event, filePath, context); + + // whether it was resolved or rejected, we need to do the same thing + buildTasksPromise + .then(buildTasksDone) + .catch(() => { + buildTasksDone({ + requiresRefresh: false, + changedFile: filePath + }); + }); + }); } -function buildUpdateWorker(event: string, filePath: string, context: BuildContext, fullBuild: boolean) { - const logger = new Logger(`build update`); +/** + * Collection of all the build tasks than need to run + * Each task will only run if it's set with eacn BuildState. + */ +function buildUpdateTasks(event: string, filePath: string, context: BuildContext) { + const resolveValue: BuildTaskResolveValue = { + requiresRefresh: false, + changedFile: filePath + }; - let transpilePromise: Promise = null; - if (fullBuild) { - transpilePromise = transpile(context); - } else { - transpilePromise = transpileUpdate(event, filePath, context); - } - return transpilePromise + return Promise.resolve() .then(() => { - if (fullBuild) { - return bundle(context); + // TEMPLATE + if (context.templateState === BuildState.RequiresUpdate) { + resolveValue.requiresRefresh = true; + return templateUpdate(event, filePath, context); } - return bundleUpdate(event, filePath, context); - }).then(() => { - if (fullBuild) { - return sass(context); - } else if (event !== 'change' || !context.successfulSass) { - // if just the TS file changed, then there's no need to do a sass update - // however, if a new TS file was added or was deleted, then we should do a sass update - return sassUpdate(event, filePath, context); + // no template updates required + return Promise.resolve(); + + }) + .then(() => { + // TRANSPILE + if (context.transpileState === BuildState.RequiresUpdate) { + resolveValue.requiresRefresh = true; + // we've already had a successful transpile once, only do an update + // not that we've also already started a transpile diagnostics only + // build that only needs to be completed by the end of buildUpdate + return transpileUpdate(event, filePath, context); + + } else if (context.transpileState === BuildState.RequiresBuild) { + // run the whole transpile + resolveValue.requiresRefresh = true; + return transpile(context); } - }).then(() => { - emit(EventType.BuildFinished); - logger.finish(); - }).catch(err => { - if (err instanceof IgnorableError) { - throw err; + // no transpiling required + return Promise.resolve(); + + }) + .then(() => { + // BUNDLE + if (context.bundleState === BuildState.RequiresUpdate) { + // we need to do a bundle update + resolveValue.requiresRefresh = true; + return bundleUpdate(event, filePath, context); + + } else if (context.bundleState === BuildState.RequiresBuild) { + // we need to do a full bundle build + resolveValue.requiresRefresh = true; + return bundle(context); } - throw logger.fail(err); + // no bundling required + return Promise.resolve(); + + }) + .then(() => { + // SASS + if (context.sassState === BuildState.RequiresUpdate) { + // we need to do a sass update + return sassUpdate(event, filePath, context).then(outputCssFile => { + resolveValue.changedFile = outputCssFile; + }); + + } else if (context.sassState === BuildState.RequiresBuild) { + // we need to do a full sass build + return sass(context).then(outputCssFile => { + resolveValue.changedFile = outputCssFile; + }); + } + // no sass build required + return Promise.resolve(); + }) + .then(() => { + return resolveValue; }); } + +interface BuildTaskResolveValue { + requiresRefresh: boolean; + changedFile: string; +} + +/** + * parallelTasks are for any tasks that can run parallel to the entire + * build, but we still need to make sure they've completed before we're + * all done, it's also possible there are no parallelTasks at all + */ +function buildUpdateParallelTasks(event: string, filePath: string, context: BuildContext) { + const parallelTasks: Promise[] = []; + + if (context.transpileState === BuildState.RequiresUpdate) { + parallelTasks.push(transpileDiagnosticsOnly(context)); + } + + return Promise.all(parallelTasks); +} + +let buildUpdateId = 0; diff --git a/src/dev-server/http-server.ts b/src/dev-server/http-server.ts index 23c741be..438e56e3 100644 --- a/src/dev-server/http-server.ts +++ b/src/dev-server/http-server.ts @@ -6,12 +6,10 @@ import * as fs from 'fs'; import * as url from 'url'; import { ServeConfig, LOGGER_DIR } from './serve-config'; import { Logger } from '../util/logger'; -import { promisify } from '../util/promisify'; import * as proxyMiddleware from 'proxy-middleware'; -import { readDiagnosticsHtmlSync } from '../util/logger-diagnostics'; +import { injectDiagnosticsHtml } from '../util/logger-diagnostics'; import { getProjectJson, IonicProject } from '../util/ionic-project'; -const readFilePromise = promisify(fs.readFile); /** * Create HTTP server @@ -58,33 +56,29 @@ function setupProxies(app: express.Application) { */ function serveIndex(req: express.Request, res: express.Response) { const config: ServeConfig = req.app.get('serveConfig'); - let htmlFile = path.join(config.wwwDir, 'index.html'); - let diagnosticsHtml = readDiagnosticsHtmlSync(config.buildDir); - function httpResponse(content: any) { + // respond with the index.html file + const indexFileName = path.join(config.wwwDir, 'index.html'); + fs.readFile(indexFileName, (err, indexHtml) => { if (config.useLiveReload) { - content = injectLiveReloadScript(content, config.host, config.liveReloadPort); + indexHtml = injectLiveReloadScript(indexHtml, config.host, config.liveReloadPort); } + if (config.useNotifier) { - content = injectNotificationScript(content, config.notifyOnConsoleLog, config.notificationPort); + indexHtml = injectNotificationScript(indexHtml, config.notifyOnConsoleLog, config.notificationPort); } - // File found so lets send it back to the response - res.set('Content-Type', 'text/html'); - res.send(content); - } + indexHtml = injectDiagnosticsHtml(config.buildDir, indexHtml); - if (diagnosticsHtml) { - httpResponse(diagnosticsHtml); - } else { - readFilePromise(htmlFile).then(httpResponse); - } + res.set('Content-Type', 'text/html'); + res.send(indexHtml); + }); } /** - * http responder for cordova.js fiel + * http responder for cordova.js file */ function serveCordovaJS(req: express.Request, res: express.Response) { res.set('Content-Type', 'application/javascript'); res.send('// mock cordova file during development'); -} \ No newline at end of file +} diff --git a/src/dev-server/injector.ts b/src/dev-server/injector.ts index 423052cf..3e6a46f6 100644 --- a/src/dev-server/injector.ts +++ b/src/dev-server/injector.ts @@ -1,5 +1,7 @@ +import { getAppScriptsVersion } from '../util/logger'; import { LOGGER_DIR } from './serve-config'; + const LOGGER_HEADER = ''; export function injectNotificationScript(content: any, notifyOnConsoleLog: boolean, notificationPort: Number): any { @@ -25,16 +27,17 @@ export function injectNotificationScript(content: any, notifyOnConsoleLog: boole } function getConsoleLoggerScript(notifyOnConsoleLog: boolean, notificationPort: Number) { + const appScriptsVersion = getAppScriptsVersion(); const ionDevServer = JSON.stringify({ sendConsoleLogs: notifyOnConsoleLog, wsPort: notificationPort, - notificationIconPath: `${LOGGER_DIR}/ionic.png` + appScriptsVersion: appScriptsVersion }); return ` ${LOGGER_HEADER} - - + + `; } diff --git a/src/dev-server/live-reload.ts b/src/dev-server/live-reload.ts index 1dcb21fb..5b8b73e1 100644 --- a/src/dev-server/live-reload.ts +++ b/src/dev-server/live-reload.ts @@ -1,3 +1,4 @@ +import { hasDiagnostics } from '../util/logger-diagnostics'; import * as path from 'path'; import * as tinylr from 'tiny-lr'; import { ServeConfig } from './serve-config'; @@ -8,59 +9,27 @@ export function createLiveReloadServer(config: ServeConfig) { const liveReloadServer = tinylr(); liveReloadServer.listen(config.liveReloadPort, config.host); - function broadcastChange(filePath: string | string[]) { - const files = Array.isArray(filePath) ? filePath : [filePath]; - const msg = { - body: { - files: files.map(f => '/' + path.relative(config.wwwDir, f)) - } - }; - liveReloadServer.changed(msg); - } - - let hasFinishedSass = false; - let hasFinishedBundle = false; - let hasFinishedBuild = false; - let hasDoneHardRefresh = false; - - events.on(events.EventType.SassFinished, (sassFile: string) => { - hasFinishedSass = true; - if (hasFinishedBuild) { - // only livereload css if a bundle has finished - // and a build has finished - // css live reload does not refresh the index page - broadcastChange(sassFile); - } - }); - - events.on(events.EventType.BundleFinished, (jsFile: string) => { - hasFinishedBundle = true; - if (hasFinishedSass && hasFinishedBuild) { - // only livereload js if sass has finished - // and a build has finished - // js live reload refreshes the index page - hasDoneHardRefresh = true; - broadcastChange(jsFile); + function fileChange(filePath: string | string[]) { + // only do a live reload if there are no diagnostics + // the notification server takes care of showing diagnostics + if (!hasDiagnostics(config.buildDir)) { + const files = Array.isArray(filePath) ? filePath : [filePath]; + liveReloadServer.changed({ + body: { + files: files.map(f => '/' + path.relative(config.wwwDir, f)) + } + }); } - }); + } - events.on(events.EventType.BuildFinished, () => { - hasFinishedBuild = true; - if (!hasDoneHardRefresh) { - hasDoneHardRefresh = true; - broadcastChange('index.html'); - } - }); + events.on(events.EventType.FileChange, fileChange); - events.on(events.EventType.UpdatedDiagnostics, () => { - // new diagnostics files have been saved - // refresh the index file so they render - broadcastChange('index.html'); + events.on(events.EventType.ReloadApp, () => { + fileChange('index.html'); }); - - events.on(events.EventType.FileChange, broadcastChange); } + export function injectLiveReloadScript(content: any, host: string, port: Number): any { let contentStr = content.toString(); const liveReloadScript = getLiveReloadScript(host, port); diff --git a/src/dev-server/notification-server.ts b/src/dev-server/notification-server.ts index cdddbf6b..a8a1542f 100644 --- a/src/dev-server/notification-server.ts +++ b/src/dev-server/notification-server.ts @@ -1,24 +1,55 @@ // Ionic Dev Server: Server Side Logger -import { Diagnostic, Logger, TaskEvent } from '../util/logger'; +import { Logger } from '../util/logger'; +import { hasDiagnostics, getDiagnosticsHtmlContent } from '../util/logger-diagnostics'; import { on, EventType } from '../util/events'; import { Server as WebSocketServer } from 'ws'; import { ServeConfig } from './serve-config'; -let wsServer: any; -const msgToClient: WsMessage[] = []; - -export interface WsMessage { - category: string; - type: string; - data: any; -} export function createNotificationServer(config: ServeConfig) { - on(EventType.TaskEvent, (taskEvent: TaskEvent) => { + let wsServer: any; + + // queue up all messages to the client + function queueMessageSend(msg: WsMessage) { + msgToClient.push(msg); + drainMessageQueue(); + } + + // drain the queue messages when the server is ready + function drainMessageQueue() { + if (wsServer) { + let msg: any; + while (msg = msgToClient.shift()) { + try { + wsServer.send(JSON.stringify(msg)); + } catch (e) { + Logger.error(`error sending client ws, ${e}`); + } + } + } + } + + // a build update has started, notify the client + on(EventType.BuildUpdateStarted, (buildUpdateId) => { + const msg: WsMessage = { + category: 'buildUpdate', + type: 'started', + data: { + buildUpdateId: buildUpdateId + } + }; + queueMessageSend(msg); + }); + + // a build update has completed, notify the client + on(EventType.BuildUpdateCompleted, (buildUpdateId) => { const msg: WsMessage = { - category: EventType.TaskEvent, - type: taskEvent.scope, - data: taskEvent + category: 'buildUpdate', + type: 'completed', + data: { + buildUpdateId: buildUpdateId, + diagnosticsHtml: hasDiagnostics(config.buildDir) ? getDiagnosticsHtmlContent(config.buildDir) : null + } }; queueMessageSend(msg); }); @@ -27,52 +58,36 @@ export function createNotificationServer(config: ServeConfig) { const wss = new WebSocketServer({ port: config.notificationPort }); wss.on('connection', (ws: any) => { + // we've successfully connected wsServer = ws; wsServer.on('message', (incomingMessage: string) => { // incoming message from the client try { - printClientMessage(JSON.parse(incomingMessage)); + printMessageFromClient(JSON.parse(incomingMessage)); } catch (e) { Logger.error(`error opening ws message: ${incomingMessage}`); } }); + // now that we're connected, send off any messages + // we might has already queued up drainMessageQueue(); }); } -function queueMessageSend(msg: WsMessage) { - msgToClient.push(msg); - drainMessageQueue(); -} - - -function drainMessageQueue() { - if (wsServer) { - var msg: any; - while (msg = msgToClient.shift()) { - try { - wsServer.send(JSON.stringify(msg)); - } catch (e) { - Logger.error(`error sending client ws, ${e}`); - } - } - } -} - -function printClientMessage(msg: WsMessage) { +function printMessageFromClient(msg: WsMessage) { if (msg.data) { switch (msg.category) { - case 'console': - printConsole(msg); - break; + case 'console': + printConsole(msg); + break; - case 'exception': - printException(msg); - break; + case 'exception': + printException(msg); + break; } } } @@ -82,24 +97,32 @@ function printConsole(msg: WsMessage) { args[0] = `console.${msg.type}: ${args[0]}`; switch (msg.type) { - case 'error': - Logger.error.apply(this, args); - break; + case 'error': + Logger.error.apply(this, args); + break; - case 'warn': - Logger.warn.apply(this, args); - break; + case 'warn': + Logger.warn.apply(this, args); + break; - case 'debug': - Logger.debug.apply(this, args); - break; + case 'debug': + Logger.debug.apply(this, args); + break; - default: - Logger.info.apply(this, args); - break; - } + default: + Logger.info.apply(this, args); + break; + } } function printException(msg: WsMessage) { } + +const msgToClient: WsMessage[] = []; + +export interface WsMessage { + category: string; + type: string; + data: any; +} diff --git a/src/index.ts b/src/index.ts index b6502086..bb858535 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { build, buildUpdate, fullBuildUpdate } from './build'; +export { build, buildUpdate } from './build'; export { bundle, bundleUpdate } from './bundle'; export { clean } from './clean'; export { cleancss } from './cleancss'; @@ -8,7 +8,6 @@ export { minify } from './minify'; export { ngc } from './ngc'; export { sass, sassUpdate } from './sass'; export { transpile } from './transpile'; -export { templateUpdate } from './template'; export { uglifyjs } from './uglifyjs'; export { watch } from './watch'; export * from './util/config'; diff --git a/src/lint.ts b/src/lint.ts index d6e28032..f353e5e5 100644 --- a/src/lint.ts +++ b/src/lint.ts @@ -4,7 +4,8 @@ import { BuildError, Logger } from './util/logger'; import { generateContext, getUserConfigFile } from './util/config'; import { join } from 'path'; import { createProgram, findConfiguration, getFileNames } from 'tslint'; -import { runDiagnostics } from './util/logger-tslint'; +import { runTsLintDiagnostics } from './util/logger-tslint'; +import { printDiagnostics, DiagnosticsType } from './util/logger-diagnostics'; import { runWorker } from './worker-client'; import * as Linter from 'tslint'; import * as fs from 'fs'; @@ -22,17 +23,10 @@ export function lint(context?: BuildContext, configFile?: string) { export function lintWorker(context: BuildContext, configFile: string) { - const logger = new Logger('lint'); return getLintConfig(context, configFile).then(configFile => { // there's a valid tslint config, let's continue return lintApp(context, configFile); - }).then(() => { - // always finish and resolve - logger.finish(); - }).catch(() => { - // always finish and resolve - logger.finish(); - }); + }).catch(() => {}); } @@ -101,7 +95,8 @@ function lintFile(context: BuildContext, program: ts.Program, filePath: string) const lintResult = linter.lint(); if (lintResult && lintResult.failures) { - runDiagnostics(context, lintResult.failures); + const diagnostics = runTsLintDiagnostics(context, lintResult.failures); + printDiagnostics(context, DiagnosticsType.TsLint, diagnostics, true, false); } } catch (e) { diff --git a/src/ngc.ts b/src/ngc.ts index 985ccccc..4406b7b1 100644 --- a/src/ngc.ts +++ b/src/ngc.ts @@ -1,7 +1,7 @@ import { basename, join } from 'path'; import { BuildContext, TaskInfo } from './util/interfaces'; import { copy as fsCopy, emptyDirSync, outputJsonSync, readFileSync, statSync } from 'fs-extra'; -import { endsWith, objectAssign } from './util/helpers'; +import { objectAssign } from './util/helpers'; import { fillConfigDefaults, generateContext, getUserConfigFile, getNodeBinExecutable } from './util/config'; import { getTsConfigPath } from './transpile'; import { BuildError, Logger } from './util/logger'; @@ -171,7 +171,7 @@ function filterCopyFiles(filePath: any, hoop: any) { shouldInclude = (EXCLUDE_DIRS.indexOf(basename(filePath)) < 0); } else { - shouldInclude = (endsWith(filePath, '.ts') || endsWith(filePath, '.html')); + shouldInclude = (filePath.endsWith('.ts') || filePath.endsWith('.html')); } } catch (e) {} diff --git a/src/rollup.ts b/src/rollup.ts index 7de0305c..fa7d7e49 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -1,7 +1,5 @@ -import { BuildContext, TaskInfo } from './util/interfaces'; +import { BuildContext, BuildState, TaskInfo } from './util/interfaces'; import { BuildError, Logger } from './util/logger'; -import { emit, EventType } from './util/events'; -import { endsWith, setModulePathsCache } from './util/helpers'; import { fillConfigDefaults, generateContext, getUserConfigFile, replacePathVars } from './util/config'; import { ionCompiler } from './plugins/ion-compiler'; import { join, isAbsolute, normalize } from 'path'; @@ -16,9 +14,11 @@ export function rollup(context: BuildContext, configFile: string) { return rollupWorker(context, configFile) .then(() => { + context.bundleState = BuildState.SuccessfulBuild; logger.finish(); }) .catch(err => { + context.bundleState = BuildState.RequiresBuild; throw logger.fail(err); }); } @@ -31,9 +31,11 @@ export function rollupUpdate(event: string, filePath: string, context: BuildCont return rollupWorker(context, configFile) .then(() => { + context.bundleState = BuildState.SuccessfulBuild; logger.finish(); }) .catch(err => { + context.bundleState = BuildState.RequiresBuild; throw logger.fail(err); }); } @@ -60,10 +62,8 @@ export function rollupWorker(context: BuildContext, configFile: string): Promise ); } - if (context.useBundleCache) { - // tell rollup to use a previous bundle as its starting point - rollupConfig.cache = cachedBundle; - } + // tell rollup to use a previous bundle as its starting point + rollupConfig.cache = cachedBundle; if (!rollupConfig.onwarn) { // use our own logger if one wasn't already provided @@ -84,10 +84,6 @@ export function rollupWorker(context: BuildContext, configFile: string): Promise // this reference can be used elsewhere in the build (sass) context.moduleFiles = bundle.modules.map((m) => m.id); - // async cache all the module paths so we don't need - // to always bundle to know which modules are used - setModulePathsCache(context.moduleFiles); - // cache our bundle for later use if (context.isWatch) { cachedBundle = bundle; @@ -98,7 +94,6 @@ export function rollupWorker(context: BuildContext, configFile: string): Promise }) .then(() => { // clean up any references (overkill yes, but let's play it safe) - emit(EventType.BundleFinished, rollupConfig.dest); rollupConfig = rollupConfig.cache = rollupConfig.onwarn = rollupConfig.plugins = null; resolve(); @@ -130,7 +125,7 @@ export function getOutputDest(context: BuildContext, rollupConfig: RollupConfig) function checkDeprecations(context: BuildContext, rollupConfig: RollupConfig) { if (!context.isProd) { - if (rollupConfig.entry.indexOf('.tmp') > -1 || endsWith(rollupConfig.entry, '.js')) { + if (rollupConfig.entry.indexOf('.tmp') > -1 || rollupConfig.entry.endsWith('.js')) { // warning added 2016-10-05, v0.0.29 throw new BuildError('\nDev builds no longer use the ".tmp" directory. Please update your rollup config\'s\n' + 'entry to use your "src" directory\'s "main.dev.ts" TypeScript file.\n' + diff --git a/src/sass.ts b/src/sass.ts index a2ccf5c2..ca82c0a4 100755 --- a/src/sass.ts +++ b/src/sass.ts @@ -1,11 +1,11 @@ import { basename, dirname, join, sep } from 'path'; -import { BuildContext, TaskInfo } from './util/interfaces'; +import { BuildContext, BuildState, TaskInfo } from './util/interfaces'; import { BuildError, Logger } from './util/logger'; -import { emit, EventType } from './util/events'; +import { bundle } from './bundle'; import { ensureDirSync, readdirSync, writeFile } from 'fs-extra'; import { fillConfigDefaults, generateContext, getUserConfigFile, replacePathVars } from './util/config'; -import { getModulePathsCache } from './util/helpers'; -import { runDiagnostics, clearSassDiagnostics } from './util/logger-sass'; +import { runSassDiagnostics } from './util/logger-sass'; +import { printDiagnostics, clearDiagnostics, DiagnosticsType } from './util/logger-diagnostics'; import { SassError, render as nodeSassRender, Result } from 'node-sass'; import * as postcss from 'postcss'; import * as autoprefixer from 'autoprefixer'; @@ -17,14 +17,14 @@ export function sass(context?: BuildContext, configFile?: string) { const logger = new Logger('sass'); - context.successfulSass = false; - return sassWorker(context, configFile) - .then(() => { - context.successfulSass = true; + .then(outFile => { + context.sassState = BuildState.SuccessfulBuild; logger.finish(); + return outFile; }) .catch(err => { + context.sassState = BuildState.RequiresBuild; throw logger.fail(err); }); } @@ -36,29 +36,28 @@ export function sassUpdate(event: string, filePath: string, context: BuildContex const logger = new Logger('sass update'); return sassWorker(context, configFile) - .then(() => { + .then(outFile => { + context.sassState = BuildState.SuccessfulBuild; logger.finish(); + return outFile; }) .catch(err => { + context.sassState = BuildState.RequiresBuild; throw logger.fail(err); }); } export function sassWorker(context: BuildContext, configFile: string) { - return new Promise((resolve, reject) => { - if (!context.moduleFiles) { - // we haven't already gotten the moduleFiles in this process - // see if we have it cached - context.moduleFiles = getModulePathsCache(); - if (!context.moduleFiles) { - reject(new BuildError('Cannot generate Sass files without first bundling JavaScript ' + - 'files in order to know all used modules. Please build JS files first.')); - return; - } - } + const bundlePromise: Promise[] = []; + if (!context.moduleFiles) { + // sass must always have a list of all the used module files + // so ensure we bundle if moduleFiles are currently unknown + bundlePromise.push(bundle(context)); + } - clearSassDiagnostics(context); + return Promise.all(bundlePromise).then(() => { + clearDiagnostics(context, DiagnosticsType.Sass); const sassConfig: SassConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile); @@ -84,15 +83,7 @@ export function sassWorker(context: BuildContext, configFile: string) { generateSassData(context, sassConfig); } - return render(context, sassConfig) - .then(() => { - resolve(true); - }, (reason: any) => { - reject(reason); - }) - .catch(err => { - reject(new BuildError(err)); - }); + return render(context, sassConfig); }); } @@ -219,8 +210,11 @@ function getComponentDirectories(moduleDirectories: string[], sassConfig: SassCo // filter out module directories we know wouldn't have sibling component sass file // just a way to reduce the amount of lookups to be done later return moduleDirectories.filter(moduleDirectory => { + // normalize this directory is using / between directories + moduleDirectory = moduleDirectory.replace(/\\/g, '/'); + for (var i = 0; i < sassConfig.excludeModules.length; i++) { - if (moduleDirectory.indexOf(sassConfig.excludeModules[i]) > -1) { + if (moduleDirectory.indexOf('/node_modules/' + sassConfig.excludeModules[i] + '/') > -1) { return false; } } @@ -229,14 +223,14 @@ function getComponentDirectories(moduleDirectories: string[], sassConfig: SassCo } -function render(context: BuildContext, sassConfig: SassConfig) { +function render(context: BuildContext, sassConfig: SassConfig): Promise { return new Promise((resolve, reject) => { - if (context.useSassCache && lastRenderKey !== null) { + if (lastRenderKey !== null) { // if the sass data imports are same, don't bother const renderKey = getRenderCacheKey(sassConfig); if (renderKey === lastRenderKey) { - resolve(); + resolve(sassConfig.outFile); return; } } @@ -249,20 +243,18 @@ function render(context: BuildContext, sassConfig: SassConfig) { } nodeSassRender(sassConfig, (sassError: SassError, sassResult: Result) => { - const diagnostics = runDiagnostics(context, sassError); + const diagnostics = runSassDiagnostics(context, sassError); if (diagnostics.length) { + printDiagnostics(context, DiagnosticsType.Sass, diagnostics, true, true); // sass render error :( - const buildError = new BuildError(); - buildError.updatedDiagnostics = true; - emit(EventType.UpdatedDiagnostics); - reject(buildError); + reject(new BuildError()); } else { // sass render success :) - renderSassSuccess(context, sassResult, sassConfig).then(() => { + renderSassSuccess(context, sassResult, sassConfig).then(outFile => { lastRenderKey = getRenderCacheKey(sassConfig); - resolve(); + resolve(outFile); }).catch(err => { reject(new BuildError(err)); @@ -273,7 +265,7 @@ function render(context: BuildContext, sassConfig: SassConfig) { } -function renderSassSuccess(context: BuildContext, sassResult: Result, sassConfig: SassConfig): Promise { +function renderSassSuccess(context: BuildContext, sassResult: Result, sassConfig: SassConfig): Promise { if (sassConfig.autoprefixer) { // with autoprefixer @@ -361,7 +353,7 @@ function generateSourceMaps(sassResult: Result, sassConfig: SassConfig) { } -function writeOutput(context: BuildContext, sassConfig: SassConfig, cssOutput: string, mappingsOutput: string) { +function writeOutput(context: BuildContext, sassConfig: SassConfig, cssOutput: string, mappingsOutput: string): Promise { return new Promise((resolve, reject) => { Logger.debug(`sass start write output: ${sassConfig.outFile}`); @@ -393,12 +385,9 @@ function writeOutput(context: BuildContext, sassConfig: SassConfig, cssOutput: s }); } - // notify a file has changed - emit(EventType.SassFinished, sassConfig.outFile); - // css file all saved // note that we're not waiting on the css map to finish saving - resolve(); + resolve(sassConfig.outFile); } }); }); diff --git a/src/serve.ts b/src/serve.ts index 07ae135c..bfc6c269 100644 --- a/src/serve.ts +++ b/src/serve.ts @@ -35,9 +35,9 @@ export function serve(context?: BuildContext) { notifyOnConsoleLog: sendClientConsoleLogs(context) }; - createHttpServer(config); - createLiveReloadServer(config); createNotificationServer(config); + createLiveReloadServer(config); + createHttpServer(config); return watch(context) .then(() => { diff --git a/src/spec/ion-compiler.spec.ts b/src/spec/ion-compiler.spec.ts index 69bc12ba..2517d7a0 100644 --- a/src/spec/ion-compiler.spec.ts +++ b/src/spec/ion-compiler.spec.ts @@ -47,7 +47,7 @@ describe('ion-compiler', () => { // arrange let context: BuildContext = {}; context.fileCache = new FileCache(); - context.fileCache.put(importer, { + context.fileCache.set(importer, { path: importer, content: null }); @@ -63,7 +63,7 @@ describe('ion-compiler', () => { // arrange let context: BuildContext = {}; context.fileCache = new FileCache(); - context.fileCache.put(importer, { + context.fileCache.set(importer, { path: importer, content: '' }); @@ -79,7 +79,7 @@ describe('ion-compiler', () => { // arrange let context: BuildContext = {}; context.fileCache = new FileCache(); - context.fileCache.put(importer, { + context.fileCache.set(importer, { path: importer, content: 'fake irrelevant data' }); @@ -88,7 +88,7 @@ describe('ion-compiler', () => { const importerBasename = dirname(importer); const importeeFullPath = resolve(join(importerBasename, importee)) + '.ts'; - context.fileCache.put(importeeFullPath, { + context.fileCache.set(importeeFullPath, { path: importeeFullPath, content: 'someContent' }); @@ -105,7 +105,7 @@ describe('ion-compiler', () => { // arrange let context: BuildContext = {}; context.fileCache = new FileCache(); - context.fileCache.put(importer, { + context.fileCache.set(importer, { path: importer, content: 'fake irrelevant data' }); @@ -114,7 +114,7 @@ describe('ion-compiler', () => { const importerBasename = dirname(importer); const importeeFullPath = resolve(join(importerBasename, importee)) + '.ts'; - context.fileCache.put(importeeFullPath, { path: importeeFullPath, content: null}); + context.fileCache.set(importeeFullPath, { path: importeeFullPath, content: null}); // act const result = resolveId(importee, importer, context); @@ -128,7 +128,7 @@ describe('ion-compiler', () => { // arrange let context: BuildContext = {}; context.fileCache = new FileCache(); - context.fileCache.put(importer, { + context.fileCache.set(importer, { path: importer, content: 'fake irrelevant data' }); @@ -137,7 +137,7 @@ describe('ion-compiler', () => { const importerBasename = dirname(importer); const importeeFullPath = join(resolve(join(importerBasename, importee)), 'index.ts'); - context.fileCache.put(importeeFullPath, { path: importeeFullPath, content: null }); + context.fileCache.set(importeeFullPath, { path: importeeFullPath, content: null }); // act const result = resolveId(importee, importer, context); @@ -151,7 +151,7 @@ describe('ion-compiler', () => { // arrange let context: BuildContext = {}; context.fileCache = new FileCache(); - context.fileCache.put(importer, { + context.fileCache.set(importer, { path: importer, content: 'fake irrelevant data' }); @@ -160,7 +160,7 @@ describe('ion-compiler', () => { const importerBasename = dirname(importer); const importeeFullPath = join(resolve(join(importerBasename, importee)), 'index.ts'); - context.fileCache.put(importeeFullPath, { path: importeeFullPath, content: null}); + context.fileCache.set(importeeFullPath, { path: importeeFullPath, content: null}); // act const result = resolveId(importee, importer, context); @@ -173,7 +173,7 @@ describe('ion-compiler', () => { // arrange let context: BuildContext = {}; context.fileCache = new FileCache(); - context.fileCache.put(importer, { + context.fileCache.set(importer, { path: importer, content: 'fake irrelevant data' }); diff --git a/src/spec/logger.spec.ts b/src/spec/logger.spec.ts index b4c0c704..e65473c4 100644 --- a/src/spec/logger.spec.ts +++ b/src/spec/logger.spec.ts @@ -16,7 +16,6 @@ describe('Logger', () => { const json = buildErrorCopy.toJson(); expect(json.hasBeenLogged).toEqual(buildError.hasBeenLogged); - expect(json.updatedDiagnostics).toEqual(buildError.updatedDiagnostics); expect(json.message).toEqual(buildError.message); expect(json.name).toEqual(buildError.name); expect(json.stack).toEqual(buildError.stack); @@ -30,7 +29,6 @@ describe('Logger', () => { buildError.stack = 'stack'; const json = buildError.toJson(); expect(json.hasBeenLogged).toEqual(buildError.hasBeenLogged); - expect(json.updatedDiagnostics).toEqual(buildError.updatedDiagnostics); expect(json.message).toEqual(buildError.message); expect(json.name).toEqual(buildError.name); expect(json.stack).toEqual(buildError.stack); diff --git a/src/spec/watch.spec.ts b/src/spec/watch.spec.ts new file mode 100644 index 00000000..f86817e7 --- /dev/null +++ b/src/spec/watch.spec.ts @@ -0,0 +1,333 @@ +import { BuildContext, BuildState } from '../util/interfaces'; +import { FileCache } from '../util/file-cache'; +import { runBuildUpdate, ChangedFile } from '../watch'; +import { Watcher, prepareWatcher } from '../watch'; +import * as path from 'path'; + + +describe('watch', () => { + + describe('runBuildUpdate', () => { + + it('should get the html file if thats the only one', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.html', + ext: '.html' + }]; + const data = runBuildUpdate(context, files); + expect(data.filePath).toEqual('file1.html'); + }); + + it('should get the scss file for the filePath over html', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.html', + ext: '.html' + }, { + event: 'change', + filePath: 'file1.scss', + ext: '.scss' + }]; + const data = runBuildUpdate(context, files); + expect(data.filePath).toEqual('file1.scss'); + }); + + it('should get the ts file for the filePath over the others', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.html', + ext: '.html' + }, { + event: 'change', + filePath: 'file1.scss', + ext: '.scss' + }, { + event: 'change', + filePath: 'file1.ts', + ext: '.ts' + }]; + const data = runBuildUpdate(context, files); + expect(data.filePath).toEqual('file1.ts'); + }); + + it('should require transpile full build for html file add', () => { + const files: ChangedFile[] = [{ + event: 'add', + filePath: 'file1.html', + ext: '.html' + }]; + runBuildUpdate(context, files); + expect(context.transpileState).toEqual(BuildState.RequiresBuild); + }); + + it('should require transpile full build for html file change and not already successful bundle', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.html', + ext: '.html' + }]; + runBuildUpdate(context, files); + expect(context.transpileState).toEqual(BuildState.RequiresBuild); + }); + + it('should require template update for html file change and already successful bundle', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.html', + ext: '.html' + }]; + context.bundleState = BuildState.SuccessfulBuild; + runBuildUpdate(context, files); + expect(context.templateState).toEqual(BuildState.RequiresUpdate); + }); + + it('should require sass update for ts file unlink', () => { + const files: ChangedFile[] = [{ + event: 'unlink', + filePath: 'file1.ts', + ext: '.ts' + }]; + runBuildUpdate(context, files); + expect(context.sassState).toEqual(BuildState.RequiresUpdate); + }); + + it('should require sass update for ts file add', () => { + const files: ChangedFile[] = [{ + event: 'add', + filePath: 'file1.ts', + ext: '.ts' + }]; + runBuildUpdate(context, files); + expect(context.sassState).toEqual(BuildState.RequiresUpdate); + }); + + it('should require sass update for scss file add', () => { + const files: ChangedFile[] = [{ + event: 'add', + filePath: 'file1.scss', + ext: '.scss' + }]; + runBuildUpdate(context, files); + expect(context.sassState).toEqual(BuildState.RequiresUpdate); + }); + + it('should require sass update for scss file change', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.scss', + ext: '.scss' + }]; + runBuildUpdate(context, files); + expect(context.sassState).toEqual(BuildState.RequiresUpdate); + }); + + it('should require transpile full build for single ts add, but only bundle update when already successful bundle', () => { + const files: ChangedFile[] = [{ + event: 'add', + filePath: 'file1.ts', + ext: '.ts' + }]; + context.bundleState = BuildState.SuccessfulBuild; + runBuildUpdate(context, files); + expect(context.transpileState).toEqual(BuildState.RequiresBuild); + expect(context.bundleState).toEqual(BuildState.RequiresUpdate); + }); + + it('should require transpile full build for single ts add', () => { + const files: ChangedFile[] = [{ + event: 'add', + filePath: 'file1.ts', + ext: '.ts' + }]; + runBuildUpdate(context, files); + expect(context.transpileState).toEqual(BuildState.RequiresBuild); + expect(context.bundleState).toEqual(BuildState.RequiresBuild); + }); + + it('should require transpile full build for single ts change and not in file cache', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.ts', + ext: '.ts' + }]; + runBuildUpdate(context, files); + expect(context.transpileState).toEqual(BuildState.RequiresBuild); + expect(context.bundleState).toEqual(BuildState.RequiresBuild); + }); + + it('should require transpile update only and full bundle build for single ts change and already in file cache and hasnt already had successful bundle', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.ts', + ext: '.ts' + }]; + context.bundleState = BuildState.SuccessfulBuild; + const resolvedFilePath = path.resolve('file1.ts'); + context.fileCache.set(resolvedFilePath, { path: 'file1.ts', content: 'content' }); + runBuildUpdate(context, files); + expect(context.transpileState).toEqual(BuildState.RequiresUpdate); + expect(context.bundleState).toEqual(BuildState.RequiresUpdate); + }); + + it('should require transpile update only and bundle update for single ts change and already in file cache and bundle already successful', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.ts', + ext: '.ts' + }]; + const resolvedFilePath = path.resolve('file1.ts'); + context.fileCache.set(resolvedFilePath, { path: 'file1.ts', content: 'content' }); + runBuildUpdate(context, files); + expect(context.transpileState).toEqual(BuildState.RequiresUpdate); + expect(context.bundleState).toEqual(BuildState.RequiresBuild); + }); + + it('should require transpile full build for multiple ts changes', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.ts', + ext: '.ts' + }, { + event: 'change', + filePath: 'file2.ts', + ext: '.ts' + }]; + runBuildUpdate(context, files); + expect(context.transpileState).toEqual(BuildState.RequiresBuild); + expect(context.bundleState).toEqual(BuildState.RequiresBuild); + }); + + it('should not update bundle state if no transpile changes', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.scss', + ext: '.scss' + }]; + runBuildUpdate(context, files); + expect(context.bundleState).toEqual(undefined); + }); + + it('should set add event when add and changed files', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file1.ts', + ext: '.ts' + }, { + event: 'add', + filePath: 'file2.ts', + ext: '.ts' + }]; + const data = runBuildUpdate(context, files); + expect(data.event).toEqual('add'); + }); + + it('should set unlink event when only unlinked files', () => { + const files: ChangedFile[] = [{ + event: 'unlink', + filePath: 'file.ts', + ext: '.ts' + }]; + const data = runBuildUpdate(context, files); + expect(data.event).toEqual('unlink'); + }); + + it('should set change event when only changed files', () => { + const files: ChangedFile[] = [{ + event: 'change', + filePath: 'file.ts', + ext: '.ts' + }]; + const data = runBuildUpdate(context, files); + expect(data.event).toEqual('change'); + }); + + it('should do nothing if there are no changed files', () => { + expect(runBuildUpdate(context, [])).toEqual(null); + expect(runBuildUpdate(context, null)).toEqual(null); + }); + + + let context: BuildContext; + beforeEach(() => { + context = { + fileCache: new FileCache() + }; + }); + + }); + + describe('prepareWatcher', () => { + + it('should do nothing when options.ignored is a function', () => { + const ignoreFn = function(){}; + const watcher: Watcher = { options: { ignored: ignoreFn } }; + const context: BuildContext = { srcDir: '/some/src/' }; + prepareWatcher(context, watcher); + expect(watcher.options.ignored).toBe(ignoreFn); + }); + + it('should set replacePathVars when options.ignored is a string', () => { + const watcher: Watcher = { options: { ignored: '{{SRC}}/**/*.spec.ts' } }; + const context: BuildContext = { srcDir: '/some/src/' }; + prepareWatcher(context, watcher); + expect(watcher.options.ignored).toEqual('/some/src/**/*.spec.ts'); + }); + + it('should set replacePathVars when paths is an array', () => { + const watcher: Watcher = { paths: [ + '{{SRC}}/some/path1', + '{{SRC}}/some/path2' + ] }; + const context: BuildContext = { srcDir: '/some/src/' }; + prepareWatcher(context, watcher); + expect(watcher.paths.length).toEqual(2); + expect(watcher.paths[0]).toEqual('/some/src/some/path1'); + expect(watcher.paths[1]).toEqual('/some/src/some/path2'); + }); + + it('should set replacePathVars when paths is a string', () => { + const watcher: Watcher = { paths: '{{SRC}}/some/path' }; + const context: BuildContext = { srcDir: '/some/src/' }; + prepareWatcher(context, watcher); + expect(watcher.paths).toEqual('/some/src/some/path'); + }); + + it('should not set options.ignoreInitial if it was provided', () => { + const watcher: Watcher = { options: { ignoreInitial: false } }; + const context: BuildContext = {}; + prepareWatcher(context, watcher); + expect(watcher.options.ignoreInitial).toEqual(false); + }); + + it('should set options.ignoreInitial to true if it wasnt provided', () => { + const watcher: Watcher = { options: {} }; + const context: BuildContext = {}; + prepareWatcher(context, watcher); + expect(watcher.options.ignoreInitial).toEqual(true); + }); + + it('should not set options.cwd from context.rootDir if it was provided', () => { + const watcher: Watcher = { options: { cwd: '/my/cwd/' } }; + const context: BuildContext = { rootDir: '/my/root/dir/' }; + prepareWatcher(context, watcher); + expect(watcher.options.cwd).toEqual('/my/cwd/'); + }); + + it('should set options.cwd from context.rootDir if it wasnt provided', () => { + const watcher: Watcher = {}; + const context: BuildContext = { rootDir: '/my/root/dir/' }; + prepareWatcher(context, watcher); + expect(watcher.options.cwd).toEqual(context.rootDir); + }); + + it('should create watcher options when not provided', () => { + const watcher: Watcher = {}; + const context: BuildContext = {}; + prepareWatcher(context, watcher); + expect(watcher.options).toBeDefined(); + }); + + }); + +}); diff --git a/src/spec/watcher.spec.ts b/src/spec/watcher.spec.ts deleted file mode 100644 index e8c30533..00000000 --- a/src/spec/watcher.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { BuildContext } from '../util/interfaces'; -import { Watcher, prepareWatcher } from '../watch'; - - -describe('watch', () => { - - describe('prepareWatcher', () => { - - it('should do nothing when options.ignored is a function', () => { - const ignoreFn = function(){}; - const watcher: Watcher = { options: { ignored: ignoreFn } }; - const context: BuildContext = { srcDir: '/some/src/' }; - prepareWatcher(context, watcher); - expect(watcher.options.ignored).toBe(ignoreFn); - }); - - it('should set replacePathVars when options.ignored is a string', () => { - const watcher: Watcher = { options: { ignored: '{{SRC}}/**/*.spec.ts' } }; - const context: BuildContext = { srcDir: '/some/src/' }; - prepareWatcher(context, watcher); - expect(watcher.options.ignored).toEqual('/some/src/**/*.spec.ts'); - }); - - it('should set replacePathVars when paths is an array', () => { - const watcher: Watcher = { paths: [ - '{{SRC}}/some/path1', - '{{SRC}}/some/path2' - ] }; - const context: BuildContext = { srcDir: '/some/src/' }; - prepareWatcher(context, watcher); - expect(watcher.paths.length).toEqual(2); - expect(watcher.paths[0]).toEqual('/some/src/some/path1'); - expect(watcher.paths[1]).toEqual('/some/src/some/path2'); - }); - - it('should set replacePathVars when paths is a string', () => { - const watcher: Watcher = { paths: '{{SRC}}/some/path' }; - const context: BuildContext = { srcDir: '/some/src/' }; - prepareWatcher(context, watcher); - expect(watcher.paths).toEqual('/some/src/some/path'); - }); - - it('should not set options.ignoreInitial if it was provided', () => { - const watcher: Watcher = { options: { ignoreInitial: false } }; - const context: BuildContext = {}; - prepareWatcher(context, watcher); - expect(watcher.options.ignoreInitial).toEqual(false); - }); - - it('should set options.ignoreInitial to true if it wasnt provided', () => { - const watcher: Watcher = { options: {} }; - const context: BuildContext = {}; - prepareWatcher(context, watcher); - expect(watcher.options.ignoreInitial).toEqual(true); - }); - - it('should not set options.cwd from context.rootDir if it was provided', () => { - const watcher: Watcher = { options: { cwd: '/my/cwd/' } }; - const context: BuildContext = { rootDir: '/my/root/dir/' }; - prepareWatcher(context, watcher); - expect(watcher.options.cwd).toEqual('/my/cwd/'); - }); - - it('should set options.cwd from context.rootDir if it wasnt provided', () => { - const watcher: Watcher = {}; - const context: BuildContext = { rootDir: '/my/root/dir/' }; - prepareWatcher(context, watcher); - expect(watcher.options.cwd).toEqual(context.rootDir); - }); - - it('should create watcher options when not provided', () => { - const watcher: Watcher = {}; - const context: BuildContext = {}; - prepareWatcher(context, watcher); - expect(watcher.options).toBeDefined(); - }); - - }); - -}); diff --git a/src/template.ts b/src/template.ts index ef28ed8d..cd462c43 100644 --- a/src/template.ts +++ b/src/template.ts @@ -1,62 +1,56 @@ -import { emit, EventType } from './util/events'; -import { BUNDLER_WEBPACK } from './util/config'; -import { BuildContext } from './util/interfaces'; -import { BuildError, IgnorableError, Logger } from './util/logger'; -import { bundleUpdate, getJsOutputDest } from './bundle'; -import { dirname, extname, join, parse, resolve } from 'path'; -import { readdirSync, readFileSync, writeFileSync } from 'fs'; -import { sassUpdate } from './sass'; -import { buildUpdate } from './build'; - - -export function templateUpdate(event: string, filePath: string, context: BuildContext) { - const logger = new Logger('template update'); - - return templateUpdateWorker(event, filePath, context) - .then(() => { - logger.finish(); - }) - .catch(err => { - if (err instanceof IgnorableError) { - throw err; - } - throw logger.fail(err); - }); -} - - -function templateUpdateWorker(event: string, filePath: string, context: BuildContext) { - Logger.debug(`templateUpdate, event: ${event}, path: ${filePath}`); - - if (event === 'change') { - if (context.bundler === BUNDLER_WEBPACK) { - Logger.debug(`templateUpdate: updating webpack file`); - const typescriptFile = getTemplatesTypescriptFile(context, filePath); - if (typescriptFile && typescriptFile.length > 0) { - return buildUpdate(event, typescriptFile, context); - } - } else if (updateBundledJsTemplate(context, filePath)) { - Logger.debug(`templateUpdate, updated js bundle, path: ${filePath}`); - // technically, the bundle has changed so emit an event saying so - emit(EventType.BundleFinished, getJsOutputDest(context)); - return Promise.resolve(); +import { BuildContext, BuildState } from './util/interfaces'; +import { Logger } from './util/logger'; +import { getJsOutputDest } from './bundle'; +import { join, parse, resolve } from 'path'; +import { readFileSync, writeFile } from 'fs'; + + +export function templateUpdate(event: string, htmlFilePath: string, context: BuildContext) { + return new Promise((resolve) => { + const start = Date.now(); + const bundleOutputDest = getJsOutputDest(context); + + function failed() { + context.transpileState = BuildState.RequiresBuild; + context.bundleState = BuildState.RequiresUpdate; + resolve(); } - } - Logger.debug('templateUpdateWorker: Can\'t update template, doing a full rebuild'); - // not sure how it changed, just do a full rebuild without the bundle cache - context.useBundleCache = false; - return bundleUpdate(event, filePath, context) - .then(() => { - context.useSassCache = true; - return sassUpdate(event, filePath, context); - }) - .catch(err => { - if (err instanceof IgnorableError) { - throw err; + try { + let bundleSourceText = readFileSync(bundleOutputDest, 'utf8'); + let newTemplateContent = readFileSync(htmlFilePath, 'utf8'); + + bundleSourceText = replaceBundleJsTemplate(bundleSourceText, newTemplateContent, htmlFilePath); + + if (bundleSourceText) { + // awesome, all good and template updated in the bundle file + const logger = new Logger(`template update`); + logger.setStartTime(start); + + writeFile(bundleOutputDest, bundleSourceText, { encoding: 'utf8'}, (err) => { + if (err) { + // eww, error saving + logger.fail(err); + failed(); + + } else { + // congrats, all gud + Logger.debug(`updateBundledJsTemplate, updated: ${htmlFilePath}`); + context.templateState = BuildState.SuccessfulBuild; + logger.finish(); + resolve(); + } + }); + + } else { + failed(); } - throw new BuildError(err); - }); + + } catch (e) { + Logger.debug(`updateBundledJsTemplate error: ${e}`); + failed(); + } + }); } @@ -111,94 +105,38 @@ export function replaceTemplateUrl(match: TemplateUrlMatch, htmlFilePath: string } -function updateBundledJsTemplate(context: BuildContext, htmlFilePath: string) { - Logger.debug(`updateBundledJsTemplate, start: ${htmlFilePath}`); - - const outputDest = getJsOutputDest(context); - - try { - let bundleSourceText = readFileSync(outputDest, 'utf8'); - let newTemplateContent = readFileSync(htmlFilePath, 'utf8'); - - bundleSourceText = replaceBundleJsTemplate(bundleSourceText, newTemplateContent, htmlFilePath); +export function replaceBundleJsTemplate(bundleSourceText: string, newTemplateContent: string, htmlFilePath: string): string { + let prefix = getTemplatePrefix(htmlFilePath); + let startIndex = bundleSourceText.indexOf(prefix); - if (bundleSourceText) { - writeFileSync(outputDest, bundleSourceText, { encoding: 'utf8'}); - Logger.debug(`updateBundledJsTemplate, updated: ${htmlFilePath}`); - return true; - } + let isStringified = false; - } catch (e) { - Logger.debug(`updateBundledJsTemplate error: ${e}`); + if (startIndex === -1) { + prefix = stringify(prefix); + isStringified = true; } - return false; -} - -function getTemplatesTypescriptFile(context: BuildContext, templatePath: string) { - try { - const srcDirName = dirname(templatePath); - const files = readdirSync(srcDirName); - const typescriptFilesNames = files.filter(file => { - return extname(file) === '.ts'; - }); - for (const fileName of typescriptFilesNames) { - const fullPath = join(srcDirName, fileName); - const fileContent = readFileSync(fullPath).toString(); - const isMatch = tryToMatchTemplateUrl(fileContent, fullPath, srcDirName, templatePath); - if (isMatch) { - return fullPath; - } - } - return null; - } catch (ex) { - Logger.debug('getTemplatesTypescriptFile: Error occurred - ', ex.message); + startIndex = bundleSourceText.indexOf(prefix); + if (startIndex === -1) { return null; } -} -function tryToMatchTemplateUrl(fileContent: string, filePath: string, componentDirPath: string, templateFilePath: string) { - let lastMatch: string = null; - let match: TemplateUrlMatch; - - while (match = getTemplateMatch(fileContent)) { - if (match.component === lastMatch) { - // panic! we don't want to melt any machines if there's a bug - Logger.debug(`Error matching component: ${match.component}`); - return false; - } - lastMatch = match.component; - - if (!match.templateUrl || match.templateUrl === '') { - Logger.error(`Error @Component templateUrl missing in: "${filePath}"`); - return false; - } - - const templatUrlPath = resolve(join(componentDirPath, match.templateUrl)); - if (templatUrlPath === templateFilePath) { - return true; - } - } - return false; -} - -export function replaceBundleJsTemplate(bundleSourceText: string, newTemplateContent: string, htmlFilePath: string): string { - const prefix = getTemplatePrefix(htmlFilePath); - const startIndex = bundleSourceText.indexOf(prefix); - - if (startIndex === -1) { - return null; + let suffix = getTemplateSuffix(htmlFilePath); + if (isStringified) { + suffix = stringify(suffix); } - const suffix = getTemplateSuffix(htmlFilePath); const endIndex = bundleSourceText.indexOf(suffix, startIndex + 1); - if (endIndex === -1) { return null; } const oldTemplate = bundleSourceText.substring(startIndex, endIndex + suffix.length); - const newTemplate = getTemplateFormat(htmlFilePath, newTemplateContent); + let newTemplate = getTemplateFormat(htmlFilePath, newTemplateContent); + + if (isStringified) { + newTemplate = stringify(newTemplate); + } let lastChange: string = null; while (bundleSourceText.indexOf(oldTemplate) > -1 && bundleSourceText !== lastChange) { @@ -208,6 +146,11 @@ export function replaceBundleJsTemplate(bundleSourceText: string, newTemplateCon return bundleSourceText; } +function stringify(str: string) { + str = JSON.stringify(str); + return str.substr(1, str.length - 2); +} + export function getTemplateFormat(htmlFilePath: string, content: string) { // turn the template into one line and espcape single quotes diff --git a/src/transpile-worker.ts b/src/transpile-worker.ts new file mode 100644 index 00000000..1d999bbd --- /dev/null +++ b/src/transpile-worker.ts @@ -0,0 +1,34 @@ +import { BuildContext } from './util/interfaces'; +import { transpileWorker, TranspileWorkerMessage, TranspileWorkerConfig } from './transpile'; + + +const context: BuildContext = {}; + +process.on('message', (incomingMsg: TranspileWorkerMessage) => { + context.rootDir = incomingMsg.rootDir; + context.buildDir = incomingMsg.buildDir; + context.isProd = incomingMsg.isProd; + + const workerConfig: TranspileWorkerConfig = { + configFile: incomingMsg.configFile, + writeInMemory: false, + sourceMaps: false, + cache: false, + inlineTemplate: false + }; + + transpileWorker(context, workerConfig) + .then(() => { + const outgoingMsg: TranspileWorkerMessage = { + transpileSuccess: true + }; + process.send(outgoingMsg); + }) + .catch(() => { + const outgoingMsg: TranspileWorkerMessage = { + transpileSuccess: false + }; + process.send(outgoingMsg); + }); + +}); diff --git a/src/transpile.ts b/src/transpile.ts index 477a9207..806f2ed2 100644 --- a/src/transpile.ts +++ b/src/transpile.ts @@ -1,15 +1,16 @@ import { FileCache } from './util/file-cache'; -import { BuildContext, File } from './util/interfaces'; +import { BuildContext, BuildState } from './util/interfaces'; import { BuildError, Logger } from './util/logger'; import { buildJsSourceMaps } from './bundle'; -import { changeExtension, endsWith } from './util/helpers'; +import { changeExtension } from './util/helpers'; +import { EventEmitter } from 'events'; import { generateContext } from './util/config'; import { inlineTemplate } from './template'; -import { join, normalize, resolve } from 'path'; -import { lintUpdate } from './lint'; import { readFileSync } from 'fs'; -import { runDiagnostics, clearTypeScriptDiagnostics } from './util/logger-typescript'; -import { runWorker } from './worker-client'; +import { runTypeScriptDiagnostics } from './util/logger-typescript'; +import { printDiagnostics, clearDiagnostics, DiagnosticsType } from './util/logger-diagnostics'; +import { fork, ChildProcess } from 'child_process'; +import * as path from 'path'; import * as ts from 'typescript'; @@ -28,20 +29,17 @@ export function transpile(context?: BuildContext) { return transpileWorker(context, workerConfig) .then(() => { + context.transpileState = BuildState.SuccessfulBuild; logger.finish(); }) .catch(err => { + context.transpileState = BuildState.RequiresBuild; throw logger.fail(err); }); } export function transpileUpdate(event: string, filePath: string, context: BuildContext) { - if (!filePath.endsWith('.ts') ) { - // however this ran, the changed file wasn't a .ts file so carry on - return Promise.resolve(); - } - const workerConfig: TranspileWorkerConfig = { configFile: getTsConfigPath(context), writeInMemory: true, @@ -54,9 +52,11 @@ export function transpileUpdate(event: string, filePath: string, context: BuildC return transpileUpdateWorker(event, filePath, context, workerConfig) .then(tsFiles => { + context.transpileState = BuildState.SuccessfulBuild; logger.finish(); }) .catch(err => { + context.transpileState = BuildState.RequiresBuild; throw logger.fail(err); }); } @@ -70,7 +70,7 @@ export function transpileWorker(context: BuildContext, workerConfig: TranspileWo // let's do this return new Promise((resolve, reject) => { - clearTypeScriptDiagnostics(context); + clearDiagnostics(context, DiagnosticsType.TypeScript); // get the tsconfig data const tsConfig = getTsConfig(context, workerConfig.configFile); @@ -101,131 +101,152 @@ export function transpileWorker(context: BuildContext, workerConfig: TranspileWo } }); + // cache the typescript program for later use + cachedProgram = program; + const tsDiagnostics = program.getSyntacticDiagnostics() .concat(program.getSemanticDiagnostics()) .concat(program.getOptionsDiagnostics()); - const diagnostics = runDiagnostics(context, tsDiagnostics); + const diagnostics = runTypeScriptDiagnostics(context, tsDiagnostics); if (diagnostics.length) { - // transpile failed :( - cachedProgram = null; + // darn, we've got some things wrong, transpile failed :( + printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, true); - const buildError = new BuildError(); - buildError.updatedDiagnostics = true; - reject(buildError); + reject(new BuildError()); } else { // transpile success :) - // cache the typescript program for later use - cachedProgram = program; - resolve(); } }); } +export function canRunTranspileUpdate(event: string, filePath: string, context: BuildContext) { + if (event === 'change' && context.fileCache) { + return context.fileCache.has(path.resolve(filePath)); + } + return false; +} + + /** * Iterative build for one TS file. If it's not an existing file change, or * something errors out then it falls back to do the full build. */ function transpileUpdateWorker(event: string, filePath: string, context: BuildContext, workerConfig: TranspileWorkerConfig) { - clearTypeScriptDiagnostics(context); + return new Promise((resolve, reject) => { + clearDiagnostics(context, DiagnosticsType.TypeScript); - filePath = resolve(filePath); + filePath = path.resolve(filePath); - // let's run tslint on this one file too, but run it in another - // processor core and don't let it's results hang anything up - lintUpdate(event, filePath, context); + // an existing ts file we already know about has changed + // let's "TRY" to do a single module build for this one file + const tsConfig = getTsConfig(context, workerConfig.configFile); - let file: File = null; - if (context.fileCache) { - file = context.fileCache.get(filePath); - } - if (event === 'change' && file) { - try { - // an existing ts file we already know about has changed - // let's "TRY" to do a single module build for this one file - const tsConfig = getTsConfig(context, workerConfig.configFile); + // build the ts source maps if the bundler is going to use source maps + tsConfig.options.sourceMap = buildJsSourceMaps(context); - // build the ts source maps if the bundler is going to use source maps - tsConfig.options.sourceMap = buildJsSourceMaps(context); + const transpileOptions: ts.TranspileOptions = { + compilerOptions: tsConfig.options, + fileName: filePath, + reportDiagnostics: true + }; + + // let's manually transpile just this one ts file + // load up the source text for this one module + const sourceText = readFileSync(filePath, 'utf8'); + + // transpile this one module + const transpileOutput = ts.transpileModule(sourceText, transpileOptions); + + const diagnostics = runTypeScriptDiagnostics(context, transpileOutput.diagnostics); + + if (diagnostics.length) { + printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, false, true); - const transpileOptions: ts.TranspileOptions = { - compilerOptions: tsConfig.options, - fileName: filePath, - reportDiagnostics: true - }; - - // let's manually transpile just this one ts file - // load up the source text for this one module - const sourceText = readFileSync(filePath, 'utf8'); - - // transpile this one module - const transpileOutput = ts.transpileModule(sourceText, transpileOptions); - - const diagnostics = runDiagnostics(context, transpileOutput.diagnostics); - - if (diagnostics.length) { - // darn, we've got some errors with this transpiling :( - // but at least we reported the errors like really really fast, so there's that - Logger.debug(`transpileUpdateWorker: transpileModule, diagnostics: ${diagnostics.length}`); - - const buildError = new BuildError(); - buildError.updatedDiagnostics = true; - return Promise.reject(buildError); - - } else if (!transpileOutput.outputText) { - // derp, not sure how there's no output text, just do a full build - Logger.debug(`transpileUpdateWorker: transpileModule, missing output text`); - - } else { - // convert the path to have a .js file extension for consistency - const newPath = changeExtension(filePath, '.js'); - - const sourceMapFile = { path: newPath + '.map', content: transpileOutput.sourceMapText }; - let jsContent: string = transpileOutput.outputText; - if (workerConfig.inlineTemplate) { - // use original path for template inlining - jsContent = inlineTemplate(transpileOutput.outputText, filePath); - } - const jsFile = { path: newPath, content: jsContent }; - const tsFile = { path: filePath, content: sourceText}; - - context.fileCache.put(sourceMapFile.path, sourceMapFile); - context.fileCache.put(jsFile.path, jsFile); - context.fileCache.put(tsFile.path, tsFile); - - // cool, the lil transpiling went through, but - // let's still do the big transpiling (on another processor core) - // and if there's anything wrong it'll print out messages - // however, it doesn't hang anything up - // also make sure it does a little as possible - const fullBuildWorkerConfig: TranspileWorkerConfig = { - configFile: workerConfig.configFile, - writeInMemory: false, - sourceMaps: false, - cache: false, - inlineTemplate: false - }; - runWorker('transpile', 'transpileWorker', context, fullBuildWorkerConfig); - - return Promise.resolve(); + // darn, we've got some errors with this transpiling :( + // but at least we reported the errors like really really fast, so there's that + Logger.debug(`transpileUpdateWorker: transpileModule, diagnostics: ${diagnostics.length}`); + + reject(new BuildError()); + + } else { + // convert the path to have a .js file extension for consistency + const newPath = changeExtension(filePath, '.js'); + + const sourceMapFile = { path: newPath + '.map', content: transpileOutput.sourceMapText }; + let jsContent: string = transpileOutput.outputText; + if (workerConfig.inlineTemplate) { + // use original path for template inlining + jsContent = inlineTemplate(transpileOutput.outputText, filePath); } + const jsFile = { path: newPath, content: jsContent }; + const tsFile = { path: filePath, content: sourceText }; - } catch (e) { - // umm, oops. Yeah let's just do a full build then - Logger.debug(`transpileModule error: ${e}`); - throw new BuildError(e); + context.fileCache.set(sourceMapFile.path, sourceMapFile); + context.fileCache.set(jsFile.path, jsFile); + context.fileCache.set(tsFile.path, tsFile); + + resolve(); } + }); +} + + +export function transpileDiagnosticsOnly(context: BuildContext) { + return new Promise(resolve => { + workerEvent.once('DiagnosticsWorkerDone', () => { + resolve(); + }); + + runDiagnosticsWorker(context); + }); +} + +const workerEvent = new EventEmitter(); +let diagnosticsWorker: ChildProcess = null; + +function runDiagnosticsWorker(context: BuildContext) { + if (!diagnosticsWorker) { + const workerModule = path.join(__dirname, 'transpile-worker.js'); + diagnosticsWorker = fork(workerModule, [], { env: { FORCE_COLOR: true } }); + + Logger.debug(`diagnosticsWorker created, pid: ${diagnosticsWorker.pid}`); + + diagnosticsWorker.on('error', (err: any) => { + Logger.error(`diagnosticsWorker error, pid: ${diagnosticsWorker.pid}, error: ${err}`); + workerEvent.emit('DiagnosticsWorkerDone'); + }); + + diagnosticsWorker.on('exit', (code: number) => { + Logger.debug(`diagnosticsWorker exited, pid: ${diagnosticsWorker.pid}`); + diagnosticsWorker = null; + }); + + diagnosticsWorker.on('message', (msg: TranspileWorkerMessage) => { + workerEvent.emit('DiagnosticsWorkerDone'); + }); } - // do a full build if it wasn't an existing file that changed - // or we haven't transpiled the whole thing yet - // or there were errors trying to transpile just the one module - Logger.debug(`transpileUpdateWorker: full build, context.tsFiles ${!!context.fileCache}, event: ${event}, file: ${filePath}`); - return transpileWorker(context, workerConfig); + const msg: TranspileWorkerMessage = { + rootDir: context.rootDir, + buildDir: context.buildDir, + isProd: context.isProd, + configFile: getTsConfigPath(context) + }; + diagnosticsWorker.send(msg); +} + + +export interface TranspileWorkerMessage { + rootDir?: string; + buildDir?: string; + isProd?: boolean; + configFile?: string; + transpileSuccess?: boolean; } @@ -237,14 +258,14 @@ function cleanFileNames(context: BuildContext, fileNames: string[]) { function writeSourceFiles(fileCache: FileCache, sourceFiles: ts.SourceFile[]) { for (const sourceFile of sourceFiles) { - fileCache.put(sourceFile.fileName, { path: sourceFile.fileName, content: sourceFile.text }); + fileCache.set(sourceFile.fileName, { path: sourceFile.fileName, content: sourceFile.text }); } } function writeTranspiledFilesCallback(fileCache: FileCache, sourcePath: string, data: string, shouldInlineTemplate: boolean) { - sourcePath = normalize(sourcePath); + sourcePath = path.normalize(sourcePath); - if (endsWith(sourcePath, '.js')) { + if (sourcePath.endsWith('.js')) { sourcePath = sourcePath.substring(0, sourcePath.length - 3) + '.js'; let file = fileCache.get(sourcePath); @@ -258,9 +279,9 @@ function writeTranspiledFilesCallback(fileCache: FileCache, sourcePath: string, file.content = data; } - fileCache.put(sourcePath, file); + fileCache.set(sourcePath, file); - } else if (endsWith(sourcePath, '.js.map')) { + } else if (sourcePath.endsWith('.js.map')) { sourcePath = sourcePath.substring(0, sourcePath.length - 7) + '.js.map'; let file = fileCache.get(sourcePath); @@ -269,7 +290,7 @@ function writeTranspiledFilesCallback(fileCache: FileCache, sourcePath: string, } file.content = data; - fileCache.put(sourcePath, file); + fileCache.set(sourcePath, file); } } @@ -295,12 +316,11 @@ export function getTsConfig(context: BuildContext, tsConfigPath?: string): TsCon ts.sys, context.rootDir, {}, tsConfigPath); - const diagnostics = runDiagnostics(context, parsedConfig.errors); + const diagnostics = runTypeScriptDiagnostics(context, parsedConfig.errors); if (diagnostics.length) { - const buildError = new BuildError(); - buildError.updatedDiagnostics = true; - throw buildError; + printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, true); + throw new BuildError(); } config = { @@ -318,7 +338,7 @@ export function getTsConfig(context: BuildContext, tsConfigPath?: string): TsCon let cachedProgram: ts.Program = null; export function getTsConfigPath(context: BuildContext) { - return join(context.rootDir, 'tsconfig.json'); + return path.join(context.rootDir, 'tsconfig.json'); } export interface TsConfig { diff --git a/src/util/events.ts b/src/util/events.ts index 9487de25..b30d59e1 100644 --- a/src/util/events.ts +++ b/src/util/events.ts @@ -17,16 +17,13 @@ export function emit(eventType: string, val?: any) { export const EventType = { - BuildFinished: 'BuildFinished', - SassFinished: 'SassFinished', - BundleFinished: 'BundleFinished', - FileChange: 'FileChange', + BuildUpdateCompleted: 'BuildUpdateCompleted', + BuildUpdateStarted: 'BuildUpdateStarted', FileAdd: 'FileAdd', + FileChange: 'FileChange', FileDelete: 'FileDelete', DirectoryAdd: 'DirectoryAdd', DirectoryDelete: 'DirectoryDelete', - TaskEvent: 'TaskEvent', - UpdatedDiagnostics: 'UpdatedDiagnostics', - + ReloadApp: 'ReloadApp', WebpackFilesChanged: 'WebpackFilesChanged' }; diff --git a/src/util/file-cache.ts b/src/util/file-cache.ts index 33292d06..cc872048 100644 --- a/src/util/file-cache.ts +++ b/src/util/file-cache.ts @@ -1,5 +1,5 @@ import { File } from './interfaces'; -import { emit, EventType } from './events'; + export class FileCache { @@ -9,19 +9,21 @@ export class FileCache { this.map = new Map(); } - put(key: string, file: File) { + set(key: string, file: File) { file.timestamp = Date.now(); this.map.set(key, file); - // emit(EventType.DanFileChanged, key); } get(key: string): File { return this.map.get(key); } + has(key: string) { + return this.map.has(key); + } + remove(key: string): Boolean { const result = this.map.delete(key); - // emit(EventType.DanFileDeleted, key); return result; } diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 2e8cee8d..0c93ad07 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -1,8 +1,7 @@ import { BuildContext } from './interfaces'; -import { outputJson, readFile, readJsonSync, writeFile } from 'fs-extra'; -import { BuildError, Logger } from './logger'; +import { readFile, writeFile } from 'fs-extra'; +import { BuildError } from './logger'; import { basename, dirname, extname, join } from 'path'; -import { tmpdir } from 'os'; let _context: BuildContext; @@ -24,14 +23,6 @@ export const objectAssign = (Object.assign) ? Object.assign : function (target: }; -export function endsWith(str: string, tail: string) { - if (str && tail) { - return !tail.length || str.slice(-tail.length).toLowerCase() === tail.toLowerCase(); - } - return false; -} - - export function titleCase(str: string) { return str.charAt(0).toUpperCase() + str.substr(1); } @@ -62,42 +53,6 @@ export function readFileAsync(filePath: string): Promise { }); } -export function setModulePathsCache(modulePaths: string[]) { - // async save the module paths for later lookup - const modulesCachePath = getModulesPathsCachePath(); - - Logger.debug(`Cached module paths: ${modulePaths && modulePaths.length}, ${modulesCachePath}`); - - outputJson(modulesCachePath, modulePaths, (err) => { - if (err) { - Logger.error(`Error writing module paths cache: ${err}`); - } - }); -} - - -export function getModulesPathsCachePath(): string { - // make a unique tmp directory for this project's module paths cache file - let cwd = process.cwd().replace(/-|:|\/|\\|\.|~|;|\s/g, '').toLowerCase(); - if (cwd.length > 40) { - cwd = cwd.substr(cwd.length - 40); - } - return join(tmpdir(), cwd, 'modulepaths.json'); -} - -export function getModulePathsCache(): string[] { - // sync get the cached array of module paths (if they exist) - let modulePaths: string[] = null; - const modulesCachePath = getModulesPathsCachePath(); - try { - modulePaths = readJsonSync(modulesCachePath, { throws: false }); - Logger.debug(`Cached module paths: ${modulePaths && modulePaths.length}, ${modulesCachePath}`); - } catch (e) { - Logger.debug(`Cached module paths not found: ${modulesCachePath}`); - } - return modulePaths; -} - export function setContext(context: BuildContext) { _context = context; } @@ -120,4 +75,4 @@ export function changeExtension(filePath: string, newExtension: string) { const extensionlessfileName = basename(filePath, extension); const newFileName = extensionlessfileName + newExtension; return join(dir, newFileName); -} \ No newline at end of file +} diff --git a/src/util/interfaces.ts b/src/util/interfaces.ts index 60121fe8..e24660e5 100644 --- a/src/util/interfaces.ts +++ b/src/util/interfaces.ts @@ -10,16 +10,23 @@ export interface BuildContext { moduleFiles?: string[]; isProd?: boolean; isWatch?: boolean; - isUpdate?: boolean; - fullBuildCompleted?: boolean; + bundler?: string; - useTranspileCache?: boolean; - useBundleCache?: boolean; - useSassCache?: boolean; fileCache?: FileCache; - successfulSass?: boolean; inlineTemplates?: boolean; webpackWatch?: any; + + sassState?: BuildState; + transpileState?: BuildState; + templateState?: BuildState; + bundleState?: BuildState; +} + + +export enum BuildState { + SuccessfulBuild, + RequiresUpdate, + RequiresBuild } @@ -49,6 +56,7 @@ export interface TaskInfo { defaultConfigFile: string; } + export interface File { path: string; content: string; diff --git a/src/util/logger-diagnostics.ts b/src/util/logger-diagnostics.ts index 47d73c4a..4febe738 100644 --- a/src/util/logger-diagnostics.ts +++ b/src/util/logger-diagnostics.ts @@ -1,21 +1,23 @@ import { BuildContext } from './interfaces'; import { Diagnostic, Logger, PrintLine } from './logger'; import { join } from 'path'; -import { readFileSync, writeFileSync, unlinkSync } from 'fs'; +import { readFileSync, unlinkSync, writeFileSync } from 'fs'; import { titleCase } from './helpers'; import * as chalk from 'chalk'; -export function printDiagnostics(context: BuildContext, type: string, diagnostics: Diagnostic[]) { - if (diagnostics.length) { - let content: string[] = []; - diagnostics.forEach(d => { - consoleLogDiagnostic(d); - content.push(generateDiagnosticHtml(d)); - }); +export function printDiagnostics(context: BuildContext, diagnosticsType: string, diagnostics: Diagnostic[], consoleLogDiagnostics: boolean, writeHtmlDiagnostics: boolean) { + if (diagnostics && diagnostics.length) { + + if (consoleLogDiagnostics) { + diagnostics.forEach(consoleLogDiagnostic); + } - const fileName = getDiagnosticsFileName(context.buildDir, type); - writeFileSync(fileName, content.join('\n'), { encoding: 'utf8' }); + if (writeHtmlDiagnostics) { + const content = diagnostics.map(generateDiagnosticHtml); + const fileName = getDiagnosticsFileName(context.buildDir, diagnosticsType); + writeFileSync(fileName, content.join('\n'), { encoding: 'utf8' }); + } } } @@ -99,87 +101,148 @@ function consoleHighlightError(errorLine: string, errorCharStart: number, errorL } -export function clearDiagnosticsHtmlSync(context: BuildContext, type: string) { +let diagnosticsHtmlCache: {[key: string]: any} = {}; + +export function clearDiagnosticsCache() { + diagnosticsHtmlCache = {}; +} + +export function clearDiagnostics(context: BuildContext, type: string) { try { + delete diagnosticsHtmlCache[type]; unlinkSync(getDiagnosticsFileName(context.buildDir, type)); } catch (e) {} } -export function readDiagnosticsHtmlSync(buildDir: string) { - let content = ''; +export function hasDiagnostics(buildDir: string) { + loadDiagnosticsHtml(buildDir); + const keys = Object.keys(diagnosticsHtmlCache); + for (var i = 0; i < keys.length; i++) { + if (typeof diagnosticsHtmlCache[keys[i]] === 'string') { + return true; + } + } + + return false; +} + + +function loadDiagnosticsHtml(buildDir: string) { try { - content = readFileSync(getDiagnosticsFileName(buildDir, 'typescript'), 'utf8') + '\n'; - } catch (e) {} + if (diagnosticsHtmlCache[DiagnosticsType.TypeScript] === undefined) { + diagnosticsHtmlCache[DiagnosticsType.TypeScript] = readFileSync(getDiagnosticsFileName(buildDir, DiagnosticsType.TypeScript), 'utf8'); + } + } catch (e) { + diagnosticsHtmlCache[DiagnosticsType.TypeScript] = false; + } try { - content += readFileSync(getDiagnosticsFileName(buildDir, 'sass'), 'utf8'); - } catch (e) {} + if (diagnosticsHtmlCache[DiagnosticsType.Sass] === undefined) { + diagnosticsHtmlCache[DiagnosticsType.Sass] = readFileSync(getDiagnosticsFileName(buildDir, DiagnosticsType.Sass), 'utf8'); + } + } catch (e) { + diagnosticsHtmlCache[DiagnosticsType.Sass] = false; + } +} + + +export function injectDiagnosticsHtml(buildDir: string, content: any) { + if (!hasDiagnostics(buildDir)) { + return content; + } + + let contentStr = content.toString(); + + const diagnosticsHtml: string[] = []; + diagnosticsHtml.push(`
`); + diagnosticsHtml.push(getDiagnosticsHtmlContent(buildDir)); + diagnosticsHtml.push(`
`); + + let match = contentStr.match(/(?![\s\S]*)/i); + if (match) { + contentStr = contentStr.replace(match[0], match[0] + '\n' + diagnosticsHtml.join('\n')); + } else { + contentStr = diagnosticsHtml.join('\n') + contentStr; + } + + return contentStr; +} + + +export function getDiagnosticsHtmlContent(buildDir: string) { + loadDiagnosticsHtml(buildDir); + + const diagnosticsHtml: string[] = []; - if (content.length) { - return generateHtml('Build Error', content); + const keys = Object.keys(diagnosticsHtmlCache); + for (var i = 0; i < keys.length; i++) { + if (typeof diagnosticsHtmlCache[keys[i]] === 'string') { + diagnosticsHtml.push(diagnosticsHtmlCache[keys[i]]); + } } - return null; + diagnosticsHtml.push(`
`);
+  diagnosticsHtml.push(`${getSystemInfo().join('\n')}`);
+  diagnosticsHtml.push(`
`); + + return diagnosticsHtml.join('\n'); } function generateDiagnosticHtml(d: Diagnostic) { const c: string[] = []; - c.push(`
`); + c.push(`
`); - c.push(`
`); + c.push(`
`); const header = `${titleCase(d.type)} ${titleCase(d.level)}`; - c.push(`

${escapeHtml(header)}

`); + c.push(`
${escapeHtml(header)}
`); - c.push(`

${escapeHtml(d.messageText)}

`); + c.push(`
${escapeHtml(d.messageText)}
`); - c.push(`
`); + c.push(`
`); // .ion-diagnostic-masthead - c.push(`
`); + c.push(`
`); - c.push(`
${escapeHtml(d.relFileName)}
`); + c.push(`
${escapeHtml(d.relFileName)}
`); if (d.lines && d.lines.length) { - c.push(`
`); + c.push(`
`); - c.push(``); + c.push(`
`); const lines = removeWhitespaceIndent(d.lines); lines.forEach(l => { let trCssClass = ''; - let lineNumberCssClass = `blob-num ${d.syntax}-line-number`; let code = l.text; if (l.errorCharStart > -1) { code = htmlHighlightError(code, l.errorCharStart, l.errorLength); - trCssClass = ' class="error-line"'; + trCssClass = ' class="ion-diagnostic-error-line"'; } - code = jsHtmlSyntaxHighlight(code); - c.push(``); - c.push(``); + c.push(``); - c.push(``); + c.push(``); c.push(``); }); c.push(`
${code}${code}
`); - c.push(`
`); // .blob-wrapper + c.push(`
`); // .ion-diagnostic-blob } - c.push(`
`); // .file + c.push(`
`); // .ion-diagnostic-file - c.push(`
`); // .diagnostic + c.push(`
`); // .ion-diagnostic return c.join('\n'); } @@ -191,7 +254,7 @@ function htmlHighlightError(errorLine: string, errorCharStart: number, errorLeng for (var i = 0; i < lineLength; i++) { var chr = errorLine.charAt(i); if (i >= errorCharStart && i < errorCharStart + errorLength) { - chr = `${chr === '' ? ' ' : chr}`; + chr = `${chr === '' ? ' ' : chr}`; } lineChars.push(chr); } @@ -200,195 +263,6 @@ function htmlHighlightError(errorLine: string, errorCharStart: number, errorLeng } -const DIAGNOSTICS_CSS = ` - -* { - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - font-size: 14px; - line-height: 1.5; - color: #333; - background-color: #fff; - word-wrap: break-word; - margin: 0; - padding: 0; -} - -main { - margin: 0; - padding: 15px; -} - -h2 { - margin: 0; - font-size: 18px; - color: #222222; -} - -p { - margin-top: 8px; - color: #666666; -} - -table { - border-spacing: 0; - border-collapse: collapse; -} - -td, th { - padding: 0; -} - -.diagnostic { - margin-bottom: 40px; - border: 1px solid #ddd; - border-radius: 3px; -} - -.diagnostic-header { - padding: 8px 12px 0 12px; -} - -.file { - position: relative; - margin-top: 16px; - border-top: 1px solid #ddd; -} - -.file-header { - padding: 5px 10px; - background-color: #f7f7f7; - border-bottom: 1px solid #d8d8d8; - border-top-left-radius: 2px; - border-top-right-radius: 2px; -} - -.blob-wrapper { - overflow-x: auto; - overflow-y: hidden; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} - -.tab-size { - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} - -.blob-num { - width: 1%; - min-width: 50px; - padding-right: 10px; - padding-left: 10px; - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - line-height: 20px; - color: rgba(0,0,0,0.3); - text-align: right; - white-space: nowrap; - vertical-align: top; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - border: solid #eee; - border-width: 0 1px 0 0; -} - -.blob-num::before { - content: attr(data-line-number); -} - -.error-line .blob-num { - background-color: #ffdddd; - border-color: #f1c0c0; -} - -.error-chr { - position: relative; -} - -.error-chr:before { - content: ""; - position: absolute; - z-index: -1; - top: -3px; - left: 0px; - width: 8px; - height: 20px; - background-color: #ffdddd; -} - -.blob-code { - position: relative; - padding-right: 10px; - padding-left: 10px; - line-height: 20px; - vertical-align: top; -} - -.blob-code-inner { - overflow: visible; - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - color: #333; - word-wrap: normal; - white-space: pre; -} - -.blob-code-inner::before { - content: ""; -} - -.js-keyword, -.css-prop { - color: #183691; -} - -.js-comment, -.sass-comment { - color: #969896; -} - -.system-info { - font-size: 10px; - color: #999; -} - -`; - - -function generateHtml(title: string, content: string) { - return ` - - - - -${escapeHtml(title)} - - - - - - - -
-${content} -
-${getSystemInfo().join('\n')}
-
-
- -`; -} - - function jsConsoleSyntaxHighlight(text: string) { if (text.trim().startsWith('//')) { return chalk.dim(text); @@ -431,54 +305,6 @@ function cssConsoleSyntaxHighlight(text: string, errorCharStart: number) { return chars.join(''); } - -function jsHtmlSyntaxHighlight(text: string) { - if (text.trim().startsWith('//')) { - return `${text}`; - } - - const words = text.split(' ').map(word => { - if (JS_KEYWORDS.indexOf(word) > -1) { - return `${word}`; - } - return word; - }); - - return words.join(' '); -} - - -function cssHtmlSyntaxHighlight(text: string, errorCharStart: number) { - if (text.trim().startsWith('//')) { - return `${text}`; - } - - let cssProp = true; - const safeChars = 'abcdefghijklmnopqrstuvwxyz-_'; - const notProp = '.#,:}@$[]/*'; - - const chars: string[] = []; - - for (var i = 0; i < text.length; i++) { - var c = text.charAt(i); - - if (c === ';' || c === '{') { - cssProp = true; - } else if (notProp.indexOf(c) > -1) { - cssProp = false; - } - if (cssProp && safeChars.indexOf(c.toLowerCase()) > -1) { - chars.push(`${c}`); - continue; - } - - chars.push(c); - } - - return chars.join(''); -} - - function escapeHtml(unsafe: string) { return unsafe .replace(/&/g, '&') @@ -527,7 +353,6 @@ function eachLineHasLeadingWhitespace(lines: PrintLine[]) { } - const JS_KEYWORDS = [ 'as', 'break', @@ -573,11 +398,6 @@ function getDiagnosticsFileName(buildDir: string, type: string) { export function getSystemInfo() { const systemData: string[] = []; - try { - // const ionicFramework = '2.0.0'; - // systemData.push(`Ionic Framework: ${ionicFramework}`); - } catch (e) {} - return systemData; } @@ -594,4 +414,8 @@ function isMeaningfulLine(line: string) { const MEH_LINES = [';', ':', '{', '}', '(', ')', '/**', '/*', '*/', '*', '({', '})']; -const FAVICON = 'data:image/png;base64,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'; +export const DiagnosticsType = { + TypeScript: 'typescript', + Sass: 'sass', + TsLint: 'tslint' +}; diff --git a/src/util/logger-sass.ts b/src/util/logger-sass.ts index 940bb569..d1c068d1 100644 --- a/src/util/logger-sass.ts +++ b/src/util/logger-sass.ts @@ -1,25 +1,10 @@ import { BuildContext } from './interfaces'; import { Diagnostic, Logger, PrintLine } from './logger'; -import { printDiagnostics, clearDiagnosticsHtmlSync } from './logger-diagnostics'; import { readFileSync } from 'fs'; import { SassError } from 'node-sass'; -export function runDiagnostics(context: BuildContext, sassError: SassError) { - const diagnostics = loadDiagnostic(context, sassError); - - printDiagnostics(context, 'sass', diagnostics); - - return diagnostics; -} - - -export function clearSassDiagnostics(context: BuildContext) { - clearDiagnosticsHtmlSync(context, 'sass'); -} - - -function loadDiagnostic(context: BuildContext, sassError: SassError) { +export function runSassDiagnostics(context: BuildContext, sassError: SassError) { if (!sassError) { return []; } diff --git a/src/util/logger-tslint.ts b/src/util/logger-tslint.ts index 5b702e4e..a71e46b9 100644 --- a/src/util/logger-tslint.ts +++ b/src/util/logger-tslint.ts @@ -1,16 +1,11 @@ import { BuildContext } from './interfaces'; -import { printDiagnostics } from './logger-diagnostics'; import { Diagnostic, Logger, PrintLine } from './logger'; -export function runDiagnostics(context: BuildContext, failures: RuleFailure[]) { - const diagnostics = failures.map(failure => { +export function runTsLintDiagnostics(context: BuildContext, failures: RuleFailure[]) { + return failures.map(failure => { return loadDiagnostic(context, failure); }); - - printDiagnostics(context, 'tslint', diagnostics); - - return diagnostics; } diff --git a/src/util/logger-typescript.ts b/src/util/logger-typescript.ts index c43f63c8..63f71c46 100644 --- a/src/util/logger-typescript.ts +++ b/src/util/logger-typescript.ts @@ -1,5 +1,4 @@ import { BuildContext } from './interfaces'; -import { printDiagnostics, clearDiagnosticsHtmlSync } from './logger-diagnostics'; import { Diagnostic, Logger, PrintLine } from './logger'; import * as ts from 'typescript'; @@ -9,19 +8,10 @@ import * as ts from 'typescript'; * error reporting within a terminal. So, yeah, let's code it up, shall we? */ -export function runDiagnostics(context: BuildContext, tsDiagnostics: ts.Diagnostic[]) { - const diagnostics = tsDiagnostics.map(tsDiagnostic => { +export function runTypeScriptDiagnostics(context: BuildContext, tsDiagnostics: ts.Diagnostic[]) { + return tsDiagnostics.map(tsDiagnostic => { return loadDiagnostic(context, tsDiagnostic); }); - - printDiagnostics(context, 'typescript', diagnostics); - - return diagnostics; -} - - -export function clearTypeScriptDiagnostics(context: BuildContext) { - clearDiagnosticsHtmlSync(context, 'typescript'); } diff --git a/src/util/logger.ts b/src/util/logger.ts index a1ee54b3..ecbc7bc2 100644 --- a/src/util/logger.ts +++ b/src/util/logger.ts @@ -1,4 +1,3 @@ -import { emit, EventType } from './events'; import { join } from 'path'; import { isDebugMode } from './config'; import { readJSONSync } from 'fs-extra'; @@ -7,7 +6,6 @@ import * as chalk from 'chalk'; export class BuildError extends Error { hasBeenLogged = false; - updatedDiagnostics = false; constructor(err?: any) { super(); @@ -26,9 +24,6 @@ export class BuildError extends Error { if (typeof err.hasBeenLogged === 'boolean') { this.hasBeenLogged = err.hasBeenLogged; } - if (typeof err.updatedDiagnostics === 'boolean') { - this.updatedDiagnostics = err.updatedDiagnostics; - } } } @@ -37,8 +32,7 @@ export class BuildError extends Error { message: this.message, name: this.name, stack: this.stack, - hasBeenLogged: this.hasBeenLogged, - updatedDiagnostics: this.updatedDiagnostics + hasBeenLogged: this.hasBeenLogged }; } } @@ -67,53 +61,41 @@ export class Logger { msg += memoryUsage(); } Logger.info(msg); - - const taskEvent: TaskEvent = { - scope: this.scope.split(' ')[0], - type: 'start', - msg: `${scope} started ...` - }; - emit(EventType.TaskEvent, taskEvent); } - ready(chalkColor?: Function) { - this.completed('ready', chalkColor); + ready(color?: string, bold?: boolean) { + this.completed('ready', color, bold); } - finish(chalkColor?: Function) { - this.completed('finished', chalkColor); + finish(color?: string, bold?: boolean) { + this.completed('finished', color, bold); } - private completed(type: string, chalkColor: Function) { - - const taskEvent: TaskEvent = { - scope: this.scope.split(' ')[0], - type: type - }; - - taskEvent.duration = Date.now() - this.start; + private completed(type: string, color: string, bold: boolean) { + const duration = Date.now() - this.start; + let time: string; - if (taskEvent.duration > 1000) { - taskEvent.time = 'in ' + (taskEvent.duration / 1000).toFixed(2) + ' s'; + if (duration > 1000) { + time = 'in ' + (duration / 1000).toFixed(2) + ' s'; } else { - let ms = parseFloat((taskEvent.duration).toFixed(3)); + let ms = parseFloat((duration).toFixed(3)); if (ms > 0) { - taskEvent.time = 'in ' + taskEvent.duration + ' ms'; + time = 'in ' + duration + ' ms'; } else { - taskEvent.time = 'in less than 1 ms'; + time = 'in less than 1 ms'; } } - taskEvent.msg = `${this.scope} ${taskEvent.type} ${taskEvent.time}`; - emit(EventType.TaskEvent, taskEvent); - let msg = `${this.scope} ${type}`; - if (chalkColor) { - msg = chalkColor(msg); + if (color) { + msg = (chalk)[color](msg); + } + if (bold) { + msg = chalk.bold(msg); } - msg += ' ' + chalk.dim(taskEvent.time); + msg += ' ' + chalk.dim(time); if (isDebugMode()) { msg += memoryUsage(); @@ -128,14 +110,6 @@ export class Logger { return; } - // only emit the event if it's a valid error - const taskEvent: TaskEvent = { - scope: this.scope.split(' ')[0], - type: 'failed', - msg: this.scope + ' failed' - }; - emit(EventType.TaskEvent, taskEvent); - if (err instanceof BuildError) { let failedMsg = `${this.scope} failed`; if (err.message) { @@ -161,6 +135,10 @@ export class Logger { return err; } + setStartTime(startTime: number) { + this.start = startTime; + } + /** * Does not print out a time prefix or color any text. Only prefix * with whitespace so the message is lined up with timestamped logs. @@ -365,15 +343,6 @@ export function getAppScriptsVersion() { } -export interface TaskEvent { - scope: string; - type: string; - duration?: number; - time?: string; - msg?: string; -} - - export interface Diagnostic { level: string; syntax: string; diff --git a/src/watch.ts b/src/watch.ts index 431c0db2..a734a56e 100644 --- a/src/watch.ts +++ b/src/watch.ts @@ -1,9 +1,9 @@ -import { build, fullBuildUpdate } from './build'; -import { BuildContext, TaskInfo } from './util/interfaces'; -import { BuildError, IgnorableError, Logger } from './util/logger'; +import * as buildTask from './build'; +import { BuildContext, BuildState, TaskInfo } from './util/interfaces'; +import { BuildError, Logger } from './util/logger'; import { fillConfigDefaults, generateContext, getUserConfigFile, replacePathVars, setIonicEnvironment } from './util/config'; -import { join, normalize } from 'path'; -import * as chalk from 'chalk'; +import { join, normalize, extname } from 'path'; +import { canRunTranspileUpdate } from './transpile'; import * as chokidar from 'chokidar'; @@ -16,17 +16,20 @@ export function watch(context?: BuildContext, configFile?: string) { // force watch options context.isProd = false; context.isWatch = true; - context.fullBuildCompleted = false; + + context.sassState = BuildState.RequiresBuild; + context.transpileState = BuildState.RequiresBuild; + context.bundleState = BuildState.RequiresBuild; const logger = new Logger('watch'); function buildDone() { return startWatchers(context, configFile).then(() => { - logger.ready(chalk.green); + logger.ready(); }); } - return build(context) + return buildTask.build(context) .then(buildDone, buildDone) .catch(err => { throw logger.fail(err); @@ -82,20 +85,9 @@ function startWatcher(index: number, watcher: Watcher, context: BuildContext, wa filePath = join(context.rootDir, filePath); - context.isUpdate = true; - Logger.debug(`watch callback start, id: ${watchCount}, isProd: ${context.isProd}, event: ${event}, path: ${filePath}`); - function taskDone() { - Logger.newLine(); - Logger.info(chalk.green.bold('watch ready')); - Logger.newLine(); - } - const callbackToExecute = function(event: string, filePath: string, context: BuildContext, watcher: Watcher) { - if (!context.fullBuildCompleted) { - return fullBuildUpdate(event, filePath, context); - } return watcher.callback(event, filePath, context); }; @@ -103,15 +95,11 @@ function startWatcher(index: number, watcher: Watcher, context: BuildContext, wa .then(() => { Logger.debug(`watch callback complete, id: ${watchCount}, isProd: ${context.isProd}, event: ${event}, path: ${filePath}`); watchCount++; - taskDone(); }) .catch(err => { Logger.debug(`watch callback error, id: ${watchCount}, isProd: ${context.isProd}, event: ${event}, path: ${filePath}`); Logger.debug(`${err}`); watchCount++; - if (!(err instanceof IgnorableError)) { - taskDone(); - } }); }); @@ -151,6 +139,128 @@ export function prepareWatcher(context: BuildContext, watcher: Watcher) { } +let queuedChangedFiles: ChangedFile[] = []; +let queuedChangeFileTimerId: any; +export interface ChangedFile { + event: string; + filePath: string; + ext: string; +} + +export function buildUpdate(event: string, filePath: string, context: BuildContext) { + const changedFile: ChangedFile = { + event: event, + filePath: filePath, + ext: extname(filePath).toLowerCase() + }; + + // do not allow duplicates + if (!queuedChangedFiles.some(f => f.filePath === filePath)) { + queuedChangedFiles.push(changedFile); + + // debounce our build update incase there are multiple files + clearTimeout(queuedChangeFileTimerId); + + // run this code in a few milliseconds if another hasn't come in behind it + queuedChangeFileTimerId = setTimeout(() => { + // figure out what actually needs to be rebuilt + const buildData = runBuildUpdate(context, queuedChangedFiles); + + // clear out all the files that are queued up for the build update + queuedChangedFiles.length = 0; + + if (buildData) { + // cool, we've got some build updating to do ;) + buildTask.buildUpdate(buildData.event, buildData.filePath, context); + } + }, BUILD_UPDATE_DEBOUNCE_MS); + } + + return Promise.resolve(); +} + + +export function runBuildUpdate(context: BuildContext, changedFiles: ChangedFile[]) { + if (!changedFiles || !changedFiles.length) { + return null; + } + + // create the data which will be returned + const data = { + event: changedFiles.map(f => f.event).find(ev => ev !== 'change') || 'change', + filePath: changedFiles[0].filePath, + changedFiles: changedFiles.map(f => f.filePath) + }; + + const tsFiles = changedFiles.filter(f => f.ext === '.ts'); + if (tsFiles.length > 1) { + // multiple .ts file changes + // if there is more than one ts file changing then + // let's just do a full transpile build + context.transpileState = BuildState.RequiresBuild; + + } else if (tsFiles.length) { + // only one .ts file changed + if (canRunTranspileUpdate(tsFiles[0].event, tsFiles[0].filePath, context)) { + // .ts file has only changed, it wasn't a file add/delete + // we can do the quick typescript update on this changed file + context.transpileState = BuildState.RequiresUpdate; + + } else { + // .ts file was added or deleted, we need a full rebuild + context.transpileState = BuildState.RequiresBuild; + } + } + + const sassFiles = changedFiles.filter(f => f.ext === '.scss'); + if (sassFiles.length) { + // .scss file was changed/added/deleted, lets do a sass update + context.sassState = BuildState.RequiresUpdate; + } + + const sassFilesNotChanges = changedFiles.filter(f => f.ext === '.ts' && f.event !== 'change'); + if (sassFilesNotChanges.length) { + // .ts file was either added or deleted, so we'll have to + // run sass again to add/remove that .ts file's potential .scss file + context.sassState = BuildState.RequiresUpdate; + } + + const htmlFiles = changedFiles.filter(f => f.ext === '.html'); + if (htmlFiles.length) { + if (context.bundleState === BuildState.SuccessfulBuild && htmlFiles.every(f => f.event === 'change')) { + // .html file was changed + // just doing a template update is fine + context.templateState = BuildState.RequiresUpdate; + + } else { + // .html file was added/deleted + // we should do a full transpile build because of this + context.transpileState = BuildState.RequiresBuild; + } + } + + if (context.transpileState === BuildState.RequiresUpdate || context.transpileState === BuildState.RequiresBuild) { + if (context.bundleState === BuildState.SuccessfulBuild) { + // transpiling needs to happen + // and there has already been a successful bundle before + // so let's just do a bundle update + context.bundleState = BuildState.RequiresUpdate; + + } else { + // transpiling needs to happen + // but we've never successfully bundled before + // so let's do a full bundle build + context.bundleState = BuildState.RequiresBuild; + } + } + + // guess which file is probably the most important here + data.filePath = tsFiles.concat(sassFiles, htmlFiles)[0].filePath; + + return data; +} + + const taskInfo: TaskInfo = { fullArg: '--watch', shortArg: '-w', @@ -181,3 +291,5 @@ export interface Watcher { } let watchCount = 0; + +const BUILD_UPDATE_DEBOUNCE_MS = 20; diff --git a/src/webpack.ts b/src/webpack.ts index f6a2058f..ae9a3fb2 100644 --- a/src/webpack.ts +++ b/src/webpack.ts @@ -1,7 +1,7 @@ import { FileCache } from './util/file-cache'; -import { BuildContext, File, TaskInfo } from './util/interfaces'; +import { BuildContext, BuildState, File, TaskInfo } from './util/interfaces'; import { BuildError, IgnorableError, Logger } from './util/logger'; -import { changeExtension, readFileAsync, setContext, setModulePathsCache } from './util/helpers'; +import { changeExtension, readFileAsync, setContext } from './util/helpers'; import { emit, EventType } from './util/events'; import { fillConfigDefaults, generateContext, getUserConfigFile, replacePathVars } from './util/config'; import { extname, join } from 'path'; @@ -34,9 +34,11 @@ export function webpack(context: BuildContext, configFile: string) { return webpackWorker(context, configFile) .then(() => { + context.bundleState = BuildState.SuccessfulBuild; logger.finish(); }) .catch(err => { + context.bundleState = BuildState.RequiresBuild; throw logger.fail(err); }); } @@ -69,8 +71,10 @@ export function webpackUpdate(event: string, path: string, context: BuildContext Logger.debug('webpackUpdate: Incremental Build Done, processing Data'); return webpackBuildComplete(stats, context, webpackConfig); }).then(() => { + context.bundleState = BuildState.SuccessfulBuild; return logger.finish(); }).catch(err => { + context.bundleState = BuildState.RequiresBuild; if (err instanceof IgnorableError) { throw err; } @@ -112,11 +116,6 @@ function webpackBuildComplete(stats: any, context: BuildContext, webpackConfig: context.moduleFiles = files; - // async cache all the module paths so we don't need - // to always bundle to know which modules are used - setModulePathsCache(context.moduleFiles); - - emit(EventType.BundleFinished, getOutputDest(context, webpackConfig)); return Promise.resolve(); } diff --git a/src/webpack/ionic-webpack-factory.ts b/src/webpack/ionic-webpack-factory.ts index ca5400cc..d7d6115f 100644 --- a/src/webpack/ionic-webpack-factory.ts +++ b/src/webpack/ionic-webpack-factory.ts @@ -1,7 +1,8 @@ import { IonicEnvironmentPlugin } from './ionic-environment-plugin'; import { getContext } from '../util/helpers'; + export function getIonicEnvironmentPlugin() { const context = getContext(); return new IonicEnvironmentPlugin(context.fileCache); -} \ No newline at end of file +} diff --git a/src/worker-client.ts b/src/worker-client.ts index 028e44d8..4bb979e4 100644 --- a/src/worker-client.ts +++ b/src/worker-client.ts @@ -2,7 +2,6 @@ import { BuildContext, WorkerProcess, WorkerMessage } from './util/interfaces'; import { BuildError, Logger } from './util/logger'; import { fork, ChildProcess } from 'child_process'; import { join } from 'path'; -import { emit, EventType } from './util/events'; export function runWorker(taskModule: string, taskWorker: string, context: BuildContext, workerConfig: any) { @@ -30,18 +29,10 @@ export function runWorker(taskModule: string, taskWorker: string, context: Build worker.on('message', (msg: WorkerMessage) => { if (msg.error) { - const buildErrorError = new BuildError(msg.error); - if (buildErrorError.updatedDiagnostics) { - emit(EventType.UpdatedDiagnostics); - } - reject(buildErrorError); + reject(new BuildError(msg.error)); } else if (msg.reject) { - const buildErrorReject = new BuildError(msg.reject); - if (buildErrorReject.updatedDiagnostics) { - emit(EventType.UpdatedDiagnostics); - } - reject(buildErrorReject); + reject(new BuildError(msg.reject)); } else { resolve(msg.resolve); diff --git a/src/worker-process.ts b/src/worker-process.ts index f86c3583..91550905 100644 --- a/src/worker-process.ts +++ b/src/worker-process.ts @@ -11,7 +11,7 @@ process.on('message', (msg: WorkerMessage) => { .then((val: any) => { taskResolve(msg.taskModule, msg.taskWorker, val); }, (val: any) => { - taskReject(msg.taskModule, msg.taskWorker, val) + taskReject(msg.taskModule, msg.taskWorker, val); }) .catch((err: any) => { taskError(msg.taskModule, msg.taskWorker, err); From 317fccd207c10608c7e7803ef6bb7213bfb6f42a Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Mon, 7 Nov 2016 23:45:28 -0600 Subject: [PATCH 2/5] fix(sass): remove broken sass caching --- src/sass.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/sass.ts b/src/sass.ts index ca82c0a4..f5574183 100755 --- a/src/sass.ts +++ b/src/sass.ts @@ -226,15 +226,6 @@ function getComponentDirectories(moduleDirectories: string[], sassConfig: SassCo function render(context: BuildContext, sassConfig: SassConfig): Promise { return new Promise((resolve, reject) => { - if (lastRenderKey !== null) { - // if the sass data imports are same, don't bother - const renderKey = getRenderCacheKey(sassConfig); - if (renderKey === lastRenderKey) { - resolve(sassConfig.outFile); - return; - } - } - sassConfig.omitSourceMapUrl = true; if (sassConfig.sourceMap) { @@ -253,7 +244,6 @@ function render(context: BuildContext, sassConfig: SassConfig): Promise } else { // sass render success :) renderSassSuccess(context, sassResult, sassConfig).then(outFile => { - lastRenderKey = getRenderCacheKey(sassConfig); resolve(outFile); }).catch(err => { @@ -436,17 +426,6 @@ function defaultSortComponentFilesFn(a: any, b: any): number { } -function getRenderCacheKey(sassConfig: SassConfig) { - return [ - sassConfig.data, - sassConfig.file, - ].join('|'); -} - - -let lastRenderKey: string = null; - - const taskInfo: TaskInfo = { fullArg: '--sass', shortArg: '-s', From fd2349c676deae16e39148c0e820081580e4ea83 Mon Sep 17 00:00:00 2001 From: Dan Bucholtz Date: Mon, 7 Nov 2016 23:53:24 -0600 Subject: [PATCH 3/5] fix(bundling): execute bundle updates if full bundle has completed at least once execute bundle updates if full bundle has completed at least once --- src/watch.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/watch.ts b/src/watch.ts index a734a56e..d1c4ccb2 100644 --- a/src/watch.ts +++ b/src/watch.ts @@ -240,12 +240,11 @@ export function runBuildUpdate(context: BuildContext, changedFiles: ChangedFile[ } if (context.transpileState === BuildState.RequiresUpdate || context.transpileState === BuildState.RequiresBuild) { - if (context.bundleState === BuildState.SuccessfulBuild) { + if (context.bundleState === BuildState.SuccessfulBuild || context.bundleState === BuildState.RequiresUpdate) { // transpiling needs to happen // and there has already been a successful bundle before // so let's just do a bundle update context.bundleState = BuildState.RequiresUpdate; - } else { // transpiling needs to happen // but we've never successfully bundled before From 1ce67826b3202fe69b0c199351ce01aa429eab41 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Tue, 8 Nov 2016 08:31:46 -0600 Subject: [PATCH 4/5] feat(error): use datauri for favicon build status --- bin/ion-build-active.png | Bin 4230 -> 0 bytes bin/ion-build-error.png | Bin 3747 -> 0 bytes bin/ion-build-success.png | Bin 4256 -> 0 bytes bin/ion-dev.js | 10 ++++++++-- src/dev-server/http-server.ts | 2 +- 5 files changed, 9 insertions(+), 3 deletions(-) delete mode 100755 bin/ion-build-active.png delete mode 100755 bin/ion-build-error.png delete mode 100755 bin/ion-build-success.png diff --git a/bin/ion-build-active.png b/bin/ion-build-active.png deleted file mode 100755 index 4550a405b8caaf951629020c3113d2c68a0f12a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4230 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 diff --git a/bin/ion-build-error.png b/bin/ion-build-error.png deleted file mode 100755 index 2a24b28fc05b371bd0201d4fc039abce24cf4528..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3747 zcmXw6c{CJ!7yiv)491d_t%wkjb?nK6X!07%*hOS7N;QV?YNCuKDU5v?QM5^xY{`g_ zbtp@+MWV$nytd!x?fbrS?(*E{Jolb+?|(POf?&eUDZ&W=;KrXevf9mze;v)T8$FR( z>Hr`vTKr{wYPW>ehW|GMXk!j-EFe(>64n33)&~EAUBNCUV*kWH{QpRcKg_Ne3$6A3 zH2znk@dy833E}@5iMr6*6j~eqJCLXciCWMa2Z^VlwHDmohF?bEx36&HANYF(E-t`d zKj5c6=x7RC>f!5h*xLo|PQdTeaA^^Cw!)hMF!MfiJ_Xm;;HzgaFB2wG;W;blU=HVI z;aN*KIt07hU|}Bgc7bm8@a-GOsD{r9;L9iQ`XzYX3&w>*2Ll**9u5z}s#1766grx~ z)<)=e9(p*!z8+Ze5GLP+cW%MNoA72Z3^)td)*zh*sbm;Nf>(T?uOoCl10SZrtQ1I# zf$nDTIUUA?K+;9{I2)$NKtDU^Z3P{1u>1v#j#!aBVY_>736d3o2+#tK;>#1u6Y1tZ zF+YcEYfMaJhWkEd-VY*Qy42EARM>W>MhyT=Gv4T=b@0$^_VkSp#zHOGI5d8cO~jZg z#bqV(o|v*W8rp)-stZDLEKCnDK~O0nWuEZ3Z*?t7@T>cnUCp9`zPB)a@yH8ralPF1 zt;x#{HdyxWBfNDsKg{ZPwvwjduNb@7I=J$w@v&V(%dvHl%$&b>TEv%^m+S72X@Tx= z%lTaIvJdX|jW?DyrCP4Q<2duZ^?G9Dl{FXrnfP#P5K^=JV^sOt^c1Egem`g*y^$9x zGg8xjC7+!c%2i3?hO39~ikg9xzk{7xCUd_U3sSO$R`{F4zg1bHTtCTm(v)}PHGhC# zN|PcVAVPBk{33oeSpTl*jRLD(=BU%Y?97;!hdLLykOH5x~!;q%K-9| z`K^f#HpZxxA3e#7C5@u_!6fSw7^E(+s)_@xnC7{RJ>y*o*)!7K#$feb5WLcy1=i}X zr@zp?hAeqej1WuPH~u|v>z0~4*bFM43R*^Ptp@UDW0e?9soA8Ae9Qt_#X+Cf&Lg4H z-&B{yx4x=W$}UKxf(8Eo!yp)X$!ard-+kD?tIwRb-e>QCp5Ld2MLeSDMIRU^{gg`a zM77%T48$Ja177pSUn2pYpKj)q9_dg;@uR&6*Op`EMZiz`48aS1D)sJ~v z@w-n$t=x|*Q}`-HFeMj{2$T5K?JDPd!Q(XIkwO9*VK9Y_Jl4{SPsNPqO=<7JnUYnE zmgbWX$ICkSfKitpCcTLeg&BY2$uB-Wd`#Kns!~$%O%5(9SAY4hu7 zb5E$ZA=dGIs2R__PjQ$bC{xNfW} znuF|A5@KblsHphn`tm#E?6y4qeVmaezT8mg;uufqdFh2oebyF(h5*XbQc4MbR`r?$ zhbO_bVm-0F>uh`DIhKz@ZIAoh;@OJ4`%H3qqIgmNu&{r*h-z7?kXx+2ab%Pr#GxFN zW2=`uOiMXI_0pc_QU2@q(EcIPW4*Y*Nf0Lf>%yyW<(>m?4?FR$lIbr%yZ>)J*D+a2 zf=NUMo+;G-@b&s<-J~aXY9i#^!HG9^!u3Io)Sr6?dSwg0lu3XSw8hV4!prOsxo� z8j=z6Q6IJkMidz#W+Q4=xxqT$>}Ez18gK7u-uTfapSR@aOlWstX%$l>(w3%!d)hr4 zpPk6|YP|}@;PTjr>Xo_N~F(Bv!Na zlk)l{cS(OOW6ffsMOQ_Filnt-U6LEm(K+bKU(R1!QN?Bw0=q-vAL*A&MLeHIcM2qJ zSeD@W4kzUaj$gLyyf3Y`Cp{|%72oOJ7J%Gz5d{lSv?(FF z#(cuf^sAa}{pCV7GnkGcrvD|wW?Y!S_C-ybu_f%`=I?W)W~@Ilf{go{*qeT&tEpb0 zN=k(xEfBoa;fp*lruBrk@=OKLtJd^3>Z}g2`aEo)FGlTW6wmoxJe0>#;xe?OGO6PMmfd{VG}Hmv z*|5NF01K?{eRt}-2N-InwLTQlig;-yBn|8+?!Yj$j7^eqEWi$bIS)}>YWR(+#01U@ z4(LoNuplO2Bp8a-GxWAknmKhg>Ufy|g3%%rd^f5G=yotSZlUZeBNLvV27K=$)Mhw2 zm{&2puJeZwsu}?e^Ls%#D-&bT&sEp%Mich9$LPY zi38${O3pV+e4OLiqGeXnkw|+U%Bmeobljb9#Un$Oa!pr^!e=sMaA_efZV*rvmCq(9 zXt%4RwVja#t#Ne>l#dF<))J&5&QfN1kbtN5Ml)*A6J*oD=%gJt%@SmedyBcVgn=V| z2ie9wOK4Mjb{BkZ`x1e>+xmmqM423ygk2E5Gwivx zAK;xqZ}0(c*C+B#{pyq`)QoLCSN=5n&Bb zb=(~dax+@wYu3j+`(V1c4}~ zFeiPALH;pd30g>58^ncEA@|(&{nGYGvH)Bdx=6UIJbUJ`I#LTQ4! zKW`@FARDu(eEO`j01grMy8#m(AQ=1ffJ9{Nk>K{K2+W)e>q0mB?ax32us1+rN9-Sw z_*rpEH(3#kJnpc`!d6xj+1Qsz{@u)(mDKZhqCgTxF@kqSfan=a;n#Wh5fQ|ugpcKL zVHLyTnsit8hfq-VloVVPphWV>4M^*B)_98ca~c#n#7UCw+?TG2TsU!yKnjZ5G~3QF zeh_+$Q#;$^AeOH#2u?-MBz#{I0inKHCU_K| zL&T44Y`cHgc8IASGhQW`iX*3Ez$i71bh>Q(adG>zy_)J=Ig0dIdZOep?a%xY;TA!QmJXxT;GQS7G$~E z!F%WkuA+5A&{|U6ki=rt#xw~R8ay`>&@yWwT4&f1#J(!%NPVSx`KIyrMMaOt6_uo9 zG3r;c4kxsBR!}c!IG9u868%qFAp!_0s;cDXOw3nS9MqoK@2?j#o0Y|j4{@}9xP&R9 zA+wi4*N-HxM$Ij4YQB%O{duUmsJ5lwOJw7bdkPO_LNjdQp{3c|*b?`zvsTEo7rMg1 zM^w?SCwqn{qX`s&mrvE)!CT~3memR%oEj7&2qBm^;~Q4kI>q1m54r_O8Hta-WfK>@ zo+3jr?}=*~d%$gB!J2BDNXkUYM$Sy^ccv6Oxm_Lb(QNiXqmfquZISCrjs_*r#<#z6 z1|g7qU&TB&{4(t{LD+$-(-K+LdQ_ay5Iv`ndL}`oq^AJMvIQbkj7Uq{@)>VE6SMK0&jVDNNa>WRqWT9o;3ga;v|_laA6SYkZ~kpE#4 zYMD7~e}I({bRvews6+GJ7#e5p6n!U=Z{JiB3l^s-5%(D>`K`8PSrf%{Y9pf>4zUhj z)go0?t&If#| zOm=s0j;tY`JTI2Oh3mbh>E~Y5N=rpM_K&_}FL7z}jk3RK6<^@1?DqMabMJMj;JqIH znRrp}O^pb0VolGal-s1=QvVcHG{?WAFqbPR#wqoKkJybW`PzVQ;S&^t_GjvEQb z)&SniPAfD0BR#BD*V-Qt-IctJdB=C+RN}%%LbqMZ!QAT_Z(%F*@Z1CYC^2JVP61dA zz0Iv$_FxTNJ+Fp=^`wEagq-fbMvpU^w!L`^UG&5%eSRu6pFJNY(Bvt<`~Cp@DS}b4 IfosHn09D!tI{*Lx diff --git a/bin/ion-build-success.png b/bin/ion-build-success.png deleted file mode 100755 index 7469634d5a7f4a3b1be7a2bd7e5a765944a6ea14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4256 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 Date: Tue, 8 Nov 2016 13:22:14 -0600 Subject: [PATCH 5/5] chore(logger): improve build error logging --- bin/ion-dev.css | 3 +- bin/ion-dev.js | 37 ++++++++++++++------ package.json | 1 + src/build.ts | 5 ++- src/declarations.d.ts | 1 + src/dev-server/http-server.ts | 4 +-- src/dev-server/injector.ts | 11 +++--- src/dev-server/serve-config.ts | 2 +- src/index.ts | 6 ++-- src/serve.ts | 6 ++-- src/util/helpers.ts | 64 +++++++++++++++++++++++++++++++++- src/util/logger-diagnostics.ts | 13 +------ src/util/logger.ts | 36 +++++++++++-------- 13 files changed, 131 insertions(+), 58 deletions(-) diff --git a/bin/ion-dev.css b/bin/ion-dev.css index 54d7f72d..826e6a6d 100644 --- a/bin/ion-dev.css +++ b/bin/ion-dev.css @@ -157,7 +157,8 @@ color: #969896; } -#ion-diagnostics .ion-diagnostics-system-info { +#ion-diagnostics-system-info { + padding-bottom: 20px; font-size: 10px; color: #999; } diff --git a/bin/ion-dev.js b/bin/ion-dev.js index 9d5c5a3b..90829eac 100644 --- a/bin/ion-dev.js +++ b/bin/ion-dev.js @@ -1,3 +1,4 @@ +window.IonicDevServerConfig = window.IonicDevServerConfig || {}; window.IonicDevServer = { start: function() { this.msgQueue = []; @@ -6,12 +7,13 @@ window.IonicDevServer = { this.consoleError = console.error; this.consoleWarn = console.warn; - if (IonicDevServerConfig && IonicDevServerConfig.sendConsoleLogs) { + IonicDevServerConfig.systemInfo.push('Navigator Platform: ' + window.navigator.platform); + IonicDevServerConfig.systemInfo.push('User Agent: ' + window.navigator.userAgent); + + if (IonicDevServerConfig.sendConsoleLogs) { this.patchConsole(); } - console.log('dev server enabled'); - this.openConnection(); this.bindKeyboardEvents(); @@ -19,6 +21,16 @@ window.IonicDevServer = { document.addEventListener("DOMContentLoaded", IonicDevServer.domReady); }, + domReady: function() { + document.removeEventListener("DOMContentLoaded", IonicDevServer.domReady); + var diagnosticsEle = document.getElementById('ion-diagnostics'); + if (diagnosticsEle) { + IonicDevServer.buildStatus('error'); + } else { + IonicDevServer.buildStatus('success'); + } + }, + handleError: function(err) { var self = this; @@ -291,15 +303,18 @@ window.IonicDevServer = { iconLink.type = 'image/png'; iconLink.href = IonicDevServer[status + 'Icon']; document.head.appendChild(iconLink); - }, - domReady: function() { - document.removeEventListener("DOMContentLoaded", IonicDevServer.domReady); - var diagnosticsEle = document.getElementById('ion-diagnostics'); - if (diagnosticsEle) { - IonicDevServer.buildStatus('error'); - } else { - IonicDevServer.buildStatus('success'); + if (status === 'error') { + var diagnosticsEle = document.getElementById('ion-diagnostics'); + if (diagnosticsEle) { + var systemInfoEle = diagnosticsEle.querySelector('#ion-diagnostics-system-info'); + if (!systemInfoEle) { + systemInfoEle = document.createElement('pre'); + systemInfoEle.id = 'ion-diagnostics-system-info'; + systemInfoEle.innerHTML = IonicDevServerConfig.systemInfo.join('\n'); + diagnosticsEle.appendChild(systemInfoEle); + } + } } }, diff --git a/package.json b/package.json index d215bd65..0aaa0bf6 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "fs-extra": "0.30.0", "json-loader": "^0.5.4", "node-sass": "3.10.1", + "os-name": "^2.0.1", "postcss": "5.2.0", "proxy-middleware": "^0.15.0", "rollup": "0.36.3", diff --git a/src/build.ts b/src/build.ts index 16690dd7..caa1ce44 100644 --- a/src/build.ts +++ b/src/build.ts @@ -118,7 +118,7 @@ function buildDev(context: BuildContext) { export function buildUpdate(event: string, filePath: string, context: BuildContext) { return new Promise(resolve => { - const logger = new Logger('build update'); + const logger = new Logger('build'); buildUpdateId++; emit(EventType.BuildUpdateStarted, buildUpdateId); @@ -150,8 +150,7 @@ export function buildUpdate(event: string, filePath: string, context: BuildConte lintUpdate(event, filePath, context); } - Logger.newLine(); - logger.ready('green', true); + logger.finish('green', true); Logger.newLine(); // we did it! diff --git a/src/declarations.d.ts b/src/declarations.d.ts index 409df78a..b416dd1f 100644 --- a/src/declarations.d.ts +++ b/src/declarations.d.ts @@ -1,5 +1,6 @@ declare module 'autoprefixer'; declare module 'mime-types'; +declare module 'os-name'; declare module 'proxy-middleware'; declare module 'rollup-pluginutils'; declare module 'rollup'; diff --git a/src/dev-server/http-server.ts b/src/dev-server/http-server.ts index f775b878..2cc80c19 100644 --- a/src/dev-server/http-server.ts +++ b/src/dev-server/http-server.ts @@ -64,9 +64,7 @@ function serveIndex(req: express.Request, res: express.Response) { indexHtml = injectLiveReloadScript(indexHtml, config.host, config.liveReloadPort); } - if (config.useNotifier) { - indexHtml = injectNotificationScript(indexHtml, config.notifyOnConsoleLog, config.notificationPort); - } + indexHtml = injectNotificationScript(config.rootDir, indexHtml, config.notifyOnConsoleLog, config.notificationPort); indexHtml = injectDiagnosticsHtml(config.buildDir, indexHtml); diff --git a/src/dev-server/injector.ts b/src/dev-server/injector.ts index 3e6a46f6..4438c8d9 100644 --- a/src/dev-server/injector.ts +++ b/src/dev-server/injector.ts @@ -1,12 +1,12 @@ -import { getAppScriptsVersion } from '../util/logger'; +import { getAppScriptsVersion, getSystemInfo } from '../util/helpers'; import { LOGGER_DIR } from './serve-config'; const LOGGER_HEADER = ''; -export function injectNotificationScript(content: any, notifyOnConsoleLog: boolean, notificationPort: Number): any { +export function injectNotificationScript(rootDir: string, content: any, notifyOnConsoleLog: boolean, notificationPort: Number): any { let contentStr = content.toString(); - const consoleLogScript = getConsoleLoggerScript(notifyOnConsoleLog, notificationPort); + const consoleLogScript = getDevLoggerScript(rootDir, notifyOnConsoleLog, notificationPort); if (contentStr.indexOf(LOGGER_HEADER) > -1) { // already added script somehow @@ -26,12 +26,13 @@ export function injectNotificationScript(content: any, notifyOnConsoleLog: boole return contentStr; } -function getConsoleLoggerScript(notifyOnConsoleLog: boolean, notificationPort: Number) { +function getDevLoggerScript(rootDir: string, notifyOnConsoleLog: boolean, notificationPort: Number) { const appScriptsVersion = getAppScriptsVersion(); const ionDevServer = JSON.stringify({ sendConsoleLogs: notifyOnConsoleLog, wsPort: notificationPort, - appScriptsVersion: appScriptsVersion + appScriptsVersion: appScriptsVersion, + systemInfo: getSystemInfo(rootDir) }); return ` diff --git a/src/dev-server/serve-config.ts b/src/dev-server/serve-config.ts index 53ebbed6..495572a9 100644 --- a/src/dev-server/serve-config.ts +++ b/src/dev-server/serve-config.ts @@ -1,6 +1,7 @@ export interface ServeConfig { httpPort: number; host: string; + rootDir: string; wwwDir: string; buildDir: string; launchBrowser: boolean; @@ -9,7 +10,6 @@ export interface ServeConfig { useLiveReload: boolean; liveReloadPort: Number; notificationPort: Number; - useNotifier: boolean; useServerLogs: boolean; notifyOnConsoleLog: boolean; useProxy: boolean; diff --git a/src/index.ts b/src/index.ts index bb858535..394bd8ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,13 +14,13 @@ export * from './util/config'; export * from './util/helpers'; export * from './util/interfaces'; -import { Logger, getAppScriptsVersion } from './util/logger'; -import * as chalk from 'chalk'; +import { getAppScriptsVersion } from './util/helpers'; +import { Logger } from './util/logger'; export function run(task: string) { try { - Logger.info(chalk.cyan(`ionic-app-scripts ${getAppScriptsVersion()}`)); + Logger.info(`ionic-app-scripts ${getAppScriptsVersion()}`, 'cyan'); } catch (e) {} try { diff --git a/src/serve.ts b/src/serve.ts index bfc6c269..33ea43ed 100644 --- a/src/serve.ts +++ b/src/serve.ts @@ -2,7 +2,6 @@ import { BuildContext } from './util/interfaces'; import { generateContext, getConfigValue, hasConfigValue } from './util/config'; import { Logger } from './util/logger'; import { watch } from './watch'; -import * as chalk from 'chalk'; import open from './util/open'; import { createNotificationServer } from './dev-server/notification-server'; import { createHttpServer } from './dev-server/http-server'; @@ -21,6 +20,7 @@ export function serve(context?: BuildContext) { const config: ServeConfig = { httpPort: getHttpServerPort(context), host: getHttpServerHost(context), + rootDir: context.rootDir, wwwDir: context.wwwDir, buildDir: context.buildDir, launchBrowser: launchBrowser(context), @@ -30,7 +30,6 @@ export function serve(context?: BuildContext) { liveReloadPort: getLiveReloadServerPort(context), notificationPort: getNotificationPort(context), useServerLogs: useServerLogs(context), - useNotifier: true, useProxy: useProxy(context), notifyOnConsoleLog: sendClientConsoleLogs(context) }; @@ -59,7 +58,8 @@ function onReady(config: ServeConfig, context: BuildContext) { open(openOptions.join(''), browserToLaunch(context)); } - Logger.info(chalk.green(`dev server running: http://${config.host}:${config.httpPort}/`)); + Logger.info(`dev server running: http://${config.host}:${config.httpPort}/`, 'green', true); + Logger.newLine(); } function getHttpServerPort(context: BuildContext) { diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 0c93ad07..474d3a4b 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -1,10 +1,72 @@ import { BuildContext } from './interfaces'; -import { readFile, writeFile } from 'fs-extra'; +import { readFile, readJsonSync, writeFile } from 'fs-extra'; import { BuildError } from './logger'; import { basename, dirname, extname, join } from 'path'; +import * as osName from 'os-name'; let _context: BuildContext; + + +let cachedAppScriptsPackageJson: any; +export function getAppScriptsPackageJson() { + if (!cachedAppScriptsPackageJson) { + try { + cachedAppScriptsPackageJson = readJsonSync(join(__dirname, '..', '..', 'package.json')); + } catch (e) {} + } + return cachedAppScriptsPackageJson; +} + + +export function getAppScriptsVersion() { + const appScriptsPackageJson = getAppScriptsPackageJson(); + return (appScriptsPackageJson && appScriptsPackageJson.version) ? appScriptsPackageJson.version : ''; +} + +function getUserPackageJson(userRootDir: string) { + try { + return readJsonSync(join(userRootDir, 'package.json')); + } catch (e) {} + return null; +} + +export function getSystemInfo(userRootDir: string) { + const d: string[] = []; + + let ionicAppScripts = getAppScriptsVersion(); + let ionicFramework: string = null; + let ionicNative: string = null; + let angularCore: string = null; + let angularCompilerCli: string = null; + + try { + const userPackageJson = getUserPackageJson(userRootDir); + if (userPackageJson) { + const userDependencies = userPackageJson.dependencies; + if (userDependencies) { + ionicFramework = userDependencies['ionic-angular']; + ionicNative = userDependencies['ionic-native']; + angularCore = userDependencies['@angular/core']; + angularCompilerCli = userDependencies['@angular/compiler-cli']; + } + } + } catch (e) {} + + d.push(`Ionic Framework: ${ionicFramework}`); + if (ionicNative) { + d.push(`Ionic Native: ${ionicNative}`); + } + d.push(`Ionic App Scripts: ${ionicAppScripts}`); + d.push(`Angular Core: ${angularCore}`); + d.push(`Angular Compiler CLI: ${angularCompilerCli}`); + d.push(`Node: ${process.version.replace('v', '')}`); + d.push(`OS Platform: ${osName()}`); + + return d; +} + + export const objectAssign = (Object.assign) ? Object.assign : function (target: any, source: any) { const output = Object(target); diff --git a/src/util/logger-diagnostics.ts b/src/util/logger-diagnostics.ts index 4febe738..f7e0a377 100644 --- a/src/util/logger-diagnostics.ts +++ b/src/util/logger-diagnostics.ts @@ -1,8 +1,8 @@ import { BuildContext } from './interfaces'; import { Diagnostic, Logger, PrintLine } from './logger'; +import { titleCase } from './helpers'; import { join } from 'path'; import { readFileSync, unlinkSync, writeFileSync } from 'fs'; -import { titleCase } from './helpers'; import * as chalk from 'chalk'; @@ -183,10 +183,6 @@ export function getDiagnosticsHtmlContent(buildDir: string) { } } - diagnosticsHtml.push(`
`);
-  diagnosticsHtml.push(`${getSystemInfo().join('\n')}`);
-  diagnosticsHtml.push(`
`); - return diagnosticsHtml.join('\n'); } @@ -395,13 +391,6 @@ function getDiagnosticsFileName(buildDir: string, type: string) { } -export function getSystemInfo() { - const systemData: string[] = []; - - return systemData; -} - - function isMeaningfulLine(line: string) { if (line) { line = line.trim(); diff --git a/src/util/logger.ts b/src/util/logger.ts index ecbc7bc2..c4437c45 100644 --- a/src/util/logger.ts +++ b/src/util/logger.ts @@ -150,15 +150,31 @@ export class Logger { } /** - * Prints out a dim colored timestamp prefix. + * Prints out a dim colored timestamp prefix, with optional color + * and bold message. */ - static info(...msg: any[]) { - const lines = Logger.wordWrap(msg); + static info(msg: string, color?: string, bold?: boolean) { + const lines = Logger.wordWrap([msg]); if (lines.length) { let prefix = timePrefix(); - lines[0] = chalk.dim(prefix) + lines[0].substr(prefix.length); + let lineOneMsg = lines[0].substr(prefix.length); + if (color) { + lineOneMsg = (chalk)[color](lineOneMsg); + } + if (bold) { + lineOneMsg = chalk.bold(lineOneMsg); + } + lines[0] = chalk.dim(prefix) + lineOneMsg; } - lines.forEach(line => { + lines.forEach((line, lineIndex) => { + if (lineIndex > 0) { + if (color) { + line = (chalk)[color](line); + } + if (bold) { + line = chalk.bold(line); + } + } console.log(line); }); } @@ -333,16 +349,6 @@ function memoryUsage() { } -export function getAppScriptsVersion() { - let rtn = ''; - try { - const packageJson = readJSONSync(join(__dirname, '..', '..', 'package.json')); - rtn = packageJson.version || ''; - } catch (e) {} - return rtn; -} - - export interface Diagnostic { level: string; syntax: string;