react-static: [Bug] Absolute paths leaking in the bundle

Reporting a bug?

Absolute paths are found in the exported bundles. This is surprising, can leak information about your build system, and will cause webpack longterm caching to not work if you build your site in different directories over time (or on multiple servers).

I believe the caching is two fold, part of it would be to use contenthash and second would be to use relative path for referencing modules.

I know there’s been a lot of work put into using absolute paths over relative paths lately, and I wonder if we could achieve the same result with relative paths instead (unclear what the issue was) ?

Environment

envinfo result
❯ npx envinfo --system --npmPackages 'react*' --binaries --npmGlobalPackages 'react*' --browsers
npx: installed 1 in 1.434s

  System:
    OS: macOS 10.14.5
    CPU: (4) x64 Intel(R) Core(TM) i7-7567U CPU @ 3.50GHz
    Memory: 6.09 GB / 16.00 GB
    Shell: 5.3 - /bin/zsh
  Binaries:
    Node: 8.14.0 - ~/n/bin/node
    Yarn: 1.16.0 - /usr/local/bin/yarn
    npm: 6.9.0 - /usr/local/bin/npm
  Browsers:
    Chrome: 76.0.3809.132
    Firefox: 69.0
    Safari: 12.1.1
  npmPackages:
    react: ^16.9.0 => 16.9.0
    react-dom: ^16.9.0 => 16.9.0
    react-static: ^7.2.0 => 7.2.2
    react-static-plugin-reach-router: ^7.2.0 => 7.2.2
    react-static-plugin-sitemap: ^7.2.0 => 7.2.2
    react-static-plugin-source-filesystem: ^7.2.0 => 7.2.2

Steps to Reproduce the problem

  1. Install a “basic” react-static site
❯ react-static create

? What should we name this project? my-static-site
? Select a template below... basic
Creating new react-static project...
  1. cd my-static-site
  2. yarn build
  3. Look in the dist/main.*.js file and find absolute paths. If you’re on Mac, this mean you’d find /Users/[your-user]/... paths in there.

Another way to see these absolute path is to run yarn serve and look at the sources tab in the devtools. You should see absolute path there: Screen Shot 2019-09-06 at 9 25 55 AM

Expected Behavior

I would expect not to find absolute paths in the bundles.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 3
  • Comments: 20 (5 by maintainers)

Most upvoted comments

I’ve played around with node to remove the root paths from the js, json and html in dist/ directory and it finally works.

Beware hardcoded paths in my snippet – it works for my setup (Unix, MacOS and default /dist & /src paths). UPDATED: there has been some bugs on Linux builds, so I added several fixes to handle empty dirs after file moving operations. So, here is my plugin which runs after bundling and exporting during build:

import path from 'path';
import replace from 'replace-in-file';
import jsStringEscape from 'js-string-escape';
import regexStringEscape from 'lodash.escaperegexp';
import fsx from 'fs-extra';

const strCWD = regexStringEscape(jsStringEscape(path.resolve(process.cwd())));
const srcPathParent = './dist/templates';
const srcPath = `${srcPathParent}${strCWD}/src/pages`;
const dstPath = `${srcPathParent}/pages`;

const cleanEmptyFoldersRecursively = (folder) => {

    fsx.readdir(folder, function (err, files) {

        if (files && files.length !== 0) {

            files.forEach(function (fileName) {
                const file = `${folder}/${fileName}`;
                fsx.stat(file, function (err, stats) {
                    if (stats && stats.isDirectory()) {
                        const files = fsx.readdirSync(file);
                        if (files.length > 0) {
                            cleanEmptyFoldersRecursively(file);
                        }
                        if (files.length === 0) {
                            fsx.remove(file);
                            return;
                        }
                    }
                });
            });

        }

    });
}

const traverseDirectory = (dirname, callback) => {
    let directory = [];
    fsx.readdir(dirname, function (err, list) {
        dirname = fsx.realpathSync(dirname);
        if (err) {
            return callback(err);
        }
        let listlength = list.length;
        list.forEach(function (fileName) {
            const file = `${dirname}/${fileName}`;

            fsx.stat(file, function (err, stats) {

                directory.push(file);

                if (stats.isDirectory()) {
                    traverseDirectory(file, function (err, parsed) {
                        directory = directory.concat(parsed);
                        if (!--listlength) {
                            callback(null, directory);
                        }
                    });
                }

                if (stats.isFile()) {
                    if (file.match(/~/)) {
                        try {
                            const fileName = path.basename(file);
                            const fileSufExt = fileName.match(/[0-9a-z]+\.[0-9a-z]+$/);
                            const newName = `functions.${fileSufExt}`;
                            fsx.rename(file, `${srcPathParent}/pages/${newName}`);

                            cleanEmptyFoldersRecursively(dirname);

                            // fix functions.hash.js file links
                            replace({
                                files: path.resolve('./dist/**/*.html'),
                                from: [
                                    /<link.*?\/templates\/pages.*?~.*?\/>/,
                                    /<script.*src=.*\/templates\/pages.*?~.*?~.*?><\/script>/
                                ],
                                to: [
                                    `<link rel="preload" as="script" href="/templates/pages/${newName}"/>`,
                                    `<script defer="" type="text/javascript" src="/templates/pages/${newName}"></script>`
                                ]
                            });
                        } catch (err) {
                            console.error(err);
                        }
                    }
                }

            });
        });
    });
}

const cb = (err, result) => {
    if (err) {
        console.log('error', err);
    }
    return result;
}

export default pluginOptions => ({
    afterBundle: async state => {
        // fix absolute paths in vendors~main.js
        await replace({
            files: path.resolve(`${srcPathParent}/vendors~main*.js`),
            from: new RegExp(strCWD, 'g'),
            to: '.'
        });
        // fix absolute paths in main.js
        await replace({
            files: path.resolve('./dist/main*.js'),
            from: [
                new RegExp(`${strCWD.slice(1)}/src/`, 'g'),
            ],
            to: [
                '',
            ]
        });
        // rewrite path from abc~xyz.[hash].js to /pages/functions.[hash].js
        await replace({
            files: path.resolve('./dist/main*.js'),
            from: /pages\/.*~.*\/.*?[~]\d+\w+/g,
            to: 'pages/functions',
        });
        return state;
    },
    afterExport: async state => {
        await replace({
            files: path.resolve('./dist/**/routeInfo.json'),
            from: new RegExp(`${strCWD}/src/`, 'g'),
            to: '/'
        });

        // replace abs paths in links and scripts (except for abc~xyz file)
        await replace({
            files: path.resolve('./dist/**/*.html'),
            from: [
                new RegExp(`${strCWD}/src/`, 'g'),
            ],
            to: [
                '/',
            ]
        });

        try {
            // console.log(` moving from ${srcPath} into ${dstPath}...`)
            await fsx.move(srcPath, dstPath);
        } catch (err) {
            console.error(err);
        }

        const dir = dstPath;

        try {
            await traverseDirectory(dir, cb);
        } catch (err) {
            console.error(err);
        }

        try {
            await fsx.remove(`${srcPathParent}/Users`);
            await fsx.remove(`${srcPathParent}/home`);
        } catch (err) {
            console.error(err);
        }

        try {
            await cleanEmptyFoldersRecursively(dir);
        } catch (err) {
            console.error(err);
        }

        return state;
    },
});

@warloque It’s perfect !! 👌🏻

Thanks, but nothing is perfect, you know 😉 Check my code update, hope that will help better)

@robertveloso the fix has not yet been released.

You’re awesome.

Why don’t I check out your branch next week and see if I can fix that.

On 2019-09-25 19:59, Nicolas wrote:

👋 it’s been difficult to find time to work on this issue lately but wanted to give a quick update. I’ve got a working branch [1] that allows me to change both paths.dist and paths.temp to point outside of the working directory. This works fine and no absolute paths end up in the generated files, ensuring consistent hashing even when the build directory changes.

However, I can’t get yarn build to successfully run when also changing paths.buildArtifacts to a directory that’s outside the working directory. I’ve got the same issue when using the upstream react-static@7.2.2.

Exporting HTML… Error: Trace: { Error: Cannot find module ‘@reach/router’ at Function.Module._resolveFilename (internal/modules/cjs/loader.js:636:15) at Function.Module._load (internal/modules/cjs/loader.js:562:25) at Module.require (internal/modules/cjs/loader.js:692:17) at Module.call [as require] (/react-static/packages/react-static/s rc/utils/binHelper.js:62:26) at require (internal/modules/cjs/helpers.js:25:18) […]

Unclear to me if this is a local setup issue with yarn link or if this would also manifest in a “real-world setup”.

– You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub [2], or mute the thread [3].

Links:

[1] https://github.com/react-static/react-static/compare/master...nddery:fix/no-absolute-paths [2] https://github.com/react-static/react-static/issues/1303?email_source=notifications&amp;email_token=AAO7SWHKBOJABUVLZRHE2OTQLORI7A5CNFSM4IUJCZR2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD7SZKBQ#issuecomment-535139590 [3] https://github.com/notifications/unsubscribe-auth/AAO7SWFAEO3DJKJS6J6HBULQLORI7ANCNFSM4IUJCZRQ

My understanding is that react-static uses absolute path to support pointing the tmpt, artifacts and dist directory to a location that is outside of the repository (for example, support building on Lambda, outputting to the /tmp directory)). Seems like Gatsby does not support changing the output directory. They’ve tried adding the option but ended up reverting it as it introduced some issues. Unclear to me exactly what the issue(s) were.

I’m going to test out using module paths (along with updating the “resolve.modules” Webpack setting) for import paths within generated bundles, while keeping absolute paths anywhere within react-static.

Once that is confirmed to work (if it does), I can revisit https://github.com/react-static/react-static/pull/1232 to see if it actually helps or not.

Hopefully this makes sense!

I ran into this issue yesterday. As @SleeplessByte said, a solution for me may not work for someone else (process.cwd() may not, in fact, be the correct directory, hence the absolute paths). For my build system, though, it is, and the directory was what was varying between builds. Until a solution can be found, adding the following workaround to my node.api.js file fixes this enough that I only track the files that actually changed:

import path from 'path';
import replace from 'replace-in-file';
import jsStringEscape from 'js-string-escape';
import regexStringEscape from 'lodash.escaperegexp';

export default pluginOptions => ({
    afterBundle: async state => {
        await replace({
            files: path.resolve("./dist/templates/vendors~main.js"),
            from: new RegExp(regexStringEscape(jsStringEscape(path.resolve(process.cwd()))), "g"),
            to: "."
        });
        return state;
    },
});