Skip to content

Instantly share code, notes, and snippets.

@mdecorte
Last active July 1, 2022 06:30
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mdecorte/d539604286f7f15d6bc8f245a6c5c37d to your computer and use it in GitHub Desktop.
Save mdecorte/d539604286f7f15d6bc8f245a6c5c37d to your computer and use it in GitHub Desktop.
Script to create a useful asset-manifest file for CRA-2 that only contains paths to assets needed on page load
const fs = require('fs')
const path = require('path')
// for CRA-2 un-comment line 5 and comment line 6
// const assetManifest = require('./build/asset-manifest.json')
const assetManifest = require('./build/asset-manifest.json')[files]
const indexFilePath = path.join(__dirname, 'build/index.html')
const BUILD_PATH = path.join(__dirname, 'build/useful-asset-manifest.json')
// Filter paths that exists in the create-react-app generated `build/asset-manifest.json` and use the path from that file because that can contain the "homepage" prefix from package.json.
const filterAssetManifestPaths = filePaths =>
filePaths.reduce((y, x) => {
Object.keys(assetManifest).forEach(key => {
if (
assetManifest[key].includes(x) &&
assetManifest[key].split('.').reverse()[0] !== 'map'
) {
y.push(assetManifest[key])
}
})
return y
}, [])
// Read and process the create-react-app generated `build/index.html`
fs.readFile(indexFilePath, 'utf8', (err, htmlData) => {
if (err) {
return console.error('err', err)
}
// Get array (or null) with js & css paths from the create-react-app generated `build/index.html`
const js = htmlData.match(/(\/static\/js\/).*?(.js)/gim)
const css = htmlData.match(/(\/static\/css\/).*?(.css)/gim)
// Create the useful-asset-manifest.json object
// NOTE: if you want to include the paths that don't exist in `build/asset-manifest.json` leave out `filterAssetManifestPaths(css/js)`.
const assets = {
css: Array.isArray(css) ? filterAssetManifestPaths(css) : [],
js: Array.isArray(js) ? filterAssetManifestPaths(js) : [],
}
fs.writeFileSync(BUILD_PATH, JSON.stringify(assets))
})
// Outputs (in the correct order) something like:
// build/useful-asset-manifest.json
//
// {
// "css": [
// "/static/css/2.1b02c459.chunk.css",
// "/static/css/main.f8da7d89.chunk.css"
// ],
// "js": [
// "/static/js/runtime~main.3e5a2071.js",
// "/static/js/2.5717aa4d.chunk.js",
// "/static/js/main.8363db78.chunk.js"
// ]
// }
@mdecorte
Copy link
Author

mdecorte commented May 3, 2019

Note 1: This is for "react-scripts": 2.*, will break from 3.*.

Note 2: If you run into trouble when using it in your ci/cd pipeline because of the relative path for const assetManifest = require('./build/asset-manifest.json') you can change that to const assetManifestPath = path.join(__dirname, 'build/asset-manifest.json') and then use something like

const manifest = fs.readFileSync(assetManifestPath)
const json = JSON.parse(manifest)

inside the fs.readFile cb function

Update Note 3: By default CRA puts a small chunk of webpack runtime logic inline in your generated index.html file. This can be solved by adding INLINE_RUNTIME_CHUNK=false to your .env or as a pipeline variable. See https://facebook.github.io/create-react-app/docs/advanced-configuration (bottom part about INLINE_RUNTIME_CHUNK)

If you need help getting it running, just @mdecorte me here :)

@scesbron
Copy link

scesbron commented May 3, 2019

With "react-scripts": 3.* just change line 3 to const assetManifest = require('./build/asset-manifest.json')['files'];.

To handle cases where there is a "homepage": "something" line in your package.json, the check in existsInAssetManifest function can be assetManifest[key].includes(filePath) instead of assetManifest[key] === filePath.

@mdecorte
Copy link
Author

mdecorte commented May 6, 2019

For 3.0.0 that would work but it might still change in other versions according to this PR and some issue threads.
You're totally right about the second point, I've actually broke that by updating the gist. Will change back to assetManifest[key].includes(filePath). Thanks for the heads-up :)

@erikhansen
Copy link

@mdecorte Thanks for sharing! I forked and edited this Gist and made two changes:

  1. Changed Gist to support CRA-3 by looking for files in the "files" key
  2. Changed from Typescript to Javascript (just added semicolons to the line endings)

@mdecorte
Copy link
Author

@erikhansen No problem :)

  1. I added the [files] suggestion by @scesbron. Now just un-comment 1 of the 2 lines of line 4-7.
  2. This is actually regular JS, just without semicolons. If you want those you can beautify the script in your editor. Or leave it as is, works fine ;) Have fun with it!

@erikhansen
Copy link

@mdecorte Looks great!

@wiziple
Copy link

wiziple commented Jun 25, 2019

Thanks for the script!
I just found out once we use code split feature (https://reactjs.org/docs/code-splitting.html), there will be an additional script line in the index.html and the app does not work without it, which means we also need to grab the whole minified script code and add it to our own html to make the app works.

<script>!function(i){function e(e){for(var t,r,n=e[0],o=e[1],a=e[2],c=0,u=[];c<n.length;c++)r=n[c],s[r]&&u.push(s[r][0]),s[r]=0;for(t in o)Object.prototype.hasOwnProperty.call(o,t)&&(i[t]=o[t]);for(h&&h(e);u.length;)u.shift()();return l.push.apply(l,a||[]),f()}function f(){for(var e,t=0;t<l.length;t++){for(var r=l[t],n=!0,o=1;o<r.length;o++){var a=r[o];0!==s[a]&&(n=!1)}n&&(l.splice(t--,1),e=p(p.s=r[0]))}return e}var r={},d={2:0},s={2:0},l=[];function p(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return i[e].call(t.exports,t,t.exports,p),t.l=!0,t.exports}p.e=function(l){var e=[];d[l]?e.push(d[l]):0!==d[l]&&{4:1,5:1,6:1,7:1,8:1,9:1,10:1,11:1}[l]&&e.push(d[l]=new Promise(function(e,n){for(var t="static/css/"+({}[l]||l)+"."+{0:"31d6cfe0",4:"aa1216c9",5:"ed86b553",6:"923398d8",7:"8e766d43",8:"61585836",9:"a3e69163",10:"4dcb6126",11:"fff9ec18",12:"31d6cfe0",13:"31d6cfe0",14:"31d6cfe0",15:"31d6cfe0"}[l]+".chunk.css",o=p.p+t,r=document.getElementsByTagName("link"),a=0;a<r.length;a++){var c=(i=r[a]).getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(c===t||c===o))return e()}var u=document.getElementsByTagName("style");for(a=0;a<u.length;a++){var i;if((c=(i=u[a]).getAttribute("data-href"))===t||c===o)return e()}var f=document.createElement("link");f.rel="stylesheet",f.type="text/css",f.onload=e,f.onerror=function(e){var t=e&&e.target&&e.target.src||o,r=new Error("Loading CSS chunk "+l+" failed.\n("+t+")");r.request=t,delete d[l],f.parentNode.removeChild(f),n(r)},f.href=o,document.getElementsByTagName("head")[0].appendChild(f)}).then(function(){d[l]=0}));var r=s[l];if(0!==r)if(r)e.push(r[2]);else{var t=new Promise(function(e,t){r=s[l]=[e,t]});e.push(r[2]=t);var n,a=document.createElement("script");a.charset="utf-8",a.timeout=120,p.nc&&a.setAttribute("nonce",p.nc),a.src=p.p+"static/js/"+({}[l]||l)+"."+{0:"c32e5a9e",4:"c0d86804",5:"ab0e257b",6:"8cad03ff",7:"d9b49386",8:"bd4ed7cc",9:"9d8a089c",10:"46e198d3",11:"1c92bbaf",12:"3292b4c9",13:"aa42f2b8",14:"a0890ec1",15:"116bd504"}[l]+".chunk.js",n=function(e){a.onerror=a.onload=null,clearTimeout(c);var t=s[l];if(0!==t){if(t){var r=e&&("load"===e.type?"missing":e.type),n=e&&e.target&&e.target.src,o=new Error("Loading chunk "+l+" failed.\n("+r+": "+n+")");o.type=r,o.request=n,t[1](o)}s[l]=void 0}};var c=setTimeout(function(){n({type:"timeout",target:a})},12e4);a.onerror=a.onload=n,document.head.appendChild(a)}return Promise.all(e)},p.m=i,p.c=r,p.d=function(e,t,r){p.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},p.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},p.t=function(t,e){if(1&e&&(t=p(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(p.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)p.d(r,n,function(e){return t[e]}.bind(null,n));return r},p.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return p.d(t,"a",t),t},p.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},p.p="/amelie/",p.oe=function(e){throw console.error(e),e};var t=window.webpackJsonp=window.webpackJsonp||[],n=t.push.bind(t);t.push=e,t=t.slice();for(var o=0;o<t.length;o++)e(t[o]);var h=n;f()}([])</script>

@mdecorte
Copy link
Author

@wiziple No problem. This can be solved by adding INLINE_RUNTIME_CHUNK=false to your .env or as a pipeline variable. See https://facebook.github.io/create-react-app/docs/advanced-configuration (bottom part about INLINE_RUNTIME_CHUNK)

@wiziple
Copy link

wiziple commented Jun 26, 2019

@mdecorte huh thanks! How come did i miss this? Let’s keep the comment for people like me 😆

@mdecorte
Copy link
Author

The original Github issue 👈

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment