-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix(utils): addEntries fix #2723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ae5b765
a07a914
317617a
438f6a4
e7eae8b
ee0c95b
ca8abc6
0cb08c8
6859414
833e70f
0a15f2f
1a91e22
82efa72
ce3a419
ebd1e60
6de37f2
dd66b7c
572767e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,12 +10,12 @@ const createDomain = require('./createDomain'); | |
|
||
/** | ||
* Add entries Method | ||
* @param {?Object} config - Webpack config | ||
* @param {?Object} originalCompiler - Webpack compiler | ||
* @param {?Object} options - Dev-Server options | ||
* @param {?Object} server | ||
* @returns {void} | ||
*/ | ||
function addEntries(config, options, server) { | ||
function addEntries(originalCompiler, options, server) { | ||
// we're stubbing the app in this method as it's static and doesn't require | ||
// a server to be supplied. createDomain requires an app with the | ||
// address() signature. | ||
|
@@ -53,42 +53,34 @@ function addEntries(config, options, server) { | |
} else if (options.hot) { | ||
hotEntry = require.resolve('webpack/hot/dev-server'); | ||
} | ||
|
||
/** | ||
* prependEntry Method | ||
* prependEntries Method | ||
* @param {Entry} originalEntry | ||
* @param {Entry} additionalEntries | ||
* @returns {Entry} | ||
*/ | ||
const prependEntry = (originalEntry, additionalEntries) => { | ||
const prependEntries = (originalEntry, additionalEntries) => { | ||
if (typeof originalEntry === 'function') { | ||
return () => | ||
Promise.resolve(originalEntry()).then((entry) => | ||
prependEntry(entry, additionalEntries) | ||
prependEntries(entry, additionalEntries) | ||
); | ||
} | ||
|
||
if (typeof originalEntry === 'object' && !Array.isArray(originalEntry)) { | ||
/** @type {Object<string,string>} */ | ||
const clone = {}; | ||
const entryObj = {}; | ||
|
||
Object.keys(originalEntry).forEach((key) => { | ||
// entry[key] should be a string here | ||
const entryDescription = originalEntry[key]; | ||
if (typeof entryDescription === 'object' && entryDescription.import) { | ||
clone[key] = Object.assign({}, entryDescription, { | ||
import: prependEntry(entryDescription.import, additionalEntries), | ||
}); | ||
} else { | ||
clone[key] = prependEntry(entryDescription, additionalEntries); | ||
} | ||
entryObj[key] = prependEntries(entryDescription, additionalEntries); | ||
}); | ||
|
||
return clone; | ||
return entryObj; | ||
} | ||
|
||
// in this case, entry is a string or an array. | ||
// in this case, originalEntry is a string or an array. | ||
// make sure that we do not add duplicates. | ||
/** @type {Entry} */ | ||
const entriesClone = additionalEntries.slice(0); | ||
[].concat(originalEntry).forEach((newEntry) => { | ||
if (!entriesClone.includes(newEntry)) { | ||
|
@@ -120,8 +112,10 @@ function addEntries(config, options, server) { | |
return defaultValue; | ||
}; | ||
|
||
// eslint-disable-next-line no-shadow | ||
[].concat(config).forEach((config) => { | ||
const compilers = originalCompiler.compilers || [originalCompiler]; | ||
|
||
compilers.forEach((compiler) => { | ||
const config = compiler.options; | ||
/** @type {Boolean} */ | ||
const webTarget = [ | ||
'web', | ||
|
@@ -145,8 +139,7 @@ function addEntries(config, options, server) { | |
additionalEntries.push(hotEntry); | ||
} | ||
|
||
config.entry = prependEntry(config.entry || './src', additionalEntries); | ||
|
||
// add the HMR plugin to each compiler config if hot is enabled | ||
if (options.hot || options.hot === 'only') { | ||
config.plugins = config.plugins || []; | ||
if ( | ||
|
@@ -159,6 +152,79 @@ function addEntries(config, options, server) { | |
config.plugins.push(new webpack.HotModuleReplacementPlugin()); | ||
} | ||
} | ||
|
||
// if there are no additional entries to be added, nothing | ||
// below is needed | ||
if (!additionalEntries.length) { | ||
return; | ||
} | ||
|
||
if ( | ||
webpack.version[0] === '5' && | ||
!compiler.hasWebpackDevServerMakeCallback | ||
) { | ||
// this counter allows the dev server to ensure only the most recent | ||
// make callback is used, preventing duplicate entry injections | ||
if (compiler.webpackDevServerMakeCallbackCount) { | ||
// this invalidates old make callbacks | ||
compiler.webpackDevServerMakeCallbackCount += 1; | ||
} else { | ||
compiler.webpackDevServerMakeCallbackCount = 1; | ||
} | ||
// this is the index of this particular make hook tap, meaning | ||
// this callback should only do something if it is the most recently | ||
// created callback | ||
const makeCallbackIndex = compiler.webpackDevServerMakeCallbackCount; | ||
|
||
// EntryPlugin is only available for webpack@5 | ||
// @ts-ignore | ||
const EntryPlugin = require('webpack/lib/EntryPlugin'); // eslint-disable-line import/no-unresolved | ||
// for webpack@5, we use a plugin to add global entries | ||
compiler.hooks.make.tapAsync( | ||
{ | ||
name: 'webpack-dev-server', | ||
stage: -100, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @evilebottnawi @sokra I've added the tapAsync method here, but the issue I've found now is that something like this where multiple Servers are attached to a compiler no longer works:
as it results in something like This worked before, as there were tests that did this which are now broken. (These tests would usually close the old server before a new one was attached) Should we go about this by making multiple servers on one compiler discouraged, or maybe a hacky method of disabling the old hook taps like in webpack/tapable#109 after the server is closed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or I could also try to prevent duplicate entries by looking at the compilation globalEntry object. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added the hacky method for disabling old hook taps, but haven't added tests for it yet There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Loonride I think we should prevent duplicate, in theory we can have property on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @evilebottnawi That is a good idea, do you think I should remove my There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is more clean hack 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @evilebottnawi I agree it is more clean, but one issue I now see is that if the user does:
If the |
||
}, | ||
(compilation, callback) => { | ||
if ( | ||
makeCallbackIndex !== compiler.webpackDevServerMakeCallbackCount | ||
) { | ||
return callback(); | ||
} | ||
|
||
const promises = additionalEntries.map((additionalEntry) => { | ||
return new Promise((fulfill, reject) => { | ||
const depOptions = { | ||
// eslint-disable-next-line no-undefined | ||
name: undefined, // global entry, added before all entries | ||
}; | ||
const dep = EntryPlugin.createDependency( | ||
additionalEntry, | ||
depOptions | ||
); | ||
compilation.addEntry(compiler.context, dep, options, (err) => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
return fulfill(); | ||
}); | ||
}); | ||
}); | ||
|
||
Promise.all(promises) | ||
.then(() => { | ||
callback(); | ||
}) | ||
.catch(callback); | ||
} | ||
); | ||
} else { | ||
// this is a hacky method of injecting entries after compiler creation | ||
// that should only be used for webpack@4 | ||
const originalEntry = config.entry || './src'; | ||
config.entry = prependEntries(originalEntry, additionalEntries); | ||
compiler.hooks.entryOption.call(config.context, config.entry); | ||
} | ||
}); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea was to make a counter for callbacks, so that only the most recent callback will be used, fixing the problem I mentioned. Let me know if this seems too hacky.