Compare commits

..

143 Commits

Author SHA1 Message Date
Steve Sanderson
c0c47e3def Example of using BrowserAnimationsModule 2017-08-24 15:44:56 -07:00
Steve Sanderson
e5f1299239 Use devDependencies consistently in templates (no packages required in production, as webpack publish builds are standalone). Fixes #747 2017-08-24 15:31:34 -07:00
Steve Sanderson
c922eee1d6 Fix artifacts dir for test 2017-08-23 17:15:37 -07:00
Steve Sanderson
8b243e8cc7 Simplify build script further 2017-08-23 17:12:06 -07:00
Steve Sanderson
28920c7691 Simplify .gitignore 2017-08-23 16:57:43 -07:00
Steve Sanderson
412ec1b427 Build templates nupkgs directly from source without copying to staging location 2017-08-23 16:56:07 -07:00
Steve Sanderson
c62a3b491c Fix templates directory structure to produce correct nupkg output 2017-08-23 16:38:16 -07:00
Steve Sanderson
559832bb6d Remove dynamic content replacement from nuspec files 2017-08-23 15:41:09 -07:00
Steve Sanderson
45d645931b Remove template build dynamic filename replacement. Working towards eliminating template build process completely. 2017-08-23 15:27:15 -07:00
Steve Sanderson
8d6119f31d Remove the Yeoman-specific gitignore workaround 2017-08-23 15:04:04 -07:00
Steve Sanderson
0291686b20 Reorganize templates into dir structure matching 'dotnet new' templates 2017-08-23 14:58:49 -07:00
Steve Sanderson
7c52be5e42 Stop generating .template.config files dynamically. Convert them to plain files on disk. 2017-08-22 18:06:48 -07:00
Steve Sanderson
900e9ca835 Add deprecation notice to Yeoman package 2017-08-22 17:11:02 -07:00
Steve Sanderson
ad758b1060 Update AppVeyor config to remove Yeoman artifact reference 2017-08-22 16:20:25 -07:00
Steve Sanderson
cd9ad38a99 Run tests against 'dotnet new' output instead of Yeoman output 2017-08-22 16:13:34 -07:00
Steve Sanderson
e057cb35ec Remove Yeoman from the template build process 2017-08-22 14:43:51 -07:00
Steve Sanderson
eea2066a6d Remove Yeoman generator - replace it with deprecation notice. 2017-08-22 14:33:30 -07:00
Steve Sanderson
d6ae8829b6 In HMR, don't rely on default JsonSerializer settings. Fixes #688 2017-08-21 17:11:47 -07:00
Steve Sanderson
a94ac6f37e For Redux dev tools, use newer __REDUX_DEVTOOLS_EXTENSION__ API. Fixes #1196 2017-08-21 16:48:30 -07:00
Steve Sanderson
a40adab38d In non-ASP.NET apps, default project path to current working directory. Fixes #1100. 2017-08-21 16:40:59 -07:00
Stephan Troyer
c2a284d5b8 small Knockout cleanup 2017-08-21 16:17:10 -07:00
frederikprijck
fc398d602a Allow lazy loading with AngularSpa in dev build
Previously, the AngularSpa didn't include `angular2-router-loader`.
This commit ensures it does.

Closes #1194
2017-08-21 16:06:18 -07:00
Steve Sanderson
90c59ff4e7 Merge branch 'fix-angular-material-publishing' into dev 2017-08-21 15:53:19 -07:00
Steve Lathrop
a7e715c88f Small grammatical fix to README.md 2017-08-21 15:37:58 -07:00
alejandro garcia
6dddc9d01d Removed json loader from react redux template 2017-08-21 15:37:26 -07:00
Steve Sanderson
5ed1a35ce0 Fix problems with AoT when using Angular Material. Fixes #1168 2017-08-03 18:00:46 +01:00
Steve Sanderson
680ba7497a Merge branch 'rel/2.0.0-templates' into dev 2017-08-03 10:52:54 +01:00
Steve Sanderson
287c10fd2e Bump additional SPA templates package version to 1.0.0 2017-08-03 10:52:08 +01:00
John Luo
63f7ac9330 Ensure fallback to curl after failed wget 2017-08-02 14:32:21 -07:00
John Luo
d2858beaa1 Update __get_remote_file logic 2017-08-02 12:44:45 -07:00
Steve Sanderson
5f1450c9ba Make aspnet-angular compatible with AoT compilation 2017-08-02 10:53:46 +01:00
Steve Sanderson
c83605baff Add aspnet-angular NPM package containing HttpWithStateTransfer utility 2017-08-02 10:26:46 +01:00
Stephen Lautier
fc12d722b8 fix(webpack): fix middleware to specifically serialize options as non-camecased 2017-08-02 09:24:42 +01:00
Eric Green
372e597f34 Added Timeout to HttpClient to always be longer than the InvocationTimeoutMilliseconds.
HttpClient defaults to 100 seconds. If InvocationTimeoutMilliseconds is greater than the default of the HttpClient, the task will fail with a Task was canceled exception.
2017-08-02 09:22:34 +01:00
Charles Lowell
3715ec7c3f Fix typo 2017-08-02 09:21:40 +01:00
Steve Sanderson
d2eaa36372 Merge branch 'rel/2.0.0-templates' into dev 2017-07-27 15:09:17 +01:00
Steve Sanderson
169ef12cd8 Change Microsoft.DotNet.Web.Spa.ProjectTemplates to use non-prerelease version number in NuGet package 2017-07-27 14:51:21 +01:00
Steve Sanderson
f5d58f3f2e Update AppVeyor build to use SDK 006857 2017-07-27 14:43:22 +01:00
Steve Sanderson
c2e4d4f261 Re-enable the "dotnet restore" postAction, like the stock templates 2017-07-27 14:41:55 +01:00
Steve Sanderson
50481fe23f Update templates to reference ASP.NET Core 2.0.0 final 2017-07-27 14:27:15 +01:00
Nate McMaster
895a61160e Fix syntax warning when running build.sh on older versions of bash
[ci skip]
2017-07-26 10:27:55 -07:00
Nate McMaster
b8b769aa74 Update bootstrappers to use the compiled version of KoreBuild
[ci skip]
2017-07-25 16:32:52 -07:00
Pranav K
c4aad6bcab Updating to InternalAspNetCoreSdkVersion 2.1.1-* 2017-07-25 15:13:39 -07:00
Ryan Brandenburg
27f1d07d21 Set AspNetCoreVersion 2017-07-24 17:56:46 -07:00
Ryan Brandenburg
0cb14a3c68 2.0.0-rtm to 2.1.0-preview1 2017-07-24 12:31:10 -07:00
Steve Sanderson
2457b4ee5d Merge branch 'rel/2.0.0-templates' into dev 2017-07-19 14:26:12 +01:00
Steve Sanderson
789ea5a320 Update to ASP.NET Core 2.0.0-rtm-26380 2017-07-19 14:24:32 +01:00
Mike Harder
a902874754 Remove unused variable AutoMapperVersion 2017-07-19 14:19:37 +01:00
Steve Sanderson
f43ea777eb Merge branch 'rel/2.0.0-templates' into dev 2017-07-13 16:17:19 +01:00
Steve Sanderson
c79db4e8e2 In React-Redux template, move Webpack config for images into sharedConfig so that adding images doesn't break the server build 2017-07-13 16:16:45 +01:00
Steve Sanderson
d1198aeab2 Merge branch 'rel/2.0.0-templates' into dev 2017-07-13 10:14:31 +01:00
Steve Sanderson
9528dd7432 Enable TS strict mode in all templates and generally clean up TS references 2017-07-13 10:11:59 +01:00
Stephan Troyer
b8c006a3e9 Update templates to support TypeScript 'strict' mode 2017-07-13 09:22:14 +01:00
Steve Sanderson
a9ddf1413f Merge branch 'rel/2.0.0-templates' into dev 2017-07-12 23:31:33 +01:00
Steve Sanderson
8b37dc8561 Update to ASP.NET Core 2.0.0-rtm-26272 2017-07-12 23:17:22 +01:00
Steve Sanderson
7b07fb66eb Update all templates to aspnet-webpack@2.0.1 2017-07-12 23:11:30 +01:00
Steve Sanderson
cdb04c74f9 Update aspnet-webpack to 2.0.1, automatically disabling the middleware if we detect the process doesn't have sufficient disk permissions 2017-07-12 23:06:05 +01:00
Steve Sanderson
a74941e3c8 Merge branch 'rel/2.0.0' into rel/2.0.0-templates 2017-07-12 15:30:14 +01:00
Steve Sanderson
a0a710a0df Merge pull request #1111 from aspnet/rel/2.0.0
Complete the lstat patching for #1101
2017-07-12 15:27:37 +01:00
Steve Sanderson
576e3debf1 Complete the lstat patching for #1101 2017-07-12 15:27:00 +01:00
Steve Sanderson
d5f5ad7fdc Merge branch 'rel/2.0.0-templates' into dev 2017-07-12 00:39:57 +01:00
Steve Sanderson
dab0faea66 Update to AspNetCore 2.0.0-rtm-26219 2017-07-12 00:39:28 +01:00
Steve Sanderson
2df0febfba Merge branch 'rel/2.0.0-templates' into dev 2017-07-12 00:05:37 +01:00
Steve Sanderson
e65ecebac6 Make templates work with nonempty baseUrls (e.g., IIS virtual directories) 2017-07-11 23:56:51 +01:00
Steve Sanderson
bb0727c34c Update templates to newer aspnet NPM dependencies 2017-07-11 23:56:42 +01:00
Steve Sanderson
4903e12373 Publish aspnet-webpack-react as 3.0.0 since it's no longer beta 2017-07-11 23:55:50 +01:00
Steve Sanderson
56c806b34e Update aspnet-prerendering to 3.0.1, fixing the default "fetch" base URL to match the real application root URL 2017-07-11 23:55:49 +01:00
Steve Sanderson
8acba88160 Update aspnet-webpack to 2.0.0, now supporting HMR when apps are running in virtual directories 2017-07-11 18:57:52 +01:00
Steve Sanderson
b434eefd83 Merge pull request #1108 from aspnet/rel/2.0.0
Fix webpack HMR proxying logic for apps running on non-root URLs (e.g…
2017-07-11 18:57:03 +01:00
Steve Sanderson
44f86eb8a1 Fix webpack HMR proxying logic for apps running on non-root URLs (e.g., full IIS virtual directories) 2017-07-11 18:56:06 +01:00
Steve Sanderson
44360b6955 Merge pull request #1103 from aspnet/rel/2.0.0
Merge Rel/2.0.0 back to dev
2017-07-11 10:45:58 +01:00
Steve Sanderson
22ff88ab79 On Windows, patch Node's lstat to work around permissions error. Fixes #1101 2017-07-11 10:43:31 +01:00
Steve Sanderson
70d89b9ad3 Update templates to ASP.NET Core 2.0.0-rtm-26190 and SDK 2.0.0-preview3-006729 2017-07-11 10:43:31 +01:00
Ryan Brandenburg
72b1e627b0 Skip first time experience on Appveyor 2017-07-10 15:22:22 -07:00
Pranav K
c94563c700 Updating KoreBuild branch 2017-07-10 11:57:58 -07:00
Pranav K
781c5dc37c Merge branch 'rel/2.0.0' into dev 2017-07-10 11:57:58 -07:00
Pranav K
814df07034 Branching for 2.0.0 rtm 2017-07-10 11:43:36 -07:00
Pranav K
c2f63f21fd Merge branch 'rel/2.0.0' into dev 2017-07-10 11:43:36 -07:00
Steve Sanderson
4492d3eab7 In Angular template, rename boot.client.ts to boot.browser.ts for consistency 2017-07-07 17:16:47 +01:00
Steve Sanderson
efdfbf5dbb In Yeoman templates, stop restoring using Yarn, since it doesn't respect npm-shrinkwrap.json 2017-07-07 15:27:50 +01:00
Steve Sanderson
6f2e408083 Update templates to ASP.NET Core 2.0.0-preview3-26096 2017-07-07 15:13:51 +01:00
Steve Sanderson
70a4a68e7c Update templates to ASP.NET Core 2.0.0-preview3-26071 2017-07-07 12:40:41 +01:00
Steve Sanderson
a1942edbfe Update AppVeyor build to use .NET Core SDK 2.0.0-preview3-006670 2017-07-07 12:31:52 +01:00
Ryan Brandenburg
fd90301b1c Remove NETStandard.Library.NETFramework 2017-07-07 12:25:04 +01:00
Steve Sanderson
58145d33e3 Update ReactRedux template dependencies 2017-07-07 12:08:01 +01:00
Steve Sanderson
2135c30d66 Update React template dependencies 2017-07-07 12:07:14 +01:00
Steve Sanderson
5191531813 Add AoT compilation to Angular template 2017-07-07 12:07:13 +01:00
Steve Sanderson
03018f32b7 Update Angular template dependencies 2017-07-07 12:07:13 +01:00
Steve Sanderson
e632d2bb69 In domain-task/fetch, only apply HTTPS cert validation workaround for HTTPS requests (not HTTP) 2017-07-07 12:07:12 +01:00
Pranav K
7a56f643f7 Update version suffix for 2.0.0 RTM release 2017-07-06 15:08:26 -07:00
Ryan Brandenburg
056b955f52 Set "TreatWarningsAsErrors" before NuGet restore
* Ensures our build stays clean of NuGet warnings
2017-07-06 14:17:52 -07:00
Nate McMaster
0993f46c9e React to aspnet/BuildTools#293
[ci skip]
2017-07-06 10:38:00 -07:00
Steve Sanderson
4ba0358987 In AngularSpa template, log any FetchData errors but don't stop prerendering completely 2017-07-06 14:57:07 +01:00
Steve Sanderson
721e3c45fd Update ReactReduxSpa NPM shrinkwrap file 2017-07-06 14:50:34 +01:00
Steve Sanderson
27f59c6971 Make domain-task/fetch disable HTTPS cert validation when making relative requests to own server as workaround for #1089 2017-07-06 14:37:29 +01:00
Steve Sanderson
943fb2f482 In core packages, set <None Remove="node_modules\**\*" /> otherwise builds are super slow 2017-07-06 11:13:24 +01:00
Pranav K
2c4bc302de Update LICENSE.txt text 2017-07-03 14:06:26 -07:00
Steve Sanderson
5b634ee33c Remove "dotnet restore" post-action from templates for consistency with stock Web templates 2017-07-03 15:50:16 +01:00
Steve Sanderson
6e1fa2b2c3 Use ASP.NET Core CI NuGet feed to enable access to preview3 packages 2017-07-03 14:44:40 +01:00
Steve Sanderson
f72ebbd66e Remove redundant preview2 NuGet feed reference 2017-07-03 14:33:44 +01:00
Steve Sanderson
391ceec559 Update AppVeyor build to use .NET Core 2.0 Preview 3 2017-07-03 14:32:43 +01:00
Steve Sanderson
076b5304e0 Update templates to reference latest preview3 packages 2017-07-03 14:32:29 +01:00
Steve Sanderson
94fc84a9b4 Add simpler prerendering API. Fixes #607 2017-07-03 14:07:28 +01:00
Steve Sanderson
9cce26ebd8 Fix error formatting by rebuilding entrypoint-http.js 2017-07-03 14:01:31 +01:00
Steve Sanderson
513d288a5d Use GetRandomFileName instead of GetTempFileName. Fixes #1060 2017-07-03 10:15:53 +01:00
Steve Sanderson
429a432dd3 Check for null during node process disposal. Fixes #1061 2017-07-03 10:09:03 +01:00
Ryan Brandenburg
76928bcbaa Remove AssetTargetFramework 2017-06-30 10:24:48 +01:00
Steve Sanderson
95c81729cd Merge branch 'rel/2.0.0-preview2-templates' into dev 2017-06-26 15:50:46 +01:00
Steve Sanderson
3da87dd3d3 In aspnet-webpack-react 3.0.0+, remove the workaround for #1066. Templates that use this version of the package need to not have issue #1066 2017-06-26 15:34:30 +01:00
Steve Sanderson
117c1a6cbd Implement workaround for #1066 2017-06-26 12:51:03 +01:00
Steve Sanderson
53f5a77490 Use preview2-final NuGet feed until it ships to NuGet.org 2017-06-23 12:08:14 +01:00
Steve Sanderson
5fd9b93324 Switch to a specific version of .NET Core 2.0 Preview 2, because "latest" is 2.1 and breaks. Fixes #1058 2017-06-23 11:59:34 +01:00
Scott Addie
55bd0c05cb Update SpaServices NuGet package installation requirement 2017-06-23 11:55:00 +01:00
Steve Sanderson
f9068819be Update AppVeyor builds to use latest .NET Core SDK. Fixes #1058 2017-06-23 11:52:09 +01:00
Steve Sanderson
85bf621386 Merge branch 'rel/2.0.0-preview2-templates' into dev 2017-06-22 16:39:58 +01:00
Steve Sanderson
bc7742148e Remove duplicate var definition as in #966 2017-06-21 15:06:07 +01:00
Sławomir Rosiek
4ceed817a3 Removed unused methods 2017-06-20 16:33:34 +01:00
Sławomir Rosiek
6d85e752e1 Throwing HttpInvocationException instead of generic Exception 2017-06-20 16:33:34 +01:00
Steve Sanderson
460f202832 In React templates, update @types/react to 15.0.29 for compatibility with TypeScript 2.4.0+ 2017-06-20 15:17:19 +01:00
Steve Sanderson
73054559cc Bump aspnet-prerendering version to 2.0.6 for publish 2017-06-20 15:11:23 +01:00
Steve Sanderson
d007a3fbba In aspnet-prerendering, fix type definitions to satisfy TypeScript 2.4.0. Fixes #1034 2017-06-20 15:10:29 +01:00
Steve Sanderson
fcd3cf9dd7 In aspnet-prerendering, simply type imports/exports by changing PrerenderingInterfaces to a regular module (not a .d.ts file) 2017-06-20 15:07:11 +01:00
Sławomir Rosiek
48bca777fe Added "private": true to package.json 2017-06-19 15:04:42 +02:00
Steve Sanderson
aadcfa1fb7 Revert change that added preview2-final NuGet feed as it's not required in dev branch 2017-06-16 13:28:00 +02:00
Steve Sanderson
f683b39806 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-16 13:26:56 +02:00
Kiran Challa
ed7e07a057 Updated to use latest netstandard.library 2017-06-15 05:20:36 -07:00
Kiran Challa
749d112b08 Merge branch 'rel/2.0.0-preview2' into dev-gate 2017-06-15 03:38:10 -07:00
Steve Sanderson
90922fb3c6 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-13 22:06:55 +01:00
Nate McMaster
bcb359e355 Update System.Threading.Tasks.Dataflow dependency to latest corefx version 2017-06-13 11:37:59 -07:00
Steve Sanderson
a7a3a359fa Merge branch 'rel/2.0.0-preview2' into dev 2017-06-13 09:47:18 +01:00
Steve Sanderson
627da65692 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-13 09:39:17 +01:00
Steve Sanderson
67ba6271a0 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-11 20:33:23 +01:00
Steve Sanderson
32d9bdee6c Merge branch 'rel/2.0.0-preview2' into dev 2017-06-10 14:36:36 +01:00
Steve Sanderson
e50d4825c3 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-08 11:44:53 +01:00
Steve Sanderson
06c94d54eb Merge branch 'rel/2.0.0-preview2' into dev 2017-06-08 11:15:48 +01:00
Steve Sanderson
3685592b01 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-08 11:09:51 +01:00
Steve Sanderson
2acdad38cd Merge branch 'rel/2.0.0-preview2' into dev 2017-06-08 10:40:05 +01:00
Steve Sanderson
e331355c1d Merge branch 'rel/2.0.0-preview2' into dev 2017-06-07 22:57:37 +01:00
Steve Sanderson
d8c0bd8898 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-07 15:02:29 +01:00
Josef Ottosson
0626bb1594 Update README.md
Fixes small typo.
2017-06-05 11:14:04 +01:00
Pranav K
d9e75c89ae Updating versions to preview3 2017-06-01 10:47:12 -07:00
Pranav K
ebe9d8ae92 Merge remote-tracking branch 'origin/rel/2.0.0-preview2' into dev 2017-05-31 20:00:38 -07:00
Pranav K
d42cd10975 Merge remote-tracking branch 'origin/rel/2.0.0-preview2' into dev 2017-05-31 19:44:28 -07:00
301 changed files with 6894 additions and 1907 deletions

11
.gitignore vendored
View File

@@ -29,14 +29,7 @@ nuget.exe
npm-debug.log npm-debug.log
/.build/ /.build/
# The templates can't contain their own .gitignore files, because Yeoman has strange default handling for
# files with that name (https://github.com/npm/npm/issues/1862). So, each template instead has a template_gitignore
# file which gets renamed after the files are copied. And so any files that need to be excluded in the source
# repo have to be excluded here.
/templates/*/node_modules/
/templates/*/wwwroot/dist/
/templates/*/ClientApp/dist/
/templates/*/yarn.lock
.vscode/ .vscode/
/templates/*/Properties/launchSettings.json global.json
korebuild-lock.txt

View File

@@ -1,7 +1,9 @@
Copyright (c) .NET Foundation. All rights reserved. Copyright (c) .NET Foundation and Contributors
All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use Licensed under the Apache License, Version 2.0 (the "License"); you may not use
these files except in compliance with the License. You may obtain a copy of the this file except in compliance with the License. You may obtain a copy of the
License at License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0

View File

@@ -2,8 +2,7 @@
<configuration> <configuration>
<packageSources> <packageSources>
<clear /> <clear />
<add key="AspNetCorePreview2Final" value="https://dotnet.myget.org/F/dotnet-2-0-0-preview2-final/api/v3/index.json" /> <add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json" />
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-release/api/v3/index.json" />
<add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" /> <add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" /> <add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources> </packageSources>

View File

@@ -94,7 +94,7 @@ If you're interested in contributing to the various packages, samples, and proje
* Run `dotnet restore` at the repo root dir * Run `dotnet restore` at the repo root dir
* Go to whatever sample or template you want to run (for example, `cd templates/AngularSpa`) * Go to whatever sample or template you want to run (for example, `cd templates/AngularSpa`)
* Restore NPM dependencies (run `npm install`) * Restore NPM dependencies (run `npm install`)
* If the sample/template you're trying to run has a file called `webpack.config.vendor.js` at its root, run `webpack --config webpack.config.vendor.js`. It it has a file called `webpack.config.js`, run `webpack` (no args). You might need to install webpack first, by running `npm install -g webpack`. * If the sample/template you're trying to run has a file called `webpack.config.vendor.js` at its root, run `webpack --config webpack.config.vendor.js`. If it has a file called `webpack.config.js`, run `webpack` (no args). You might need to install webpack first, by running `npm install -g webpack`.
* Launch it (`dotnet run`) * Launch it (`dotnet run`)
If you're planning to submit a pull request, and if it's more than a trivial fix (for example, for a typo), it's usually a good idea first to file an issue describing what you're proposing to do and how it will work. Then you can find out if it's likely that such a pull request will be accepted, and how it fits into wider ongoing plans. If you're planning to submit a pull request, and if it's more than a trivial fix (for example, for a typo), it's usually a good idea first to file an issue describing what you're proposing to do and how it will work. Then you can find out if it's likely that such a pull request will be accepted, and how it fits into wider ongoing plans.

View File

@@ -1,13 +1,13 @@
init: init:
- git config --global core.autocrlf true - git config --global core.autocrlf true
install: install:
- ps: Install-Product node 6.9.2 x64 - ps: Install-Product node 6.9.2 x64
# .NET Core SDK binaries # .NET Core SDK binaries
# Download .NET Core 2.0 Preview 1 SDK and add to PATH # Download .NET Core 2.0 Preview 3 SDK and add to PATH
- ps: $urlCurrent = "https://download.microsoft.com/download/0/6/5/0656B047-5F2F-4281-A851-F30776F8616D/dotnet-dev-win-x64.2.0.0-preview1-005977.zip" - ps: $urlCurrent = "https://dotnetcli.azureedge.net/dotnet/Sdk/2.0.0-preview3-006857/dotnet-sdk-2.0.0-preview3-006857-win-x64.zip"
- ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetsdk" - ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetsdk"
- ps: mkdir $env:DOTNET_INSTALL_DIR -Force | Out-Null - ps: mkdir $env:DOTNET_INSTALL_DIR -Force | Out-Null
- ps: $tempFileCurrent = [System.IO.Path]::GetTempFileName() - ps: $tempFileCurrent = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
- ps: (New-Object System.Net.WebClient).DownloadFile($urlCurrent, $tempFileCurrent) - ps: (New-Object System.Net.WebClient).DownloadFile($urlCurrent, $tempFileCurrent)
- ps: Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFileCurrent, $env:DOTNET_INSTALL_DIR) - ps: Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFileCurrent, $env:DOTNET_INSTALL_DIR)
- ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path" - ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path"
@@ -18,13 +18,15 @@ build_script:
- npm run build - npm run build
- ps: Pop-Location - ps: Pop-Location
artifacts: artifacts:
- path: templates\package-builder\dist\artifacts\generator-aspnetcore-spa.tar.gz - path: templates\package-builder\artifacts\*.nupkg
name: generator-aspnetcore-spa
- path: templates\package-builder\dist\artifacts\*.nupkg
name: Microsoft.AspNetCore.SpaTemplates name: Microsoft.AspNetCore.SpaTemplates
type: NuGetPackage type: NuGetPackage
# - ps: .\build.ps1 # - ps: .\build.ps1
clone_depth: 1 clone_depth: 1
environment:
global:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: 1
test_script: test_script:
- dotnet restore - dotnet restore
- ps: Push-Location - ps: Push-Location

222
build.ps1
View File

@@ -1,67 +1,177 @@
$ErrorActionPreference = "Stop" #!/usr/bin/env powershell
#requires -version 4
function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries) <#
{ .SYNOPSIS
while($true) Build this repository
{
try
{
Invoke-WebRequest $url -OutFile $downloadLocation
break
}
catch
{
$exceptionMessage = $_.Exception.Message
Write-Host "Failed to download '$url': $exceptionMessage"
if ($retries -gt 0) {
$retries--
Write-Host "Waiting 10 seconds before retrying. Retries left: $retries"
Start-Sleep -Seconds 10
} .DESCRIPTION
else Downloads korebuild if required. Then builds the repository.
{
$exception = $_.Exception .PARAMETER Path
throw $exception The folder to build. Defaults to the folder containing this script.
}
} .PARAMETER Channel
} The channel of KoreBuild to download. Overrides the value from the config file.
.PARAMETER DotNetHome
The directory where .NET Core tools will be stored.
.PARAMETER ToolsSource
The base url where build tools can be downloaded. Overrides the value from the config file.
.PARAMETER Update
Updates KoreBuild to the latest version even if a lock file is present.
.PARAMETER ConfigFile
The path to the configuration file that stores values. Defaults to version.xml.
.PARAMETER MSBuildArgs
Arguments to be passed to MSBuild
.NOTES
This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be.
When the lockfile is not present, KoreBuild will create one using latest available version from $Channel.
The $ConfigFile is expected to be an XML file. It is optional, and the configuration values in it are optional as well.
.EXAMPLE
Example config file:
```xml
<!-- version.xml -->
<Project>
<PropertyGroup>
<KoreBuildChannel>dev</KoreBuildChannel>
<KoreBuildToolsSource>https://aspnetcore.blob.core.windows.net/buildtools</KoreBuildToolsSource>
</PropertyGroup>
</Project>
```
#>
[CmdletBinding(PositionalBinding = $false)]
param(
[string]$Path = $PSScriptRoot,
[Alias('c')]
[string]$Channel,
[Alias('d')]
[string]$DotNetHome,
[Alias('s')]
[string]$ToolsSource,
[Alias('u')]
[switch]$Update,
[string]$ConfigFile = (Join-Path $PSScriptRoot 'version.xml'),
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$MSBuildArgs
)
Set-StrictMode -Version 2
$ErrorActionPreference = 'Stop'
#
# Functions
#
function Get-KoreBuild {
$lockFile = Join-Path $Path 'korebuild-lock.txt'
if (!(Test-Path $lockFile) -or $Update) {
Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile
} }
cd $PSScriptRoot $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1
if (!$version) {
$repoFolder = $PSScriptRoot Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'"
$env:REPO_FOLDER = $repoFolder
$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/rel/2.0.0-preview2.zip"
if ($env:KOREBUILD_ZIP)
{
$koreBuildZip=$env:KOREBUILD_ZIP
} }
$version = $version.TrimStart('version:').Trim()
$korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version)
$buildFolder = ".build" if (!(Test-Path $korebuildPath)) {
$buildFile="$buildFolder\KoreBuild.ps1" Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version"
New-Item -ItemType Directory -Path $korebuildPath | Out-Null
if (!(Test-Path $buildFolder)) { $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip"
Write-Host "Downloading KoreBuild from $koreBuildZip"
$tempFolder=$env:TEMP + "\KoreBuild-" + [guid]::NewGuid()
New-Item -Path "$tempFolder" -Type directory | Out-Null
$localZipFile="$tempFolder\korebuild.zip"
DownloadWithRetry -url $koreBuildZip -downloadLocation $localZipFile -retries 6
try {
$tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip"
Get-RemoteFile $remotePath $tmpfile
if (Get-Command -Name 'Expand-Archive' -ErrorAction Ignore) {
# Use built-in commands where possible as they are cross-plat compatible
Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath
}
else {
# Fallback to old approach for old installations of PowerShell
Add-Type -AssemblyName System.IO.Compression.FileSystem Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($localZipFile, $tempFolder) [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath)
}
New-Item -Path "$buildFolder" -Type directory | Out-Null }
copy-item "$tempFolder\**\build\*" $buildFolder -Recurse catch {
Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore
# Cleanup throw
if (Test-Path $tempFolder) { }
Remove-Item -Recurse -Force $tempFolder finally {
Remove-Item $tmpfile -ErrorAction Ignore
} }
} }
&"$buildFile" @args return $korebuildPath
}
function Join-Paths([string]$path, [string[]]$childPaths) {
$childPaths | ForEach-Object { $path = Join-Path $path $_ }
return $path
}
function Get-RemoteFile([string]$RemotePath, [string]$LocalPath) {
if ($RemotePath -notlike 'http*') {
Copy-Item $RemotePath $LocalPath
return
}
$retries = 10
while ($retries -gt 0) {
$retries -= 1
try {
Invoke-WebRequest -UseBasicParsing -Uri $RemotePath -OutFile $LocalPath
return
}
catch {
Write-Verbose "Request failed. $retries retries remaining"
}
}
Write-Error "Download failed: '$RemotePath'."
}
#
# Main
#
# Load configuration or set defaults
if (Test-Path $ConfigFile) {
[xml] $config = Get-Content $ConfigFile
if (!($Channel)) { [string] $Channel = Select-Xml -Xml $config -XPath '/Project/PropertyGroup/KoreBuildChannel' }
if (!($ToolsSource)) { [string] $ToolsSource = Select-Xml -Xml $config -XPath '/Project/PropertyGroup/KoreBuildToolsSource' }
}
if (!$DotNetHome) {
$DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } `
elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} `
elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}`
else { Join-Path $PSScriptRoot '.dotnet'}
}
if (!$Channel) { $Channel = 'dev' }
if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' }
# Execute
$korebuildPath = Get-KoreBuild
Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1')
try {
Install-Tools $ToolsSource $DotNetHome
Invoke-RepositoryBuild $Path @MSBuildArgs
}
finally {
Remove-Module 'KoreBuild' -ErrorAction Ignore
}

227
build.sh
View File

@@ -1,46 +1,199 @@
#!/usr/bin/env bash #!/usr/bin/env bash
repoFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $repoFolder
koreBuildZip="https://github.com/aspnet/KoreBuild/archive/rel/2.0.0-preview2.zip" set -euo pipefail
if [ ! -z $KOREBUILD_ZIP ]; then
koreBuildZip=$KOREBUILD_ZIP #
# variables
#
RESET="\033[0m"
RED="\033[0;31m"
MAGENTA="\033[0;95m"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
[ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet"
config_file="$DIR/version.xml"
verbose=false
update=false
repo_path="$DIR"
channel=''
tools_source=''
#
# Functions
#
__usage() {
echo "Usage: $(basename "${BASH_SOURCE[0]}") [options] [[--] <MSBUILD_ARG>...]"
echo ""
echo "Arguments:"
echo " <MSBUILD_ARG>... Arguments passed to MSBuild. Variable number of arguments allowed."
echo ""
echo "Options:"
echo " --verbose Show verbose output."
echo " -c|--channel <CHANNEL> The channel of KoreBuild to download. Overrides the value from the config file.."
echo " --config-file <FILE> TThe path to the configuration file that stores values. Defaults to version.xml."
echo " -d|--dotnet-home <DIR> The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet."
echo " --path <PATH> The directory to build. Defaults to the directory containing the script."
echo " -s|--tools-source <URL> The base url where build tools can be downloaded. Overrides the value from the config file."
echo " -u|--update Update to the latest KoreBuild even if the lock file is present."
echo ""
echo "Description:"
echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be."
echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel."
if [[ "${1:-}" != '--no-exit' ]]; then
exit 2
fi
}
get_korebuild() {
local version
local lock_file="$repo_path/korebuild-lock.txt"
if [ ! -f "$lock_file" ] || [ "$update" = true ]; then
__get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file"
fi
version="$(grep 'version:*' -m 1 "$lock_file")"
if [[ "$version" == '' ]]; then
__error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'"
return 1
fi
version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version"
{
if [ ! -d "$korebuild_path" ]; then
mkdir -p "$korebuild_path"
local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip"
tmpfile="$(mktemp)"
echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}"
if __get_remote_file "$remote_path" "$tmpfile"; then
unzip -q -d "$korebuild_path" "$tmpfile"
fi
rm "$tmpfile" || true
fi fi
buildFolder=".build" source "$korebuild_path/KoreBuild.sh"
buildFile="$buildFolder/KoreBuild.sh" } || {
if [ -d "$korebuild_path" ]; then
if test ! -d $buildFolder; then echo "Cleaning up after failed installation"
echo "Downloading KoreBuild from $koreBuildZip" rm -rf "$korebuild_path" || true
tempFolder="/tmp/KoreBuild-$(uuidgen)"
mkdir $tempFolder
localZipFile="$tempFolder/korebuild.zip"
retries=6
until (wget -O $localZipFile $koreBuildZip 2>/dev/null || curl -o $localZipFile --location $koreBuildZip 2>/dev/null)
do
echo "Failed to download '$koreBuildZip'"
if [ "$retries" -le 0 ]; then
exit 1
fi fi
retries=$((retries - 1)) return 1
echo "Waiting 10 seconds before retrying. Retries left: $retries" }
sleep 10s }
__error() {
echo -e "${RED}$*${RESET}" 1>&2
}
__machine_has() {
hash "$1" > /dev/null 2>&1
return $?
}
__get_remote_file() {
local remote_path=$1
local local_path=$2
if [[ "$remote_path" != 'http'* ]]; then
cp "$remote_path" "$local_path"
return 0
fi
local failed=false
if __machine_has wget; then
wget --tries 10 --quiet -O "$local_path" "$remote_path" || failed=true
else
failed=true
fi
if [ "$failed" = true ] && __machine_has curl; then
failed=false
curl --retry 10 -sSL -f --create-dirs -o "$local_path" "$remote_path" || failed=true
fi
if [ "$failed" = true ]; then
__error "Download failed: $remote_path" 1>&2
return 1
fi
}
__read_dom () { local IFS=\> ; read -r -d \< ENTITY CONTENT ;}
#
# main
#
while [[ $# -gt 0 ]]; do
case $1 in
-\?|-h|--help)
__usage --no-exit
exit 0
;;
-c|--channel|-Channel)
shift
channel="${1:-}"
[ -z "$channel" ] && __usage
;;
--config-file|-ConfigFile)
shift
config_file="${1:-}"
[ -z "$config_file" ] && __usage
;;
-d|--dotnet-home|-DotNetHome)
shift
DOTNET_HOME="${1:-}"
[ -z "$DOTNET_HOME" ] && __usage
;;
--path|-Path)
shift
repo_path="${1:-}"
[ -z "$repo_path" ] && __usage
;;
-s|--tools-source|-ToolsSource)
shift
tools_source="${1:-}"
[ -z "$tools_source" ] && __usage
;;
-u|--update|-Update)
update=true
;;
--verbose|-Verbose)
verbose=true
;;
--)
shift
break
;;
*)
break
;;
esac
shift
done done
unzip -q -d $tempFolder $localZipFile if ! __machine_has unzip; then
__error 'Missing required command: unzip'
mkdir $buildFolder exit 1
cp -r $tempFolder/**/build/** $buildFolder
chmod +x $buildFile
# Cleanup
if test -d $tempFolder; then
rm -rf $tempFolder
fi
fi fi
$buildFile -r $repoFolder "$@" if ! __machine_has curl && ! __machine_has wget; then
__error 'Missing required command. Either wget or curl is required.'
exit 1
fi
if [ -f "$config_file" ]; then
comment=false
while __read_dom; do
if [ "$comment" = true ]; then [[ $CONTENT == *'-->'* ]] && comment=false ; continue; fi
if [[ $ENTITY == '!--'* ]]; then comment=true; continue; fi
if [ -z "$channel" ] && [[ $ENTITY == "KoreBuildChannel" ]]; then channel=$CONTENT; fi
if [ -z "$tools_source" ] && [[ $ENTITY == "KoreBuildToolsSource" ]]; then tools_source=$CONTENT; fi
done < "$config_file"
fi
[ -z "$channel" ] && channel='dev'
[ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools'
get_korebuild
install_tools "$tools_source" "$DOTNET_HOME"
invoke_repository_build "$repo_path" "$@"

View File

@@ -1,6 +1,6 @@
<Project> <Project>
<Import Project="dependencies.props" /> <Import Project="dependencies.props" />
<Import Project="..\version.props" /> <Import Project="..\version.xml" />
<PropertyGroup> <PropertyGroup>
<Product>Microsoft ASP.NET Core</Product> <Product>Microsoft ASP.NET Core</Product>
@@ -10,6 +10,7 @@
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign> <PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
<VersionSuffix Condition="'$(VersionSuffix)'!='' AND '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix> <VersionSuffix Condition="'$(VersionSuffix)'!='' AND '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -17,7 +18,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)'=='.NETFramework'"> <ItemGroup Condition="'$(TargetFrameworkIdentifier)'=='.NETFramework'">
<PackageReference Include="NETStandard.Library" Version="$(BundledNETStandardPackageVersion)" /> <PackageReference Include="NETStandard.Library" Version="$(NETStandardImplicitPackageVersion)" />
<PackageReference Include="NETStandard.Library.NETFramework" Version="$(NETStandardLibraryNETFrameworkVersion)" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,12 +1,11 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<AspNetCoreVersion>2.0.0-preview2-*</AspNetCoreVersion> <AspNetCoreVersion>2.1.0-*</AspNetCoreVersion>
<AutoMapperVersion>5.0.2</AutoMapperVersion> <InternalAspNetCoreSdkVersion>2.1.1-*</InternalAspNetCoreSdkVersion>
<InternalAspNetCoreSdkVersion>2.1.0-*</InternalAspNetCoreSdkVersion>
<JsonNetVersion>10.0.1</JsonNetVersion> <JsonNetVersion>10.0.1</JsonNetVersion>
<NETStandardImplicitPackageVersion>$(BundledNETStandardPackageVersion)</NETStandardImplicitPackageVersion> <NETStandardImplicitPackageVersion>2.0.0-*</NETStandardImplicitPackageVersion>
<NETStandardLibraryNETFrameworkVersion>2.0.0-*</NETStandardLibraryNETFrameworkVersion> <NETStandardLibraryNETFrameworkVersion>2.0.0-*</NETStandardLibraryNETFrameworkVersion>
<RuntimeFrameworkVersion Condition="'$(TargetFramework)'=='netcoreapp2.0'">2.0.0-*</RuntimeFrameworkVersion> <RuntimeFrameworkVersion Condition="'$(TargetFramework)'=='netcoreapp2.0'">2.0.0-*</RuntimeFrameworkVersion>
<ThreadingDataflowVersion>4.7.0</ThreadingDataflowVersion> <ThreadingDataflowVersion>4.8.0-*</ThreadingDataflowVersion>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@@ -21,7 +21,6 @@ namespace ConsoleApplication
// Since .NET Core 1.1, the HTTP hosting model has become basically as fast as the Socket hosting model // Since .NET Core 1.1, the HTTP hosting model has become basically as fast as the Socket hosting model
//options.UseSocketHosting(); //options.UseSocketHosting();
options.ProjectPath = Directory.GetCurrentDirectory();
options.WatchFileExtensions = new string[] {}; // Don't watch anything options.WatchFileExtensions = new string[] {}; // Don't watch anything
}); });
var serviceProvider = services.BuildServiceProvider(); var serviceProvider = services.BuildServiceProvider();

View File

@@ -1,6 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.SpaServices.Prerendering;
namespace NodeServicesExamples.Controllers namespace NodeServicesExamples.Controllers
{ {
@@ -34,6 +35,20 @@ namespace NodeServicesExamples.Controllers
return View(); return View();
} }
public async Task<IActionResult> Prerendering([FromServices] ISpaPrerenderer prerenderer)
{
var result = await prerenderer.RenderToString("./Node/prerenderPage");
if (!string.IsNullOrEmpty(result.RedirectUrl))
{
return Redirect(result.RedirectUrl);
}
ViewData["PrerenderedHtml"] = result.Html;
ViewData["PrerenderedGlobals"] = result.CreateGlobalsAssignmentScript();
return View();
}
public IActionResult Error() public IActionResult Error()
{ {
return View("~/Views/Shared/Error.cshtml"); return View("~/Views/Shared/Error.cshtml");

View File

@@ -0,0 +1,14 @@
var createServerRenderer = require('aspnet-prerendering').createServerRenderer;
module.exports = createServerRenderer(function(params) {
return new Promise(function (resolve, reject) {
var message = 'The HTML was returned by the prerendering boot function. '
+ 'The boot function received the following params:'
+ '<pre>' + JSON.stringify(params, null, 4) + '</pre>';
resolve({
html: '<h3>Hello, world!</h3>' + message,
globals: { sampleData: { nodeVersion: process.version } }
});
});
});

View File

@@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" /> <ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.SpaServices\Microsoft.AspNetCore.SpaServices.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -17,6 +17,7 @@ namespace NodeServicesExamples
// Enable Node Services // Enable Node Services
services.AddNodeServices(); services.AddNodeServices();
services.AddSpaPrerenderer();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@@ -9,4 +9,5 @@
<ul> <ul>
<li><a asp-action="ES2015Transpilation">ES2015 transpilation</a></li> <li><a asp-action="ES2015Transpilation">ES2015 transpilation</a></li>
<li><a asp-action="Chart">Server-side chart rendering</a></li> <li><a asp-action="Chart">Server-side chart rendering</a></li>
<li><a asp-action="Prerendering">Server-side SPA prerendering</a></li>
</ul> </ul>

View File

@@ -0,0 +1,21 @@
<h1>Server-side prerendering</h1>
<p>
This sample demonstrates how you can invoke a JavaScript module that contains
prerendering logic for a Single-Page Application framework.
</p>
</p>
Your prerendering boot function will receive parameters that describe the page
being rendered and any data supplied by the .NET code. The return value should be
a promise that resolves with data to be injected into the page, such as the
rendered HTML and any global data that should be made available to client-side code.
</p>
@Html.Raw(ViewData["PrerenderedHtml"])
<script>@Html.Raw(ViewData["PrerenderedGlobals"])</script>
<script>
// Demonstrates how client-side code can receive data from the prerendering process
console.log('Received Node version from prerendering logic: ' + sampleData.nodeVersion);
</script>

View File

@@ -2,6 +2,7 @@
"name": "nodeservicesexamples", "name": "nodeservicesexamples",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"aspnet-prerendering": "^2.0.6",
"babel-core": "^6.7.4", "babel-core": "^6.7.4",
"babel-preset-es2015": "^6.6.0", "babel-preset-es2015": "^6.6.0",
"node-chartist": "^1.0.2" "node-chartist": "^1.0.2"

View File

@@ -10,6 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="node_modules\**\*" />
<EmbeddedResource Include="Content\**\*" /> <EmbeddedResource Include="Content\**\*" />
</ItemGroup> </ItemGroup>

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Threading; using System.Threading;
using Microsoft.AspNetCore.NodeServices.HostingModels; using Microsoft.AspNetCore.NodeServices.HostingModels;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -34,14 +35,18 @@ namespace Microsoft.AspNetCore.NodeServices
InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds; InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds;
WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone(); WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone();
// In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few
// things that you'd otherwise have to specify manually
var hostEnv = serviceProvider.GetService<IHostingEnvironment>(); var hostEnv = serviceProvider.GetService<IHostingEnvironment>();
if (hostEnv != null) if (hostEnv != null)
{ {
// In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few
// things that you'd otherwise have to specify manually
ProjectPath = hostEnv.ContentRootPath; ProjectPath = hostEnv.ContentRootPath;
EnvironmentVariables["NODE_ENV"] = hostEnv.IsDevelopment() ? "development" : "production"; // De-facto standard values for Node EnvironmentVariables["NODE_ENV"] = hostEnv.IsDevelopment() ? "development" : "production"; // De-facto standard values for Node
} }
else
{
ProjectPath = Directory.GetCurrentDirectory();
}
var applicationLifetime = serviceProvider.GetService<IApplicationLifetime>(); var applicationLifetime = serviceProvider.GetService<IApplicationLifetime>();
if (applicationLifetime != null) if (applicationLifetime != null)

View File

@@ -55,10 +55,11 @@
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive, // Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
// but simplifies things for the consumer of this module. // but simplifies things for the consumer of this module.
__webpack_require__(2); __webpack_require__(2);
var http = __webpack_require__(3); __webpack_require__(4);
var path = __webpack_require__(4); var http = __webpack_require__(5);
var ArgsUtil_1 = __webpack_require__(5); var path = __webpack_require__(3);
var ExitWhenParentExits_1 = __webpack_require__(6); var ArgsUtil_1 = __webpack_require__(6);
var ExitWhenParentExits_1 = __webpack_require__(7);
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct // Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
// reference to Node's runtime 'require' function. // reference to Node's runtime 'require' function.
var dynamicRequire = eval('require'); var dynamicRequire = eval('require');
@@ -133,12 +134,73 @@
} }
function respondWithError(res, errorValue) { function respondWithError(res, errorValue) {
res.statusCode = 500; res.statusCode = 500;
res.end(errorValue.stack || errorValue.toString()); res.end(JSON.stringify({
errorMessage: errorValue.message || errorValue,
errorDetails: errorValue.stack || null
}));
} }
/***/ }, /***/ },
/* 2 */ /* 2 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
var path = __webpack_require__(3);
var startsWith = function (str, prefix) { return str.substring(0, prefix.length) === prefix; };
var appRootDir = process.cwd();
function patchedLStat(pathToStatLong, fsReqWrap) {
try {
// If the lstat completes without errors, we don't modify its behavior at all
return origLStat.apply(this, arguments);
}
catch (ex) {
var shouldOverrideError = startsWith(ex.message, 'EPERM') // It's a permissions error
&& typeof appRootDirLong === 'string'
&& startsWith(appRootDirLong, pathToStatLong) // ... for an ancestor directory
&& ex.stack.indexOf('Object.realpathSync ') >= 0; // ... during symlink resolution
if (shouldOverrideError) {
// Fake the result to give the same result as an 'lstat' on the app root dir.
// This stops Node failing to load modules just because it doesn't know whether
// ancestor directories are symlinks or not. If there's a genuine file
// permissions issue, it will still surface later when Node actually
// tries to read the file.
return origLStat.call(this, appRootDir, fsReqWrap);
}
else {
// In any other case, preserve the original error
throw ex;
}
}
}
;
// It's only necessary to apply this workaround on Windows
var appRootDirLong = null;
var origLStat = null;
if (/^win/.test(process.platform)) {
try {
// Get the app's root dir in Node's internal "long" format (e.g., \\?\C:\dir\subdir)
appRootDirLong = path._makeLong(appRootDir);
// Actually apply the patch, being as defensive as possible
var bindingFs = process.binding('fs');
origLStat = bindingFs.lstat;
if (typeof origLStat === 'function') {
bindingFs.lstat = patchedLStat;
}
}
catch (ex) {
}
}
/***/ },
/* 3 */
/***/ function(module, exports) {
module.exports = require("path");
/***/ },
/* 4 */
/***/ function(module, exports) { /***/ function(module, exports) {
// When Node writes to stdout/strerr, we capture that and convert the lines into calls on the // When Node writes to stdout/strerr, we capture that and convert the lines into calls on the
@@ -179,19 +241,13 @@
/***/ }, /***/ },
/* 3 */ /* 5 */
/***/ function(module, exports) { /***/ function(module, exports) {
module.exports = require("http"); module.exports = require("http");
/***/ }, /***/ },
/* 4 */ /* 6 */
/***/ function(module, exports) {
module.exports = require("path");
/***/ },
/* 5 */
/***/ function(module, exports) { /***/ function(module, exports) {
"use strict"; "use strict";
@@ -217,7 +273,7 @@
/***/ }, /***/ },
/* 6 */ /* 7 */
/***/ function(module, exports) { /***/ function(module, exports) {
/* /*

View File

@@ -50,6 +50,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
options.DebuggingPort) options.DebuggingPort)
{ {
_client = new HttpClient(); _client = new HttpClient();
_client.Timeout = TimeSpan.FromMilliseconds(options.InvocationTimeoutMilliseconds + 1000);
} }
private static string MakeCommandLineOptions(int port) private static string MakeCommandLineOptions(int port)
@@ -67,8 +68,10 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
// Unfortunately there's no true way to cancel ReadAsStringAsync calls, hence AbandonIfCancelled // Unfortunately there's no true way to cancel ReadAsStringAsync calls, hence AbandonIfCancelled
var responseErrorString = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken); var responseJson = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken);
throw new Exception("Call to Node module failed with error: " + responseErrorString); var responseError = JsonConvert.DeserializeObject<RpcJsonResponse>(responseJson, jsonSerializerSettings);
throw new NodeInvocationException(responseError.ErrorMessage, responseError.ErrorDetails);
} }
var responseContentType = response.Content.Headers.ContentType; var responseContentType = response.Content.Headers.ContentType;
@@ -136,5 +139,13 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
_disposed = true; _disposed = true;
} }
} }
#pragma warning disable 649 // These properties are populated via JSON deserialization
private class RpcJsonResponse
{
public string ErrorMessage { get; set; }
public string ErrorDetails { get; set; }
}
#pragma warning restore 649
} }
} }

View File

@@ -297,7 +297,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
// Make sure the Node process is finished // Make sure the Node process is finished
// TODO: Is there a more graceful way to end it? Or does this still let it perform any cleanup? // TODO: Is there a more graceful way to end it? Or does this still let it perform any cleanup?
if (!_nodeProcess.HasExited) if (_nodeProcess != null && !_nodeProcess.HasExited)
{ {
_nodeProcess.Kill(); _nodeProcess.Kill();
} }

View File

@@ -11,6 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="node_modules\**\*" />
<EmbeddedResource Include="Content\**\*" /> <EmbeddedResource Include="Content\**\*" />
</ItemGroup> </ItemGroup>

View File

@@ -317,7 +317,7 @@ module.exports = {
## Hosting models ## Hosting models
NodeServices has a pluggable hosting/transport mechanism, because it is an abstraction over various possible ways to invoke Node.js from .NET. This allows more high-level facilities (e.g., for Angular prerendering) to be agnostic to the details of launching Node and communicating it - those high-level facilities can just trust that *somehow* we can invoke code in Node for them. NodeServices has a pluggable hosting/transport mechanism, because it is an abstraction over various possible ways to invoke Node.js from .NET. This allows more high-level facilities (e.g., for Angular prerendering) to be agnostic to the details of launching Node and communicating with it - those high-level facilities can just trust that *somehow* we can invoke code in Node for them.
Using this abstraction, we could run Node inside the .NET process, in a separate process on the same machine, or even on a different machine altogether. At the time of writing, all the built-in hosting mechanisms work by launching Node as a separate process on the same machine as your .NET code. Using this abstraction, we could run Node inside the .NET process, in a separate process on the same machine, or even on a different machine altogether. At the time of writing, all the built-in hosting mechanisms work by launching Node as a separate process on the same machine as your .NET code.

View File

@@ -1,5 +1,6 @@
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive, // Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
// but simplifies things for the consumer of this module. // but simplifies things for the consumer of this module.
import './Util/PatchModuleResolutionLStat';
import './Util/OverrideStdOutputs'; import './Util/OverrideStdOutputs';
import * as http from 'http'; import * as http from 'http';
import * as path from 'path'; import * as path from 'path';
@@ -86,5 +87,8 @@ function readRequestBodyAsJson(request, callback) {
function respondWithError(res: http.ServerResponse, errorValue: any) { function respondWithError(res: http.ServerResponse, errorValue: any) {
res.statusCode = 500; res.statusCode = 500;
res.end(errorValue.stack || errorValue.toString()); res.end(JSON.stringify({
errorMessage: errorValue.message || errorValue,
errorDetails: errorValue.stack || null
}));
} }

View File

@@ -0,0 +1,48 @@
import * as path from 'path';
const startsWith = (str: string, prefix: string) => str.substring(0, prefix.length) === prefix;
const appRootDir = process.cwd();
function patchedLStat(pathToStatLong: string, fsReqWrap?: any) {
try {
// If the lstat completes without errors, we don't modify its behavior at all
return origLStat.apply(this, arguments);
} catch(ex) {
const shouldOverrideError =
startsWith(ex.message, 'EPERM') // It's a permissions error
&& typeof appRootDirLong === 'string'
&& startsWith(appRootDirLong, pathToStatLong) // ... for an ancestor directory
&& ex.stack.indexOf('Object.realpathSync ') >= 0; // ... during symlink resolution
if (shouldOverrideError) {
// Fake the result to give the same result as an 'lstat' on the app root dir.
// This stops Node failing to load modules just because it doesn't know whether
// ancestor directories are symlinks or not. If there's a genuine file
// permissions issue, it will still surface later when Node actually
// tries to read the file.
return origLStat.call(this, appRootDir, fsReqWrap);
} else {
// In any other case, preserve the original error
throw ex;
}
}
};
// It's only necessary to apply this workaround on Windows
let appRootDirLong: string = null;
let origLStat: Function = null;
if (/^win/.test(process.platform)) {
try {
// Get the app's root dir in Node's internal "long" format (e.g., \\?\C:\dir\subdir)
appRootDirLong = (path as any)._makeLong(appRootDir);
// Actually apply the patch, being as defensive as possible
const bindingFs = (process as any).binding('fs');
origLStat = bindingFs.lstat;
if (typeof origLStat === 'function') {
bindingFs.lstat = patchedLStat;
}
} catch(ex) {
// If some future version of Node throws (e.g., to prevent use of process.binding()),
// don't apply the patch, but still let the application run.
}
}

View File

@@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.NodeServices
/// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param> /// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
public StringAsTempFile(string content, CancellationToken applicationStoppingToken) public StringAsTempFile(string content, CancellationToken applicationStoppingToken)
{ {
FileName = Path.GetTempFileName(); FileName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
File.WriteAllText(FileName, content); File.WriteAllText(FileName, content);
// Because .NET finalizers don't reliably run when the process is terminating, also // Because .NET finalizers don't reliably run when the process is terminating, also

View File

@@ -11,6 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="node_modules\**\*" />
<EmbeddedResource Include="Content\**\*" /> <EmbeddedResource Include="Content\**\*" />
<ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" /> <ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.TagHelpers" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.TagHelpers" Version="$(AspNetCoreVersion)" />

View File

@@ -0,0 +1,49 @@
using System.Threading;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.NodeServices;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.SpaServices.Prerendering
{
/// <summary>
/// Default implementation of a DI service that provides convenient access to
/// server-side prerendering APIs. This is an alternative to prerendering via
/// the asp-prerender-module tag helper.
/// </summary>
internal class DefaultSpaPrerenderer : ISpaPrerenderer
{
private readonly string _applicationBasePath;
private readonly CancellationToken _applicationStoppingToken;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly INodeServices _nodeServices;
public DefaultSpaPrerenderer(
INodeServices nodeServices,
IApplicationLifetime applicationLifetime,
IHostingEnvironment hostingEnvironment,
IHttpContextAccessor httpContextAccessor)
{
_applicationBasePath = hostingEnvironment.ContentRootPath;
_applicationStoppingToken = applicationLifetime.ApplicationStopping;
_httpContextAccessor = httpContextAccessor;
_nodeServices = nodeServices;
}
public Task<RenderToStringResult> RenderToString(
string moduleName,
string exportName = null,
object customDataParameter = null,
int timeoutMilliseconds = default(int))
{
return Prerenderer.RenderToString(
_applicationBasePath,
_nodeServices,
_applicationStoppingToken,
new JavaScriptModuleExport(moduleName) { ExportName = exportName },
_httpContextAccessor.HttpContext,
customDataParameter,
timeoutMilliseconds);
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.SpaServices.Prerendering
{
/// <summary>
/// Represents a service that can perform server-side prerendering for
/// JavaScript-based Single Page Applications. This is an alternative
/// to using the 'asp-prerender-module' tag helper.
/// </summary>
public interface ISpaPrerenderer
{
/// <summary>
/// Invokes JavaScript code to perform server-side prerendering for a
/// Single-Page Application. This is an alternative to using the
/// 'asp-prerender-module' tag helper.
/// </summary>
/// <param name="moduleName">The JavaScript module that exports a prerendering function.</param>
/// <param name="exportName">The name of the export from the JavaScript module, if it is not the default export.</param>
/// <param name="customDataParameter">An optional JSON-serializable object to pass to the JavaScript prerendering function.</param>
/// <param name="timeoutMilliseconds">If specified, the prerendering task will time out after this duration if not already completed.</param>
/// <returns></returns>
Task<RenderToStringResult> RenderToString(
string moduleName,
string exportName = null,
object customDataParameter = null,
int timeoutMilliseconds = default(int));
}
}

View File

@@ -1,14 +1,11 @@
using System; using System;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Newtonsoft.Json;
namespace Microsoft.AspNetCore.SpaServices.Prerendering namespace Microsoft.AspNetCore.SpaServices.Prerendering
{ {
@@ -90,19 +87,6 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
/// <returns>A <see cref="Task"/> representing the operation.</returns> /// <returns>A <see cref="Task"/> representing the operation.</returns>
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{ {
// We want to pass the original, unencoded incoming URL data through to Node, so that
// server-side code has the same view of the URL as client-side code (on the client,
// location.pathname returns an unencoded string).
// The following logic handles special characters in URL paths in the same way that
// Node and client-side JS does. For example, the path "/a=b%20c" gets passed through
// unchanged (whereas other .NET APIs do change it - Path.Value will return it as
// "/a=b c" and Path.ToString() will return it as "/a%3db%20c")
var requestFeature = ViewContext.HttpContext.Features.Get<IHttpRequestFeature>();
var unencodedPathAndQuery = requestFeature.RawTarget;
var request = ViewContext.HttpContext.Request;
var unencodedAbsoluteUrl = $"{request.Scheme}://{request.Host}{unencodedPathAndQuery}";
var result = await Prerenderer.RenderToString( var result = await Prerenderer.RenderToString(
_applicationBasePath, _applicationBasePath,
_nodeServices, _nodeServices,
@@ -111,11 +95,9 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
{ {
ExportName = ExportName ExportName = ExportName
}, },
unencodedAbsoluteUrl, ViewContext.HttpContext,
unencodedPathAndQuery,
CustomDataParameter, CustomDataParameter,
TimeoutMillisecondsParameter, TimeoutMillisecondsParameter);
request.PathBase.ToString());
if (!string.IsNullOrEmpty(result.RedirectUrl)) if (!string.IsNullOrEmpty(result.RedirectUrl))
{ {
@@ -134,19 +116,10 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
// Also attach any specified globals to the 'window' object. This is useful for transferring // Also attach any specified globals to the 'window' object. This is useful for transferring
// general state between server and client. // general state between server and client.
if (result.Globals != null) var globalsScript = result.CreateGlobalsAssignmentScript();
if (!string.IsNullOrEmpty(globalsScript))
{ {
var stringBuilder = new StringBuilder(); output.PostElement.SetHtmlContent($"<script>{globalsScript}</script>");
foreach (var property in result.Globals.Properties())
{
stringBuilder.AppendFormat("window.{0} = {1};",
property.Name,
property.Value.ToString(Formatting.None));
}
if (stringBuilder.Length > 0)
{
output.PostElement.SetHtmlContent($"<script>{stringBuilder}</script>");
}
} }
} }
} }

View File

@@ -2,6 +2,8 @@ using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.SpaServices.Prerendering namespace Microsoft.AspNetCore.SpaServices.Prerendering
{ {
@@ -14,6 +16,40 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
private static StringAsTempFile NodeScript; private static StringAsTempFile NodeScript;
internal static Task<RenderToStringResult> RenderToString(
string applicationBasePath,
INodeServices nodeServices,
CancellationToken applicationStoppingToken,
JavaScriptModuleExport bootModule,
HttpContext httpContext,
object customDataParameter,
int timeoutMilliseconds)
{
// We want to pass the original, unencoded incoming URL data through to Node, so that
// server-side code has the same view of the URL as client-side code (on the client,
// location.pathname returns an unencoded string).
// The following logic handles special characters in URL paths in the same way that
// Node and client-side JS does. For example, the path "/a=b%20c" gets passed through
// unchanged (whereas other .NET APIs do change it - Path.Value will return it as
// "/a=b c" and Path.ToString() will return it as "/a%3db%20c")
var requestFeature = httpContext.Features.Get<IHttpRequestFeature>();
var unencodedPathAndQuery = requestFeature.RawTarget;
var request = httpContext.Request;
var unencodedAbsoluteUrl = $"{request.Scheme}://{request.Host}{unencodedPathAndQuery}";
return RenderToString(
applicationBasePath,
nodeServices,
applicationStoppingToken,
bootModule,
unencodedAbsoluteUrl,
unencodedPathAndQuery,
customDataParameter,
timeoutMilliseconds,
request.PathBase.ToString());
}
/// <summary> /// <summary>
/// Performs server-side prerendering by invoking code in Node.js. /// Performs server-side prerendering by invoking code in Node.js.
/// </summary> /// </summary>

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.SpaServices.Prerendering;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extension methods for setting up prerendering features in an <see cref="IServiceCollection" />.
/// </summary>
public static class PrerenderingServiceCollectionExtensions
{
/// <summary>
/// Configures the dependency injection system to supply an implementation
/// of <see cref="ISpaPrerenderer"/>.
/// </summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
public static void AddSpaPrerenderer(this IServiceCollection serviceCollection)
{
serviceCollection.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
serviceCollection.AddSingleton<ISpaPrerenderer, DefaultSpaPrerenderer>();
}
}
}

View File

@@ -1,4 +1,6 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System.Text;
namespace Microsoft.AspNetCore.SpaServices.Prerendering namespace Microsoft.AspNetCore.SpaServices.Prerendering
{ {
@@ -30,5 +32,29 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
/// If set, specifies the HTTP status code that should be sent back with the server response. /// If set, specifies the HTTP status code that should be sent back with the server response.
/// </summary> /// </summary>
public int? StatusCode { get; set; } public int? StatusCode { get; set; }
/// <summary>
/// Constructs a block of JavaScript code that assigns data from the
/// <see cref="Globals"/> property to the global namespace.
/// </summary>
/// <returns>A block of JavaScript code.</returns>
public string CreateGlobalsAssignmentScript()
{
if (Globals == null)
{
return string.Empty;
}
var stringBuilder = new StringBuilder();
foreach (var property in Globals.Properties())
{
stringBuilder.AppendFormat("window.{0} = {1};",
property.Name,
property.Value.ToString(Formatting.None));
}
return stringBuilder.ToString();
}
} }
} }

View File

@@ -20,7 +20,7 @@ Behind the scenes, it uses the [`Microsoft.AspNetCore.NodeServices`](https://git
### Installation into existing projects ### Installation into existing projects
* Add `Microsoft.AspNetCore.SpaServices` to the dependencies list in your `project.json` file * Install the `Microsoft.AspNetCore.SpaServices` NuGet package
* Run `dotnet restore` (or if you use Visual Studio, just wait a moment - it will restore dependencies automatically) * Run `dotnet restore` (or if you use Visual Studio, just wait a moment - it will restore dependencies automatically)
* Install supporting NPM packages for the features you'll be using: * Install supporting NPM packages for the features you'll be using:
* For **server-side prerendering**, install `aspnet-prerendering` * For **server-side prerendering**, install `aspnet-prerendering`

View File

@@ -28,6 +28,11 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
string pathPrefix, string pathPrefix,
ConditionalProxyMiddlewareOptions options) ConditionalProxyMiddlewareOptions options)
{ {
if (!pathPrefix.StartsWith("/"))
{
pathPrefix = "/" + pathPrefix;
}
_next = next; _next = next;
_pathPrefix = pathPrefix; _pathPrefix = pathPrefix;
_options = options; _options = options;
@@ -65,7 +70,7 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
requestMessage.Headers.Host = _options.Host + ":" + _options.Port; requestMessage.Headers.Host = _options.Host + ":" + _options.Port;
var uriString = var uriString =
$"{_options.Scheme}://{_options.Host}:{_options.Port}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}"; $"{_options.Scheme}://{_options.Host}:{_options.Port}{context.Request.Path}{context.Request.QueryString}";
requestMessage.RequestUri = new Uri(uriString); requestMessage.RequestUri = new Uri(uriString);
requestMessage.Method = new HttpMethod(context.Request.Method); requestMessage.Method = new HttpMethod(context.Request.Method);

View File

@@ -4,6 +4,7 @@ using System.Threading;
using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.SpaServices.Webpack; using Microsoft.AspNetCore.SpaServices.Webpack;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Microsoft.AspNetCore.Builder namespace Microsoft.AspNetCore.Builder
{ {
@@ -14,6 +15,12 @@ namespace Microsoft.AspNetCore.Builder
{ {
private const string DefaultConfigFile = "webpack.config.js"; private const string DefaultConfigFile = "webpack.config.js";
private static readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
TypeNameHandling = TypeNameHandling.None
};
/// <summary> /// <summary>
/// Enables Webpack dev middleware support. This hosts an instance of the Webpack compiler in memory /// Enables Webpack dev middleware support. This hosts an instance of the Webpack compiler in memory
/// in your application so that you can always serve up-to-date Webpack-built resources without having /// in your application so that you can always serve up-to-date Webpack-built resources without having
@@ -87,7 +94,7 @@ namespace Microsoft.AspNetCore.Builder
}; };
var devServerInfo = var devServerInfo =
nodeServices.InvokeExportAsync<WebpackDevServerInfo>(nodeScript.FileName, "createWebpackDevServer", nodeServices.InvokeExportAsync<WebpackDevServerInfo>(nodeScript.FileName, "createWebpackDevServer",
JsonConvert.SerializeObject(devServerOptions)).Result; JsonConvert.SerializeObject(devServerOptions, jsonSerializerSettings)).Result;
// If we're talking to an older version of aspnet-webpack, it will return only a single PublicPath, // If we're talking to an older version of aspnet-webpack, it will return only a single PublicPath,
// not an array of PublicPaths. Handle that scenario. // not an array of PublicPaths. Handle that scenario.
@@ -101,9 +108,9 @@ namespace Microsoft.AspNetCore.Builder
// plus /__webpack_hmr is proxied with infinite timeout, because it's an EventSource (long-lived request). // plus /__webpack_hmr is proxied with infinite timeout, because it's an EventSource (long-lived request).
foreach (var publicPath in devServerInfo.PublicPaths) foreach (var publicPath in devServerInfo.PublicPaths)
{ {
appBuilder.UseProxyToLocalWebpackDevMiddleware(publicPath + hmrEndpoint, devServerInfo.Port, Timeout.InfiniteTimeSpan);
appBuilder.UseProxyToLocalWebpackDevMiddleware(publicPath, devServerInfo.Port, TimeSpan.FromSeconds(100)); appBuilder.UseProxyToLocalWebpackDevMiddleware(publicPath, devServerInfo.Port, TimeSpan.FromSeconds(100));
} }
appBuilder.UseProxyToLocalWebpackDevMiddleware(hmrEndpoint, devServerInfo.Port, Timeout.InfiniteTimeSpan);
} }
private static void UseProxyToLocalWebpackDevMiddleware(this IApplicationBuilder appBuilder, string publicPath, int proxyToPort, TimeSpan requestTimeout) private static void UseProxyToLocalWebpackDevMiddleware(this IApplicationBuilder appBuilder, string publicPath, int proxyToPort, TimeSpan requestTimeout)

View File

@@ -0,0 +1,5 @@
/node_modules/
**/*.js
**/*.d.ts
**/*.metadata.json
/compiled

View File

@@ -0,0 +1,3 @@
!/*.js
!/*.d.ts
/compiled

View File

@@ -0,0 +1,12 @@
Copyright (c) .NET Foundation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
these files except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.

View File

@@ -0,0 +1,33 @@
{
"name": "aspnet-angular",
"version": "0.1.1",
"description": "Helpers for using Angular in ASP.NET Core projects",
"main": "index.js",
"scripts": {
"prepublish": "rimraf *.d.ts && ngc && echo 'Finished building NPM package \"aspnet-angular\"'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/aspnet/JavaScriptServices.git"
},
"author": "Microsoft",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/aspnet/JavaScriptServices/issues"
},
"devDependencies": {
"@angular/common": "^4.3.2",
"@angular/compiler": "^4.3.2",
"@angular/compiler-cli": "^4.3.2",
"@angular/core": "^4.3.2",
"@angular/http": "^4.3.2",
"@angular/platform-browser": "^4.3.2",
"rimraf": "^2.6.1",
"rxjs": "^5.4.2",
"zone.js": "^0.8.16"
},
"peerDependencies": {
"@angular/core": "^4.2.5 || ^5.0.0-beta"
}
}

View File

@@ -0,0 +1,94 @@
import { Provider, NgModule, Inject } from '@angular/core';
import { Headers, Http, ResponseOptions, RequestOptionsArgs, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
const globalSerializedStateKey = 'HTTP_STATE_TRANSFER';
const backingStoreDIToken = 'HTTP_STATE_BACKING_STORE';
export interface CacheOptions {
permanent: boolean;
}
export interface CachedHttpResponse {
headers: { [name: string]: any } | null;
status: number;
statusText: string | null;
text: string;
url: string;
}
export type BackingStore = { [key: string]: CachedHttpResponse };
export class HttpWithStateTransfer {
private backingStore: BackingStore;
private http: Http;
constructor(@Inject(Http) http: Http, @Inject(backingStoreDIToken) backingStore: BackingStore) {
this.http = http;
this.backingStore = backingStore;
}
public stateForTransfer(): any {
return { [globalSerializedStateKey]: this.backingStore };
}
public get(url: string, options?: CacheOptions, requestOptions?: RequestOptionsArgs): Observable<Response> {
return this.getCachedResponse(/* cacheKey */ url, () => this.http.get(url, requestOptions), options);
}
private getCachedResponse(cacheKey: string, provider: () => Observable<Response>, options?: CacheOptions): Observable<Response> {
// By default, the cache is only used for the *first* client-side read. So, we're only performing
// a one-time transfer of server-side response to the client. If you want to keep and reuse cached
// responses continually during server-side and client-side execution, set 'permanent' to 'true.
const isClient = typeof window !== 'undefined';
const isPermanent = options && options.permanent;
const allowReadFromCache = isClient || isPermanent;
if (allowReadFromCache && this.backingStore.hasOwnProperty(cacheKey)) {
const cachedValue = this.backingStore[cacheKey];
if (!isPermanent) {
delete this.backingStore[cacheKey];
}
return Observable.of(new Response(new ResponseOptions({
body: cachedValue.text,
headers: new Headers(cachedValue.headers),
status: cachedValue.status,
url: cachedValue.url
})));
}
return provider()
.map(response => {
const allowWriteToCache = !isClient || isPermanent;
if (allowWriteToCache) {
this.backingStore[cacheKey] = {
headers: response.headers ? response.headers.toJSON() : null,
status: response.status,
statusText: response.statusText,
text: response.text(),
url: response.url
};
}
return response;
});
}
}
export function defaultBackingStoreFactory() {
const transferredData = typeof window !== 'undefined' ? (window as any)[globalSerializedStateKey] : null;
return transferredData || {};
}
@NgModule({
providers: [
// The backing store is a separate DI service so you could override exactly how it gets
// transferred from server to client
{ provide: backingStoreDIToken, useFactory: defaultBackingStoreFactory },
{ provide: HttpWithStateTransfer, useClass: HttpWithStateTransfer },
]
})
export class HttpWithStateTransferModule {
}

View File

@@ -0,0 +1 @@
export * from './HttpWithStateTransfer';

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"moduleResolution": "node",
"module": "commonjs",
"target": "es5",
"declaration": true,
"outDir": ".",
"lib": ["es2015", "dom"]
},
"files": [
"src/index.ts"
],
"exclude": [
"node_modules"
],
"angularCompilerOptions": {
"genDir": "compiled"
}
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "aspnet-prerendering", "name": "aspnet-prerendering",
"version": "2.0.5", "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.", "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", "main": "index.js",
"scripts": { "scripts": {
@@ -17,7 +17,7 @@
"url": "https://github.com/aspnet/JavaScriptServices.git" "url": "https://github.com/aspnet/JavaScriptServices.git"
}, },
"dependencies": { "dependencies": {
"domain-task": "^2.0.2" "domain-task": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^6.0.42", "@types/node": "^6.0.42",

View File

@@ -1,8 +1,8 @@
/// <reference path="./PrerenderingInterfaces.d.ts" />
import * as url from 'url'; import * as url from 'url';
import * as path from 'path'; import * as path from 'path';
import * as domain from 'domain'; import * as domain from 'domain';
import { run as domainTaskRun, baseUrl as domainTaskBaseUrl } from 'domain-task/main'; import { run as domainTaskRun, baseUrl as domainTaskBaseUrl } from 'domain-task/main';
import { BootFunc, BootFuncParams, BootModuleInfo, RenderToStringCallback, RenderToStringFunc } from './PrerenderingInterfaces';
const defaultTimeoutMilliseconds = 30 * 1000; const defaultTimeoutMilliseconds = 30 * 1000;
@@ -26,6 +26,7 @@ export function createServerRenderer(bootFunc: BootFunc): RenderToStringFunc {
domainTasks: domainTaskCompletionPromise, domainTasks: domainTaskCompletionPromise,
data: customDataParameter data: customDataParameter
}; };
const absoluteBaseUrl = params.origin + params.baseUrl; // Should be same value as page's <base href>
// Open a new domain that can track all the async tasks involved in the app's execution // Open a new domain that can track all the async tasks involved in the app's execution
domainTaskRun(/* code to run */ () => { domainTaskRun(/* code to run */ () => {
@@ -35,7 +36,7 @@ export function createServerRenderer(bootFunc: BootFunc): RenderToStringFunc {
bindPromiseContinuationsToDomain(domainTaskCompletionPromise, domain['active']); bindPromiseContinuationsToDomain(domainTaskCompletionPromise, domain['active']);
// Make the base URL available to the 'domain-tasks/fetch' helper within this execution context // 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 // Begin rendering, and apply a timeout
const bootFuncPromise = bootFunc(params); const bootFuncPromise = bootFunc(params);

View File

@@ -1,26 +1,28 @@
interface RenderToStringFunc { export interface RenderToStringFunc {
(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number, requestPathBase: string): void; (callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number, requestPathBase: string): void;
} }
interface RenderToStringCallback { export interface RenderToStringCallback {
(error: any, result?: RenderToStringResult): void; (error: any, result?: RenderResult): void;
} }
interface RenderToStringResult { export interface RenderToStringResult {
html: string; html: string;
statusCode?: number; statusCode?: number;
globals?: { [key: string]: any }; globals?: { [key: string]: any };
} }
interface RedirectResult { export interface RedirectResult {
redirectUrl: string; redirectUrl: string;
} }
interface BootFunc { export type RenderResult = RenderToStringResult | RedirectResult;
(params: BootFuncParams): Promise<RenderToStringResult>;
export interface BootFunc {
(params: BootFuncParams): Promise<RenderResult>;
} }
interface BootFuncParams { export interface BootFuncParams {
location: any; // e.g., Location object containing information '/some/path' location: any; // e.g., Location object containing information '/some/path'
origin: string; // e.g., 'https://example.com:1234' origin: string; // e.g., 'https://example.com:1234'
url: string; // e.g., '/some/path' url: string; // e.g., '/some/path'
@@ -30,7 +32,7 @@ interface BootFuncParams {
data: any; // any custom object passed through from .NET data: any; // any custom object passed through from .NET
} }
interface BootModuleInfo { export interface BootModuleInfo {
moduleName: string; moduleName: string;
exportName?: string; exportName?: string;
webpackConfig?: string; webpackConfig?: string;

View File

@@ -1,5 +1,2 @@
/// <reference path="./PrerenderingInterfaces.d.ts" />
export * from './Prerendering'; export * from './Prerendering';
export * from './PrerenderingInterfaces';
export type RenderResult = RenderToStringResult | RedirectResult;

View File

@@ -1,6 +1,6 @@
{ {
"name": "aspnet-webpack-react", "name": "aspnet-webpack-react",
"version": "2.0.0", "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.", "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", "main": "index.js",
"scripts": { "scripts": {

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "aspnet-webpack", "name": "aspnet-webpack",
"version": "1.0.29", "version": "2.0.1",
"description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@@ -5,11 +5,11 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as querystring from 'querystring'; import * as querystring from 'querystring';
import { requireNewCopy } from './RequireNewCopy'; import { requireNewCopy } from './RequireNewCopy';
import { hasSufficientPermissions } from './WebpackTestPermissions';
export type CreateDevServerResult = { export type CreateDevServerResult = {
Port: number, Port: number,
PublicPaths: string[], PublicPaths: string[]
PublicPath: string // For backward compatibility with older verions of Microsoft.AspNetCore.SpaServices. Will be removed soon.
}; };
export interface CreateDevServerCallback { export interface CreateDevServerCallback {
@@ -108,7 +108,7 @@ function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configurati
const compiler = webpack(webpackConfig); const compiler = webpack(webpackConfig);
app.use(require('webpack-dev-middleware')(compiler, { app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true, noInfo: true,
publicPath: webpackConfig.output.publicPath, publicPath: ensureLeadingSlash(webpackConfig.output.publicPath),
watchOptions: webpackConfig.watchOptions 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) { function pathJoinSafe(rootPath: string, filePath: string) {
// On Windows, MemoryFileSystem's readdirSync output produces directory entries like 'C:' // 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 // which then trigger errors if you call statSync for them. Avoid this by detecting drive
@@ -216,6 +224,16 @@ function beginWebpackWatcher(webpackConfig: webpack.Configuration) {
export function createWebpackDevServer(callback: CreateDevServerCallback, optionsJson: string) { export function createWebpackDevServer(callback: CreateDevServerCallback, optionsJson: string) {
const options: CreateDevServerOptions = JSON.parse(optionsJson); const options: CreateDevServerOptions = JSON.parse(optionsJson);
// See the large comment in WebpackTestPermissions.ts for details about this
if (!hasSufficientPermissions()) {
console.log('WARNING: Webpack dev middleware is not enabled because the server process does not have sufficient permissions. You should either remove the UseWebpackDevMiddleware call from your code, or to make it work, give your server process user account permission to write to your application directory and to read all ancestor-level directories.');
callback(null, {
Port: 0,
PublicPaths: []
});
return;
}
// Read the webpack config's export, and normalize it into the more general 'array of configs' format // Read the webpack config's export, and normalize it into the more general 'array of configs' format
let webpackConfigExport: WebpackConfigFileExport = requireNewCopy(options.webpackConfigPath); let webpackConfigExport: WebpackConfigFileExport = requireNewCopy(options.webpackConfigPath);
if (webpackConfigExport instanceof Function) { if (webpackConfigExport instanceof Function) {
@@ -257,22 +275,32 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
if (!publicPath) { 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)'); 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 // This is the URL the client will connect to, except that since it's a relative URL
// (because it's relative to the app's URL space root, which the client doesn't otherwise know). // (no leading slash), Webpack will resolve it against the runtime <base href> URL
// For back-compatibility, fall back on connecting directly to the underlying HMR server (though // plus it also adds the publicPath
// that won't work if the app is hosted on HTTPS because of the mixed-content rule, and we can't const hmrClientEndpoint = removeLeadingSlash(options.hotModuleReplacementEndpointUrl);
// 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) // This is the URL inside the Webpack middleware Node server that we'll proxy to.
|| `http://localhost:${listener.address().port}/__webpack_hmr`; // Fall back on absolute URL to bypass proxying // We have to prefix with the public path because Webpack will add the publicPath
const hmrServerEndpoint = options.hotModuleReplacementEndpointUrl // when it resolves hmrClientEndpoint as a relative URL.
|| '/__webpack_hmr'; // URL is relative to webpack dev server root const hmrServerEndpoint = ensureLeadingSlash(publicPathNoTrailingSlash + options.hotModuleReplacementEndpointUrl);
// We always overwrite the 'path' option as it needs to match what the .NET side is expecting // We always overwrite the 'path' option as it needs to match what the .NET side is expecting
const hmrClientOptions = options.suppliedOptions.HotModuleReplacementClientOptions || <StringMap<string>>{}; const hmrClientOptions = options.suppliedOptions.HotModuleReplacementClientOptions || <StringMap<string>>{};
hmrClientOptions['path'] = hmrClientEndpoint; 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); attachWebpackDevMiddleware(app, webpackConfig, enableHotModuleReplacement, enableReactHotModuleReplacement, hmrClientOptions, hmrServerEndpoint);
} }
}); });
@@ -280,11 +308,7 @@ export function createWebpackDevServer(callback: CreateDevServerCallback, option
// Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here // Tell the ASP.NET app what addresses we're listening on, so that it can proxy requests here
callback(null, { callback(null, {
Port: listener.address().port, Port: listener.address().port,
PublicPaths: normalizedPublicPaths, PublicPaths: normalizedPublicPaths
// For back-compatibility with older versions of Microsoft.AspNetCore.SpaServices, in the case where
// you have exactly one webpackConfigArray entry. This will be removed soon.
PublicPath: normalizedPublicPaths[0]
}); });
} catch (ex) { } catch (ex) {
callback(ex.stack, null); callback(ex.stack, null);
@@ -292,6 +316,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) { function removeTrailingSlash(str: string) {
if (str.lastIndexOf('/') === str.length - 1) { if (str.lastIndexOf('/') === str.length - 1) {
str = str.substring(0, str.length - 1); str = str.substring(0, str.length - 1);

View File

@@ -0,0 +1,58 @@
import * as fs from 'fs';
import * as path from 'path';
const isWindows = /^win/.test(process.platform);
// On Windows, Node (still as of v8.1.3) has an issue whereby, when locating JavaScript modules
// on disk, it walks up the directory hierarchy to the disk root, testing whether each directory
// is a symlink or not. This fails with an exception if the process doesn't have permission to
// read those directories. This is a problem when hosting in full IIS, because in typical cases
// the process does not have read permission for higher-level directories.
//
// NodeServices itself works around this by injecting a patched version of Node's 'lstat' API that
// suppresses these irrelevant errors during module loads. This covers most scenarios, but isn't
// enough to make Webpack dev middleware work, because typical Webpack configs use loaders such as
// 'awesome-typescript-loader', which works by forking a child process to do some of its work. The
// child process does not get the patched 'lstat', and hence fails. It's an especially bad failure,
// because the Webpack compiler doesn't even surface the exception - it just never completes the
// compilation process, causing the application to hang indefinitely.
//
// Additionally, Webpack dev middleware will want to write its output to disk, which is also going
// to fail in a typical IIS process, because you won't have 'write' permission to the app dir by
// default. We have to actually write the build output to disk (and not purely keep it in the in-
// memory file system) because the server-side prerendering Node instance is a separate process
// that only knows about code changes when it sees the compiled files on disk change.
//
// In the future, we'll hopefully get Node to fix its underlying issue, and figure out whether VS
// could give 'write' access to the app dir when launching sites in IIS. But until then, disable
// Webpack dev middleware if we detect the server process doesn't have the necessary permissions.
export function hasSufficientPermissions() {
if (isWindows) {
return canReadDirectoryAndAllAncestors(process.cwd());
} else {
return true;
}
}
function canReadDirectoryAndAllAncestors(dir: string): boolean {
if (!canReadDirectory(dir)) {
return false;
}
const parentDir = path.resolve(dir, '..');
if (parentDir === dir) {
// There are no more parent directories - we've reached the disk root
return true;
} else {
return canReadDirectoryAndAllAncestors(parentDir);
}
}
function canReadDirectory(dir: string): boolean {
try {
fs.statSync(dir);
return true;
} catch(ex) {
return false;
}
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "domain-task", "name": "domain-task",
"version": "3.0.0", "version": "3.0.3",
"description": "Tracks outstanding operations for a logical thread of execution", "description": "Tracks outstanding operations for a logical thread of execution",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -18,6 +18,7 @@
}, },
"dependencies": { "dependencies": {
"domain-context": "^0.5.1", "domain-context": "^0.5.1",
"is-absolute-url": "^2.1.0",
"isomorphic-fetch": "^2.2.1" "isomorphic-fetch": "^2.2.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,11 +1,17 @@
import * as url from 'url'; import * as url from 'url';
import * as domain from 'domain'; import * as domain from 'domain';
import * as domainContext from 'domain-context'; import * as domainContext from 'domain-context';
import * as isAbsoluteUrl from 'is-absolute-url';
import { baseUrl } from './main'; import { baseUrl } from './main';
const isomorphicFetch = require('isomorphic-fetch'); const isomorphicFetch = require('isomorphic-fetch');
const isNode = typeof process === 'object' && process.versions && !!process.versions.node; const isNode = typeof process === 'object' && process.versions && !!process.versions.node;
const nodeHttps = isNode && require('https');
const isHttpsRegex = /^https\:/;
function issueRequest(baseUrl: string, req: string | Request, init?: RequestInit): Promise<any> { function issueRequest(baseUrl: string, req: string | Request, init?: RequestInit): Promise<any> {
const reqUrl = (req instanceof Request) ? req.url : req;
const isRelativeUrl = reqUrl && !isAbsoluteUrl(reqUrl);
// Resolve relative URLs // Resolve relative URLs
if (baseUrl) { if (baseUrl) {
if (req instanceof Request) { if (req instanceof Request) {
@@ -25,9 +31,45 @@ function issueRequest(baseUrl: string, req: string | Request, init?: RequestInit
`); `);
} }
init = applyHttpsAgentPolicy(init, isRelativeUrl, baseUrl);
return isomorphicFetch(req, init); return isomorphicFetch(req, init);
} }
function applyHttpsAgentPolicy(init: RequestInit, isRelativeUrl: boolean, baseUrl: string): RequestInit {
// HTTPS is awkward in Node because it uses a built-in list of CAs, rather than recognizing
// the OS's system-level CA list. There are dozens of issues filed against Node about this,
// but still (as of v8.0.0) no resolution besides manually duplicating your CA config.
//
// The biggest problem for typical isomorphic-SPA development this causes is that if you're
// using a self-signed localhost cert in development, Node won't be able to make API calls
// to it (e.g., https://github.com/aspnet/JavaScriptServices/issues/1089). Developers could
// fix this by either manually configuring the cert in Node (which is extremely inconvenient,
// especially if multiple devs on a team have different self-signed localhost certs), or by
// disabling cert verification on their API requests.
//
// Fortunately, 'domain-task/fetch' knows when you're making a relative-URL request to your
// own web server (as opposed to an arbitrary request to anywhere else). In this specific case,
// there's no real point in cert verification, since the request never even leaves the machine
// so a MitM attack isn't meaningful. So by default, when your code is running in Node and
// is making a relative-URL request, *and* if you haven't explicitly configured any option
// for 'agent' (which would let you set up other HTTPS-handling policies), then we automatically
// disable cert verification for that request.
if (isNode && isRelativeUrl) {
const isHttps = baseUrl && isHttpsRegex.test(baseUrl);
if (isHttps) {
const hasAgentConfig = init && ('agent' in init);
if (!hasAgentConfig) {
const agentForRequest = new (nodeHttps.Agent)({ rejectUnauthorized: false });
init = init || {};
(init as any).agent = agentForRequest;
}
}
}
return init;
}
export function fetch(url: string | Request, init?: RequestInit): Promise<any> { export function fetch(url: string | Request, init?: RequestInit): Promise<any> {
// As of domain-task 2.0.0, we no longer auto-add the 'fetch' promise to the current domain task list. // As of domain-task 2.0.0, we no longer auto-add the 'fetch' promise to the current domain task list.
// This is because it's misleading to do so, and can result in race-condition bugs, e.g., // This is because it's misleading to do so, and can result in race-condition bugs, e.g.,

View File

@@ -1,23 +0,0 @@
import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
@Component({
selector: 'fetchdata',
templateUrl: './fetchdata.component.html'
})
export class FetchDataComponent {
public forecasts: WeatherForecast[];
constructor(http: Http, @Inject('ORIGIN_URL') originUrl: string) {
http.get(originUrl + '/api/SampleData/WeatherForecasts').subscribe(result => {
this.forecasts = result.json() as WeatherForecast[];
});
}
}
interface WeatherForecast {
dateFormatted: string;
temperatureC: number;
temperatureF: number;
summary: string;
}

View File

@@ -1,60 +0,0 @@
{
"name": "WebApplicationBasic",
"version": "0.0.0",
"scripts": {
"test": "karma start ClientApp/test/karma.conf.js"
},
"dependencies": {
"@angular/animations": "4.1.2",
"@angular/common": "4.1.2",
"@angular/compiler": "4.1.2",
"@angular/core": "4.1.2",
"@angular/forms": "4.1.2",
"@angular/http": "4.1.2",
"@angular/platform-browser": "4.1.2",
"@angular/platform-browser-dynamic": "4.1.2",
"@angular/platform-server": "4.1.2",
"@angular/router": "4.1.2",
"@types/node": "7.0.18",
"angular2-template-loader": "0.6.2",
"aspnet-prerendering": "^2.0.5",
"aspnet-webpack": "^1.0.29",
"awesome-typescript-loader": "3.1.3",
"bootstrap": "3.3.7",
"css": "2.2.1",
"css-loader": "0.28.1",
"es6-shim": "0.35.3",
"event-source-polyfill": "0.0.9",
"expose-loader": "0.7.3",
"extract-text-webpack-plugin": "2.1.0",
"file-loader": "0.11.1",
"html-loader": "0.4.5",
"isomorphic-fetch": "2.2.1",
"jquery": "3.2.1",
"json-loader": "0.5.4",
"preboot": "4.5.2",
"raw-loader": "0.5.1",
"reflect-metadata": "0.1.10",
"rxjs": "5.4.0",
"style-loader": "0.17.0",
"to-string-loader": "1.1.5",
"typescript": "2.3.2",
"url-loader": "0.5.8",
"webpack": "2.5.1",
"webpack-hot-middleware": "2.18.0",
"webpack-merge": "4.1.0",
"zone.js": "0.8.10"
},
"devDependencies": {
"@types/chai": "3.5.2",
"@types/jasmine": "2.5.47",
"chai": "3.5.0",
"jasmine-core": "2.6.1",
"karma": "1.7.0",
"karma-chai": "0.1.0",
"karma-chrome-launcher": "2.1.1",
"karma-cli": "1.0.1",
"karma-jasmine": "1.1.0",
"karma-webpack": "2.0.3"
}
}

View File

@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Microsoft.AspNetCore.SpaTemplates</id> <id>Microsoft.AspNetCore.SpaTemplates</id>
<version>{yeomanversion}</version> <version>0.0.0</version>
<description>Single Page Application templates for ASP.NET Core</description> <description>Single Page Application templates for ASP.NET Core</description>
<authors>Microsoft</authors> <authors>Microsoft</authors>
<language>en-US</language> <language>en-US</language>
@@ -14,4 +14,10 @@
<packageType name="Template" /> <packageType name="Template" />
</packageTypes> </packageTypes>
</metadata> </metadata>
<files>
<file
src="**/*"
exclude="*/node_modules/**;*/bin/**;*/obj/**;*/ClientApp/dist/**;*/wwwroot/dist/**"
target="Content" />
</files>
</package> </package>

View File

@@ -0,0 +1,17 @@
{
"$schema": "http://json.schemastore.org/dotnetcli.host",
"symbolInfo": {
"TargetFrameworkOverride": {
"isHidden": "true",
"longName": "target-framework-override",
"shortName": ""
},
"Framework": {
"longName": "framework"
},
"skipRestore": {
"longName": "no-restore",
"shortName": ""
}
}
}

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,87 @@
{
"author": "Microsoft",
"classifications": [
"Web",
"MVC",
"SPA"
],
"groupIdentity": "Microsoft.AspNetCore.SpaTemplates.Aurelia",
"identity": "Microsoft.AspNetCore.SpaTemplates.Aurelia.CSharp",
"name": "ASP.NET Core with Aurelia",
"preferNameDirectory": true,
"primaryOutputs": [
{
"path": "AureliaSpa.csproj"
}
],
"shortName": "aurelia",
"sourceName": "AureliaSpa",
"sources": [
{
"source": "./",
"target": "./",
"exclude": [
".template.config/**"
]
}
],
"symbols": {
"TargetFrameworkOverride": {
"type": "parameter",
"description": "Overrides the target framework",
"replaces": "TargetFrameworkOverride",
"datatype": "string",
"defaultValue": ""
},
"Framework": {
"type": "parameter",
"description": "The target framework for the project.",
"datatype": "choice",
"choices": [
{
"choice": "netcoreapp2.0",
"description": "Target netcoreapp2.0"
}
],
"replaces": "netcoreapp2.0",
"defaultValue": "netcoreapp2.0"
},
"HostIdentifier": {
"type": "bind",
"binding": "HostIdentifier"
},
"skipRestore": {
"type": "parameter",
"datatype": "bool",
"description": "If specified, skips the automatic restore of the project on create.",
"defaultValue": "false"
}
},
"tags": {
"language": "C#",
"type": "project"
},
"postActions": [
{
"condition": "(!skipRestore)",
"description": "Restore NuGet packages required by this project.",
"manualInstructions": [
{
"text": "Run 'dotnet restore'"
}
],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
},
{
"condition": "(HostIdentifier == \"dotnetcli\" || HostIdentifier == \"dotnetcli-preview\")",
"actionId": "AC1156F7-BB77-4DB8-B28F-24EEBCCA1E5C",
"description": "\n\n-------------------------------------------------------------------\nIMPORTANT: Before running this project on the command line,\n you must restore NPM packages by running \"npm install\"\n-------------------------------------------------------------------\n",
"manualInstructions": [
{
"text": "Run \"npm install\""
}
]
}
]
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "http://json.schemastore.org/vs-2017.3.host",
"name": {
"text": "Aurelia",
"package": "{0CD94836-1526-4E85-87D3-FB5274C5AFC9}",
"id": "1200"
},
"description": {
"text": "A project template for creating an ASP.NET Core application with Aurelia",
"package": "{0CD94836-1526-4E85-87D3-FB5274C5AFC9}",
"id": "1201"
},
"order": 301,
"icon": "icon.png",
"learnMoreLink": "https://github.com/aspnet/JavaScriptServices",
"uiFilters": [
"oneaspnet"
],
"minFullFrameworkVersion": "4.6.1"
}

View File

@@ -5,22 +5,21 @@
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">TargetFrameworkOverride</TargetFramework> <TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">TargetFrameworkOverride</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(TargetFrameworkOverride)' == ''"> <ItemGroup Condition="'$(TargetFrameworkOverride)' == ''">
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-preview2-final" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkOverride)' != ''"> <ItemGroup Condition="'$(TargetFrameworkOverride)' != ''">
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0-preview2-final" /> <PackageReference Include="Microsoft.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0-preview2-final" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-preview2-final" /> <PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0-preview2-final" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0-*" /> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup> </ItemGroup>
<!--/-:cnd:noEmit --> <!--/-:cnd:noEmit -->

View File

@@ -6,7 +6,7 @@ export class Fetchdata {
public forecasts: WeatherForecast[]; public forecasts: WeatherForecast[];
constructor(http: HttpClient) { constructor(http: HttpClient) {
http.fetch('/api/SampleData/WeatherForecasts') http.fetch('api/SampleData/WeatherForecasts')
.then(result => result.json() as Promise<WeatherForecast[]>) .then(result => result.json() as Promise<WeatherForecast[]>)
.then(data => { .then(data => {
this.forecasts = data; this.forecasts = data;

View File

@@ -9,7 +9,7 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="#/home">WebApplicationBasic</a> <a class="navbar-brand" href="#/home">AureliaSpa</a>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
<div class="navbar-collapse collapse"> <div class="navbar-collapse collapse">

View File

@@ -1,5 +1,6 @@
import 'isomorphic-fetch'; import 'isomorphic-fetch';
import { Aurelia, PLATFORM } from 'aurelia-framework'; import { Aurelia, PLATFORM } from 'aurelia-framework';
import { HttpClient } from 'aurelia-fetch-client';
import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap'; import 'bootstrap';
declare const IS_DEV_BUILD: boolean; // The value is supplied by Webpack during the build 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(); 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'))); aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app/components/app/app')));
} }

View File

@@ -5,7 +5,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace WebApplicationBasic.Controllers namespace AureliaSpa.Controllers
{ {
public class HomeController : Controller public class HomeController : Controller
{ {

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace WebApplicationBasic.Controllers namespace AureliaSpa.Controllers
{ {
[Route("api/[controller]")] [Route("api/[controller]")]
public class SampleDataController : Controller public class SampleDataController : Controller

View File

@@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace WebApplicationBasic namespace AureliaSpa
{ {
public class Program public class Program
{ {

View File

@@ -8,7 +8,7 @@ using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace WebApplicationBasic namespace AureliaSpa
{ {
public class Startup public class Startup
{ {

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplicationBasic</title> <title>@ViewData["Title"] - AureliaSpa</title>
<base href="~/" /> <base href="~/" />
<link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true" /> <link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true" />

View File

@@ -1,3 +1,3 @@
@using WebApplicationBasic @using AureliaSpa
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.SpaServices @addTagHelper *, Microsoft.AspNetCore.SpaServices

View File

@@ -1,11 +1,11 @@
{ {
"name": "WebApplicationBasic", "name": "AureliaSpa",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@types/node": { "@types/webpack-env": {
"version": "7.0.32", "version": "1.13.0",
"from": "@types/node@>=7.0.12 <8.0.0", "from": "@types/webpack-env@>=1.13.0 <2.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.32.tgz", "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.13.0.tgz",
"dev": true "dev": true
}, },
"acorn": { "acorn": {
@@ -113,9 +113,9 @@
"dev": true "dev": true
}, },
"aspnet-webpack": { "aspnet-webpack": {
"version": "1.0.29", "version": "2.0.1",
"from": "aspnet-webpack@>=1.0.28 <2.0.0", "from": "aspnet-webpack@2.0.1",
"resolved": "https://registry.npmjs.org/aspnet-webpack/-/aspnet-webpack-1.0.29.tgz", "resolved": "https://registry.npmjs.org/aspnet-webpack/-/aspnet-webpack-2.0.1.tgz",
"dev": true "dev": true
}, },
"assert": { "assert": {

View File

@@ -1,25 +1,24 @@
{ {
"name": "WebApplicationBasic", "name": "AureliaSpa",
"private": true,
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "devDependencies": {
"@types/webpack-env": "^1.13.0",
"aspnet-webpack": "^2.0.1",
"aurelia-bootstrapper": "^2.0.1", "aurelia-bootstrapper": "^2.0.1",
"aurelia-fetch-client": "^1.0.1", "aurelia-fetch-client": "^1.0.1",
"aurelia-framework": "^1.1.0", "aurelia-framework": "^1.1.0",
"aurelia-loader-webpack": "^2.0.0", "aurelia-loader-webpack": "^2.0.0",
"aurelia-pal": "^1.3.0", "aurelia-pal": "^1.3.0",
"aurelia-router": "^1.2.1", "aurelia-router": "^1.2.1",
"bootstrap": "^3.3.7",
"isomorphic-fetch": "^2.2.1",
"jquery": "^3.2.1"
},
"devDependencies": {
"@types/node": "^7.0.12",
"aspnet-webpack": "^1.0.28",
"aurelia-webpack-plugin": "^2.0.0-rc.2", "aurelia-webpack-plugin": "^2.0.0-rc.2",
"bootstrap": "^3.3.7",
"css-loader": "^0.28.0", "css-loader": "^0.28.0",
"extract-text-webpack-plugin": "^2.1.0", "extract-text-webpack-plugin": "^2.1.0",
"file-loader": "^0.11.1", "file-loader": "^0.11.1",
"html-loader": "^0.4.5", "html-loader": "^0.4.5",
"isomorphic-fetch": "^2.2.1",
"jquery": "^3.2.1",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"style-loader": "^0.16.1", "style-loader": "^0.16.1",
"ts-loader": "^2.0.3", "ts-loader": "^2.0.3",

View File

@@ -1,13 +1,15 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "es2015",
"moduleResolution": "node", "moduleResolution": "node",
"target": "es5", "target": "es5",
"sourceMap": true, "sourceMap": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"skipDefaultLibCheck": true, "skipDefaultLibCheck": true,
"strict": true,
"lib": [ "es2015", "dom" ], "lib": [ "es2015", "dom" ],
"types": [ "node" ] "types": [ "webpack-env" ]
}, },
"exclude": [ "bin", "node_modules" ], "exclude": [ "bin", "node_modules" ],
"atom": { "rewriteTsconfig": false } "atom": { "rewriteTsconfig": false }

View File

@@ -14,7 +14,7 @@ module.exports = (env) => {
}, },
output: { output: {
path: path.resolve(bundleOutputDir), path: path.resolve(bundleOutputDir),
publicPath: '/dist/', publicPath: 'dist/',
filename: '[name].js' filename: '[name].js'
}, },
module: { module: {

View File

@@ -38,7 +38,7 @@ module.exports = ({ prod } = {}) => {
}, },
output: { output: {
path: path.join(__dirname, 'wwwroot', 'dist'), path: path.join(__dirname, 'wwwroot', 'dist'),
publicPath: '/dist/', publicPath: 'dist/',
filename: '[name].js', filename: '[name].js',
library: '[name]_[hash]', library: '[name]_[hash]',
}, },

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -0,0 +1,17 @@
{
"$schema": "http://json.schemastore.org/dotnetcli.host",
"symbolInfo": {
"TargetFrameworkOverride": {
"isHidden": "true",
"longName": "target-framework-override",
"shortName": ""
},
"Framework": {
"longName": "framework"
},
"skipRestore": {
"longName": "no-restore",
"shortName": ""
}
}
}

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,87 @@
{
"author": "Microsoft",
"classifications": [
"Web",
"MVC",
"SPA"
],
"groupIdentity": "Microsoft.AspNetCore.SpaTemplates.Knockout",
"identity": "Microsoft.AspNetCore.SpaTemplates.Knockout.CSharp",
"name": "ASP.NET Core with Knockout.js",
"preferNameDirectory": true,
"primaryOutputs": [
{
"path": "KnockoutSpa.csproj"
}
],
"shortName": "knockout",
"sourceName": "KnockoutSpa",
"sources": [
{
"source": "./",
"target": "./",
"exclude": [
".template.config/**"
]
}
],
"symbols": {
"TargetFrameworkOverride": {
"type": "parameter",
"description": "Overrides the target framework",
"replaces": "TargetFrameworkOverride",
"datatype": "string",
"defaultValue": ""
},
"Framework": {
"type": "parameter",
"description": "The target framework for the project.",
"datatype": "choice",
"choices": [
{
"choice": "netcoreapp2.0",
"description": "Target netcoreapp2.0"
}
],
"replaces": "netcoreapp2.0",
"defaultValue": "netcoreapp2.0"
},
"HostIdentifier": {
"type": "bind",
"binding": "HostIdentifier"
},
"skipRestore": {
"type": "parameter",
"datatype": "bool",
"description": "If specified, skips the automatic restore of the project on create.",
"defaultValue": "false"
}
},
"tags": {
"language": "C#",
"type": "project"
},
"postActions": [
{
"condition": "(!skipRestore)",
"description": "Restore NuGet packages required by this project.",
"manualInstructions": [
{
"text": "Run 'dotnet restore'"
}
],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
},
{
"condition": "(HostIdentifier == \"dotnetcli\" || HostIdentifier == \"dotnetcli-preview\")",
"actionId": "AC1156F7-BB77-4DB8-B28F-24EEBCCA1E5C",
"description": "\n\n-------------------------------------------------------------------\nIMPORTANT: Before running this project on the command line,\n you must restore NPM packages by running \"npm install\"\n-------------------------------------------------------------------\n",
"manualInstructions": [
{
"text": "Run \"npm install\""
}
]
}
]
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "http://json.schemastore.org/vs-2017.3.host",
"name": {
"text": "Knockout.js",
"package": "{0CD94836-1526-4E85-87D3-FB5274C5AFC9}",
"id": "1300"
},
"description": {
"text": "A project template for creating an ASP.NET Core application with Knockout.js",
"package": "{0CD94836-1526-4E85-87D3-FB5274C5AFC9}",
"id": "1301"
},
"order": 301,
"icon": "icon.png",
"learnMoreLink": "https://github.com/aspnet/JavaScriptServices",
"uiFilters": [
"oneaspnet"
],
"minFullFrameworkVersion": "4.6.1"
}

View File

@@ -1,19 +1,20 @@
import './css/site.css'; import './css/site.css';
import 'bootstrap'; import 'bootstrap';
import * as ko from 'knockout'; import * as ko from 'knockout';
import { createBrowserHistory } from 'history';
import './webpack-component-loader'; import './webpack-component-loader';
import AppRootComponent from './components/app-root/app-root'; 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 <app-root> component // Load and register the <app-root> component
ko.components.register('app-root', AppRootComponent); ko.components.register('app-root', AppRootComponent);
// Tell Knockout to start up an instance of your application // Tell Knockout to start up an instance of your application
ko.applyBindings({ history: createHistory() }); ko.applyBindings({ history: createBrowserHistory({ basename }), basename });
// Basic hot reloading support. Automatically reloads and restarts the Knockout app each time // 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. // you modify source files. This will not preserve any application state other than the URL.
declare var module: any;
if (module.hot) { if (module.hot) {
module.hot.accept(); module.hot.accept();
module.hot.dispose(() => ko.cleanNode(document.body)); module.hot.dispose(() => ko.cleanNode(document.body));

View File

@@ -1,7 +1,7 @@
<div class='container-fluid'> <div class='container-fluid'>
<div class='row'> <div class='row'>
<div class='col-sm-3'> <div class='col-sm-3'>
<nav-menu params='route: route'></nav-menu> <nav-menu params='router: router'></nav-menu>
</div> </div>
<div class='col-sm-9' data-bind='component: { name: route().page, params: route }'></div> <div class='col-sm-9' data-bind='component: { name: route().page, params: route }'></div>
</div> </div>

Some files were not shown because too many files have changed in this diff Show More