Skip to content

Commit 9847ea6

Browse files
committed
bug #921 Fixing manifest paths (by temporarily embedding webpack-manifest-plugin fix) (weaverryan)
This PR was squashed before being merged into the main branch. Discussion ---------- Fixing manifest paths (by temporarily embedding webpack-manifest-plugin fix) This temporarily embeds shellscape/webpack-manifest-plugin#249, which fixes #907 and also a comment on #914 (comment) To test this: ``` $ yarn remove @symfony/webpack-encore $ yarn add git://github.com/weaverryan/webpack-encore.git#try-manifest-fix ``` Please let me know if this does or doesn't fix your `manifest.json` file. I'm also interested in hearing from Windows users, as the fix requires some "path" manipulation. Thanks! Commits ------- b81428d Fixing manifest paths (by temporarily embedding webpack-manifest-plugin fix)
2 parents 321ba13 + b81428d commit 9847ea6

File tree

14 files changed

+411
-34
lines changed

14 files changed

+411
-34
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = {
1616
"es6": true,
1717
},
1818
"parserOptions": { "ecmaVersion": 2017 },
19+
"ignorePatterns": ["lib/webpack-manifest-plugin"],
1920
"rules": {
2021
"quotes": ["error", "single"],
2122
"no-undef": "error",

.github/workflows/node-windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99

1010
strategy:
1111
matrix:
12-
node: [ '14', '12', '10' ]
12+
node: [ '14', '10' ]
1313

1414
name: ${{ matrix.node }} (Windows)
1515
steps:
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// this helps us trigger a manifest.json bug
2+
// https://github.com/shellscape/webpack-manifest-plugin/pull/249
3+
import 'mocha/assets/growl/ok.png';
4+
import '../images/symfony_logo.png';
5+
6+
7+
// module.userRequest
8+
// growl: /Users/weaverryan/Sites/os/webpack-encore/node_modules/mocha/assets/growl/ok.png
9+
// logo: /Users/weaverryan/Sites/os/webpack-encore/test_tmp/51witp/images/symfony_logo.png
10+

lib/plugins/manifest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
'use strict';
1111

1212
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
13-
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
13+
const { WebpackManifestPlugin } = require('../webpack-manifest-plugin');
1414
const PluginPriorities = require('./plugin-priorities');
1515
const applyOptionsCallback = require('../utils/apply-options-callback');
1616
const copyEntryTmpName = require('../utils/copyEntryTmpName');

lib/webpack-manifest-plugin/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) Dane Thurber <[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

lib/webpack-manifest-plugin/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# webpack-manifest-plugin
2+
3+
This is a copy of https://github.com/shellscape/webpack-manifest-plugin
4+
at sha: 9f408f609d9b1af255491036b6fc127777ee6f9a.
5+
6+
It has been modified to fix this bug: https://github.com/shellscape/webpack-manifest-plugin/pull/249
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
const { dirname, join, basename } = require('path');
2+
3+
const generateManifest = (compilation, files, { generate, seed = {} }) => {
4+
let result;
5+
if (generate) {
6+
const entrypointsArray = Array.from(compilation.entrypoints.entries());
7+
const entrypoints = entrypointsArray.reduce(
8+
(e, [name, entrypoint]) => Object.assign(e, { [name]: entrypoint.getFiles() }),
9+
{}
10+
);
11+
result = generate(seed, files, entrypoints);
12+
} else {
13+
result = files.reduce(
14+
(manifest, file) => Object.assign(manifest, { [file.name]: file.path }),
15+
seed
16+
);
17+
}
18+
19+
return result;
20+
};
21+
22+
const getFileType = (fileName, { transformExtensions }) => {
23+
const replaced = fileName.replace(/\?.*/, '');
24+
const split = replaced.split('.');
25+
const extension = split.pop();
26+
return transformExtensions.test(extension) ? `${split.pop()}.${extension}` : extension;
27+
};
28+
29+
const reduceAssets = (files, asset, moduleAssets) => {
30+
let name;
31+
if (moduleAssets[asset.name]) {
32+
name = moduleAssets[asset.name];
33+
} else if (asset.info.sourceFilename) {
34+
name = join(dirname(asset.name), basename(asset.info.sourceFilename));
35+
}
36+
37+
if (name) {
38+
return files.concat({
39+
path: asset.name,
40+
name,
41+
isInitial: false,
42+
isChunk: false,
43+
isAsset: true,
44+
isModuleAsset: true
45+
});
46+
}
47+
48+
const isEntryAsset = asset.chunks && asset.chunks.length > 0;
49+
if (isEntryAsset) {
50+
return files;
51+
}
52+
53+
return files.concat({
54+
path: asset.name,
55+
name: asset.name,
56+
isInitial: false,
57+
isChunk: false,
58+
isAsset: true,
59+
isModuleAsset: false
60+
});
61+
};
62+
63+
const reduceChunk = (files, chunk, options, auxiliaryFiles) => {
64+
// auxiliary files contain things like images, fonts AND, most
65+
// importantly, other files like .map sourcemap files
66+
// we modify the auxiliaryFiles so that we can add any of these
67+
// to the manifest that was not added by another method
68+
// (sourcemaps files are not added via any other method)
69+
Array.from(chunk.auxiliaryFiles || []).forEach((auxiliaryFile) => {
70+
auxiliaryFiles[auxiliaryFile] = {
71+
path: auxiliaryFile,
72+
name: basename(auxiliaryFile),
73+
isInitial: false,
74+
isChunk: false,
75+
isAsset: true,
76+
isModuleAsset: true
77+
};
78+
});
79+
80+
return Array.from(chunk.files).reduce((prev, path) => {
81+
let name = chunk.name ? chunk.name : null;
82+
// chunk name, or for nameless chunks, just map the files directly.
83+
name = name
84+
? options.useEntryKeys && !path.endsWith('.map')
85+
? name
86+
: `${name}.${getFileType(path, options)}`
87+
: path;
88+
89+
return prev.concat({
90+
path,
91+
chunk,
92+
name,
93+
isInitial: chunk.isOnlyInitial(),
94+
isChunk: true,
95+
isAsset: false,
96+
isModuleAsset: false
97+
});
98+
}, files);
99+
};
100+
101+
const standardizeFilePaths = (file) => {
102+
const result = Object.assign({}, file);
103+
result.name = file.name.replace(/\\/g, '/');
104+
result.path = file.path.replace(/\\/g, '/');
105+
return result;
106+
};
107+
108+
const transformFiles = (files, options) =>
109+
['filter', 'map', 'sort']
110+
.filter((fname) => !!options[fname])
111+
// TODO: deprecate these
112+
.reduce((prev, fname) => prev[fname](options[fname]), files)
113+
.map(standardizeFilePaths);
114+
115+
module.exports = { generateManifest, reduceAssets, reduceChunk, transformFiles };

lib/webpack-manifest-plugin/hooks.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
const { mkdirSync, writeFileSync } = require('fs');
2+
const { basename, dirname, join } = require('path');
3+
4+
const { SyncWaterfallHook } = require('tapable');
5+
const webpack = require('webpack');
6+
// eslint-disable-next-line global-require
7+
const { RawSource } = webpack.sources || require('webpack-sources');
8+
9+
const { generateManifest, reduceAssets, reduceChunk, transformFiles } = require('./helpers');
10+
11+
const compilerHookMap = new WeakMap();
12+
13+
const getCompilerHooks = (compiler) => {
14+
let hooks = compilerHookMap.get(compiler);
15+
if (typeof hooks === 'undefined') {
16+
hooks = {
17+
afterEmit: new SyncWaterfallHook(['manifest']),
18+
beforeEmit: new SyncWaterfallHook(['manifest'])
19+
};
20+
compilerHookMap.set(compiler, hooks);
21+
}
22+
return hooks;
23+
};
24+
25+
const beforeRunHook = ({ emitCountMap, manifestFileName }, compiler, callback) => {
26+
const emitCount = emitCountMap.get(manifestFileName) || 0;
27+
emitCountMap.set(manifestFileName, emitCount + 1);
28+
29+
/* istanbul ignore next */
30+
if (callback) {
31+
callback();
32+
}
33+
};
34+
35+
const emitHook = function emit(
36+
{ compiler, emitCountMap, manifestAssetId, manifestFileName, moduleAssets, options },
37+
compilation
38+
) {
39+
const emitCount = emitCountMap.get(manifestFileName) - 1;
40+
// Disable everything we don't use, add asset info, show cached assets
41+
const stats = compilation.getStats().toJson({
42+
all: false,
43+
assets: true,
44+
cachedAssets: true,
45+
ids: true,
46+
publicPath: true
47+
});
48+
49+
const publicPath = options.publicPath !== null ? options.publicPath : stats.publicPath;
50+
const { basePath, removeKeyHash } = options;
51+
52+
emitCountMap.set(manifestFileName, emitCount);
53+
54+
const auxiliaryFiles = {};
55+
let files = Array.from(compilation.chunks).reduce(
56+
(prev, chunk) => reduceChunk(prev, chunk, options, auxiliaryFiles),
57+
[]
58+
);
59+
60+
// module assets don't show up in assetsByChunkName, we're getting them this way
61+
files = stats.assets.reduce((prev, asset) => reduceAssets(prev, asset, moduleAssets), files);
62+
63+
// don't add hot updates and don't add manifests from other instances
64+
files = files.filter(
65+
({ name, path }) =>
66+
!path.includes('hot-update') &&
67+
typeof emitCountMap.get(join(compiler.options.output.path, name)) === 'undefined'
68+
);
69+
70+
// auxiliary files are "extra" files that are probably already included
71+
// in other ways. Loop over files and remove any from auxiliaryFiles
72+
files.forEach((file) => {
73+
delete auxiliaryFiles[file.path];
74+
});
75+
// if there are any auxiliaryFiles left, add them to the files
76+
// this handles, specifically, sourcemaps
77+
Object.keys(auxiliaryFiles).forEach((auxiliaryFile) => {
78+
files = files.concat(auxiliaryFiles[auxiliaryFile]);
79+
});
80+
81+
files = files.map((file) => {
82+
const changes = {
83+
// Append optional basepath onto all references. This allows output path to be reflected in the manifest.
84+
name: basePath ? basePath + file.name : file.name,
85+
// Similar to basePath but only affects the value (e.g. how output.publicPath turns
86+
// require('foo/bar') into '/public/foo/bar', see https://github.com/webpack/docs/wiki/configuration#outputpublicpath
87+
path: publicPath ? publicPath + file.path : file.path
88+
};
89+
90+
// Fixes #210
91+
changes.name = removeKeyHash ? changes.name.replace(removeKeyHash, '') : changes.name;
92+
93+
return Object.assign(file, changes);
94+
});
95+
96+
files = transformFiles(files, options);
97+
98+
let manifest = generateManifest(compilation, files, options);
99+
const isLastEmit = emitCount === 0;
100+
101+
manifest = getCompilerHooks(compiler).beforeEmit.call(manifest);
102+
103+
if (isLastEmit) {
104+
const output = options.serialize(manifest);
105+
//
106+
// Object.assign(compilation.assets, {
107+
// [manifestAssetId]: {
108+
// source() {
109+
// return output;
110+
// },
111+
// size() {
112+
// return output.length;
113+
// }
114+
// }
115+
// });
116+
//
117+
compilation.emitAsset(manifestAssetId, new RawSource(output));
118+
119+
if (options.writeToFileEmit) {
120+
mkdirSync(dirname(manifestFileName), { recursive: true });
121+
writeFileSync(manifestFileName, output);
122+
}
123+
}
124+
125+
getCompilerHooks(compiler).afterEmit.call(manifest);
126+
};
127+
128+
const normalModuleLoaderHook = ({ moduleAssets }, loaderContext, module) => {
129+
const { emitFile } = loaderContext;
130+
131+
// eslint-disable-next-line no-param-reassign
132+
loaderContext.emitFile = (file, content, sourceMap) => {
133+
if (module.userRequest && !moduleAssets[file]) {
134+
Object.assign(moduleAssets, { [file]: join(dirname(file), basename(module.userRequest)) });
135+
}
136+
137+
return emitFile.call(module, file, content, sourceMap);
138+
};
139+
};
140+
141+
module.exports = { beforeRunHook, emitHook, getCompilerHooks, normalModuleLoaderHook };

lib/webpack-manifest-plugin/index.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const { relative, resolve } = require('path');
2+
3+
const webpack = require('webpack');
4+
const NormalModule = require('webpack/lib/NormalModule');
5+
6+
const { beforeRunHook, emitHook, getCompilerHooks, normalModuleLoaderHook } = require('./hooks');
7+
8+
const emitCountMap = new Map();
9+
10+
const defaults = {
11+
basePath: '',
12+
fileName: 'manifest.json',
13+
filter: null,
14+
generate: void 0,
15+
map: null,
16+
publicPath: null,
17+
removeKeyHash: /([a-f0-9]{32}\.?)/gi,
18+
// seed must be reset for each compilation. let the code initialize it to {}
19+
seed: void 0,
20+
serialize(manifest) {
21+
return JSON.stringify(manifest, null, 2);
22+
},
23+
sort: null,
24+
transformExtensions: /^(gz|map)$/i,
25+
useEntryKeys: false,
26+
writeToFileEmit: false
27+
};
28+
29+
class WebpackManifestPlugin {
30+
constructor(opts) {
31+
this.options = Object.assign({}, defaults, opts);
32+
}
33+
34+
apply(compiler) {
35+
const moduleAssets = {};
36+
const manifestFileName = resolve(compiler.options.output.path, this.options.fileName);
37+
const manifestAssetId = relative(compiler.options.output.path, manifestFileName);
38+
const beforeRun = beforeRunHook.bind(this, { emitCountMap, manifestFileName });
39+
const emit = emitHook.bind(this, {
40+
compiler,
41+
emitCountMap,
42+
manifestAssetId,
43+
manifestFileName,
44+
moduleAssets,
45+
options: this.options
46+
});
47+
const normalModuleLoader = normalModuleLoaderHook.bind(this, { moduleAssets });
48+
const hookOptions = {
49+
name: 'WebpackManifestPlugin',
50+
stage: Infinity
51+
};
52+
53+
compiler.hooks.compilation.tap(hookOptions, (compilation) => {
54+
const hook = !NormalModule.getCompilationHooks
55+
? compilation.hooks.normalModuleLoader
56+
: NormalModule.getCompilationHooks(compilation).loader;
57+
hook.tap(hookOptions, normalModuleLoader);
58+
});
59+
60+
if (webpack.version.startsWith('4')) {
61+
compiler.hooks.emit.tap(hookOptions, emit);
62+
} else {
63+
compiler.hooks.thisCompilation.tap(hookOptions, (compilation) => {
64+
compilation.hooks.processAssets.tap(hookOptions, () => emit(compilation));
65+
});
66+
}
67+
68+
compiler.hooks.run.tap(hookOptions, beforeRun);
69+
compiler.hooks.watchRun.tap(hookOptions, beforeRun);
70+
}
71+
}
72+
73+
module.exports = { getCompilerHooks, WebpackManifestPlugin };

0 commit comments

Comments
 (0)