[{"data":1,"prerenderedAt":586},["ShallowReactive",2],{"blog-post-jenkins-pipeline-for-frontend-apps":3},{"id":4,"title":5,"body":6,"created":578,"description":5,"extension":579,"isVisible":360,"meta":580,"navigation":360,"path":581,"seo":582,"stem":583,"tags":584,"__hash__":585},"blog\u002Fblog\u002Fjenkins-pipeline-for-frontend-apps.md","A complete Jenkinsfile example for frontend applications",{"type":7,"value":8,"toc":568},"minimark",[9,14,18,21,24,46,50,53,61,64,68,76,87,94,101,104,110,121,125,128,134,141,144,150,153,157,166,173,179,183,190,193,207,210,216,219,226,230,233,240,246,253,259,262,277,281,292,564],[10,11,13],"h2",{"id":12},"about","About",[15,16,17],"p",{},"After working on developing the build pipelines for my company, I noticed how modern building tools can help with the automation of important tasks in frontend development.",[15,19,20],{},"I also migrated my personal website to use Jenkins for automatically building and deploying it. This guide explains how I was able to do it.",[15,22,23],{},"The technology stack I am using for my website is the following",[25,26,27,31,34,37,40,43],"ul",{},[28,29,30],"li",{},"Vue.js",[28,32,33],{},"Typescript",[28,35,36],{},"NPM",[28,38,39],{},"Eslint",[28,41,42],{},"Jest",[28,44,45],{},"Cypress",[10,47,49],{"id":48},"why-jenkinsfile","Why Jenkinsfile",[15,51,52],{},"I personally prefer to give freedom to developers and let them modify the building process as they need. Each git project also contains the Jenkinsfile in the root folder. We setup a Jenkins project as following:",[25,54,55,58],{},[28,56,57],{},"Add a Pipeline option to job",[28,59,60],{},"Set \"Pipeline Script from SCM\"",[15,62,63],{},"I have worked in the past for projects where the building process and Jenkins were part of the admin team. As frontend developers we had difficulties to introduce new processes and steps in the pipeline and it constrained us to use new technologies we wanted.",[10,65,67],{"id":66},"install-dependencies","Install dependencies",[15,69,70,71,75],{},"It is a common ",[72,73,74],"em",{},"but wrong"," practice to install dependencies in CI pipelines using the following command:",[77,78,83],"pre",{"className":79,"code":81,"language":82},[80],"language-text","npm install\n","text",[84,85,81],"code",{"__ignoreMap":86},"",[15,88,89,90,93],{},"This is actually wrong, since npm install ",[72,91,92],{},"may"," install a newer minor release for either your dependencies and\u002For sub-dependencies.",[15,95,96,97,100],{},"In a nutshell: This way it is possible that your build works on your developer laptop but it fails to compile in Jenkins, simply because ",[84,98,99],{},"npm install"," installed a minor sub-dependency that had a breaking change.",[15,102,103],{},"In order to avoid this issue simple use:",[77,105,108],{"className":106,"code":107,"language":82},[80],"npm ci\n",[84,109,107],{"__ignoreMap":86},[15,111,112,113,116,117,120],{},"and add ",[84,114,115],{},"package-lock.json"," in GIT. Believe me, this is a life savior! It will ",[72,118,119],{},"always"," install the exact package and versions listed in this file.",[10,122,124],{"id":123},"audit-dependencies","Audit dependencies",[15,126,127],{},"There is no need for anything else than:",[77,129,132],{"className":130,"code":131,"language":82},[80],"npm audit --parseable --production\n",[84,133,131],{"__ignoreMap":86},[15,135,136,137,140],{},"This way we are sure that main dependencies have no security issues. I am not checking for any vulnerabilities in dev packages since they are not directly used and it is not possible at the moment to define a ",[72,138,139],{},"threshold"," to exclude minor issues.",[15,142,143],{},"I am also checking if packages are outdated by executing the command:",[77,145,148],{"className":146,"code":147,"language":82},[80],"npm outdated || exit 0\n",[84,149,147],{"__ignoreMap":86},[15,151,152],{},"This way the pipeline won't fail if the packages are outdated. It is also not possible here to define a threshold here.",[10,154,156],{"id":155},"check-linting","Check linting",[15,158,159,161,162,165],{},[72,160,39],{}," is my favorite option for linting and format checks, since the\nintegration with IDEs and console command is ",[72,163,164],{},"almost perfect","!",[15,167,168,169,172],{},"If you are using ",[72,170,171],{},"vue-cli"," to setup your project, we simply need to execute in Jenkinsfile:",[77,174,177],{"className":175,"code":176,"language":82},[80],"npm run lint\n",[84,178,176],{"__ignoreMap":86},[10,180,182],{"id":181},"unit-testing","Unit testing",[15,184,185,186,189],{},"A unit testing ",[72,187,188],{},"with coverage"," is another safety lock for capturing bugs before entering\nproduction.",[15,191,192],{},"The following software has been used for this step:",[25,194,195,198,201,204],{},[28,196,197],{},"jest",[28,199,200],{},"@vue\u002Fcli-plugin-unit-jest",[28,202,203],{},"jest-junit",[28,205,206],{},"Coberture jenkins plugin",[15,208,209],{},"Simple execute the following:",[77,211,214],{"className":212,"code":213,"language":82},[80],"npm run test:unit -- --ci --coverage\n",[84,215,213],{"__ignoreMap":86},[15,217,218],{},"After successfully integrating Jenkins with Jest you will get a nice overview of the test\nresults directly into Jenkins:",[15,220,221],{},[222,223],"img",{"alt":224,"src":225},"Screenshot 1","https:\u002F\u002Fdev-to-uploads.s3.amazonaws.com\u002Fi\u002Fgpc91qroet7m9r53yjn9.jpg",[10,227,229],{"id":228},"e2e-testing","E2E testing",[15,231,232],{},"I used an end-to-end testing as a smoke test for my website. This way I am sure that the website was deployed successfully.",[15,234,235,236,239],{},"I chose for this case ",[72,237,238],{},"nightwatch"," and installed it using vue-cli:",[77,241,244],{"className":242,"code":243,"language":82},[80],"vue add e2e-nightwatch\n",[84,245,243],{"__ignoreMap":86},[15,247,248,249,252],{},"After building the application, I made the ",[72,250,251],{},"dist"," directory as the root directory for my web server.\nOnce it is successfully build, I can execute the smoke test as following:",[77,254,257],{"className":255,"code":256,"language":82},[80],"npm run test:e2e -- --headless --url https:\u002F\u002Fwww.trelobifteki.com --config video=false\n",[84,258,256],{"__ignoreMap":86},[15,260,261],{},"Some points there:",[25,263,264,271,274],{},[28,265,266,267,270],{},"I used ",[84,268,269],{},"--headless"," since this is an automated test (no interaction is required)",[28,272,273],{},"I am using the actual URL of my website for end-to-end testing",[28,275,276],{},"I deactivated video recording (since I don't need it)",[10,278,280],{"id":279},"complete-example","Complete example",[15,282,283,284,291],{},"This is the actual pipeline I am using in my ",[285,286,290],"a",{"href":287,"rel":288},"https:\u002F\u002Fgithub.com\u002Ftrelo-bifteki\u002Fwww.trelobifteki.com\u002F",[289],"nofollow","project",". Feel free to check it out and experiment with it!",[77,293,297],{"className":294,"code":295,"language":296,"meta":86,"style":86},"language-groovy shiki shiki-themes github-dark","pipeline {\n  agent any\n  stages {\n    stage('Check for vulnerabilities') {\n      steps {\n        sh 'npm audit --parseable --production'\n        sh 'npm outdated || exit 0'\n      }\n    }\n\n    stage('download dependencies') {\n      steps {\n        sh 'npm ci'\n      }\n    }\n\n    stage('Check linting') {\n      steps {\n        sh 'npm run lint'\n      }\n    }\n\n    stage('Check unit:test') {\n      steps {\n        sh 'npm run test:unit -- --ci --coverage'\n      }\n      post {\n        always {\n          junit 'junit.xml'\n          cobertura coberturaReportFile: 'coverage\u002Fcobertura-coverage.xml'\n        }\n      }\n    }\n\n    stage('Build') {\n      steps {\n        sh 'npm run build'\n      }\n    }\n\n    stage('Check unit:e2e') {\n      steps {\n        sh 'npm run test:e2e -- --headless --url https:\u002F\u002Fwww.trelobifteki.com --config video=false'\n      }\n    }\n  }\n}\n","groovy",[84,298,299,307,313,319,325,331,337,343,349,355,362,368,373,379,384,389,394,400,405,411,416,421,426,432,437,443,448,454,460,466,472,478,483,488,493,499,504,510,515,520,525,531,536,542,547,552,558],{"__ignoreMap":86},[300,301,304],"span",{"class":302,"line":303},"line",1,[300,305,306],{},"pipeline {\n",[300,308,310],{"class":302,"line":309},2,[300,311,312],{},"  agent any\n",[300,314,316],{"class":302,"line":315},3,[300,317,318],{},"  stages {\n",[300,320,322],{"class":302,"line":321},4,[300,323,324],{},"    stage('Check for vulnerabilities') {\n",[300,326,328],{"class":302,"line":327},5,[300,329,330],{},"      steps {\n",[300,332,334],{"class":302,"line":333},6,[300,335,336],{},"        sh 'npm audit --parseable --production'\n",[300,338,340],{"class":302,"line":339},7,[300,341,342],{},"        sh 'npm outdated || exit 0'\n",[300,344,346],{"class":302,"line":345},8,[300,347,348],{},"      }\n",[300,350,352],{"class":302,"line":351},9,[300,353,354],{},"    }\n",[300,356,358],{"class":302,"line":357},10,[300,359,361],{"emptyLinePlaceholder":360},true,"\n",[300,363,365],{"class":302,"line":364},11,[300,366,367],{},"    stage('download dependencies') {\n",[300,369,371],{"class":302,"line":370},12,[300,372,330],{},[300,374,376],{"class":302,"line":375},13,[300,377,378],{},"        sh 'npm ci'\n",[300,380,382],{"class":302,"line":381},14,[300,383,348],{},[300,385,387],{"class":302,"line":386},15,[300,388,354],{},[300,390,392],{"class":302,"line":391},16,[300,393,361],{"emptyLinePlaceholder":360},[300,395,397],{"class":302,"line":396},17,[300,398,399],{},"    stage('Check linting') {\n",[300,401,403],{"class":302,"line":402},18,[300,404,330],{},[300,406,408],{"class":302,"line":407},19,[300,409,410],{},"        sh 'npm run lint'\n",[300,412,414],{"class":302,"line":413},20,[300,415,348],{},[300,417,419],{"class":302,"line":418},21,[300,420,354],{},[300,422,424],{"class":302,"line":423},22,[300,425,361],{"emptyLinePlaceholder":360},[300,427,429],{"class":302,"line":428},23,[300,430,431],{},"    stage('Check unit:test') {\n",[300,433,435],{"class":302,"line":434},24,[300,436,330],{},[300,438,440],{"class":302,"line":439},25,[300,441,442],{},"        sh 'npm run test:unit -- --ci --coverage'\n",[300,444,446],{"class":302,"line":445},26,[300,447,348],{},[300,449,451],{"class":302,"line":450},27,[300,452,453],{},"      post {\n",[300,455,457],{"class":302,"line":456},28,[300,458,459],{},"        always {\n",[300,461,463],{"class":302,"line":462},29,[300,464,465],{},"          junit 'junit.xml'\n",[300,467,469],{"class":302,"line":468},30,[300,470,471],{},"          cobertura coberturaReportFile: 'coverage\u002Fcobertura-coverage.xml'\n",[300,473,475],{"class":302,"line":474},31,[300,476,477],{},"        }\n",[300,479,481],{"class":302,"line":480},32,[300,482,348],{},[300,484,486],{"class":302,"line":485},33,[300,487,354],{},[300,489,491],{"class":302,"line":490},34,[300,492,361],{"emptyLinePlaceholder":360},[300,494,496],{"class":302,"line":495},35,[300,497,498],{},"    stage('Build') {\n",[300,500,502],{"class":302,"line":501},36,[300,503,330],{},[300,505,507],{"class":302,"line":506},37,[300,508,509],{},"        sh 'npm run build'\n",[300,511,513],{"class":302,"line":512},38,[300,514,348],{},[300,516,518],{"class":302,"line":517},39,[300,519,354],{},[300,521,523],{"class":302,"line":522},40,[300,524,361],{"emptyLinePlaceholder":360},[300,526,528],{"class":302,"line":527},41,[300,529,530],{},"    stage('Check unit:e2e') {\n",[300,532,534],{"class":302,"line":533},42,[300,535,330],{},[300,537,539],{"class":302,"line":538},43,[300,540,541],{},"        sh 'npm run test:e2e -- --headless --url https:\u002F\u002Fwww.trelobifteki.com --config video=false'\n",[300,543,545],{"class":302,"line":544},44,[300,546,348],{},[300,548,550],{"class":302,"line":549},45,[300,551,354],{},[300,553,555],{"class":302,"line":554},46,[300,556,557],{},"  }\n",[300,559,561],{"class":302,"line":560},47,[300,562,563],{},"}\n",[565,566,567],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":86,"searchDepth":309,"depth":309,"links":569},[570,571,572,573,574,575,576,577],{"id":12,"depth":309,"text":13},{"id":48,"depth":309,"text":49},{"id":66,"depth":309,"text":67},{"id":123,"depth":309,"text":124},{"id":155,"depth":309,"text":156},{"id":181,"depth":309,"text":182},{"id":228,"depth":309,"text":229},{"id":279,"depth":309,"text":280},1609502092,"md",{},"\u002Fblog\u002Fjenkins-pipeline-for-frontend-apps",{"title":5,"description":5},"blog\u002Fjenkins-pipeline-for-frontend-apps",[],"Jyt5PTpXEPlv0_Wm5TwRXgC2Ja9D5TNRQfeJP160nuM",1786914024204]