facebook / create-react-app

Set up a modern web app by running one command.
https://create-react-app.dev
MIT License
102.54k stars 26.79k forks source link

Generate build without hashed file names. #821

Closed lgomez closed 7 years ago

lgomez commented 7 years ago

Hi all,

I'm trying this project out to simplify work on a web app that's to be embedded into a hardware device. For reasons I have no control over, the "embed" team is requesting I commit builds without hashed names. Is there a way to disable hashed names completely for the build without having to eject? This is the only thing I need to override and ejecting for that one reason seems excessive.

In case it matters, I also don't need to distribute sourcemap files in the build.

I'm happy to contribute to the docs if there is a way to do this.

Thank you,

gaearon commented 7 years ago

This is not supported and it’s unlikely we would support it. Generating hashes is the right default for a vast majority of cases. If for some reason you’d rather not have them generated, you would need to maintain your own Webpack config.

As an alternative to ejecting, you can fork react-scripts: #779.

lgomez commented 7 years ago

Thank you. I wasn't expecting you to add it. Just wanted to know if perhaps it was supported.

Thanks for the suggestions. I'll look into it.

jrzerr commented 7 years ago

A different way to handle this is to do a rename as a part of your build process with a new npm run script so you don't have to do an eject. I used the renamer npm library to do this for me.

npm install --save-dev renamer

Then in package.json scripts section I added some rename helpers:

    "build-rename": "npm run build-rename-js && npm run build-rename-css",
    "build-rename-js": "renamer --regex --find 'main\\.[^\\.]+\\.js' --replace 'main.js' build/static/js/*.js",
    "build-rename-css": "renamer --regex --find 'main\\.[^\\.]+\\.css' --replace 'main.css' build/static/css/*.css",

then you can modify your build script to do the rename post-build like this:

"build": "react-scripts build && npm run build-rename",

Anyways, this is just one way to do it. You will give up source maps unless you do a search and replace inside the js file, but in your specific case of putting on a mobile device that probably doesn't matter.

gaearon commented 7 years ago

Note that this will break code splitting (i.e import('./module').then(...)) because the hardcoded JS path in the bundle will be wrong.

ghost commented 7 years ago

Having changing hashes makes working in Visual Studio projects a bit annoying as you can't just update the files, you have to add the new ones and delete the old ones from the project file.

Previously I dealt with cache busting on the chunk files like this in my webpack.config:

    output: {
      filename: 'main.js',
      chunkFilename: 'main.[id].js?v=[hash]',
    },

Then in my HTML template I would add a cache busting query string like so: <script src="main.js?v<%=LastModified('main.js') %>"></script>

So adding an option to CRA to support something like this seems like a reasonably common use case.

StefanSchoof commented 6 years ago

Based on @jrzerr workaround, I added the replace inside the js and css file to get source maps working. For the change of the sourcemap url I used replace. I use this on a windows machine:

"scripts": {
    ...
    "build": "react-scripts-ts build&&npm run build-rename",
    "build-rename": "npm run build-rename-js&&npm run build-rename-css&&npm run build-fix-sourcemap",
    "build-rename-js": "renamer --regex --find \"main\\.[^\\.]+\\.\" --replace \"main.\" build\\static\\js\\*",
    "build-rename-css": "renamer --regex --find \"main\\.[^\\.]+\\.\" --replace \"main.\" build\\static\\css\\*",
    "build-fix-sourcemap": "npm run build-fix-sourcemap-js&&npm run build-fix-sourcemap-css",
    "build-fix-sourcemap-js": "replace \"# sourceMappingURL=main..*.js.map\" \"# sourceMappingURL=main.js.map\" build\\static\\js\\main.js",
    "build-fix-sourcemap-css": "replace \"# sourceMappingURL=main..*.map\" \"# sourceMappingURL=main.css.map\" build\\static\\css\\main.css"
  },
marcoc1712 commented 6 years ago

@StefanSchoof 👍 It works, but you should replace css and js also in index.html and service-worker.js in the main directory.

StefanSchoof commented 6 years ago

@marcoc1712 Thanks for pointing this out. In my setup I embed js in a bigger site and have no need for these files.

olgeorge commented 6 years ago

@gaearon what's your suggestion on using hashed file names together with cached CDNs?

I'm using S3 to store the app and CloudFront as a CDN. When I deploy a new version, the files with old hashes are removed and new files are added. However, since CloudFront caches index.html, it points to old non-existent files until it refreshes the cache. Thus, I'm breaking the website with each build, and even if I manually refresh the cache, the website still might be down for some time until the cache is refreshed. I could store all prior versions on S3 instead of deleting them, but that doesn't seem like a good long-term solution either.

Meemaw commented 6 years ago

@olgeorge did you solve this?

aftabnaveed commented 6 years ago

I am using CRA for server side rendering along with Airbnb Hypernova. Because my backend server is not in NodeJS and I need to point to the css file from my backend APP. It is very annoying to keep updating the css paths, therefore, file without hashes should be supported.

Sofianio commented 6 years ago

Here's what i did: in the file index.html I get the bundle name from asset-manifest.json then add the script with the bundle name

<script type="text/javascript">
        var bundle_name = "bundle.js"; // dev mode
        $.getJSON("/asset-manifest.json", function(json){ var bundle_name = json["main.js"]; })
        .always(function(){
            var bundle = document.createElement('script');
            bundle.setAttribute('type', "text/javascript");
            bundle.setAttribute('src', "/static/js/" + bundle_name);
            document.body.appendChild(bundle);
        });
</script>
lisandrolan commented 6 years ago

@Sofianio that solution helped you? I couldnt do that work! :(

aftabnaveed commented 6 years ago

I came up with a shell script which I execute instead of just yarn build

Place it in your site root to make it work. In my case the destination is ../../../public/js/main.js you may change it your desired destination.

#!/bin/bash
yarn build
cp -v build/static/js/main.*.js ../../../public/js/main.js
cp -v build/static/css/main.*.css ../../../public/css/main.css
Sofianio commented 6 years ago

@lisandrolan yes it worked for me.

@aftabnaveed your solution is good but it has a downside, if you make an update, the client might not receive it because of browser cache (if file name didn't change ... the browser uses cache) ... there's a reason why they add random numbers to the file name

chunhui2001 commented 6 years ago

$ yarn build

/# replace hash contents /# get hash definition in build/asset-manifest.json and replace hashed contents using awk and sed $ cat build/asset-manifest.json | grep -o -E "\.[0-9a-z]{8}\." | \ awk '{system("find ./build/static -exec sed -i \"\" -E \"s/\\" $0 "/./g\" {} \\; 2>/dev/null")}'

/# rename hashed files inside build/static $ find ./build/static -type f | grep -E "\.[0-9a-z]{8}\." | rename "s/.[a-z0-9]{8}././g"

DONE

taggartj commented 6 years ago

"build": "react-scripts build && yarn run build-dist", "build-dist": "cd build && cp static/js/.js main.js && cp static/css/.css main.css"

Kopatch commented 6 years ago

@taggartj your solution looks great. Just want to add to solution - for Windows users use
"build-dist": "cd build && copy static\\js\\*.js main.js && copy static\\css\\*.css main.css"

jamstooks commented 6 years ago

Because someone might find this useful...

I opted to do this in the application layer at startup so that I'm not affecting load time. I'm using django, but this could apply to anyone using another application layer (like rails) to serve their react app. I simply added the following to my settings.py:

REACT_BUILD_DIR = os.path.join(BASE_DIR, 'frontend', 'build')

# Pull the js and css filenames from the current build
path = os.path.join(REACT_BUILD_DIR, "asset-manifest.json")
with open(path) as f:
    data = json.load(f)

REACT_CSS_PATH = data['main.css'].replace('static/', '')
REACT_JS_PATH = data['main.js'].replace('static/', '')

I then simply pass REACT_CSS_PATH and REACT_JS_PATH to the template that renders the react app.

jubalm commented 6 years ago

In case someone finds this helpful...

Built a component mycomponent that is completely detached from the main application, even styled on it's own with emotionjs so no CSS, so I just created a file postbuild.sh and set it's permission chmod +x postbuild.sh

#!/bin/sh

# copy files | https://tosbourn.com/copy-rename-multiple-files-linux/
for i in ./build/static/js/main.*; do cp $i `echo $i | sed "s/main.*.js/mycomponent.js/g"`; done

# update sourcemap | sed osx backup - https://stackoverflow.com/a/25486705/1072776 
sed -i "" -e "s/sourceMappingURL=main.*.js/sourceMappingURL=mycomponent.js/g" 
./build/static/js/mycomponent.js

Now to fire it automatically after build, i add this to package.json

{
  "scripts": {
    // ... create react app stuff
    "postbuild": "./postbuild.sh"
  }
}
josefbetancourt commented 6 years ago

I accomplished this with a Groovy language script. But, I wonder if react-app-rewired could be used to accomplish this rename and fix of map references.

essar05 commented 6 years ago

As some of the solutions above were a bit outdated and incomplete, I came up with these scripts for package.json in case anyone finds them useful:

{
    "build": "react-scripts build && npm run build-rename",
    "build-rename": "npm run build-rename-js && npm run build-rename-css && npm run build-fix-references",
    "build-rename-js": "renamer --find \"/main\\.[^\\.]+\\./i\" --replace \"main.\" build\\static\\js\\*",
    "build-rename-css": "renamer --find \"/main\\.[^\\.]+\\./i\" --replace \"main.\" build\\static\\css\\*",
    "build-fix-references": "npm run build-fix-sourcemap && npm run build-fix-index && npm run build-fix-serviceworker && npm run build-fix-assetmanifest",
    "build-fix-sourcemap": "npm run build-fix-sourcemap-js && npm run build-fix-sourcemap-css",
    "build-fix-sourcemap-js": "replace-in-file \"/sourceMappingURL=main\\.[^\\.]+\\.js\\.map/i\" \"sourceMappingURL=main.js.map\" build/static/js/main.js --isRegex",
    "build-fix-sourcemap-css": "replace-in-file \"/sourceMappingURL=main\\.[^\\.]+\\.css\\.map/i\" \"sourceMappingURL=main.css.map\" build/static/css/main.css --isRegex",
    "build-fix-index": "npm run build-fix-index-js && npm run build-fix-index-css",
    "build-fix-index-js": "replace-in-file \"/main\\.[^\\.]+\\.js/i\" \"main.js?v=%npm_package_version%\" build/index.html --isRegex",
    "build-fix-index-css": "replace-in-file \"/main\\.[^\\.]+\\.css/i\" \"main.css?v=%npm_package_version%\" build/index.html --isRegex",
    "build-fix-serviceworker": "npm run build-fix-serviceworker-js && npm run build-fix-serviceworker-css",
    "build-fix-serviceworker-js": "replace-in-file \"/main\\.[^\\.]+\\.js/i\" \"main.js\" build/service-worker.js --isRegex",
    "build-fix-serviceworker-css": "replace-in-file \"/main\\.[^\\.]+\\.css/i\" \"main.css\" build/service-worker.js --isRegex",
    "build-fix-assetmanifest": "npm run build-fix-assetmanifest-js && npm run build-fix-assetmanifest-css && npm run build-fix-assetmanifest-js-map && npm run build-fix-assetmanifest-css-map",
    "build-fix-assetmanifest-js": "replace-in-file \"/main\\.[^\\.]+\\.js/i\" \"main.js\" build/asset-manifest.json --isRegex",
    "build-fix-assetmanifest-css": "replace-in-file \"/main\\.[^\\.]+\\.css/i\" \"main.css\" build/asset-manifest.json --isRegex",
    "build-fix-assetmanifest-js-map": "replace-in-file \"/main\\.[^\\.]+\\.js\\.map/i\" \"main.js.map\" build/asset-manifest.json --isRegex",
    "build-fix-assetmanifest-css-map": "replace-in-file \"/main\\.[^\\.]+\\.css\\.map/i\" \"main.css.map\" build/asset-manifest.json --isRegex"
}

Also, don't forget to run

npm i --save-dev renamer
npm i --save-dev replace-in-file
reactiongears commented 5 years ago

@raizenpk would one need to run all those commands to have a clean build?

bologer commented 5 years ago

I work on the plugin which allows user to change styles of the component. Meaning that they can choose what was build with react-create-app or change styles of SCSS file and rebuild new CSS so here is what I came-up with (maybe will be useful for someone):

$staticFolder = '/path/to/static/media/';
$assets       = $staticFolder . '*.*';

$fileAssetList = glob( $assets );

if ( ! empty( $fileAssetList ) ) {
    foreach ( $fileAssetList as $key => $assetFullPath ) {
        preg_match( '/\/media\/(.*)\.[a-z0-9]+\.(svg|png|jpg|jpeg|ico|gif)$/m', $assetFullPath, $matches );

        if ( count( $matches ) !== 3 ) {
            continue;
        }

        $fullMatchAndUrl = '/path/to/static' . $matches[0];
        $fileName        = $matches[1];
        $extension       = $matches[2];

        $pattern = "/\.\.\/img\/?([\w-_]*\/)$fileName\.$extension/m";

        if ( preg_match( $pattern, $compiled ) ) {
            $compiled = preg_replace( $pattern, $fullMatchAndUrl, $compiled );
        }
    }
}