javascripthtmlrelative-pathdynamic-loading

How to get a resource's relative path to HTML document?


Let's say I have a simple web project structured like this:

ā”œā”€ šŸ—Ž index.html
ā”œā”€ šŸ“ collection
ā”‚  ā”œā”€ šŸ—Ž item1.html
ā”‚  ā”œā”€ šŸ—Ž item2.html
ā”‚  ā””ā”€ ...
ā”œā”€ šŸ“ css
ā”‚  ā”œā”€ šŸ—Ž main_styles.css
ā”‚  ā””ā”€ šŸ—Ž additional_styles.css
ā””ā”€ šŸ“ js
   ā””ā”€ šŸ—Ž script.js

Within script.js, it has the ability to dynamically include a CSS file into the HTML document with something like this:

const cssPath = 'css/additional_styles.css';
var head  = document.getElementsByTagName('head')[0];
var link  = document.createElement('link');
link.href = cssPath;
...
head.appendChild(link);

My problem of the above code is cssPath = 'css/additional_styles.css'

Suppose script.js knows the path of the CSS file relative to itself (in this case ../css/additional_styles.css) . How does JavaScript know what path should it use for loading the CSS file with the HTML document? For example, index.html would have to load the CSS file using the path css/additional_styles.css but collection/item1.html would have to load the CSS file using the path ../css/additional_styles.css

Since there is no one reliable way for JavaScript to find out which HTML document script.js is being loaded from, what would be a safe way for script.js to create such resource path relative to the HTML document for dynamic loading?

I intend to make script.js project-agnostic, meaning that the JS file can be reused for different projects. In another project, for instance, the relative paths between the CSS and JS directories may not change, but the absolute paths of both directories may be different from the above set up. For example:

ā”œā”€ šŸ—Ž index.html
ā”œā”€ šŸ“ collection
ā”‚  ā”œā”€ šŸ—Ž item1.html
ā”‚  ā”œā”€ šŸ—Ž item2.html
ā”‚  ā””ā”€ ...
ā””ā”€ šŸ“ my_library
   ā”œā”€ šŸ“ css
   ā”‚  ā”œā”€ šŸ—Ž main_styles.css
   ā”‚  ā””ā”€ šŸ—Ž additional_styles.css
   ā””ā”€ šŸ“ js
      ā””ā”€ šŸ—Ž script.js

I know there are some 'dirty' methods, such as forcing each HTML document to have <base> tags or to have some sort global variables to indicate the relative location of the HTML document. But again, I would like the script.js to be project-agnostic and reusable in different projects by different developers. I am aiming to have a pure JS approach so that other developers can just load the script and have it work straight away. I hope I don't need to enforce other developers to apply these 'dirty' methods in their HTML documents.


Solution

  • Well, tricks, tricks... if the script is intended to be run using <script src="..."></script> notation, i.e. with script tags, we can think of the following: check src using document.currentScript and crop the script name and the name of script-containing folder (basically 2 levels up as per your example, but you can modify depth by changing crop_depth)

    // absolute path of the script
    const abs_js_path = document.currentScript.src;
    const crop_depth = 2;
    // e.g., if the path is .../static/js/index.js, then:
    // 1 will produce .../static/js/
    // 2 will produce .../static (which you seem to need)
    // and so on
    
    // this is your folder which contains /js and other stuff
    const folder_path = abs_js_path.split("/").slice(0, -crop_depth).join("/")
    
    // that's how your cssPath variable now looks:
    const cssPath = folder_path + "/css/styles.css"
    

    As far as I understand, this is not the way to go if the script runs any differently than using script tags. Correct me if I'm wrong? (MDN link to document.currentScript docs)


    document.currentScript is compatible with anything newer than 2013-2014.

    If you need to support older browsers, the solution would be more complicated: you would have to loop through all scripts (getElementsByTagName("script")) and find a src which matches your script name (basically to search for */js/script.js).

    This would also require to hard-code script name and could have uncertainties if there're multiple scripts on the page which match */js/script.js. The latter one is circumventable if you come up with a less generic script name.