diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json index b2813a4..e239460 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-prerendering", - "version": "2.0.6", + "version": "3.0.1", "description": "Helpers for server-side rendering of JavaScript applications in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { @@ -17,7 +17,7 @@ "url": "https://github.com/aspnet/JavaScriptServices.git" }, "dependencies": { - "domain-task": "^2.0.2" + "domain-task": "^3.0.0" }, "devDependencies": { "@types/node": "^6.0.42", diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts index 80a0259..a313cba 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts @@ -26,6 +26,7 @@ export function createServerRenderer(bootFunc: BootFunc): RenderToStringFunc { domainTasks: domainTaskCompletionPromise, data: customDataParameter }; + const absoluteBaseUrl = params.origin + params.baseUrl; // Should be same value as page's // Open a new domain that can track all the async tasks involved in the app's execution domainTaskRun(/* code to run */ () => { @@ -35,7 +36,7 @@ export function createServerRenderer(bootFunc: BootFunc): RenderToStringFunc { bindPromiseContinuationsToDomain(domainTaskCompletionPromise, domain['active']); // Make the base URL available to the 'domain-tasks/fetch' helper within this execution context - domainTaskBaseUrl(absoluteRequestUrl); + domainTaskBaseUrl(absoluteBaseUrl); // Begin rendering, and apply a timeout const bootFuncPromise = bootFunc(params); diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json index a6f0b0f..19a2a1b 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack-react/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-webpack-react", - "version": "3.0.0-beta.1", + "version": "3.0.0", "description": "Helpers for using Webpack with React in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json index 010d226..47b3381 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/package.json @@ -1,6 +1,6 @@ { "name": "aspnet-webpack", - "version": "1.0.29", + "version": "2.0.0", "description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "main": "index.js", "scripts": { diff --git a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts index bf31d82..7fba9de 100644 --- a/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts +++ b/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/WebpackDevMiddleware.ts @@ -108,7 +108,7 @@ function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configurati const compiler = webpack(webpackConfig); app.use(require('webpack-dev-middleware')(compiler, { noInfo: true, - publicPath: webpackConfig.output.publicPath, + publicPath: ensureLeadingSlash(webpackConfig.output.publicPath), watchOptions: webpackConfig.watchOptions })); @@ -195,6 +195,14 @@ function copyRecursiveToRealFsSync(from: typeof fs, rootDir: string, exclude: Re }); } +function ensureLeadingSlash(value: string) { + if (value !== null && value.substring(0, 1) !== '/') { + value = '/' + value; + } + + return value; +} + function pathJoinSafe(rootPath: string, filePath: string) { // On Windows, MemoryFileSystem's readdirSync output produces directory entries like 'C:' // which then trigger errors if you call statSync for them. Avoid this by detecting drive @@ -257,22 +265,32 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option 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)'); } - normalizedPublicPaths.push(removeTrailingSlash(publicPath)); + const publicPathNoTrailingSlash = removeTrailingSlash(publicPath); + normalizedPublicPaths.push(publicPathNoTrailingSlash); - // Newer versions of Microsoft.AspNetCore.SpaServices will explicitly pass an HMR endpoint URL - // (because it's relative to the app's URL space root, which the client doesn't otherwise know). - // For back-compatibility, fall back on connecting directly to the underlying HMR server (though - // that won't work if the app is hosted on HTTPS because of the mixed-content rule, and we can't - // run the HMR server itself on HTTPS because in general it has no valid cert). - const hmrClientEndpoint = options.hotModuleReplacementEndpointUrl // The URL that we'll proxy (e.g., /__asp_webpack_hmr) - || `http://localhost:${listener.address().port}/__webpack_hmr`; // Fall back on absolute URL to bypass proxying - const hmrServerEndpoint = options.hotModuleReplacementEndpointUrl - || '/__webpack_hmr'; // URL is relative to webpack dev server root + // 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 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 || >{}; 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); } }); @@ -292,6 +310,14 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option }); } +function removeLeadingSlash(str: string) { + if (str.indexOf('/') === 0) { + str = str.substring(1); + } + + return str; +} + function removeTrailingSlash(str: string) { if (str.lastIndexOf('/') === str.length - 1) { str = str.substring(0, str.length - 1); diff --git a/templates/AngularSpa/ClientApp/app/app.module.browser.ts b/templates/AngularSpa/ClientApp/app/app.module.browser.ts index 7e71ca9..03104b4 100644 --- a/templates/AngularSpa/ClientApp/app/app.module.browser.ts +++ b/templates/AngularSpa/ClientApp/app/app.module.browser.ts @@ -10,12 +10,12 @@ import { AppComponent } from './components/app/app.component'; AppModuleShared ], providers: [ - { provide: 'ORIGIN_URL', useFactory: getOriginUrl } + { provide: 'BASE_URL', useFactory: getBaseUrl } ] }) export class AppModule { } -export function getOriginUrl() { - return location.origin; +export function getBaseUrl() { + return document.getElementsByTagName('base')[0].href; } diff --git a/templates/AngularSpa/ClientApp/app/components/fetchdata/fetchdata.component.ts b/templates/AngularSpa/ClientApp/app/components/fetchdata/fetchdata.component.ts index 6296925..a4c06f4 100644 --- a/templates/AngularSpa/ClientApp/app/components/fetchdata/fetchdata.component.ts +++ b/templates/AngularSpa/ClientApp/app/components/fetchdata/fetchdata.component.ts @@ -8,8 +8,8 @@ import { Http } from '@angular/http'; export class FetchDataComponent { public forecasts: WeatherForecast[]; - constructor(http: Http, @Inject('ORIGIN_URL') originUrl: string) { - http.get(originUrl + '/api/SampleData/WeatherForecasts').subscribe(result => { + constructor(http: Http, @Inject('BASE_URL') baseUrl: string) { + http.get(baseUrl + 'api/SampleData/WeatherForecasts').subscribe(result => { this.forecasts = result.json() as WeatherForecast[]; }, error => console.error(error)); } diff --git a/templates/AngularSpa/ClientApp/boot.server.ts b/templates/AngularSpa/ClientApp/boot.server.ts index 08474cf..851e137 100644 --- a/templates/AngularSpa/ClientApp/boot.server.ts +++ b/templates/AngularSpa/ClientApp/boot.server.ts @@ -1,6 +1,7 @@ import 'reflect-metadata'; import 'zone.js'; import 'rxjs/add/operator/first'; +import { APP_BASE_HREF } from '@angular/common'; import { enableProdMode, ApplicationRef, NgZone, ValueProvider } from '@angular/core'; import { platformDynamicServer, PlatformState, INITIAL_CONFIG } from '@angular/platform-server'; import { createServerRenderer, RenderResult } from 'aspnet-prerendering'; @@ -11,7 +12,8 @@ enableProdMode(); export default createServerRenderer(params => { const providers = [ { provide: INITIAL_CONFIG, useValue: { document: '', url: params.url } }, - { provide: 'ORIGIN_URL', useValue: params.origin } + { provide: APP_BASE_HREF, useValue: params.baseUrl }, + { provide: 'BASE_URL', useValue: params.origin + params.baseUrl }, ]; return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => { diff --git a/templates/AngularSpa/npm-shrinkwrap.json b/templates/AngularSpa/npm-shrinkwrap.json index f4f3ada..e87dcf5 100644 --- a/templates/AngularSpa/npm-shrinkwrap.json +++ b/templates/AngularSpa/npm-shrinkwrap.json @@ -274,14 +274,14 @@ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz" }, "aspnet-prerendering": { - "version": "2.0.6", - "from": "aspnet-prerendering@>=2.0.5 <3.0.0", - "resolved": "https://registry.npmjs.org/aspnet-prerendering/-/aspnet-prerendering-2.0.6.tgz" + "version": "3.0.1", + "from": "aspnet-prerendering@3.0.1", + "resolved": "https://registry.npmjs.org/aspnet-prerendering/-/aspnet-prerendering-3.0.1.tgz" }, "aspnet-webpack": { - "version": "1.0.29", - "from": "aspnet-webpack@>=1.0.29 <2.0.0", - "resolved": "https://registry.npmjs.org/aspnet-webpack/-/aspnet-webpack-1.0.29.tgz" + "version": "2.0.0", + "from": "aspnet-webpack@2.0.0", + "resolved": "https://registry.npmjs.org/aspnet-webpack/-/aspnet-webpack-2.0.0.tgz" }, "assert": { "version": "1.4.1", @@ -932,9 +932,9 @@ "resolved": "https://registry.npmjs.org/domain-context/-/domain-context-0.5.1.tgz" }, "domain-task": { - "version": "2.0.3", - "from": "domain-task@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/domain-task/-/domain-task-2.0.3.tgz" + "version": "3.0.3", + "from": "domain-task@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/domain-task/-/domain-task-3.0.3.tgz" }, "ee-first": { "version": "1.1.1", diff --git a/templates/AngularSpa/package.json b/templates/AngularSpa/package.json index 58494ec..65f753f 100644 --- a/templates/AngularSpa/package.json +++ b/templates/AngularSpa/package.json @@ -20,8 +20,8 @@ "@ngtools/webpack": "1.5.0", "@types/node": "8.0.8", "angular2-template-loader": "0.6.2", - "aspnet-prerendering": "^2.0.5", - "aspnet-webpack": "^1.0.29", + "aspnet-prerendering": "^3.0.1", + "aspnet-webpack": "^2.0.0", "awesome-typescript-loader": "3.2.1", "bootstrap": "3.3.7", "css": "2.2.1", diff --git a/templates/AngularSpa/webpack.config.js b/templates/AngularSpa/webpack.config.js index fd1ff6e..85e238d 100644 --- a/templates/AngularSpa/webpack.config.js +++ b/templates/AngularSpa/webpack.config.js @@ -13,7 +13,7 @@ module.exports = (env) => { resolve: { extensions: [ '.js', '.ts' ] }, output: { filename: '[name].js', - publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix + publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix }, module: { rules: [ diff --git a/templates/AngularSpa/webpack.config.vendor.js b/templates/AngularSpa/webpack.config.vendor.js index ed7fa93..36e4386 100644 --- a/templates/AngularSpa/webpack.config.vendor.js +++ b/templates/AngularSpa/webpack.config.vendor.js @@ -36,7 +36,7 @@ module.exports = (env) => { ] }, output: { - publicPath: '/dist/', + publicPath: 'dist/', filename: '[name].js', library: '[name]_[hash]' }, diff --git a/templates/AureliaSpa/ClientApp/app/components/fetchdata/fetchdata.ts b/templates/AureliaSpa/ClientApp/app/components/fetchdata/fetchdata.ts index ff86025..6cd72ac 100644 --- a/templates/AureliaSpa/ClientApp/app/components/fetchdata/fetchdata.ts +++ b/templates/AureliaSpa/ClientApp/app/components/fetchdata/fetchdata.ts @@ -6,7 +6,7 @@ export class Fetchdata { public forecasts: WeatherForecast[]; constructor(http: HttpClient) { - http.fetch('/api/SampleData/WeatherForecasts') + http.fetch('api/SampleData/WeatherForecasts') .then(result => result.json() as Promise) .then(data => { this.forecasts = data; diff --git a/templates/AureliaSpa/ClientApp/boot.ts b/templates/AureliaSpa/ClientApp/boot.ts index 5c9e2fd..053b9c3 100644 --- a/templates/AureliaSpa/ClientApp/boot.ts +++ b/templates/AureliaSpa/ClientApp/boot.ts @@ -1,5 +1,6 @@ import 'isomorphic-fetch'; import { Aurelia, PLATFORM } from 'aurelia-framework'; +import { HttpClient } from 'aurelia-fetch-client'; import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap'; declare const IS_DEV_BUILD: boolean; // The value is supplied by Webpack during the build @@ -11,5 +12,10 @@ export function configure(aurelia: Aurelia) { aurelia.use.developmentLogging(); } + new HttpClient().configure(config => { + const baseUrl = document.getElementsByTagName('base')[0].href; + config.withBaseUrl(baseUrl); + }); + aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app/components/app/app'))); } diff --git a/templates/AureliaSpa/npm-shrinkwrap.json b/templates/AureliaSpa/npm-shrinkwrap.json index 611e8b6..5a52826 100644 --- a/templates/AureliaSpa/npm-shrinkwrap.json +++ b/templates/AureliaSpa/npm-shrinkwrap.json @@ -113,9 +113,9 @@ "dev": true }, "aspnet-webpack": { - "version": "1.0.29", - "from": "aspnet-webpack@>=1.0.28 <2.0.0", - "resolved": "https://registry.npmjs.org/aspnet-webpack/-/aspnet-webpack-1.0.29.tgz", + "version": "2.0.0", + "from": "aspnet-webpack@2.0.0", + "resolved": "https://registry.npmjs.org/aspnet-webpack/-/aspnet-webpack-2.0.0.tgz", "dev": true }, "assert": { diff --git a/templates/AureliaSpa/package.json b/templates/AureliaSpa/package.json index 99bc02b..7e92b5a 100644 --- a/templates/AureliaSpa/package.json +++ b/templates/AureliaSpa/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@types/node": "^7.0.12", - "aspnet-webpack": "^1.0.28", + "aspnet-webpack": "^2.0.0", "aurelia-webpack-plugin": "^2.0.0-rc.2", "css-loader": "^0.28.0", "extract-text-webpack-plugin": "^2.1.0", diff --git a/templates/AureliaSpa/webpack.config.js b/templates/AureliaSpa/webpack.config.js index 4ddc35c..5ff94d0 100644 --- a/templates/AureliaSpa/webpack.config.js +++ b/templates/AureliaSpa/webpack.config.js @@ -14,7 +14,7 @@ module.exports = (env) => { }, output: { path: path.resolve(bundleOutputDir), - publicPath: '/dist/', + publicPath: 'dist/', filename: '[name].js' }, module: { diff --git a/templates/AureliaSpa/webpack.config.vendor.js b/templates/AureliaSpa/webpack.config.vendor.js index 9625a31..1dc579c 100644 --- a/templates/AureliaSpa/webpack.config.vendor.js +++ b/templates/AureliaSpa/webpack.config.vendor.js @@ -38,7 +38,7 @@ module.exports = ({ prod } = {}) => { }, output: { path: path.join(__dirname, 'wwwroot', 'dist'), - publicPath: '/dist/', + publicPath: 'dist/', filename: '[name].js', library: '[name]_[hash]', }, diff --git a/templates/KnockoutSpa/ClientApp/boot.ts b/templates/KnockoutSpa/ClientApp/boot.ts index 143e8e5..b8da1dd 100644 --- a/templates/KnockoutSpa/ClientApp/boot.ts +++ b/templates/KnockoutSpa/ClientApp/boot.ts @@ -4,12 +4,14 @@ import * as ko from 'knockout'; import './webpack-component-loader'; import AppRootComponent from './components/app-root/app-root'; const createHistory = require('history').createBrowserHistory; +const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href'); +const basename = baseUrl.substring(0, baseUrl.length - 1); // History component needs no trailing slash // Load and register the component ko.components.register('app-root', AppRootComponent); // Tell Knockout to start up an instance of your application -ko.applyBindings({ history: createHistory() }); +ko.applyBindings({ history: createHistory({ basename }), basename }); // Basic hot reloading support. Automatically reloads and restarts the Knockout app each time // you modify source files. This will not preserve any application state other than the URL. diff --git a/templates/KnockoutSpa/ClientApp/components/app-root/app-root.html b/templates/KnockoutSpa/ClientApp/components/app-root/app-root.html index f1f11b4..88e345b 100644 --- a/templates/KnockoutSpa/ClientApp/components/app-root/app-root.html +++ b/templates/KnockoutSpa/ClientApp/components/app-root/app-root.html @@ -1,7 +1,7 @@
- +
diff --git a/templates/KnockoutSpa/ClientApp/components/app-root/app-root.ts b/templates/KnockoutSpa/ClientApp/components/app-root/app-root.ts index af9062c..2ca1bfe 100644 --- a/templates/KnockoutSpa/ClientApp/components/app-root/app-root.ts +++ b/templates/KnockoutSpa/ClientApp/components/app-root/app-root.ts @@ -12,12 +12,12 @@ const routes: Route[] = [ class AppRootViewModel { public route: KnockoutObservable; - private _router: Router; + public router: Router; - constructor(params: { history: History.History }) { + constructor(params: { history: History.History, basename: string }) { // Activate the client-side router - this._router = new Router(params.history, routes) - this.route = this._router.currentRoute; + this.router = new Router(params.history, routes, params.basename); + this.route = this.router.currentRoute; // Load and register all the KO components needed to handle the routes // The optional 'bundle-loader?lazy!' prefix is a Webpack feature that causes the referenced modules @@ -32,7 +32,7 @@ class AppRootViewModel { // To support hot module replacement, this method unregisters the router and KO components. // In production scenarios where hot module replacement is disabled, this would not be invoked. public dispose() { - this._router.dispose(); + this.router.dispose(); // TODO: Need a better API for this Object.getOwnPropertyNames((ko).components._allRegisteredComponents).forEach(componentName => { diff --git a/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts b/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts index 9576d66..b053890 100644 --- a/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts +++ b/templates/KnockoutSpa/ClientApp/components/fetch-data/fetch-data.ts @@ -12,7 +12,7 @@ class FetchDataViewModel { public forecasts = ko.observableArray(); constructor() { - fetch('/api/SampleData/WeatherForecasts') + fetch('api/SampleData/WeatherForecasts') .then(response => response.json() as Promise) .then(data => { this.forecasts(data); diff --git a/templates/KnockoutSpa/ClientApp/components/nav-menu/nav-menu.html b/templates/KnockoutSpa/ClientApp/components/nav-menu/nav-menu.html index 0bfd32c..f053cef 100644 --- a/templates/KnockoutSpa/ClientApp/components/nav-menu/nav-menu.html +++ b/templates/KnockoutSpa/ClientApp/components/nav-menu/nav-menu.html @@ -13,17 +13,17 @@