Skip to content
This repository was archived by the owner on Mar 17, 2021. It is now read-only.

Ability to specify custom public file name for resources #81

Merged
merged 4 commits into from
Nov 24, 2016

Conversation

moughxyz
Copy link
Contributor

@moughxyz moughxyz commented Jul 4, 2016

file-loader places files in the directory specified by the name query parameter, i.e:

loader: "file-loader?name=app/images/[name].[ext]"

will place the file in the directory app/images, and also references the file using a URL of this same path, i.e

localhost:3000/app/images/logo.png

This doesn't make sense since the location you place the resource isn't necessarily the same as how you'd want it to be accessed.

output.publicPath works when you just want to prefix the name with a directory, like assets, but if how you want the file to be accessed is entirely different from the combination of publicPath + name, you're out of luck.

This pull request allows you to specific a public name for resources in the query params:

loader: "file-loader?name=app/images/[name].[ext]&publicName=assets/foo/[name].[ext]"

This will place the file in the directory app/images, but the URL the file will be accessed by is assets/foo.

: config.publicPath + url
typeof config.publicPath === "function"
? config.publicPath(url)
: publicUrl

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is going to behave as intended. publicUrl isn't defined here, it was initialized only inside the if branch of the if...else. Shouldn't this be the original code (config.publicPath + url)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya you're right, this was a typo.

@jskrzypek
Copy link

jskrzypek commented Jul 13, 2016

If I understand you correctly, you're not trying to modify/replace the publicPath so much as you're trying to provide a different prefix to the url that gets passed to this.emitFile on line 48. This is the line that is responsible for where the file ends up getting created.

As far as I can tell, this is more or less the idea of #57 that added publicPath, with the exception that there the intent was to prefix the url that was returned to the requiring module. Here the intent is ti Given that, I think an implementation that more closely follows that of #57 and just adds an outputPath option would be a better pattern, to follow. It could look something like this:

    var config = {
        outputPath: false,
        publicPath: false,
        name: "[hash].[ext]"
    };

    // options takes precedence over config
    Object.keys(options).forEach(function(attr) {
        config[attr] = options[attr];
    });

    // query takes precedence over config and options
    Object.keys(query).forEach(function(attr) {
        config[attr] = query[attr];
    });

    var url = loaderUtils.interpolateName(this, config.name, {
        context: config.context || this.options.context,
        content: content,
        regExp: config.regExp
    });

    var outputPath = url;

    var publicPath = "__webpack_public_path__ + " + JSON.stringify(url);

    if (config.outputPath) {
        // support functions as outputPath to generate them dynamically
        outputPath = JSON.stringify(
                typeof config.outputPath === "function" 
                 ? config.outputPath(url) 
                 : config.outputPath + url
        );
    }

    if (config.publicPath) {
        // support functions as publicPath to generate them dynamically
        publicPath = JSON.stringify(
                typeof config.publicPath === "function" 
                 ? config.publicPath(url) 
                 : config.publicPath + url
        );
    }

    if (query.emitFile === undefined || query.emitFile) {
        this.emitFile(outputPath, content);
    }

    return "module.exports = " + publicPath + ";";

@jskrzypek
Copy link

This should allow you to configure the loader like this and I think it would also make it easier to reason about the behavior.

    loader: "file-loader?name=[name].[ext]&publicPath=assets/foo/&outputPath=app/images/"

@moughxyz
Copy link
Contributor Author

Ya ok, that works too. I've updated the code to go with your pattern. Thanks for your input.

@jskrzypek
Copy link

Cool 👍

One other thing: I'd add a outputPath:false, after line 16

@moughxyz
Copy link
Contributor Author

Why's that? Wouldn't its lack of presence evaluate to the same thing?

@jskrzypek
Copy link

It makes the code a bit more readable. You can look at the config object and know right away that there's a default outputPath property that can be overridden, without having to look down and see if something tries to consume that property later in the file. Consuming a config property that you expect your caller to provide, without providing a default value makes the code harder to maintain.

It's not really crucial in this small of a file, but it's generally a good practice.

@Malax
Copy link

Malax commented Aug 15, 2016

@mobitar described a feature that does exactly what I need. Being able to specify a publicName that can also modify the name of the file and not only the path is very much required in my case.

I am using Play! that has its own little asset controller that handles the typical aggressive caching using hashes in file names. However, the implementation is a bit weird. It does not only require a hash in the file name, but also a .md5 file containing the hash to determine if the requested hash is correct (I assume they don't want to actually create a digest on requests). This file creation is all handled by play's asset pipeline that creates those files on distribution and would also affect the files coming out of webpack.

Because of this, I want to webpack to emit files without a hash (as it will be created later by play), but have the public path containing said hashes.

However, it seems that this PR changed the original code so that having a public file name that differs from the emitted file name is no longer possible. I think by having publicName file-loader could handle the different path-prefixes @mobitar needs and also fixes my problem. Thoughts?

@DiegoYungh
Copy link

DiegoYungh commented Nov 1, 2016

Why is it taking so long for this PR to be merged?

I got another case where this is a must:

On Symfony2 projects you add public files to

src/MyProject/SampleBundle/Resources/public/[js|css|images|etc...]

but you access them trough a link:

/bundles/sample/[js|css|images|etc...]

Which is rooted in the /web folder outside the root.
All I needed here was to specify a public and output path:

test: /\.(eot|svg|ttf|woff|woff2)(\?[\w= ]*)?$/,
loader: 'file',
query: {
  name: '[name].[ext]',
  outputPath: '/src/MyProject/Samplebundle/Resources/public/fonts/',
  publicPath: '/bundles/sample/fonts/'
}

or, supporting different namings

test: /\.(eot|svg|ttf|woff|woff2)(\?[\w= ]*)?$/,
loader: 'file',
query: {
  output: '/src/MyProject/Samplebundle/Resources/public/fonts/[name].[ext]',
  public: '/bundles/sample/fonts/[name].[ext]?v=[hash:6]'
}

The same will be done on images and Extracted CSS generated from SASS/Stylus
I will probably use a fork on my project

@SpaceK33z
Copy link
Contributor

@DiegoYungh, because everyone is very busy. After the webpack v2 release I'm probably going to maintain this library. Meanwhile, if anyone could test this PR and verifies it works, that would be appreciated and will result in a quicker merge.

@mobitar, could you sign the CLA?

@moughxyz
Copy link
Contributor Author

moughxyz commented Nov 7, 2016

@SpaceK33z done

Copy link
Contributor

@SpaceK33z SpaceK33z left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I tested it and verified it worked.

  • It seems like you changed the permissions of the index.js file from 644 to 755. Could you change that back?
  • Could you document this in the README?

After this I'll merge :).

@DiegoYungh
Copy link

@mobitar please add to your git config to ignore fileMode.
We are very close into merging this 👍

@moughxyz
Copy link
Contributor Author

@DiegoYungh @SpaceK33z done.

@SpaceK33z SpaceK33z merged commit 4dfecba into webpack-contrib:master Nov 24, 2016
@SpaceK33z
Copy link
Contributor

Thanks @mobitar! I'll release a new version when we release webpack v2 (since I want to release it with #99 as well).

@derekdon
Copy link

derekdon commented Jan 5, 2017

@jskrzypek Do you think there is value in having the result of publicPath/outputPath functions interpolated by the loader-utils? In the example below I believe I need the path information from the name property (incoming url) to know if I need to replace a part of it, however when creating the public path I just want to do as follows.

name: '[path][name].[ext]',
outputPath: url => url.replace(/tools\//, ''),
publicPath: url => 'assets/images/[name].[ext]'

Perhaps there is another way to do this. When publishing the output in my case, shared the assets need to go on the root/assets/images/x.png, while tool assets need to go under a variable directory name on the root, ie. root/toolA/assets/images/x.png, root/toolB/assets/images/x.png etc. These image files actually exist on disk as root/tools/toolA/assets/images/x.png, root/tools/toolB/assets/images/x.png, but all the tools are moved to the root when published.

@Malax
Copy link

Malax commented Jan 5, 2017

@derekdon @jskrzypek As stated in my comment from 15 Aug 2016 I have the same requirement. I currently help myself with a fork but would really like to see this in the official codebase.

@damianobarbati
Copy link

damianobarbati commented Jun 9, 2017

Is this possible right now, I mean specify an output folder relative to the entry?
Dup: #114 (comment)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants