shellscape / webpack-manifest-plugin

webpack plugin for generating asset manifests
MIT License
1.43k stars 185 forks source link

manifest.json contains output prefix for image assets, but is missing for (js,css) assets? #131

Closed ghost closed 6 years ago

ghost commented 6 years ago

My project's assets are arranged as

<project>/
    assets/
        js/
            test0.js
        scss/
            test1.scss
        img/
            test2.png

I hash files, and have set up manifest generation

const ManifestPlugin = require('webpack-manifest-plugin');
...
    plugins: [
        new ManifestPlugin({
            fileName: ('manifest.json'),
            publicPath: '',
            map: (file) => {
                return file;
            },
        })
    ]

My fileloader's gather/process all, and output as

<project>/
    build/
        manifest.json
        js/
            test0.<hash>.js
            test0.<hash>.js.map
            manifest.<hash>.js
            manifest.<hash>.js.map
        css/
            test1.<hash>.css
            test1.<hash>.css.map
        img/
            test2.<hash>.png

the generated manifest contains

cat manifest.json
    {
      "manifest.js": "js/manifest.<hash>.js",
      "manifest.js.map": "js/manifest.<hash>.js.map",
      "test0.js": "js/test0.<hash>.js",
      "test1.css": "css/test1.<hash>.css",
      "test0.js.map": "js/test0.<hash>.js.map",
      "test1.css.map": "css/test1.<hash>.css.map",
      "img/test2.png": "img/test2.<hash>.png",
    }

Notice that ONLY the png image file's manifest alias has the prefixed "img/" path; the .js/.css entries do not.

(1) Should manifest aliases consistently have the full relative path preceding the alias, or not?

(2) What in my single manifest config is the cause of the difference in behavior prefixing behavior? Is there additional config needed in the manifest plugin's config -- or other module?

mastilver commented 6 years ago

For the js / css, we are doing:

https://github.com/danethurber/webpack-manifest-plugin/blob/052970ef91b9b319e0ba06772f2debf998e5a241/lib/plugin.js#L51

For images / assets:

https://github.com/danethurber/webpack-manifest-plugin/blob/052970ef91b9b319e0ba06772f2debf998e5a241/lib/plugin.js#L39-L42

I would need your output options and your image loader config to help you further

ghost commented 6 years ago

@mastilver

Do I read your snippets above correctly to mean that, in manifest.son, the alias should be UN-prefixed with any dir path?

And, as requested (I think I got the relevant pieces ...)

webpack.config.js
    const webpack = require('webpack');
    const publicPath = "build/";
    const commonConfig = merge([
        ...
            output: {
                publicPath,
                path: PATHS.build,
                libraryTarget: 'umd'
            },
        ...
    ]);

    const productionConfig = merge([
        ...
        {
            output: {
                chunkFilename: 'js/[name].[chunkhash].js',
                filename: 'js/[name].[chunkhash].js',
                pathinfo: false,
            },
        },
        ...
        parts.loadImages({
            name: "[name].[hash].[ext]"
        }),
        ...
        parts.hashNamedModules(),
        parts.attachRevision(),
        parts.manifest()
    ]);

webpack.parts.js
    ...
    exports.hashNamedModules = () => ({
        plugins: [
            new webpack.NamedModulesPlugin()
        ]
    });

    exports.manifest = () => ({
        plugins: [
            new ManifestPlugin({
                fileName: ('manifest.json'),
                basePath: "",
                map: (file) => {
                    return file;
                }
            })
        ]
    });
    exports.attachRevision = () => ({
        plugins: [
            new webpack.BannerPlugin({
                banner: new GitRevisionPlugin().version(),
            }),
        ],
    });
    exports.loadImages = ({ include, exclude, name } = {}) => ({
        module: {
            rules: [
                {
                    test: /\.(gif|png|jpe?g|svg)$/,
                    include,
                    exclude,
                    use: [
                        {
                            loader: "file-loader",
                            options: {
                                name,
                                context: "assets",
                                useRelativePath: true,
                                hashType: "sha512",
                                digestType: "hex",
                                length: 32
                            }
                        },
                    ]
                }
            ],
        },
    });
mastilver commented 6 years ago

Alright so you want to remove js/ from output and add it into entry (as the key)

that way it should generate js/test0.js, ...

ghost commented 6 years ago

removing the js from output simply lands the targets in

public/build/*{js,map}

rather than as currently, & as intended,

public/build/js/*{js,map}

The issue's not that the actual assets are ending up in the wrong place, it's the confusion around why the manifest is generated DIFFERENTLY for /js/ vs /img/ assets

mastilver commented 6 years ago

Oh, sorry I thought you wanted them the same So js/css: name is coming from entry Assets: comes from emitFile: https://github.com/webpack-contrib/file-loader/blob/master/src/index.js#L73

ghost commented 6 years ago

Sorry I really don't know what to do with that.

Starting with

<project>
    assets/
        img/
            test.png
        js/
            test.js
        scss/
            test.scss

I want to end up with

<project>
    public/
        build/
            img/
                test.<hash>.png
            js/
                test.<hash>.js
            css/
                test.<hash>.css

so far so good, that's exactly what I do have.

but I want a consistent manifest path+naming not what I'm currently getting

manifest.json
    {
        "test.css": "build/js/test.<hash>.css",
        "test.css.map": "build/js/test.<hash>.css.map",
        "test.js": "build/js/test.<hash>.js",
        "test.js.map": "build/js/test.<hash>.js.map",
        "img/test.png": "build/img/test.<hash>.png",
    }

but this, with consistent alias path/file usage

manifest.json
    {
        "css/test.css": "build/js/test.<hash>.css",
        "css/test.css.map": "build/js/test.<hash>.css.map",
        "js/test.js": "build/js/test.<hash>.js",
        "js/test.js.map": "build/js/test.<hash>.js.map",
        "img/test.png": "build/img/test.<hash>.png",
    }

What exactly in my single manifest plugin config do I need to change/add to get there?

mastilver commented 6 years ago

Use webpack publicPath option

ghost commented 6 years ago

If you're not interested in helping, just say so.

mastilver commented 6 years ago

I can't help you if you don't help me...

What is the manifest you are getting, and what are you expecting?

ghost commented 6 years ago

https://github.com/danethurber/webpack-manifest-plugin/issues/131#issuecomment-372787617

airtonix commented 6 years ago

@mastilver

he (and also myself) has

    {
        "test.css": "build/js/test.<hash>.css",
        "test.css.map": "build/js/test.<hash>.css.map",
        "test.js": "build/js/test.<hash>.js",
        "test.js.map": "build/js/test.<hash>.js.map",
        "img/test.png": "build/img/test.<hash>.png",
    }

but we both want:

    {
        "<publicPath>css/test.css": "<publicPath>js/test.<hash>.css",
        "<publicPath>css/test.css.map": "<publicPath>js/test.<hash>.css.map",
        "<publicPath>js/test.js": "<publicPath>js/test.<hash>.js",
        "<publicPath>js/test.js.map": "<publicPath>js/test.<hash>.js.map",
        "<publicPath>img/test.png": "<publicPath>img/test.<hash>.png",
    }

My reasoning is that the point of the manifest.json is to help backend template function resolve logical names into absolute names.

Therefore in dev where there are no hashes, it should resolve to the actual filename, which means often that we have to query with the unhashed filename:

If you consider my publicPath is static meaning i would have /static/img/test.png, then:

MagicalTemplateHelpers.assetUrl = (path) => {
  let manifest = {};
  try {
    const manifest = require(`${MAGICAL_ROOT}/manifest.json`);
  } catch (err) {
  }
  return Object.keys(manifest).includes(path) && manifest[path] || path;
}
<html>
 ...
<img src="{{ asseturl '/static/img/test.png'}}">
 ...
</html>

Then in dev it would look like:

<html>
 ...
<img src="/static/img/test.png">
 ...
</html>

and in prod it would look like:

<html>
 ...
<img src="/static/img/test.93845j45.png">
 ...
</html>

So the reason why I default to the provided path is so that :

  1. assets not part of webpack dropped in later as a quick hack by backenders still works
  2. when manifest.json doesn't exist, we just use the requested path.
  3. when team is looking at the html views, we don't have to add congnitive load to understand where the assets might be on disk.
mastilver commented 6 years ago

Hi @airtonix

As I said earlier, I believe the solution is to add js/ to the keys of your entry and remove it from output.filename. Do the same with the filename options on ExtractTextPlugin

If it doesn't work, give me the manifest, entry, output and ExtractTextPlugin options, before and after you applied this advise

And can you stop using <publicPath> and <hash>, it's really confusing...

sschueller commented 5 years ago

I am having the same issue. All assets except js and css have the correct key in the manifest.json but the main.css and main.js keys are wrong.

Input Files

app.js
css/style.scss
js/script.js
images/...
fonts/...

Ouput Files

dist/manifest.json
dist/js/main.js
dist/css/main.css
dist/fonts/...
dist/images/...

manifest.json

{
  "/dist/main.js": "/dist/js/main.62df052c202febdd97aa.js",
  "/dist/main.css": "/dist/css/main.8ee4f2b4d0e137b9ef6ee98fb901ab28.css",
  "/dist/images/th03.jpg": "/dist/images/th03.3190e504e69661755c3983492c6e66a0.jpg",
  "/dist/fonts/roboto-black-webfont.woff2": "/dist/fonts/90f4fdfe1436e82be81f1a1572275375.woff2",

Webpack Config parts

var baseConfig = {
        entry: '/app.js',
        output: {
            filename: PROD ? 'js/[name].[hash].js' : 'js/[name].js',
            chunkFilename: PROD ? 'js/[name].[hash].js' : 'js/[name].js',

            // Absolute output directory
            path: path.resolve(designAssetsOutputFolder + '/dist'),

            // Output path from the view of the page
            publicPath: '/dist/',
        },
        module: {
            rules: [
                {
                    test: /\.(jpg|jpeg|gif|png)$/,
                    exclude: /node_modules/,
                    loader: 'url-loader',
                    options: {
                        limit: 10000,
                        name: PROD ? 'images/[hash].[ext]' : 'images/[name].[ext]'
                    }
                },
                {
                    test: /\.(woff|woff2|eot|ttf|svg)$/,
                    exclude: /node_modules/,
                    loader: 'url-loader',
                    options: {
                        limit: 10000,
                        name: PROD ? 'fonts/[hash].[ext]' : 'fonts/[name].[ext]'
                    }
                },        
new ExtractTextPlugin({
    filename: function (getPath) {
        return getPath(PROD ? 'css/[name].[contenthash].css' : 'css/[name].css');
    },
    allChunks: true
}),
new ManifestPlugin({
    basePath: '/dist/',
    publicPath: '/dist/',        
    map: function (file) {
        if (PROD) {
            // Remove hashes from keys created in CopyWebpackPlugin which does not correctly support manifest files
            file.name = file.name.replace(/(\.[a-f0-9]{32})(\..*)$/, '$2');
        }
        return file;
    }
})

Changing the entry to /js/app.js and removing js from output.filename does not work as that file is not in the js folder. It also would not work as the css files are still wrong.

Doing the above and moving the app.js into /js/app.js will still result in an incorrect manifest file:

manifest.json

{
  "/dist/main.js": "/dist/main.21e3024ef4194d6c65b7.js",
  "/dist/main.css": "/dist/css/main.8ee4f2b4d0e137b9ef6ee98fb901ab28.css",
  "/dist/images/th03.jpg": "/dist/images/th03.3190e504e69661755c3983492c6e66a0.jpg",
  "/dist/fonts/roboto-black-webfont.woff2": "/dist/fonts/90f4fdfe1436e82be81f1a1572275375.woff2",

I need my manifest to look like this:

manifest.json

{
  "/dist/js/main.js": "/dist/js/main.62df052c202febdd97aa.js",
  "/dist/css/main.css": "/dist/css/main.8ee4f2b4d0e137b9ef6ee98fb901ab28.css",
  "/dist/images/th03.jpg": "/dist/images/th03.3190e504e69661755c3983492c6e66a0.jpg",
  "/dist/fonts/roboto-black-webfont.woff2": "/dist/fonts/90f4fdfe1436e82be81f1a1572275375.woff2",

I am using "webpack-manifest-plugin": "^2.0.4"

To get it work you can do the following hack:

new ManifestPlugin({
    map: function (file) {
        if (file.name.match(/main\.css/g)) {
            file.name = file.name.replace(/(main\.css)/, 'css/$1');
        }
        if (file.name.match(/main\.js/g)) {
            file.name = file.name.replace(/(main\.js)/, 'js/$1');
        }
        return file;
    }
})