mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-22 17:47:53 +00:00
Added support for Thenables
This commit is contained in:
committed by
Steve Sanderson
parent
78e583d0fb
commit
5f6f288056
@@ -35,15 +35,25 @@ interface DevServerOptions {
|
|||||||
EnvParam: any;
|
EnvParam: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We support these three kinds of webpack.config.js export. We don't currently support exported promises
|
// Interface as defined in es6-promise
|
||||||
// (though we might be able to add that in the future, if there's a need).
|
interface Thenable<T> {
|
||||||
type WebpackConfigOrArray = webpack.Configuration | webpack.Configuration[];
|
then<U>(onFulfilled?: (value: T) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
|
||||||
interface WebpackConfigFunc {
|
then<U>(onFulfilled?: (value: T) => U | Thenable<U>, onRejected?: (error: any) => void): Thenable<U>;
|
||||||
(env?: any): WebpackConfigOrArray;
|
|
||||||
}
|
}
|
||||||
type WebpackConfigExport = WebpackConfigOrArray | WebpackConfigFunc;
|
|
||||||
|
// We support these four kinds of webpack.config.js export
|
||||||
|
type WebpackConfigOrArray = webpack.Configuration | webpack.Configuration[];
|
||||||
|
type WebpackConfigOrArrayOrThenable = WebpackConfigOrArray | Thenable<WebpackConfigOrArray>;
|
||||||
|
interface WebpackConfigFunc {
|
||||||
|
(env?: any): WebpackConfigOrArrayOrThenable;
|
||||||
|
}
|
||||||
|
type WebpackConfigExport = WebpackConfigOrArrayOrThenable | WebpackConfigFunc;
|
||||||
type WebpackConfigModuleExports = WebpackConfigExport | EsModuleExports<WebpackConfigExport>;
|
type WebpackConfigModuleExports = WebpackConfigExport | EsModuleExports<WebpackConfigExport>;
|
||||||
|
|
||||||
|
function isThenable(obj: any) {
|
||||||
|
return obj && typeof (<Thenable<any>>obj).then === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configuration, enableHotModuleReplacement: boolean, enableReactHotModuleReplacement: boolean, hmrClientOptions: StringMap<string>, hmrServerEndpoint: string) {
|
function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configuration, enableHotModuleReplacement: boolean, enableReactHotModuleReplacement: boolean, hmrClientOptions: StringMap<string>, hmrServerEndpoint: string) {
|
||||||
// Build the final Webpack config based on supplied options
|
// Build the final Webpack config based on supplied options
|
||||||
if (enableHotModuleReplacement) {
|
if (enableHotModuleReplacement) {
|
||||||
@@ -251,76 +261,85 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
|
|||||||
// your Startup.cs.
|
// your Startup.cs.
|
||||||
webpackConfigExport = webpackConfigExport(options.suppliedOptions.EnvParam);
|
webpackConfigExport = webpackConfigExport(options.suppliedOptions.EnvParam);
|
||||||
}
|
}
|
||||||
const webpackConfigArray = webpackConfigExport instanceof Array ? webpackConfigExport : [webpackConfigExport];
|
|
||||||
|
|
||||||
const enableHotModuleReplacement = options.suppliedOptions.HotModuleReplacement;
|
const webpackConfigThenable = isThenable(webpackConfigExport)
|
||||||
const enableReactHotModuleReplacement = options.suppliedOptions.ReactHotModuleReplacement;
|
? webpackConfigExport
|
||||||
if (enableReactHotModuleReplacement && !enableHotModuleReplacement) {
|
: { then: callback => callback(webpackConfigExport) };
|
||||||
callback('To use ReactHotModuleReplacement, you must also enable the HotModuleReplacement option.', null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The default value, 0, means 'choose randomly'
|
webpackConfigThenable.then(webpackConfigResolved => {
|
||||||
const suggestedHMRPortOrZero = options.suppliedOptions.HotModuleReplacementServerPort || 0;
|
const webpackConfigArray = webpackConfigResolved instanceof Array ? webpackConfigResolved : [webpackConfigResolved];
|
||||||
|
|
||||||
const app = connect();
|
const enableHotModuleReplacement = options.suppliedOptions.HotModuleReplacement;
|
||||||
const listener = app.listen(suggestedHMRPortOrZero, () => {
|
const enableReactHotModuleReplacement = options.suppliedOptions.ReactHotModuleReplacement;
|
||||||
try {
|
if (enableReactHotModuleReplacement && !enableHotModuleReplacement) {
|
||||||
// For each webpack config that specifies a public path, add webpack dev middleware for it
|
callback('To use ReactHotModuleReplacement, you must also enable the HotModuleReplacement option.', null);
|
||||||
const normalizedPublicPaths: string[] = [];
|
return;
|
||||||
webpackConfigArray.forEach(webpackConfig => {
|
|
||||||
if (webpackConfig.target === 'node') {
|
|
||||||
// For configs that target Node, it's meaningless to set up an HTTP listener, since
|
|
||||||
// Node isn't going to load those modules over HTTP anyway. It just loads them directly
|
|
||||||
// from disk. So the most relevant thing we can do with such configs is just write
|
|
||||||
// updated builds to disk, just like "webpack --watch".
|
|
||||||
beginWebpackWatcher(webpackConfig);
|
|
||||||
} else {
|
|
||||||
// For configs that target browsers, we can set up an HTTP listener, and dynamically
|
|
||||||
// modify the config to enable HMR etc. This just requires that we have a publicPath.
|
|
||||||
const publicPath = (webpackConfig.output.publicPath || '').trim();
|
|
||||||
if (!publicPath) {
|
|
||||||
throw new Error('To use the Webpack dev server, you must specify a value for \'publicPath\' on the \'output\' section of your webpack config (for any configuration that targets browsers)');
|
|
||||||
}
|
|
||||||
const publicPathNoTrailingSlash = removeTrailingSlash(publicPath);
|
|
||||||
normalizedPublicPaths.push(publicPathNoTrailingSlash);
|
|
||||||
|
|
||||||
// This is the URL the client will connect to, except that since it's a relative URL
|
|
||||||
// (no leading slash), Webpack will resolve it against the runtime <base href> URL
|
|
||||||
// plus it also adds the publicPath
|
|
||||||
const hmrClientEndpoint = removeLeadingSlash(options.hotModuleReplacementEndpointUrl);
|
|
||||||
|
|
||||||
// This is the URL inside the Webpack middleware Node server that we'll proxy to.
|
|
||||||
// We have to prefix with the public path because Webpack will add the publicPath
|
|
||||||
// when it resolves hmrClientEndpoint as a relative URL.
|
|
||||||
const hmrServerEndpoint = ensureLeadingSlash(publicPathNoTrailingSlash + options.hotModuleReplacementEndpointUrl);
|
|
||||||
|
|
||||||
// We always overwrite the 'path' option as it needs to match what the .NET side is expecting
|
|
||||||
const hmrClientOptions = options.suppliedOptions.HotModuleReplacementClientOptions || <StringMap<string>>{};
|
|
||||||
hmrClientOptions['path'] = hmrClientEndpoint;
|
|
||||||
|
|
||||||
const dynamicPublicPathKey = 'dynamicPublicPath';
|
|
||||||
if (!(dynamicPublicPathKey in hmrClientOptions)) {
|
|
||||||
// dynamicPublicPath default to true, so we can work with nonempty pathbases (virtual directories)
|
|
||||||
hmrClientOptions[dynamicPublicPathKey] = true;
|
|
||||||
} else {
|
|
||||||
// ... but you can set it to any other value explicitly if you want (e.g., false)
|
|
||||||
hmrClientOptions[dynamicPublicPathKey] = JSON.parse(hmrClientOptions[dynamicPublicPathKey]);
|
|
||||||
}
|
|
||||||
|
|
||||||
attachWebpackDevMiddleware(app, webpackConfig, enableHotModuleReplacement, enableReactHotModuleReplacement, hmrClientOptions, hmrServerEndpoint);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
|
|
||||||
callback(null, {
|
|
||||||
Port: listener.address().port,
|
|
||||||
PublicPaths: normalizedPublicPaths
|
|
||||||
});
|
|
||||||
} catch (ex) {
|
|
||||||
callback(ex.stack, null);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// The default value, 0, means 'choose randomly'
|
||||||
|
const suggestedHMRPortOrZero = options.suppliedOptions.HotModuleReplacementServerPort || 0;
|
||||||
|
|
||||||
|
const app = connect();
|
||||||
|
const listener = app.listen(suggestedHMRPortOrZero, () => {
|
||||||
|
try {
|
||||||
|
// For each webpack config that specifies a public path, add webpack dev middleware for it
|
||||||
|
const normalizedPublicPaths: string[] = [];
|
||||||
|
webpackConfigArray.forEach(webpackConfig => {
|
||||||
|
if (webpackConfig.target === 'node') {
|
||||||
|
// For configs that target Node, it's meaningless to set up an HTTP listener, since
|
||||||
|
// Node isn't going to load those modules over HTTP anyway. It just loads them directly
|
||||||
|
// from disk. So the most relevant thing we can do with such configs is just write
|
||||||
|
// updated builds to disk, just like "webpack --watch".
|
||||||
|
beginWebpackWatcher(webpackConfig);
|
||||||
|
} else {
|
||||||
|
// For configs that target browsers, we can set up an HTTP listener, and dynamically
|
||||||
|
// modify the config to enable HMR etc. This just requires that we have a publicPath.
|
||||||
|
const publicPath = (webpackConfig.output.publicPath || '').trim();
|
||||||
|
if (!publicPath) {
|
||||||
|
throw new Error('To use the Webpack dev server, you must specify a value for \'publicPath\' on the \'output\' section of your webpack config (for any configuration that targets browsers)');
|
||||||
|
}
|
||||||
|
const publicPathNoTrailingSlash = removeTrailingSlash(publicPath);
|
||||||
|
normalizedPublicPaths.push(publicPathNoTrailingSlash);
|
||||||
|
|
||||||
|
// This is the URL the client will connect to, except that since it's a relative URL
|
||||||
|
// (no leading slash), Webpack will resolve it against the runtime <base href> URL
|
||||||
|
// plus it also adds the publicPath
|
||||||
|
const hmrClientEndpoint = removeLeadingSlash(options.hotModuleReplacementEndpointUrl);
|
||||||
|
|
||||||
|
// This is the URL inside the Webpack middleware Node server that we'll proxy to.
|
||||||
|
// We have to prefix with the public path because Webpack will add the publicPath
|
||||||
|
// when it resolves hmrClientEndpoint as a relative URL.
|
||||||
|
const hmrServerEndpoint = ensureLeadingSlash(publicPathNoTrailingSlash + options.hotModuleReplacementEndpointUrl);
|
||||||
|
|
||||||
|
// We always overwrite the 'path' option as it needs to match what the .NET side is expecting
|
||||||
|
const hmrClientOptions = options.suppliedOptions.HotModuleReplacementClientOptions || <StringMap<string>>{};
|
||||||
|
hmrClientOptions['path'] = hmrClientEndpoint;
|
||||||
|
|
||||||
|
const dynamicPublicPathKey = 'dynamicPublicPath';
|
||||||
|
if (!(dynamicPublicPathKey in hmrClientOptions)) {
|
||||||
|
// dynamicPublicPath default to true, so we can work with nonempty pathbases (virtual directories)
|
||||||
|
hmrClientOptions[dynamicPublicPathKey] = true;
|
||||||
|
} else {
|
||||||
|
// ... but you can set it to any other value explicitly if you want (e.g., false)
|
||||||
|
hmrClientOptions[dynamicPublicPathKey] = JSON.parse(hmrClientOptions[dynamicPublicPathKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
attachWebpackDevMiddleware(app, webpackConfig, enableHotModuleReplacement, enableReactHotModuleReplacement, hmrClientOptions, hmrServerEndpoint);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
|
||||||
|
callback(null, {
|
||||||
|
Port: listener.address().port,
|
||||||
|
PublicPaths: normalizedPublicPaths
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
callback(ex.stack, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
err => callback(err.stack, null)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeLeadingSlash(str: string) {
|
function removeLeadingSlash(str: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user