[{"data":1,"prerenderedAt":244},["ShallowReactive",2],{"DefaultLayouten":3,"language-blog-slug-super-fast-npm-install-on-github-actions-i18n-slugs":134,"language-blog-slug-en-super-fast-npm-install-on-github-actions":138},{"app":4,"menu":31,"footer":66},{"githubUrl":5,"youtubeUrl":6,"linkedinUrl":7,"phoneNumber":8,"emailAddress":9,"legal":10,"addresses":20},"https:\u002F\u002Fgithub.com\u002Fvoorhoede\u002F","https:\u002F\u002Fwww.youtube.com\u002Fchannel\u002FUCzHuhQVYFRixtQN2-swcuGg","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fde-voorhoede","+31 20 2610 954","post@voorhoede.nl",[11,14,17],{"title":12,"value":13},"KvK","56017235",{"title":15,"value":16},"BTW","NL851944620B01",{"title":18,"value":19},"IBAN","NL14TRIO0320142078",[21,26],{"address":22,"city":23,"googleMapsLink":24,"postalCode":25},"Koivistokade 70","Amsterdam","https:\u002F\u002Fwww.google.com\u002Fmaps\u002Fplace\u002FDe+Voorhoede+%7C+Front-end+Development\u002F@52.396847,4.8700823,17z\u002Fdata=!3m1!4b1!4m5!3m4!1s0x47c5e21d502d2d59:0xbf570944a96ebf45!8m2!3d52.347647!4d4.8502154","1013 BB",{"address":27,"city":28,"googleMapsLink":29,"postalCode":30},"Koornmarkt 22","Delft","https:\u002F\u002Fwww.google.nl\u002Fmaps\u002Fplace\u002FKoornmarkt+22,+2611+EG+Delft\u002F@52.0093477,4.3573054,17z\u002F","2611 EG",{"title":32,"callToActions":33,"links":39},"Site Menu",[34],{"id":35,"title":36,"link":37},"163140902","Contact",{"__typename":38},"ContactRecord",[40,46,51,56,61],{"id":41,"title":42,"link":43},"163140904","Impact",{"__typename":44,"slug":45},"PageRecord","impact",{"id":47,"title":48,"link":49},"163140905","Services",{"__typename":50},"ServiceOverviewRecord",{"id":52,"title":53,"link":54},"163140906","Cases",{"__typename":55},"CaseOverviewRecord",{"id":57,"title":58,"link":59},"163140908","About us",{"__typename":44,"slug":60},"about-us",{"id":62,"title":63,"link":64},"d6WdFJq2SOuc3dWtpibbXQ","Work at",{"__typename":44,"slug":65},"work-at",{"links":67,"copyrightTitle":93,"copyrightLabel":94,"copyrightLink":95,"privacyTitle":96,"privacyLabel":97,"privacyLink":98,"certificatesGrid":99},[68,71,74,77,82,85,88],{"id":69,"title":42,"link":70},"144185264",{"__typename":44,"slug":45},{"id":72,"title":48,"link":73},"144185265",{"__typename":50},{"id":75,"title":53,"link":76},"144185266",{"__typename":55},{"id":78,"title":79,"link":80},"144185267","Blog",{"__typename":81},"BlogPostOverviewRecord",{"id":83,"title":58,"link":84},"144185268",{"__typename":44,"slug":60},{"id":86,"title":36,"link":87},"144185269",{"__typename":38},{"id":89,"title":90,"link":91},"144185270","FAQ",{"__typename":44,"slug":92},"faq","Creative Commons licence and disclaimer","CC BY 4.0","https:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby\u002F4.0\u002F","De Voorhoede privacy statement (pdf)","Privacy statement","https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1763455455-vh-isms-006-privacy-statement-de-voorhoede-en.pdf",[100,112,123],{"id":101,"image":102,"link":107},"Xq4bBfg_TZ6Fkjax9mkbLQ",{"url":103,"alt":104,"width":105,"height":106},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1687353463-b-corp-logo-black-rgb.png","B Corp logo",404,680,{"__typename":108,"id":109,"title":110,"url":111},"ExternalLinkRecord","fGW1ak8XQYaYDLkBSyncog","B Corp","https:\u002F\u002Fwww.bcorporation.net\u002Fen-us\u002Ffind-a-b-corp\u002Fcompany\u002Fde-voorhoede\u002F",{"id":113,"image":114,"link":119},"c5mCXRTiSraRIB25fw1p7Q",{"url":115,"alt":116,"width":117,"height":118},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1687353461-dda-boxlogo-black.png","Dutch Digital Agencies logo",627,480,{"__typename":108,"id":120,"title":121,"url":122},"P6Jh7B0cTv2cKyNEeKVWVQ","Dutch Digital Agencies","https:\u002F\u002Fdutchdigitalagencies.com\u002Fleden\u002Fde-voorhoede\u002F",{"id":124,"image":125,"link":129},"MT5SCyNxSTSr_v5eeATMZw",{"url":126,"alt":127,"width":128,"height":128},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1775730283-dnv.png","DNV logo",518,{"id":130,"title":131,"link":132},"BRtNB5HnT5i-7HkA8IYzBw","DIV",{"__typename":44,"slug":133},"impact\u002Fdigitale-producten-privacy-by-design",[135],{"locale":136,"value":137},"en","super-fast-npm-install-on-github-actions",{"page":139},{"slug":137,"i18nSlugs":140,"social":142,"title":143,"subtitle":79,"isArchived":147,"headerIllustration":148,"date":149,"authors":150,"introTitle":159,"items":160,"pivots":231,"relatedBlogPosts":242,"tags":243,"onMountedScript":153,"onUnmountedScript":153},[141],{"locale":136,"value":137},{"title":143,"description":144,"image":145},"Super fast npm install on Github Actions","A guide to speed up installing Node.js dependencies in a CI environment.",{"url":146},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1614344050-super-fast-npm-installs-on-github-actions.jpg",false,null,"2021-02-26T01:00:00.000+01:00",[151],{"name":152,"lastName":153,"slug":154,"image":155},"Selwyn","","selwyn",{"url":156,"alt":148,"width":157,"height":158},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1683534636-placeholder.jpg",1235,1646,"Installing npm dependencies with Github Actions is a breeze. But it’s a slow breeze.",[161,165,169,173,177,182,185,189,193,196,200,203,207,217,220,224,227],{"__typename":162,"id":163,"title":153,"body":164},"TextSectionRecord","21066173","\u003Cp>As always, performance tweaking takes experimentation&mdash; but we got your back. We did the hard work, and have the numbers to prove it. With our 4-step approach, you can reduce a 16-second task to take only 2 seconds. 2 seconds! If the installation kicked off when you started reading this sentence, it&rsquo;s done right about&hellip; now. As a bonus, you&rsquo;re doing the world a favour: that&rsquo;s a 87.5% reduction of energy use. 🌳 ❤️ \u003C\u002Fp>",{"__typename":162,"id":166,"title":167,"body":168},"21066174","The baseline","\u003Cp>GitHub Actions make it easy to use external official actions like \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Factions\u002Fsetup-node\">setup-node\u003C\u002Fa> in a single line: \u003Ccode>- uses: actions\u002Fsetup-node@v2\u003C\u002Fcode>. Followed by running \u003Ccode>npm install\u003C\u002Fcode> like the setup-node readme suggests, takes care of Node.js and installing all needed dependencies.\u003C\u002Fp>",{"__typename":162,"id":170,"title":171,"body":172},"21066175","Use a manifestation of the manifest","\u003Cp>The biggest win in speed and efficiency is achieved by installing dependencies from the package lock file: \u003Ccode>package-lock.json\u003C\u002Fcode>. Npm generates this file by default, and by using the command \u003Ccode>npm ci\u003C\u002Fcode>, only the lock file is used during install. Since it contains a resolved dependency tree, npm can skip a whole lot of steps.\u003C\u002Fp>",{"__typename":162,"id":174,"title":175,"body":176},"21066176","Caching on GitHub","\u003Cp>Secondly, caching dependencies saves download time otherwise needed for each package. All cached dependencies are fetched in one go from GitHub, using a \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Factions\u002Fcache\u002F\">cache action\u003C\u002Fa>:\u003C\u002Fp>",{"__typename":178,"id":179,"language":180,"body":181},"CodeBlockRecord","21066177","yaml","- name: Cache dependencies\n  uses: actions\u002Fcache@v2\n  with:\n    path: ~\u002F.npm\n    key: npm-${{ hashFiles('package-lock.json') }}\n    restore-keys: npm-",{"__typename":162,"id":183,"title":153,"body":184},"21066178","\u003Cp>By using this cache npm copies dependencies from this cache instead of downloading them. If \u003Ccode>package-lock.json\u003C\u002Fcode> changes, the then outdated GitHub cache is still used as the base for a new GitHub cache, under a new key, because of the \u003Ccode>restore-keys\u003C\u002Fcode> option.\u003C\u002Fp>",{"__typename":162,"id":186,"title":187,"body":188},"21066179","No side effects allowed","\u003Cp>The final small win is ignoring installation scripts with the \u003Ccode>--ignore-scripts\u003C\u002Fcode> flag. This could break certain dependencies that use installation scripts.\u003Csup>\u003Ca href=\"#fn1\" id=\"r1\">[1]\u003C\u002Fa>\u003C\u002Fsup> Instead of crossing fingers and giving it a try you can list native dependencies that might need these scripts with the \u003Ca href=\"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fnative-modules\">native-modules CLI\u003C\u002Fa>.\u003C\u002Fp>",{"__typename":162,"id":190,"title":191,"body":192},"21066180","The result: faster workflow","\u003Cp>Putting these three together in an example workflow, gives:\u003C\u002Fp>",{"__typename":178,"id":194,"language":180,"body":195},"21066181","name: Continuous integration\n\non: pull_request\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v2\n\n      - name: Setup Node.js\n        uses: actions\u002Fsetup-node@v2\n        with:\n          node-version: '14'\n\n      - name: Cache dependencies\n        uses: actions\u002Fcache@v2\n        with:\n          path: ~\u002F.npm\n          key: npm-${{ hashFiles('package-lock.json') }}\n          restore-keys: npm-\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      # run npm test, build, lint, etc.",{"__typename":162,"id":197,"title":198,"body":199},"21066182","An even faster alternative","\u003Cp>Combining \u003Ccode>npm ci\u003C\u002Fcode> with caching of \u003Ccode>~\u002F.npm\u003C\u002Fcode> is recommended by GitHub and npm, however an interesting alternative is caching the \u003Ccode>node_modules\u003C\u002Fcode> directory. This is not suggested because it contains potential footguns:\u003C\u002Fp>\n\u003Cp>First off, combining a \u003Ccode>node_modules\u003C\u002Fcode> directory with \u003Ccode>npm ci\u003C\u002Fcode> is slow since the latter will first remove \u003Ccode>node_modules\u003C\u002Fcode> before installing dependencies. Secondly, when running multiple Node.js versions in your CI and\u002For when changing the Node version that runs on your CI, old native modules might break.\u003C\u002Fp>\n\u003Cp>So given that no installation scripts are used, you can completely skip the installation step! To prevent restoring \u003Ccode>node_modules\u003C\u002Fcode> when the cache changed, the cache action is given no \u003Ccode>restore-keys\u003C\u002Fcode>. In other words: the cache is only used if there is an exact key match:\u003C\u002Fp>",{"__typename":178,"id":201,"language":180,"body":202},"21066183","- name: Cache dependencies\n  id: cache\n  uses: actions\u002Fcache@v3\n  with:\n    path: .\u002Fnode_modules\n    key: modules-${{ hashFiles('package-lock.json') }}\n\n- name: Install dependencies\n  if: steps.cache.outputs.cache-hit != 'true'\n  run: npm ci --ignore-scripts",{"__typename":162,"id":204,"title":205,"body":206},"21066184","Measure all the things","\u003Cp>Step by step measuring the installation time, including restoring the cache, on a project with a thousand (indirect) dependencies gives the following:\u003C\u002Fp>",{"__typename":208,"id":209,"image":210,"caption":153,"fullWidth":215,"captionPosition":216},"ImageRecord","21066185",{"url":211,"alt":212,"width":213,"height":214},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1613556138-vh-bar-chart.svg","bar chart showing difference between no cache, exact cache and changed cache",860,450,true,"bottom",{"__typename":162,"id":218,"title":153,"body":219},"21066186","\u003Cp>Changing the cache was done by modifying \u003Ccode>package-lock.json\u003C\u002Fcode>, using the alternative method with an exact key shows the same timing as expected with no cache.\u003C\u002Fp>",{"__typename":162,"id":221,"title":222,"body":223},"21066187","Our verdict","\u003Cp>The first approach shows a better approach for a variety of cases, a fit-all solution if you will. The alternative is definitely a lot faster if the workflow is often ran without package lock changes. Keep in mind that GitHub does remove caches that have not been accessed within the last week. So choose wisely, depending on the project, the stage of development and the regularity of workflow runs. Happy Github Actioning!\u003C\u002Fp>",{"__typename":162,"id":225,"title":153,"body":226},"55725904","\u003Cp>\u003Cem>Update 2021-09-06\u003C\u002Fem>: The \u003Ccode>setup-node\u003C\u002Fcode> action now includes caching which I personally do not like, it goes against doing one thing and doing it well, though one could argue it is hiding an implementation detail. This is not enabled by default so all of the above still works like described. If enabled it uses the caching action internally on \u003Ccode>~\u002F.npm\u002Fcode\u003C\u002Fcode>.\u003C\u002Fp>",{"__typename":162,"id":228,"title":229,"body":230},"21139209","Footnotes","\u003Cp id=\"fn1\">\u003Ca href=\"#r1\">[1]\u003C\u002Fa> Installation scripts are only necessary for native packages that do not pre-bundle compiled code using the N-API. These scripts are often abused to log information about a package. It is also a \u003Ca href=\"https:\u002F\u002Fwww.bleepingcomputer.com\u002Fnews\u002Fsecurity\u002Fresearcher-hacks-over-35-tech-firms-in-novel-supply-chain-attack\u002F\">convenient place to spread malware\u003C\u002Fa>.\u003C\u002Fp>",[232],{"title":233,"body":234,"links":235,"mailchimpValue":153,"mailchimpName":153,"mailchimpId":153,"formType":153,"contactPerson":148},"Also in love with the web?","\u003Cp>For us, that’s about technology and user experience. Fast, available for all, enjoyable to use. And fun to build. This is how our team bands together, adhering to the same values, to make sure we achieve a solid result for clients both large and small. Does that fit you?\u003C\u002Fp>\n",[236],{"__typename":237,"id":238,"title":239,"link":240},"InternalLinkRecord","163140992","Join our team",{"__typename":44,"slug":241},"jobs",[],[],1776256145448]