Compare commits

..

132 Commits

Author SHA1 Message Date
Nate McMaster
09ae669370 Bump version to 2.0.4 2018-04-17 14:14:39 -07:00
Nate McMaster
44d39bc0d4 Bump version, update build tools, and dependency versions 2018-01-18 16:29:08 -08:00
Nate McMaster
79fd6debad Bump version to 2.0.3 2018-01-10 14:56:13 -08:00
Nate McMaster
d5a664e481 Bump version to 2.0.2 2017-12-06 16:20:14 -08:00
Nate McMaster
64389a9bbe Update build tools to 2.0.2-rc1-15526 and dependencies to 2.0.1-rtm-105 2017-10-13 13:11:55 -07:00
Nate McMaster
86e94d7812 Update how PackageReference versions are set
Changes:
 - Remove floating versions
 - Disable myget feeds during a Universe build
 - Use package-specific MSBuild variables. Pattern = `packageId.Pascalize() + "PackageVersion"`, with a few exceptions.
2017-10-09 11:10:58 -07:00
Nate McMaster
9f05a3d34b Use MSBuild to set NuGet feeds instead of NuGet.config 2017-10-02 14:12:55 -07:00
Nate McMaster
63e0af2ee8 Import dependencies.props last to ensure TargetFramework is set first 2017-09-29 17:02:10 -07:00
Nate McMaster
dc5e980efa Update build scripts, tools, and dependencies for 2.0.x 2017-09-20 17:23:18 -07:00
Nate McMaster
e0ab3ddcca Update the list of packages patching in 2.0.x 2017-09-20 13:40:18 -07:00
Ryan Brandenburg
0c058894c2 Update bootstrappers 2017-09-19 14:44:54 -07:00
Ryan Brandenburg
98385cbcb0 Update bootstrappers 2017-09-19 14:44:54 -07:00
Nate McMaster
77cac3b6be Update package feeds and dependencies for 2.0.1 (#1284) 2017-09-18 12:42:26 -07:00
Nate McMaster
051150475f Bump version to 2.0.1 2017-09-15 18:00:22 -07:00
Pranav K
128683be0e Pinning versions for 2.0.0 2017-08-17 15:00:10 -07:00
Steve Sanderson
576e3debf1 Complete the lstat patching for #1101 2017-07-12 15:27:00 +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
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
Pranav K
c94563c700 Updating KoreBuild branch 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
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
8352ec7626 Add npm-shrinkwrap.json files for all templates 2017-06-26 15:48:49 +01:00
Steve Sanderson
bebb82eb69 Implement longer-term fix for #1066 (see description)
- declare explict @types/react-router version
- update to newer aspnet-webpack-react that doesn't include the hacky
workaround since it's not needed if you have the correct
@types/react-router version

In a subsequent commit, will also shrinkwrap to pin all dependency
versions. This isn't strictly required right now, but is the right thing
for long-term stability.
2017-06-26 15:39:19 +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
328eb0451b Remove NETStandard.Library.NETFramework from the templates 2017-06-22 16:39:35 +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
Steve Sanderson
c128fa6e06 Switch NuGet config to use preview2-final feed. This change needs to be reverted when merged back to dev branch. 2017-06-16 13:14:05 +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
Mike Harder
3ef23ece23 Changed all references of PackageTargetFallback to AssetTargetFallback (#1035) 2017-06-14 15:47:11 -07:00
Steve Sanderson
90922fb3c6 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-13 22:06:55 +01:00
Steve Sanderson
68e84bac3f Update templates' ASP.NET dependencies to 2.0.0-preview2-final 2017-06-13 22:05:19 +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
a38d3bdc4b Change NETStandard.Library.NETFramework reference to preview2-25405-01 for consistency with stock Web templates 2017-06-13 09:46: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
8e5f255346 Update templates to reference preview2-25661 2017-06-13 09:37:40 +01:00
Steve Sanderson
67ba6271a0 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-11 20:33:23 +01:00
Steve Sanderson
a3bcc0d863 Update debugger detection to support Node v8.1+, and stop aborting if debugger messages are unrecognised. 2017-06-11 20:28:14 +01:00
Steve Sanderson
5e7bb0f5c5 Don't log OperationCanceledException every time an HMR client disconnects 2017-06-11 19:57:26 +01:00
Steve Sanderson
8396be24c8 Fix VS2017.3 warning about not specifying TypeScriptToolsVersion 2017-06-11 19:39:46 +01:00
Steve Sanderson
728b18431d In dotnet new templates, only show "npm install" instruction when executing in CLI 2017-06-11 19:28:51 +01:00
Steve Sanderson
32d9bdee6c Merge branch 'rel/2.0.0-preview2' into dev 2017-06-10 14:36:36 +01:00
Steve Sanderson
9a59bccad9 If Node isn't installed, give clear instructions rather than random build errors 2017-06-10 14:11:20 +01:00
Steve Sanderson
e50d4825c3 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-08 11:44:53 +01:00
Steve Sanderson
bf6d2227db In dotnet new templates, display instruction to run "npm install" manually 2017-06-08 11:43:38 +01:00
Steve Sanderson
06c94d54eb Merge branch 'rel/2.0.0-preview2' into dev 2017-06-08 11:15:48 +01:00
danobri
bcaa3af92e Re-add bootstrap import
In the update to Angular 4, the bootstrap import got removed from boot.client.ts, causing the navbar-toggle menu to longer work in the template.  Re-adding so navbar-toggle is functional again.
2017-06-08 11:14:18 +01:00
Steve Sanderson
3685592b01 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-08 11:09:51 +01:00
Steve Sanderson
f3b7103c83 In AngularSpa template, redefine app.module.shared as a real NgModule. Fixes #986. 2017-06-08 11:08:36 +01:00
Steve Sanderson
2acdad38cd Merge branch 'rel/2.0.0-preview2' into dev 2017-06-08 10:40:05 +01:00
Steve Sanderson
0dd24068ca Update VS template name localisation IDs and texts as instructed 2017-06-08 10:38:00 +01:00
Steve Sanderson
0526852e05 Pin templates' .NET reference version numbers on Coherence 25571 in same way as stock Web templates do. This also fixes net461 support. 2017-06-08 10:33:27 +01:00
Steve Sanderson
e331355c1d Merge branch 'rel/2.0.0-preview2' into dev 2017-06-07 22:57:37 +01:00
Steve Sanderson
60c8a3471d Enable localisation for template names/descriptions in VS 2017-06-07 22:53:55 +01:00
Steve Sanderson
c7b7f40d2c Update template package to support generating net461 projects as well as netcoreapp2.0 2017-06-07 22:27:43 +01:00
Steve Sanderson
d8c0bd8898 Merge branch 'rel/2.0.0-preview2' into dev 2017-06-07 15:02:29 +01:00
Steve Sanderson
5127a74d3f Clean up dependencies.props 2017-06-07 14:59:00 +01:00
Steve Sanderson
e38399fbf4 Remove redundant explicit SpaServices references from templates now it's on the .All metapackage anyway 2017-06-07 14:58:54 +01:00
Steve Sanderson
045c05fc88 Update samples to target both netcoreapp2.0 and net461 2017-06-07 14:58:49 +01:00
Steve Sanderson
b444831c8d Remove System.Runtime.Loader dependency to enable net461 support. Use IApplicationLifetime instead of AssemblyLoadContext.Default.Unloading. 2017-06-07 14:58:44 +01:00
Steve Sanderson
2aaceaa9f8 Move core packages to netstandard2.0 2017-06-07 14:58:33 +01:00
Steve Sanderson
1c5bd98d8a Stop doing NPM restores during CI builds, as the result isn't used anyway 2017-06-07 14:58:28 +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
dbdc81a06a Updating build scripts to point to 2.0.0-preview2 KoreBuild 2017-05-31 19:53:23 -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
Pranav K
8a1d64cc73 Branching for rel/2.0.0-preview2 2017-05-31 19:36:47 -07:00
Kiran Challa
087183ea18 Updated to use the latest shared runtime 2017-05-29 04:40:44 -07:00
Pranav K
9b131ea7d3 Remove unused usings 2017-05-27 18:58:17 -07:00
Steve Sanderson
75fd215b1a Change Microsoft.DotNet.Web.Spa.ProjectTemplates to use 1.0.0-preview-* version numbers since the first release will be 1.0.0 2017-05-25 13:06:42 +01:00
Steve Sanderson
38997c3f3f Pad build numbers so that alpha-sort produces correct ordering 2017-05-25 12:22:48 +01:00
Steve Sanderson
bee4c6ff11 Stop "dotnet new" from stripping out important parts of .csproj files 2017-05-25 12:08:10 +01:00
Steve Sanderson
cc859306a3 Make template package version numbers more descriptive 2017-05-25 12:08:09 +01:00
Steve Sanderson
44512226c6 Stop including prebuilt .js bundles with template packages, since they are now generated on first build 2017-05-25 12:08:09 +01:00
Steve Sanderson
a2bde750be Make templates auto-run webpack on Debug build if wwwroot/dist is not already present 2017-05-25 12:08:09 +01:00
Thomas Hermann
c592282646 Fix Node debugging by catching warning 2017-05-24 17:32:01 +01:00
Steve Sanderson
b86e9f9254 Change template description to be consistent with others in 2.0 2017-05-24 15:10:37 +01:00
Steve Sanderson
970dc6c457 Add --silent to npm install command in CI. Fixes #972 (at least, to the extent that fixing it is reasonably possible) 2017-05-24 15:01:00 +01:00
Steve Sanderson
6baa2faf09 Split out built-in templates into new package Microsoft.DotNet.Web.Spa.ProjectTemplates 2017-05-24 14:47:23 +01:00
Steve Sanderson
918e7edbff Remove global.json files from templates for consistency with stock MVC template. 2017-05-23 17:05:42 +01:00
Steve Sanderson
e1c90b3601 Change "dotnet new" config choices to reference netcoreapp2.0 (not that it makes a difference when it's the only choice) 2017-05-23 11:20:04 +01:00
Steve Sanderson
b9e62cd4c9 Fix "dotnet new" postAction issues (and disable the "npm install" action) 2017-05-23 11:18:51 +01:00
Steve Sanderson
ad9cfbb176 Make quotes consistent 2017-05-23 10:08:41 +01:00
Steve Sanderson
941ae9a34d Add postActions to "dotnet new" templates 2017-05-23 10:06:35 +01:00
Ryan Brandenburg
3645ae6cf2 Generate Documentation files 2017-05-22 10:54:25 +01:00
101 changed files with 19230 additions and 625 deletions

2
.gitignore vendored
View File

@@ -28,6 +28,8 @@ nuget.exe
.vs/ .vs/
npm-debug.log npm-debug.log
/.build/ /.build/
global.json
*.g.targets
# The templates can't contain their own .gitignore files, because Yeoman has strange default handling for # 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 # files with that name (https://github.com/npm/npm/issues/1862). So, each template instead has a template_gitignore

9
Directory.Build.props Normal file
View File

@@ -0,0 +1,9 @@
<Project>
<Import Project="version.props" />
<Import Project="build\dependencies.props" />
<Import Project="build\sources.props" />
<PropertyGroup>
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
</PropertyGroup>
</Project>

6
Directory.Build.targets Normal file
View File

@@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
</PropertyGroup>
</Project>

View File

@@ -1,10 +1,12 @@
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
Unless required by applicable law or agreed to in writing, software distributed 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 under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR

View File

@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json" /> <clear />
<add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" /> <!-- Restore sources should be defined in build/sources.props. -->
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>

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

@@ -3,11 +3,11 @@ init:
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-006729/dotnet-sdk-2.0.0-preview3-006729-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"

219
build.ps1
View File

@@ -1,67 +1,186 @@
$ErrorActionPreference = "Stop" #!/usr/bin/env powershell
#requires -version 4
function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries) <#
.SYNOPSIS
Build this repository
.DESCRIPTION
Downloads korebuild if required. Then builds the repository.
.PARAMETER Path
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.props.
.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 JSON file. It is optional, and the configuration values in it are optional as well. Any options set
in the file are overridden by command line parameters.
.EXAMPLE
Example config file:
```json
{ {
while($true) "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json",
{ "channel": "dev",
try "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools"
{
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
}
else
{
$exception = $_.Exception
throw $exception
}
}
}
} }
```
#>
[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 = $null,
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$MSBuildArgs
)
cd $PSScriptRoot Set-StrictMode -Version 2
$ErrorActionPreference = 'Stop'
$repoFolder = $PSScriptRoot #
$env:REPO_FOLDER = $repoFolder # Functions
#
$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" function Get-KoreBuild {
if ($env:KOREBUILD_ZIP)
{
$koreBuildZip=$env:KOREBUILD_ZIP
}
$buildFolder = ".build" $lockFile = Join-Path $Path 'korebuild-lock.txt'
$buildFile="$buildFolder\KoreBuild.ps1"
if (!(Test-Path $buildFolder)) { if (!(Test-Path $lockFile) -or $Update) {
Write-Host "Downloading KoreBuild from $koreBuildZip" Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile
}
$tempFolder=$env:TEMP + "\KoreBuild-" + [guid]::NewGuid() $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1
New-Item -Path "$tempFolder" -Type directory | Out-Null if (!$version) {
Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'"
}
$version = $version.TrimStart('version:').Trim()
$korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version)
$localZipFile="$tempFolder\korebuild.zip" if (!(Test-Path $korebuildPath)) {
Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version"
DownloadWithRetry -url $koreBuildZip -downloadLocation $localZipFile -retries 6 New-Item -ItemType Directory -Path $korebuildPath | Out-Null
$remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip"
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)
}
}
catch {
Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore
throw
}
finally {
Remove-Item $tmpfile -ErrorAction Ignore
}
}
New-Item -Path "$buildFolder" -Type directory | Out-Null return $korebuildPath
copy-item "$tempFolder\**\build\*" $buildFolder -Recurse }
# Cleanup function Join-Paths([string]$path, [string[]]$childPaths) {
if (Test-Path $tempFolder) { $childPaths | ForEach-Object { $path = Join-Path $path $_ }
Remove-Item -Recurse -Force $tempFolder 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
$Path = Resolve-Path $Path
if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' }
if (Test-Path $ConfigFile) {
try {
$config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json
if ($config) {
if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel }
if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource}
}
} catch {
Write-Warning "$ConfigFile could not be read. Its settings will be ignored."
Write-Warning $Error[0]
} }
} }
&"$buildFile" @args 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
}

242
build.sh
View File

@@ -1,46 +1,220 @@
#!/usr/bin/env bash #!/usr/bin/env bash
repoFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $repoFolder
koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" set -euo pipefail
if [ ! -z $KOREBUILD_ZIP ]; then
koreBuildZip=$KOREBUILD_ZIP
fi
buildFolder=".build" #
buildFile="$buildFolder/KoreBuild.sh" # variables
#
if test ! -d $buildFolder; then RESET="\033[0m"
echo "Downloading KoreBuild from $koreBuildZip" RED="\033[0;31m"
YELLOW="\033[0;33m"
MAGENTA="\033[0;95m"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
[ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet"
config_file="$DIR/korebuild.json"
verbose=false
update=false
repo_path="$DIR"
channel=''
tools_source=''
tempFolder="/tmp/KoreBuild-$(uuidgen)" #
mkdir $tempFolder # 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> The path to the configuration file that stores values. Defaults to korebuild.json."
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."
localZipFile="$tempFolder/korebuild.zip" if [[ "${1:-}" != '--no-exit' ]]; then
exit 2
fi
}
retries=6 get_korebuild() {
until (wget -O $localZipFile $koreBuildZip 2>/dev/null || curl -o $localZipFile --location $koreBuildZip 2>/dev/null) local version
do local lock_file="$repo_path/korebuild-lock.txt"
echo "Failed to download '$koreBuildZip'" if [ ! -f "$lock_file" ] || [ "$update" = true ]; then
if [ "$retries" -le 0 ]; 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
source "$korebuild_path/KoreBuild.sh"
} || {
if [ -d "$korebuild_path" ]; then
echo "Cleaning up after failed installation"
rm -rf "$korebuild_path" || true
fi
return 1
}
}
__error() {
echo -e "${RED}error: $*${RESET}" 1>&2
}
__warn() {
echo -e "${YELLOW}warning: $*${RESET}"
}
__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
}
#
# 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
if [ ! -f "$config_file" ]; then
__error "Invalid value for --config-file. $config_file does not exist."
exit 1 exit 1
fi fi
retries=$((retries - 1)) ;;
echo "Waiting 10 seconds before retrying. Retries left: $retries" -d|--dotnet-home|-DotNetHome)
sleep 10s shift
done 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
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
[ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json"
if [ -f "$config_file" ]; then
if __machine_has jq ; then
if jq '.' "$config_file" >/dev/null ; then
config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")"
config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")"
else
__warn "$config_file is invalid JSON. Its settings will be ignored."
fi
elif __machine_has python ; then
if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then
config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")"
config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")"
else
__warn "$config_file is invalid JSON. Its settings will be ignored."
fi
else
__warn 'Missing required command: jq or pyton. Could not parse the JSON file. Its settings will be ignored.'
fi
[ ! -z "${config_channel:-}" ] && channel="$config_channel"
[ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source"
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,4 @@
<Project> <Project>
<Import Project="dependencies.props" />
<Import Project="..\version.props" />
<PropertyGroup> <PropertyGroup>
<Product>Microsoft ASP.NET Core</Product> <Product>Microsoft ASP.NET Core</Product>
@@ -9,14 +7,11 @@
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)Key.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)Key.snk</AssemblyOriginatorKeyFile>
<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> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Internal.AspNetCore.Sdk" Version="$(InternalAspNetCoreSdkVersion)" PrivateAssets="All" /> <PackageReference Include="Internal.AspNetCore.Sdk" Version="$(InternalAspNetCoreSdkPackageVersion)" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)'=='.NETFramework' AND '$(OutputType)'=='library'">
<PackageReference Include="NETStandard.Library" Version="$(BundledNETStandardPackageVersion)" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,11 +1,23 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<AspNetCoreVersion>2.0.0-*</AspNetCoreVersion> <InternalAspNetCoreSdkPackageVersion>2.0.2-rc1-15526</InternalAspNetCoreSdkPackageVersion>
<AutoMapperVersion>5.0.2</AutoMapperVersion> <MicrosoftAspNetCoreDiagnosticsPackageVersion>2.0.1-rtm-105</MicrosoftAspNetCoreDiagnosticsPackageVersion>
<CoreFxVersion>4.3.0</CoreFxVersion> <MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.0.1-rtm-105</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<InternalAspNetCoreSdkVersion>2.1.0-*</InternalAspNetCoreSdkVersion> <MicrosoftAspNetCoreHostingPackageVersion>2.0.1-rtm-105</MicrosoftAspNetCoreHostingPackageVersion>
<JsonNetVersion>10.0.1</JsonNetVersion> <MicrosoftAspNetCoreMvcPackageVersion>2.0.1-rtm-105</MicrosoftAspNetCoreMvcPackageVersion>
<NETStandardImplicitPackageVersion>$(BundledNETStandardPackageVersion)</NETStandardImplicitPackageVersion> <MicrosoftAspNetCoreMvcTagHelpersPackageVersion>2.0.1-rtm-105</MicrosoftAspNetCoreMvcTagHelpersPackageVersion>
<ThreadingDataflowVersion>4.7.0</ThreadingDataflowVersion> <MicrosoftAspNetCoreMvcViewFeaturesPackageVersion>2.0.1-rtm-105</MicrosoftAspNetCoreMvcViewFeaturesPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.0.1-rtm-105</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.0.1-rtm-105</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.0.1-rtm-105</MicrosoftAspNetCoreStaticFilesPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.0.0</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.0.0</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingDebugPackageVersion>2.0.0</MicrosoftExtensionsLoggingDebugPackageVersion>
<NewtonsoftJsonRuntimePackageVersion>10.0.1</NewtonsoftJsonRuntimePackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.5</MicrosoftNETCoreApp20PackageVersion>
<NETStandardLibrary20PackageVersion>2.0.1</NETStandardLibrary20PackageVersion>
<SystemThreadingTasksDataflowPackageVersion>4.8.0</SystemThreadingTasksDataflowPackageVersion>
</PropertyGroup> </PropertyGroup>
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
</Project> </Project>

View File

@@ -1,12 +0,0 @@
<Project>
<Target Name="NpmRestore" AfterTargets="Restore" Condition="'$(PreflightRestore)' != 'true'">
<ItemGroup>
<NpmModules Include="$(RepositoryRoot)**\package.json"
Exclude="$(RepositoryRoot)**\node_modules\**\*" />
</ItemGroup>
<Message Text="Restoring NPM modules for: %0A - @(NpmModules -> '%(FullPath)','%0A - ')" Importance="high" />
<Exec Command="npm install" WorkingDirectory="%(NpmModules.RootDir)%(Directory)" />
</Target>
</Project>

17
build/sources.props Normal file
View File

@@ -0,0 +1,17 @@
<Project>
<Import Project="$(DotNetRestoreSourcePropsPath)" Condition="'$(DotNetRestoreSourcePropsPath)' != ''"/>
<PropertyGroup>
<RestoreSources>$(DotNetRestoreSources)</RestoreSources>
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true' AND '$(AspNetUniverseBuildOffline)' != 'true' ">
$(RestoreSources);
https://dotnet.myget.org/F/aspnet-2-0-2-october2017-patch/api/v3/index.json;
https://dotnet.myget.org/F/aspnetcore-master/api/v3/index.json;
https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
</RestoreSources>
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
$(RestoreSources);
https://api.nuget.org/v3/index.json;
</RestoreSources>
</PropertyGroup>
</Project>

2
korebuild-lock.txt Normal file
View File

@@ -0,0 +1,2 @@
version:2.0.5-rtm-10016
commithash:02bda79ac9c564229da734a836f258d6c1321eb7

4
korebuild.json Normal file
View File

@@ -0,0 +1,4 @@
{
"$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.0/tools/korebuild.schema.json",
"channel": "release/2.0"
}

View File

@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\build\dependencies.props" /> <Import Project="..\..\..\build\common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
</PropertyGroup> </PropertyGroup>
@@ -14,7 +14,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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

@@ -1,25 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\build\dependencies.props" /> <Import Project="..\..\..\build\common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</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>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(MicrosoftAspNetCoreMvcPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
</ItemGroup> </ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish"> <Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">

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

@@ -27,6 +27,7 @@ namespace Webpack.ActionResults
{ {
var nodeServices = context.HttpContext.RequestServices.GetRequiredService<INodeServices>(); var nodeServices = context.HttpContext.RequestServices.GetRequiredService<INodeServices>();
var hostEnv = context.HttpContext.RequestServices.GetRequiredService<IHostingEnvironment>(); var hostEnv = context.HttpContext.RequestServices.GetRequiredService<IHostingEnvironment>();
var applicationLifetime = context.HttpContext.RequestServices.GetRequiredService<IApplicationLifetime>();
var applicationBasePath = hostEnv.ContentRootPath; var applicationBasePath = hostEnv.ContentRootPath;
var request = context.HttpContext.Request; var request = context.HttpContext.Request;
var response = context.HttpContext.Response; var response = context.HttpContext.Response;
@@ -34,6 +35,7 @@ namespace Webpack.ActionResults
var prerenderedHtml = await Prerenderer.RenderToString( var prerenderedHtml = await Prerenderer.RenderToString(
applicationBasePath, applicationBasePath,
nodeServices, nodeServices,
applicationLifetime.ApplicationStopping,
_moduleExport, _moduleExport,
request.GetEncodedUrl(), request.GetEncodedUrl(),
request.Path + request.QueryString.Value, request.Path + request.QueryString.Value,

View File

@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\build\dependencies.props" /> <Import Project="..\..\..\build\common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
@@ -13,13 +13,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(MicrosoftAspNetCoreMvcPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
</ItemGroup> </ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish"> <Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">

View File

@@ -4,26 +4,19 @@
<PropertyGroup> <PropertyGroup>
<Description>Socket-based RPC for Microsoft.AspNetCore.NodeServices.</Description> <Description>Socket-based RPC for Microsoft.AspNetCore.NodeServices.</Description>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags> <PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="node_modules\**\*" />
<EmbeddedResource Include="Content\**\*" /> <EmbeddedResource Include="Content\**\*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" /> <ProjectReference Include="..\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
</ItemGroup> <PackageReference Include="System.Threading.Tasks.Dataflow" Version="$(SystemThreadingTasksDataflowPackageVersion)" />
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
<PackageReference Include="Microsoft.Tpl.Dataflow" Version="$(MicrosoftDataflowVersion)" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">
<PackageReference Include="System.IO.Pipes" Version="$(CoreFxVersion)" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="$(ThreadingDataflowVersion)" />
</ItemGroup> </ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' "> <Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">

View File

@@ -18,11 +18,7 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
PipeDirection.InOut, PipeDirection.InOut,
PipeOptions.Asynchronous); PipeOptions.Asynchronous);
#if NET451
_namedPipeClientStream.Connect();
#else
await _namedPipeClientStream.ConnectAsync().ConfigureAwait(false); await _namedPipeClientStream.ConnectAsync().ConfigureAwait(false);
#endif
return _namedPipeClientStream; return _namedPipeClientStream;
} }

View File

@@ -11,9 +11,6 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
public static StreamConnection Create() public static StreamConnection Create()
{ {
#if NET451
return new NamedPipeConnection();
#else
var useNamedPipes = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( var useNamedPipes = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
System.Runtime.InteropServices.OSPlatform.Windows); System.Runtime.InteropServices.OSPlatform.Windows);
if (useNamedPipes) if (useNamedPipes)
@@ -24,7 +21,6 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
{ {
return new UnixDomainSocketConnection(); return new UnixDomainSocketConnection();
} }
#endif
} }
} }
} }

View File

@@ -10,16 +10,6 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
private NetworkStream _networkStream; private NetworkStream _networkStream;
private Socket _socket; private Socket _socket;
#if NET451
public override Task<Stream> Open(string address)
{
// The 'null' assignments avoid the compiler warnings about unassigned fields.
// Note that this whole class isn't supported on .NET 4.5.1, since that's not cross-platform.
_networkStream = null;
_socket = null;
throw new System.PlatformNotSupportedException();
}
#else
public override async Task<Stream> Open(string address) public override async Task<Stream> Open(string address)
{ {
var endPoint = new UnixDomainSocketEndPoint("/tmp/" + address); var endPoint = new UnixDomainSocketEndPoint("/tmp/" + address);
@@ -28,7 +18,6 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
_networkStream = new NetworkStream(_socket); _networkStream = new NetworkStream(_socket);
return _networkStream; return _networkStream;
} }
#endif
public override void Dispose() public override void Dispose()
{ {

View File

@@ -59,11 +59,7 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
} }
else else
{ {
#if NET451
_encodedPath = new byte[0];
#else
_encodedPath = Array.Empty<byte>(); _encodedPath = Array.Empty<byte>();
#endif
_path = string.Empty; _path = string.Empty;
} }
} }

View File

@@ -49,6 +49,7 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets
options.ProjectPath, options.ProjectPath,
options.WatchFileExtensions, options.WatchFileExtensions,
MakeNewCommandLineOptions(socketAddress), MakeNewCommandLineOptions(socketAddress),
options.ApplicationStoppingToken,
options.NodeInstanceOutputLogger, options.NodeInstanceOutputLogger,
options.EnvironmentVariables, options.EnvironmentVariables,
options.InvocationTimeoutMilliseconds, options.InvocationTimeoutMilliseconds,

View File

@@ -12,11 +12,7 @@ namespace Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections
/// </summary> /// </summary>
internal class VirtualConnection : Stream internal class VirtualConnection : Stream
{ {
#if NET451
private readonly static Task CompletedTask = Task.FromResult((object)null);
#else
private readonly static Task CompletedTask = Task.CompletedTask; private readonly static Task CompletedTask = Task.CompletedTask;
#endif
private VirtualConnectionClient _host; private VirtualConnectionClient _host;
private readonly BufferBlock<byte[]> _receivedDataQueue = new BufferBlock<byte[]>(); private readonly BufferBlock<byte[]> _receivedDataQueue = new BufferBlock<byte[]>();
private ArraySegment<byte> _receivedDataNotYetUsed; private ArraySegment<byte> _receivedDataNotYetUsed;

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using Microsoft.AspNetCore.NodeServices.HostingModels; using Microsoft.AspNetCore.NodeServices.HostingModels;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -42,6 +43,12 @@ namespace Microsoft.AspNetCore.NodeServices
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
} }
var applicationLifetime = serviceProvider.GetService<IApplicationLifetime>();
if (applicationLifetime != null)
{
ApplicationStoppingToken = applicationLifetime.ApplicationStopping;
}
// If the DI system gives us a logger, use it. Otherwise, set up a default one. // If the DI system gives us a logger, use it. Otherwise, set up a default one.
var loggerFactory = serviceProvider.GetService<ILoggerFactory>(); var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
NodeInstanceOutputLogger = loggerFactory != null NodeInstanceOutputLogger = loggerFactory != null
@@ -93,5 +100,10 @@ namespace Microsoft.AspNetCore.NodeServices
/// Specifies the maximum duration, in milliseconds, that your .NET code should wait for Node.js RPC calls to return. /// Specifies the maximum duration, in milliseconds, that your .NET code should wait for Node.js RPC calls to return.
/// </summary> /// </summary>
public int InvocationTimeoutMilliseconds { get; set; } public int InvocationTimeoutMilliseconds { get; set; }
/// <summary>
/// A token that indicates when the host application is stopping.
/// </summary>
public CancellationToken ApplicationStoppingToken { get; set; }
} }
} }

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

@@ -42,6 +42,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
options.ProjectPath, options.ProjectPath,
options.WatchFileExtensions, options.WatchFileExtensions,
MakeCommandLineOptions(port), MakeCommandLineOptions(port),
options.ApplicationStoppingToken,
options.NodeInstanceOutputLogger, options.NodeInstanceOutputLogger,
options.EnvironmentVariables, options.EnvironmentVariables,
options.InvocationTimeoutMilliseconds, options.InvocationTimeoutMilliseconds,
@@ -66,8 +67,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;
@@ -135,5 +138,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

@@ -45,6 +45,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
/// <param name="projectPath">The root path of the current project. This is used when resolving Node.js module paths relative to the project root.</param> /// <param name="projectPath">The root path of the current project. This is used when resolving Node.js module paths relative to the project root.</param>
/// <param name="watchFileExtensions">The filename extensions that should be watched within the project root. The Node instance will automatically shut itself down if any matching file changes.</param> /// <param name="watchFileExtensions">The filename extensions that should be watched within the project root. The Node instance will automatically shut itself down if any matching file changes.</param>
/// <param name="commandLineArguments">Additional command-line arguments to be passed to the Node.js instance.</param> /// <param name="commandLineArguments">Additional command-line arguments to be passed to the Node.js instance.</param>
/// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
/// <param name="nodeOutputLogger">The <see cref="ILogger"/> to which the Node.js instance's stdout/stderr (and other log information) should be written.</param> /// <param name="nodeOutputLogger">The <see cref="ILogger"/> to which the Node.js instance's stdout/stderr (and other log information) should be written.</param>
/// <param name="environmentVars">Environment variables to be set on the Node.js process.</param> /// <param name="environmentVars">Environment variables to be set on the Node.js process.</param>
/// <param name="invocationTimeoutMilliseconds">The maximum duration, in milliseconds, to wait for RPC calls to complete.</param> /// <param name="invocationTimeoutMilliseconds">The maximum duration, in milliseconds, to wait for RPC calls to complete.</param>
@@ -55,6 +56,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
string projectPath, string projectPath,
string[] watchFileExtensions, string[] watchFileExtensions,
string commandLineArguments, string commandLineArguments,
CancellationToken applicationStoppingToken,
ILogger nodeOutputLogger, ILogger nodeOutputLogger,
IDictionary<string, string> environmentVars, IDictionary<string, string> environmentVars,
int invocationTimeoutMilliseconds, int invocationTimeoutMilliseconds,
@@ -67,7 +69,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
} }
OutputLogger = nodeOutputLogger; OutputLogger = nodeOutputLogger;
_entryPointScript = new StringAsTempFile(entryPointScript); _entryPointScript = new StringAsTempFile(entryPointScript, applicationStoppingToken);
_invocationTimeoutMilliseconds = invocationTimeoutMilliseconds; _invocationTimeoutMilliseconds = invocationTimeoutMilliseconds;
_launchWithDebugging = launchWithDebugging; _launchWithDebugging = launchWithDebugging;
@@ -295,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();
} }
@@ -315,11 +317,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
private static void SetEnvironmentVariable(ProcessStartInfo startInfo, string name, string value) private static void SetEnvironmentVariable(ProcessStartInfo startInfo, string name, string value)
{ {
#if NET451
startInfo.EnvironmentVariables[name] = value;
#else
startInfo.Environment[name] = value; startInfo.Environment[name] = value;
#endif
} }
private static Process LaunchNodeProcess(ProcessStartInfo startInfo) private static Process LaunchNodeProcess(ProcessStartInfo startInfo)
@@ -383,12 +381,6 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
{ {
OutputLogger.LogWarning(evt.Data); OutputLogger.LogWarning(evt.Data);
} }
else if (!initializationIsCompleted)
{
_connectionIsReadySource.SetException(
new InvalidOperationException("The Node.js process failed to initialize: " + evt.Data));
initializationIsCompleted = true;
}
else else
{ {
OnErrorDataReceived(UnencodeNewlines(evt.Data)); OnErrorDataReceived(UnencodeNewlines(evt.Data));
@@ -402,9 +394,11 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
private static bool IsDebuggerMessage(string message) private static bool IsDebuggerMessage(string message)
{ {
return message.StartsWith("Debugger attached", StringComparison.OrdinalIgnoreCase) || return message.StartsWith("Debugger attached", StringComparison.Ordinal) ||
message.StartsWith("Debugger listening ", StringComparison.OrdinalIgnoreCase) || message.StartsWith("Debugger listening ", StringComparison.Ordinal) ||
message.StartsWith("To start debugging", StringComparison.OrdinalIgnoreCase) || message.StartsWith("To start debugging", StringComparison.Ordinal) ||
message.Equals("Warning: This is an experimental feature and could change at any time.", StringComparison.Ordinal) ||
message.Equals("For help see https://nodejs.org/en/docs/inspector", StringComparison.Ordinal) ||
message.Contains("chrome-devtools:"); message.Contains("chrome-devtools:");
} }

View File

@@ -4,25 +4,21 @@
<PropertyGroup> <PropertyGroup>
<Description>Invoke Node.js modules at runtime in ASP.NET Core applications.</Description> <Description>Invoke Node.js modules at runtime in ASP.NET Core applications.</Description>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags> <PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="node_modules\**\*" />
<EmbeddedResource Include="Content\**\*" /> <EmbeddedResource Include="Content\**\*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" /> <PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonRuntimePackageVersion)" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">
<PackageReference Include="System.Diagnostics.Process" Version="$(CoreFxVersion)" />
<PackageReference Include="System.IO.FileSystem.Watcher" Version="$(CoreFxVersion)" />
<PackageReference Include="System.Runtime.Loader" Version="$(CoreFxVersion)" />
</ItemGroup> </ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' "> <Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">

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

@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
namespace Microsoft.AspNetCore.NodeServices namespace Microsoft.AspNetCore.NodeServices
{ {
@@ -11,27 +12,21 @@ namespace Microsoft.AspNetCore.NodeServices
private bool _disposedValue; private bool _disposedValue;
private bool _hasDeletedTempFile; private bool _hasDeletedTempFile;
private object _fileDeletionLock = new object(); private object _fileDeletionLock = new object();
private IDisposable _applicationLifetimeRegistration;
/// <summary> /// <summary>
/// Create a new instance of <see cref="StringAsTempFile"/>. /// Create a new instance of <see cref="StringAsTempFile"/>.
/// </summary> /// </summary>
/// <param name="content">The contents of the temporary file to be created.</param> /// <param name="content">The contents of the temporary file to be created.</param>
public StringAsTempFile(string content) /// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
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
// add event handlers for other shutdown scenarios. // add event handlers for other shutdown scenarios.
#if NET451 _applicationLifetimeRegistration = applicationStoppingToken.Register(EnsureTempFileDeleted);
AppDomain.CurrentDomain.ProcessExit += HandleProcessExit;
AppDomain.CurrentDomain.DomainUnload += HandleProcessExit;
#else
// Note that this still doesn't capture SIGKILL (at least on macOS) - there doesn't
// appear to be a way of doing that. So in that case, the temporary file will be
// left behind.
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += HandleAssemblyUnloading;
#endif
} }
/// <summary> /// <summary>
@@ -55,12 +50,7 @@ namespace Microsoft.AspNetCore.NodeServices
if (disposing) if (disposing)
{ {
// Dispose managed state // Dispose managed state
#if NET451 _applicationLifetimeRegistration.Dispose();
AppDomain.CurrentDomain.ProcessExit -= HandleProcessExit;
AppDomain.CurrentDomain.DomainUnload -= HandleProcessExit;
#else
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading -= HandleAssemblyUnloading;
#endif
} }
EnsureTempFileDeleted(); EnsureTempFileDeleted();
@@ -81,18 +71,6 @@ namespace Microsoft.AspNetCore.NodeServices
} }
} }
#if NET451
private void HandleProcessExit(object sender, EventArgs args)
{
EnsureTempFileDeleted();
}
#else
private void HandleAssemblyUnloading(System.Runtime.Loader.AssemblyLoadContext context)
{
EnsureTempFileDeleted();
}
#endif
/// <summary> /// <summary>
/// Implements the finalization part of the IDisposable pattern by calling Dispose(false). /// Implements the finalization part of the IDisposable pattern by calling Dispose(false).
/// </summary> /// </summary>

View File

@@ -4,15 +4,18 @@
<PropertyGroup> <PropertyGroup>
<Description>Helpers for building single-page applications on ASP.NET MVC Core.</Description> <Description>Helpers for building single-page applications on ASP.NET MVC Core.</Description>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags> <PackageTags>aspnetcore;aspnetcoremvc;nodeservices</PackageTags>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</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" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.TagHelpers" Version="$(MicrosoftAspNetCoreMvcTagHelpersPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" Version="$(MicrosoftAspNetCoreMvcViewFeaturesPackageVersion)" />
</ItemGroup> </ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' "> <Target Name="PrepublishScript" BeforeTargets="PrepareForPublish" Condition=" '$(IsCrossTargetingBuild)' != 'true' ">

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,13 +1,11 @@
using System; using System;
using System.Text; 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
{ {
@@ -24,6 +22,7 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
private static INodeServices _fallbackNodeServices; // Used only if no INodeServices was registered with DI private static INodeServices _fallbackNodeServices; // Used only if no INodeServices was registered with DI
private readonly string _applicationBasePath; private readonly string _applicationBasePath;
private readonly CancellationToken _applicationStoppingToken;
private readonly INodeServices _nodeServices; private readonly INodeServices _nodeServices;
/// <summary> /// <summary>
@@ -36,6 +35,9 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
_nodeServices = (INodeServices) serviceProvider.GetService(typeof(INodeServices)) ?? _fallbackNodeServices; _nodeServices = (INodeServices) serviceProvider.GetService(typeof(INodeServices)) ?? _fallbackNodeServices;
_applicationBasePath = hostEnv.ContentRootPath; _applicationBasePath = hostEnv.ContentRootPath;
var applicationLifetime = (IApplicationLifetime) serviceProvider.GetService(typeof(IApplicationLifetime));
_applicationStoppingToken = applicationLifetime.ApplicationStopping;
// Consider removing the following. Having it means you can get away with not putting app.AddNodeServices() // Consider removing the following. Having it means you can get away with not putting app.AddNodeServices()
// in your startup file, but then again it might be confusing that you don't need to. // in your startup file, but then again it might be confusing that you don't need to.
if (_nodeServices == null) if (_nodeServices == null)
@@ -85,31 +87,17 @@ 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,
_applicationStoppingToken,
new JavaScriptModuleExport(ModuleName) new JavaScriptModuleExport(ModuleName)
{ {
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))
{ {
@@ -128,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

@@ -1,6 +1,9 @@
using System; using System;
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
{ {
@@ -9,15 +12,42 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
/// </summary> /// </summary>
public static class Prerenderer public static class Prerenderer
{ {
private static readonly Lazy<StringAsTempFile> NodeScript; private static readonly object CreateNodeScriptLock = new object();
static Prerenderer() private static StringAsTempFile NodeScript;
internal static Task<RenderToStringResult> RenderToString(
string applicationBasePath,
INodeServices nodeServices,
CancellationToken applicationStoppingToken,
JavaScriptModuleExport bootModule,
HttpContext httpContext,
object customDataParameter,
int timeoutMilliseconds)
{ {
NodeScript = new Lazy<StringAsTempFile>(() => // 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,
var script = EmbeddedResourceReader.Read(typeof(Prerenderer), "/Content/Node/prerenderer.js"); // location.pathname returns an unencoded string).
return new StringAsTempFile(script); // Will be cleaned up on process exit // 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>
@@ -25,6 +55,7 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
/// </summary> /// </summary>
/// <param name="applicationBasePath">The root path to your application. This is used when resolving project-relative paths.</param> /// <param name="applicationBasePath">The root path to your application. This is used when resolving project-relative paths.</param>
/// <param name="nodeServices">The instance of <see cref="INodeServices"/> that will be used to invoke JavaScript code.</param> /// <param name="nodeServices">The instance of <see cref="INodeServices"/> that will be used to invoke JavaScript code.</param>
/// <param name="applicationStoppingToken">A token that indicates when the host application is stopping.</param>
/// <param name="bootModule">The path to the JavaScript file containing the prerendering logic.</param> /// <param name="bootModule">The path to the JavaScript file containing the prerendering logic.</param>
/// <param name="requestAbsoluteUrl">The URL of the currently-executing HTTP request. This is supplied to the prerendering code.</param> /// <param name="requestAbsoluteUrl">The URL of the currently-executing HTTP request. This is supplied to the prerendering code.</param>
/// <param name="requestPathAndQuery">The path and query part of the URL of the currently-executing HTTP request. This is supplied to the prerendering code.</param> /// <param name="requestPathAndQuery">The path and query part of the URL of the currently-executing HTTP request. This is supplied to the prerendering code.</param>
@@ -35,6 +66,7 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
public static Task<RenderToStringResult> RenderToString( public static Task<RenderToStringResult> RenderToString(
string applicationBasePath, string applicationBasePath,
INodeServices nodeServices, INodeServices nodeServices,
CancellationToken applicationStoppingToken,
JavaScriptModuleExport bootModule, JavaScriptModuleExport bootModule,
string requestAbsoluteUrl, string requestAbsoluteUrl,
string requestPathAndQuery, string requestPathAndQuery,
@@ -43,7 +75,7 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
string requestPathBase) string requestPathBase)
{ {
return nodeServices.InvokeExportAsync<RenderToStringResult>( return nodeServices.InvokeExportAsync<RenderToStringResult>(
NodeScript.Value.FileName, GetNodeScriptFilename(applicationStoppingToken),
"renderToString", "renderToString",
applicationBasePath, applicationBasePath,
bootModule, bootModule,
@@ -53,5 +85,19 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
timeoutMilliseconds, timeoutMilliseconds,
requestPathBase); requestPathBase);
} }
private static string GetNodeScriptFilename(CancellationToken applicationStoppingToken)
{
lock(CreateNodeScriptLock)
{
if (NodeScript == null)
{
var script = EmbeddedResourceReader.Read(typeof(Prerenderer), "/Content/Node/prerenderer.js");
NodeScript = new StringAsTempFile(script, applicationStoppingToken); // Will be cleaned up on process exit
}
}
return NodeScript.FileName;
}
} }
} }

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);
@@ -97,9 +102,17 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack
context.Response.Headers.Remove("transfer-encoding"); context.Response.Headers.Remove("transfer-encoding");
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync()) using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
try
{ {
await responseStream.CopyToAsync(context.Response.Body, DefaultHttpBufferSize, context.RequestAborted); await responseStream.CopyToAsync(context.Response.Body, DefaultHttpBufferSize, context.RequestAborted);
} }
catch (OperationCanceledException)
{
// The CopyToAsync task will be canceled if the client disconnects (e.g., user
// closes or refreshes the browser tab). Don't treat this as an error.
}
}
return true; return true;
} }

View File

@@ -1,14 +1,9 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.SpaServices.Webpack; using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.PlatformAbstractions;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Builder namespace Microsoft.AspNetCore.Builder
{ {
@@ -54,7 +49,7 @@ namespace Microsoft.AspNetCore.Builder
// middleware). And since this is a dev-time-only feature, it doesn't matter if the default transport isn't // middleware). And since this is a dev-time-only feature, it doesn't matter if the default transport isn't
// as fast as some theoretical future alternative. // as fast as some theoretical future alternative.
var nodeServicesOptions = new NodeServicesOptions(appBuilder.ApplicationServices); var nodeServicesOptions = new NodeServicesOptions(appBuilder.ApplicationServices);
nodeServicesOptions.WatchFileExtensions = new string[] {}; // Don't watch anything nodeServicesOptions.WatchFileExtensions = new string[] { }; // Don't watch anything
if (!string.IsNullOrEmpty(options.ProjectPath)) if (!string.IsNullOrEmpty(options.ProjectPath))
{ {
nodeServicesOptions.ProjectPath = options.ProjectPath; nodeServicesOptions.ProjectPath = options.ProjectPath;
@@ -73,7 +68,7 @@ namespace Microsoft.AspNetCore.Builder
// Get a filename matching the middleware Node script // Get a filename matching the middleware Node script
var script = EmbeddedResourceReader.Read(typeof(WebpackDevMiddleware), var script = EmbeddedResourceReader.Read(typeof(WebpackDevMiddleware),
"/Content/Node/webpack-dev-middleware.js"); "/Content/Node/webpack-dev-middleware.js");
var nodeScript = new StringAsTempFile(script); // Will be cleaned up on process exit var nodeScript = new StringAsTempFile(script, nodeServicesOptions.ApplicationStoppingToken); // Will be cleaned up on process exit
// Ideally, this would be relative to the application's PathBase (so it could work in virtual directories) // Ideally, this would be relative to the application's PathBase (so it could work in virtual directories)
// but it's not clear that such information exists during application startup, as opposed to within the context // but it's not clear that such information exists during application startup, as opposed to within the context
@@ -106,9 +101,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

@@ -1,6 +1,6 @@
{ {
"name": "aspnet-prerendering", "name": "aspnet-prerendering",
"version": "2.0.5", "version": "2.0.6",
"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": {

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;

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-beta.1",
"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": {

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,19 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework Condition="'$(TargetFrameworkOverride)' == ''">netcoreapp2.0</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">TargetFrameworkOverride</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup Condition="'$(TargetFrameworkOverride)' == ''">
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" /> </ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkOverride)' != ''">
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0-rtm-26190" />
</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-rtm-26190" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -21,7 +27,23 @@
<Content Remove="ClientApp\**" /> <Content Remove="ClientApp\**" />
</ItemGroup> </ItemGroup>
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish"> <!--/-:cnd:noEmit -->
<Target Name="DebugRunWebpack" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('wwwroot\dist') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<!-- In development, the dist files won't exist on the first run or when cloning to
a different machine, so rebuild them if not already present. -->
<Message Importance="high" Text="Performing first-run Webpack build..." />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js" />
<Exec Command="node node_modules/webpack/bin/webpack.js" />
</Target>
<!--/+:cnd:noEmit -->
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode --> <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec Command="npm install" /> <Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" /> <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />

View File

@@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppModuleShared } from './app.module.shared';
import { AppComponent } from './components/app/app.component';
@NgModule({
bootstrap: [ AppComponent ],
imports: [
BrowserModule,
AppModuleShared
],
providers: [
{ provide: 'ORIGIN_URL', useFactory: getOriginUrl }
]
})
export class AppModule {
}
export function getOriginUrl() {
return location.origin;
}

View File

@@ -1,22 +0,0 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { sharedConfig } from './app.module.shared';
@NgModule({
bootstrap: sharedConfig.bootstrap,
declarations: sharedConfig.declarations,
imports: [
BrowserModule,
FormsModule,
HttpModule,
...sharedConfig.imports
],
providers: [
{ provide: 'ORIGIN_URL', useValue: location.origin },
{ provide: 'PRERENDERING_DATA', useValue: (window as any).PRERENDERING_DATA }
]
})
export class AppModule {
}

View File

@@ -1,13 +1,13 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server'; import { ServerModule } from '@angular/platform-server';
import { sharedConfig } from './app.module.shared'; import { AppModuleShared } from './app.module.shared';
import { AppComponent } from './components/app/app.component';
@NgModule({ @NgModule({
bootstrap: sharedConfig.bootstrap, bootstrap: [ AppComponent ],
declarations: sharedConfig.declarations,
imports: [ imports: [
ServerModule, ServerModule,
...sharedConfig.imports AppModuleShared
] ]
}) })
export class AppModule { export class AppModule {

View File

@@ -1,14 +1,16 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { AppComponent } from './components/app/app.component' import { AppComponent } from './components/app/app.component';
import { NavMenuComponent } from './components/navmenu/navmenu.component'; import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component'; import { HomeComponent } from './components/home/home.component';
import { FetchDataComponent } from './components/fetchdata/fetchdata.component'; import { FetchDataComponent } from './components/fetchdata/fetchdata.component';
import { CounterComponent } from './components/counter/counter.component'; import { CounterComponent } from './components/counter/counter.component';
export const sharedConfig: NgModule = { @NgModule({
bootstrap: [ AppComponent ],
declarations: [ declarations: [
AppComponent, AppComponent,
NavMenuComponent, NavMenuComponent,
@@ -17,6 +19,9 @@ export const sharedConfig: NgModule = {
HomeComponent HomeComponent
], ],
imports: [ imports: [
CommonModule,
HttpModule,
FormsModule,
RouterModule.forRoot([ RouterModule.forRoot([
{ path: '', redirectTo: 'home', pathMatch: 'full' }, { path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent }, { path: 'home', component: HomeComponent },
@@ -25,4 +30,6 @@ export const sharedConfig: NgModule = {
{ path: '**', redirectTo: 'home' } { path: '**', redirectTo: 'home' }
]) ])
] ]
}; })
export class AppModuleShared {
}

View File

@@ -1,7 +1,5 @@
<h1>Weather forecast</h1> <h1>Weather forecast</h1>
<p>Prerendering data: {{ prerenderingDataString }}</p>
<p>This component demonstrates fetching data from the server.</p> <p>This component demonstrates fetching data from the server.</p>
<p *ngIf="!forecasts"><em>Loading...</em></p> <p *ngIf="!forecasts"><em>Loading...</em></p>

View File

@@ -7,14 +7,11 @@ import { Http } from '@angular/http';
}) })
export class FetchDataComponent { export class FetchDataComponent {
public forecasts: WeatherForecast[]; public forecasts: WeatherForecast[];
public prerenderingDataString: string;
constructor(http: Http, @Inject('ORIGIN_URL') originUrl: string, @Inject('PRERENDERING_DATA') prerenderingData: any) {
this.prerenderingDataString = JSON.stringify(prerenderingData);
constructor(http: Http, @Inject('ORIGIN_URL') originUrl: string) {
http.get(originUrl + '/api/SampleData/WeatherForecasts').subscribe(result => { http.get(originUrl + '/api/SampleData/WeatherForecasts').subscribe(result => {
this.forecasts = result.json() as WeatherForecast[]; this.forecasts = result.json() as WeatherForecast[];
}); }, error => console.error(error));
} }
} }

View File

@@ -1,8 +1,9 @@
import 'reflect-metadata'; import 'reflect-metadata';
import 'zone.js'; import 'zone.js';
import 'bootstrap';
import { enableProdMode } from '@angular/core'; import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module.client'; import { AppModule } from './app/app.module.browser';
if (module['hot']) { if (module['hot']) {
module['hot'].accept(); module['hot'].accept();

View File

@@ -11,8 +11,7 @@ enableProdMode();
export default createServerRenderer(params => { export default createServerRenderer(params => {
const providers = [ const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } }, { provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }, { provide: 'ORIGIN_URL', useValue: params.origin }
{ provide: 'PRERENDERING_DATA', useValue: params.data }
]; ];
return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => { return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {
@@ -27,8 +26,7 @@ export default createServerRenderer(params => {
// completing the request in case there's an error to report // completing the request in case there's an error to report
setImmediate(() => { setImmediate(() => {
resolve({ resolve({
html: state.renderToString(), html: state.renderToString()
globals: { PRERENDERING_DATA: params.data }
}); });
moduleRef.destroy(); moduleRef.destroy();
}); });

View File

@@ -2,11 +2,7 @@
ViewData["Title"] = "Home Page"; ViewData["Title"] = "Home Page";
} }
<app asp-prerender-module="ClientApp/dist/main-server" <app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
asp-prerender-data="new {
IsGoldUser = true,
Cookies = ViewContext.HttpContext.Request.Cookies
}">Loading...</app>
<script src="~/dist/vendor.js" asp-append-version="true"></script> <script src="~/dist/vendor.js" asp-append-version="true"></script>
@section scripts { @section scripts {

View File

@@ -1,3 +0,0 @@
{
"sdk": { "version": "sdkVersionInjectedHere" }
}

3730
templates/AngularSpa/npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,6 +1,7 @@
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const merge = require('webpack-merge'); const merge = require('webpack-merge');
const AotPlugin = require('@ngtools/webpack').AotPlugin;
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin; const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
module.exports = (env) => { module.exports = (env) => {
@@ -16,7 +17,7 @@ module.exports = (env) => {
}, },
module: { module: {
rules: [ rules: [
{ test: /\.ts$/, include: /ClientApp/, use: ['awesome-typescript-loader?silent=true', 'angular2-template-loader'] }, { test: /\.ts$/, include: /ClientApp/, use: isDevBuild ? ['awesome-typescript-loader?silent=true', 'angular2-template-loader'] : '@ngtools/webpack' },
{ test: /\.html$/, use: 'html-loader?minimize=false' }, { test: /\.html$/, use: 'html-loader?minimize=false' },
{ test: /\.css$/, use: [ 'to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] }, { test: /\.css$/, use: [ 'to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' } { test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
@@ -28,7 +29,7 @@ module.exports = (env) => {
// Configuration for client-side bundle suitable for running in browsers // Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = './wwwroot/dist'; const clientBundleOutputDir = './wwwroot/dist';
const clientBundleConfig = merge(sharedConfig, { const clientBundleConfig = merge(sharedConfig, {
entry: { 'main-client': './ClientApp/boot-client.ts' }, entry: { 'main-client': './ClientApp/boot.browser.ts' },
output: { path: path.join(__dirname, clientBundleOutputDir) }, output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [ plugins: [
new webpack.DllReferencePlugin({ new webpack.DllReferencePlugin({
@@ -43,14 +44,19 @@ module.exports = (env) => {
}) })
] : [ ] : [
// Plugins that apply in production builds only // Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin() new webpack.optimize.UglifyJsPlugin(),
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.module.browser#AppModule'),
exclude: ['./**/*.server.ts']
})
]) ])
}); });
// Configuration for server-side (prerendering) bundle suitable for running in Node // Configuration for server-side (prerendering) bundle suitable for running in Node
const serverBundleConfig = merge(sharedConfig, { const serverBundleConfig = merge(sharedConfig, {
resolve: { mainFields: ['main'] }, resolve: { mainFields: ['main'] },
entry: { 'main-server': './ClientApp/boot-server.ts' }, entry: { 'main-server': './ClientApp/boot.server.ts' },
plugins: [ plugins: [
new webpack.DllReferencePlugin({ new webpack.DllReferencePlugin({
context: __dirname, context: __dirname,
@@ -58,7 +64,14 @@ module.exports = (env) => {
sourceType: 'commonjs2', sourceType: 'commonjs2',
name: './vendor' name: './vendor'
}) })
], ].concat(isDevBuild ? [] : [
// Plugins that apply in production builds only
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.module.server#AppModule'),
exclude: ['./**/*.browser.ts']
})
]),
output: { output: {
libraryTarget: 'commonjs', libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist') path: path.join(__dirname, './ClientApp/dist')

View File

@@ -2,6 +2,27 @@ const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin');
const merge = require('webpack-merge'); const merge = require('webpack-merge');
const treeShakableModules = [
'@angular/animations',
'@angular/common',
'@angular/compiler',
'@angular/core',
'@angular/forms',
'@angular/http',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/router',
'zone.js',
];
const nonTreeShakableModules = [
'bootstrap',
'bootstrap/dist/css/bootstrap.css',
'es6-promise',
'es6-shim',
'event-source-polyfill',
'jquery',
];
const allModules = treeShakableModules.concat(nonTreeShakableModules);
module.exports = (env) => { module.exports = (env) => {
const extractCSS = new ExtractTextPlugin('vendor.css'); const extractCSS = new ExtractTextPlugin('vendor.css');
@@ -14,26 +35,6 @@ module.exports = (env) => {
{ test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, use: 'url-loader?limit=100000' } { test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, use: 'url-loader?limit=100000' }
] ]
}, },
entry: {
vendor: [
'@angular/animations',
'@angular/common',
'@angular/compiler',
'@angular/core',
'@angular/forms',
'@angular/http',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/router',
'bootstrap',
'bootstrap/dist/css/bootstrap.css',
'es6-shim',
'es6-promise',
'event-source-polyfill',
'jquery',
'zone.js',
]
},
output: { output: {
publicPath: '/dist/', publicPath: '/dist/',
filename: '[name].js', filename: '[name].js',
@@ -48,6 +49,11 @@ module.exports = (env) => {
}; };
const clientBundleConfig = merge(sharedConfig, { const clientBundleConfig = merge(sharedConfig, {
entry: {
// To keep development builds fast, include all vendor dependencies in the vendor bundle.
// But for production builds, leave the tree-shakable ones out so the AOT compiler can produce a smaller bundle.
vendor: isDevBuild ? allModules : nonTreeShakableModules
},
output: { path: path.join(__dirname, 'wwwroot', 'dist') }, output: { path: path.join(__dirname, 'wwwroot', 'dist') },
module: { module: {
rules: [ rules: [
@@ -68,6 +74,7 @@ module.exports = (env) => {
const serverBundleConfig = merge(sharedConfig, { const serverBundleConfig = merge(sharedConfig, {
target: 'node', target: 'node',
resolve: { mainFields: ['main'] }, resolve: { mainFields: ['main'] },
entry: { vendor: allModules.concat(['aspnet-prerendering']) },
output: { output: {
path: path.join(__dirname, 'ClientApp', 'dist'), path: path.join(__dirname, 'ClientApp', 'dist'),
libraryTarget: 'commonjs2', libraryTarget: 'commonjs2',
@@ -75,7 +82,6 @@ module.exports = (env) => {
module: { module: {
rules: [ { test: /\.css(\?|$)/, use: ['to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] } ] rules: [ { test: /\.css(\?|$)/, use: ['to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] } ]
}, },
entry: { vendor: ['aspnet-prerendering'] },
plugins: [ plugins: [
new webpack.DllPlugin({ new webpack.DllPlugin({
path: path.join(__dirname, 'ClientApp', 'dist', '[name]-manifest.json'), path: path.join(__dirname, 'ClientApp', 'dist', '[name]-manifest.json'),

View File

@@ -1,22 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework Condition="'$(TargetFrameworkOverride)' == ''">netcoreapp2.0</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">TargetFrameworkOverride</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup Condition="'$(TargetFrameworkOverride)' == ''">
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" /> </ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkOverride)' != ''">
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0-rtm-26190" />
</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>
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish"> <!--/-:cnd:noEmit -->
<Target Name="DebugRunWebpack" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('wwwroot\dist') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<!-- In development, the dist files won't exist on the first run or when cloning to
a different machine, so rebuild them if not already present. -->
<Message Importance="high" Text="Performing first-run Webpack build..." />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js" />
<Exec Command="node node_modules/webpack/bin/webpack.js" />
</Target>
<!--/+:cnd:noEmit -->
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode --> <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec Command="npm install" /> <Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" /> <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />

View File

@@ -1,3 +0,0 @@
{
"sdk": { "version": "sdkVersionInjectedHere" }
}

2615
templates/AureliaSpa/npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
{ {
"name": "WebApplicationBasic", "name": "WebApplicationBasic",
"private": true,
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"aurelia-bootstrapper": "^2.0.1", "aurelia-bootstrapper": "^2.0.1",

View File

@@ -1,22 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework Condition="'$(TargetFrameworkOverride)' == ''">netcoreapp2.0</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">TargetFrameworkOverride</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup Condition="'$(TargetFrameworkOverride)' == ''">
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" /> </ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkOverride)' != ''">
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0-rtm-26190" />
</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>
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish"> <!--/-:cnd:noEmit -->
<Target Name="DebugRunWebpack" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('wwwroot\dist') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<!-- In development, the dist files won't exist on the first run or when cloning to
a different machine, so rebuild them if not already present. -->
<Message Importance="high" Text="Performing first-run Webpack build..." />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js" />
<Exec Command="node node_modules/webpack/bin/webpack.js" />
</Target>
<!--/+:cnd:noEmit -->
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode --> <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec Command="npm install" /> <Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" /> <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />

View File

@@ -1,3 +0,0 @@
{
"sdk": { "version": "sdkVersionInjectedHere" }
}

2503
templates/KnockoutSpa/npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
{ {
"name": "WebApplicationBasic", "name": "WebApplicationBasic",
"private": true,
"version": "0.0.0", "version": "0.0.0",
"devDependencies": { "devDependencies": {
"@types/core-js": "^0.9.34", "@types/core-js": "^0.9.34",

View File

@@ -62,7 +62,8 @@ export const actionCreators = {
const unloadedState: WeatherForecastsState = { startDateIndex: null, forecasts: [], isLoading: false }; const unloadedState: WeatherForecastsState = { startDateIndex: null, forecasts: [], isLoading: false };
export const reducer: Reducer<WeatherForecastsState> = (state: WeatherForecastsState, action: KnownAction) => { export const reducer: Reducer<WeatherForecastsState> = (state: WeatherForecastsState, incomingAction: Action) => {
const action = incomingAction as KnownAction;
switch (action.type) { switch (action.type) {
case 'REQUEST_WEATHER_FORECASTS': case 'REQUEST_WEATHER_FORECASTS':
return { return {

View File

@@ -1,15 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework Condition="'$(TargetFrameworkOverride)' == ''">netcoreapp2.0</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">TargetFrameworkOverride</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup Condition="'$(TargetFrameworkOverride)' == ''">
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" /> </ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkOverride)' != ''">
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0-rtm-26190" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -21,7 +27,23 @@
<Content Remove="ClientApp\**" /> <Content Remove="ClientApp\**" />
</ItemGroup> </ItemGroup>
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish"> <!--/-:cnd:noEmit -->
<Target Name="DebugRunWebpack" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('wwwroot\dist') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<!-- In development, the dist files won't exist on the first run or when cloning to
a different machine, so rebuild them if not already present. -->
<Message Importance="high" Text="Performing first-run Webpack build..." />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js" />
<Exec Command="node node_modules/webpack/bin/webpack.js" />
</Target>
<!--/+:cnd:noEmit -->
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode --> <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec Command="npm install" /> <Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" /> <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />

View File

@@ -1,3 +0,0 @@
{
"sdk": { "version": "sdkVersionInjectedHere" }
}

2923
templates/ReactReduxSpa/npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +1,44 @@
{ {
"name": "WebApplicationBasic", "name": "WebApplicationBasic",
"private": true,
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@types/history": "4.5.1", "@types/history": "4.6.0",
"@types/react": "15.0.24", "@types/react": "15.0.35",
"@types/react-dom": "15.5.0", "@types/react-dom": "15.5.1",
"@types/react-redux": "4.4.40", "@types/react-redux": "4.4.45",
"@types/react-router-dom": "4.0.4", "@types/react-router": "4.0.12",
"@types/react-router-redux": "5.0.1", "@types/react-router-dom": "4.0.5",
"@types/react-router-redux": "5.0.3",
"@types/webpack": "2.2.15", "@types/webpack": "2.2.15",
"@types/webpack-env": "1.13.0", "@types/webpack-env": "1.13.0",
"aspnet-prerendering": "^2.0.5", "aspnet-prerendering": "^2.0.5",
"aspnet-webpack": "^1.0.29", "aspnet-webpack": "^1.0.29",
"aspnet-webpack-react": "^2.0.0", "aspnet-webpack-react": "^3.0.0-beta",
"awesome-typescript-loader": "3.1.3", "awesome-typescript-loader": "3.2.1",
"bootstrap": "3.3.7", "bootstrap": "3.3.7",
"css-loader": "0.28.1", "css-loader": "0.28.4",
"domain-task": "^3.0.0", "domain-task": "^3.0.3",
"event-source-polyfill": "0.0.9", "event-source-polyfill": "0.0.9",
"extract-text-webpack-plugin": "2.1.0", "extract-text-webpack-plugin": "2.1.2",
"file-loader": "0.11.1", "file-loader": "0.11.2",
"history": "4.6.1", "history": "4.6.3",
"jquery": "3.2.1", "jquery": "3.2.1",
"json-loader": "0.5.4", "json-loader": "0.5.4",
"node-noop": "1.0.0", "node-noop": "1.0.0",
"react": "15.5.4", "react": "15.6.1",
"react-dom": "15.5.4", "react-dom": "15.6.1",
"react-hot-loader": "3.0.0-beta.7", "react-hot-loader": "3.0.0-beta.7",
"react-redux": "5.0.4", "react-redux": "5.0.5",
"react-router-dom": "4.1.1", "react-router-dom": "4.1.1",
"react-router-redux": "5.0.0-alpha.6", "react-router-redux": "5.0.0-alpha.6",
"redux": "3.6.0", "redux": "3.7.1",
"redux-thunk": "2.2.0", "redux-thunk": "2.2.0",
"style-loader": "0.17.0", "style-loader": "0.18.2",
"typescript": "2.3.2", "typescript": "2.4.1",
"url-loader": "0.5.8", "url-loader": "0.5.9",
"webpack": "2.5.1", "webpack": "2.5.1",
"webpack-hot-middleware": "2.18.0", "webpack-hot-middleware": "2.18.2",
"webpack-merge": "4.1.0" "webpack-merge": "4.1.0"
} }
} }

View File

@@ -1,22 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework Condition="'$(TargetFrameworkOverride)' == ''">netcoreapp2.0</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">TargetFrameworkOverride</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup Condition="'$(TargetFrameworkOverride)' == ''">
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" /> </ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkOverride)' != ''">
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0-rtm-26190" />
</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>
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish"> <!--/-:cnd:noEmit -->
<Target Name="DebugRunWebpack" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('wwwroot\dist') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<!-- In development, the dist files won't exist on the first run or when cloning to
a different machine, so rebuild them if not already present. -->
<Message Importance="high" Text="Performing first-run Webpack build..." />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js" />
<Exec Command="node node_modules/webpack/bin/webpack.js" />
</Target>
<!--/+:cnd:noEmit -->
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode --> <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec Command="npm install" /> <Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" /> <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />

View File

@@ -1,3 +0,0 @@
{
"sdk": { "version": "sdkVersionInjectedHere" }
}

3365
templates/ReactSpa/npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,32 @@
{ {
"name": "WebApplicationBasic", "name": "WebApplicationBasic",
"private": true,
"version": "0.0.0", "version": "0.0.0",
"devDependencies": { "devDependencies": {
"@types/history": "4.5.1", "@types/history": "4.6.0",
"@types/react": "15.0.24", "@types/react": "15.0.35",
"@types/react-dom": "15.5.0", "@types/react-dom": "15.5.1",
"@types/react-router": "4.0.9", "@types/react-router": "4.0.12",
"@types/webpack-env": "1.13.0", "@types/webpack-env": "1.13.0",
"aspnet-webpack": "^1.0.29", "aspnet-webpack": "^1.0.29",
"aspnet-webpack-react": "^2.0.0", "aspnet-webpack-react": "^3.0.0-beta",
"awesome-typescript-loader": "3.1.3", "awesome-typescript-loader": "3.2.1",
"bootstrap": "3.3.7", "bootstrap": "3.3.7",
"css-loader": "0.28.1", "css-loader": "0.28.4",
"event-source-polyfill": "0.0.9", "event-source-polyfill": "0.0.9",
"extract-text-webpack-plugin": "2.1.0", "extract-text-webpack-plugin": "2.1.2",
"file-loader": "0.11.1", "file-loader": "0.11.2",
"isomorphic-fetch": "2.2.1", "isomorphic-fetch": "2.2.1",
"jquery": "3.2.1", "jquery": "3.2.1",
"json-loader": "0.5.4", "json-loader": "0.5.4",
"react": "15.5.4", "react": "15.6.1",
"react-dom": "15.5.4", "react-dom": "15.6.1",
"react-hot-loader": "3.0.0-beta.7", "react-hot-loader": "3.0.0-beta.7",
"react-router-dom": "4.1.1", "react-router-dom": "4.1.1",
"style-loader": "0.17.0", "style-loader": "0.18.2",
"typescript": "2.3.2", "typescript": "2.4.1",
"url-loader": "0.5.8", "url-loader": "0.5.9",
"webpack": "2.5.1", "webpack": "2.5.1",
"webpack-hot-middleware": "2.18.0" "webpack-hot-middleware": "2.18.2"
} }
} }

View File

@@ -1,22 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework Condition="'$(TargetFrameworkOverride)' == ''">netcoreapp2.0</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">TargetFrameworkOverride</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup Condition="'$(TargetFrameworkOverride)' == ''">
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-*" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-*" /> </ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkOverride)' != ''">
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="2.0.0-rtm-26190" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0-rtm-26190" />
</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>
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish"> <!--/-:cnd:noEmit -->
<Target Name="DebugRunWebpack" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('wwwroot\dist') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<!-- In development, the dist files won't exist on the first run or when cloning to
a different machine, so rebuild them if not already present. -->
<Message Importance="high" Text="Performing first-run Webpack build..." />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js" />
<Exec Command="node node_modules/webpack/bin/webpack.js" />
</Target>
<!--/+:cnd:noEmit -->
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode --> <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec Command="npm install" /> <Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" /> <Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />

View File

@@ -1,3 +0,0 @@
{
"sdk": { "version": "sdkVersionInjectedHere" }
}

2603
templates/VueSpa/npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
{ {
"name": "WebApplicationBasic", "name": "WebApplicationBasic",
"private": true,
"version": "0.0.0", "version": "0.0.0",
"devDependencies": { "devDependencies": {
"@types/requirejs": "^2.1.28", "@types/requirejs": "^2.1.28",

View File

@@ -7,7 +7,6 @@ const bundleOutputDir = './wwwroot/dist';
module.exports = (env) => { module.exports = (env) => {
const isDevBuild = !(env && env.prod); const isDevBuild = !(env && env.prod);
const bundleOutputDir = './wwwroot/dist';
return [{ return [{
stats: { modules: false }, stats: { modules: false },
context: __dirname, context: __dirname,

View File

@@ -11,26 +11,29 @@ import * as targz from 'tar.gz';
const isWindows = /^win/.test(process.platform); const isWindows = /^win/.test(process.platform);
const textFileExtensions = ['.gitignore', 'template_gitignore', '.config', '.cs', '.cshtml', '.csproj', '.html', '.js', '.json', '.jsx', '.md', '.nuspec', '.ts', '.tsx']; const textFileExtensions = ['.gitignore', 'template_gitignore', '.config', '.cs', '.cshtml', '.csproj', '.html', '.js', '.json', '.jsx', '.md', '.nuspec', '.ts', '.tsx'];
const yeomanGeneratorSource = './src/yeoman'; const yeomanGeneratorSource = './src/yeoman';
const webToolsVSPackageGuid = '{0CD94836-1526-4E85-87D3-FB5274C5AFC9}';
// To support the "dotnet new" templates, we want to bundle prebuilt dist dev-mode files, because "dotnet new" can't auto-run const dotNetPackages = {
// webpack on project creation. Note that these script entries are *not* the same as the project's usual prepublish builtIn: 'Microsoft.DotNet.Web.Spa.ProjectTemplates',
// scripts, because here we want dev-mode builds (e.g., to support HMR), not prod-mode builds. extra: 'Microsoft.AspNetCore.SpaTemplates'
const commonTemplatePrepublishSteps = [
'npm install',
'node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js',
'node node_modules/webpack/bin/webpack.js'
];
const commonForceInclusionRegex = /^(wwwroot|ClientApp)\/dist\//; // Files to be included in template, even though gitignored
const templates: { [key: string]: { dir: string, dotNetNewId: string, displayName: string, prepublish?: string[], forceInclusion?: RegExp } } = {
'angular': { dir: '../../templates/AngularSpa/', dotNetNewId: 'Angular', displayName: 'Angular' },
'aurelia': { dir: '../../templates/AureliaSpa/', dotNetNewId: 'Aurelia', displayName: 'Aurelia' },
'knockout': { dir: '../../templates/KnockoutSpa/', dotNetNewId: 'Knockout', displayName: 'Knockout.js' },
'react-redux': { dir: '../../templates/ReactReduxSpa/', dotNetNewId: 'ReactRedux', displayName: 'React.js and Redux' },
'react': { dir: '../../templates/ReactSpa/', dotNetNewId: 'React', displayName: 'React.js' },
'vue': { dir: '../../templates/VueSpa/', dotNetNewId: 'Vue', displayName: 'Vue.js' }
}; };
interface TemplateConfig {
dir: string;
dotNetNewId: string;
dotNetPackageId: string;
displayName: string;
localizationIdStart: number;
}
const templates: { [key: string]: TemplateConfig } = {
'angular': { dotNetPackageId: dotNetPackages.builtIn, dir: '../../templates/AngularSpa/', dotNetNewId: 'Angular', displayName: 'Angular', localizationIdStart: 1100 },
'aurelia': { dotNetPackageId: dotNetPackages.extra, dir: '../../templates/AureliaSpa/', dotNetNewId: 'Aurelia', displayName: 'Aurelia', localizationIdStart: 1200 },
'knockout': { dotNetPackageId: dotNetPackages.extra, dir: '../../templates/KnockoutSpa/', dotNetNewId: 'Knockout', displayName: 'Knockout.js', localizationIdStart: 1300 },
'react-redux': { dotNetPackageId: dotNetPackages.builtIn, dir: '../../templates/ReactReduxSpa/', dotNetNewId: 'ReactRedux', displayName: 'React.js and Redux', localizationIdStart: 1400 },
'react': { dotNetPackageId: dotNetPackages.builtIn, dir: '../../templates/ReactSpa/', dotNetNewId: 'React', displayName: 'React.js', localizationIdStart: 1500 },
'vue': { dotNetPackageId: dotNetPackages.extra, dir: '../../templates/VueSpa/', dotNetNewId: 'Vue', displayName: 'Vue.js', localizationIdStart: 1600 }
};
function isTextFile(filename: string): boolean { function isTextFile(filename: string): boolean {
return textFileExtensions.indexOf(path.extname(filename).toLowerCase()) >= 0 return textFileExtensions.indexOf(path.extname(filename).toLowerCase()) >= 0
@@ -43,7 +46,7 @@ function writeFileEnsuringDirExists(root: string, filename: string, contents: st
fs.writeFileSync(fullPath, contents); fs.writeFileSync(fullPath, contents);
} }
function listFilesExcludingGitignored(root: string, forceInclusion: RegExp): string[] { function listFilesExcludingGitignored(root: string): string[] {
// Note that the gitignore files, prior to be written by the generator, are called 'template_gitignore' // Note that the gitignore files, prior to be written by the generator, are called 'template_gitignore'
// instead of '.gitignore'. This is a workaround for Yeoman doing strange stuff with .gitignore files // instead of '.gitignore'. This is a workaround for Yeoman doing strange stuff with .gitignore files
// (it renames them to .npmignore, which is not helpful). // (it renames them to .npmignore, which is not helpful).
@@ -52,21 +55,23 @@ function listFilesExcludingGitignored(root: string, forceInclusion: RegExp): str
? gitignore.compile(fs.readFileSync(gitIgnorePath, 'utf8')) ? gitignore.compile(fs.readFileSync(gitIgnorePath, 'utf8'))
: { accepts: () => true }; : { accepts: () => true };
return glob.sync('**/*', { cwd: root, dot: true, nodir: true }) return glob.sync('**/*', { cwd: root, dot: true, nodir: true })
.filter(fn => gitignoreEvaluator.accepts(fn) || (forceInclusion && forceInclusion.test(fn))); .filter(fn => gitignoreEvaluator.accepts(fn));
} }
function writeTemplate(sourceRoot: string, destRoot: string, contentReplacements: { from: RegExp, to: string }[], filenameReplacements: { from: RegExp, to: string }[], forceInclusion: RegExp) { function applyContentReplacements(sourceContent: Buffer, contentReplacements: { from: RegExp, to: string }[]) {
listFilesExcludingGitignored(sourceRoot, forceInclusion).forEach(fn => {
let sourceContent = fs.readFileSync(path.join(sourceRoot, fn));
// For text files, replace hardcoded values with template tags
if (isTextFile(fn)) {
let sourceText = sourceContent.toString('utf8'); let sourceText = sourceContent.toString('utf8');
contentReplacements.forEach(replacement => { contentReplacements.forEach(replacement => {
sourceText = sourceText.replace(replacement.from, replacement.to); sourceText = sourceText.replace(replacement.from, replacement.to);
}); });
sourceContent = new Buffer(sourceText, 'utf8'); return new Buffer(sourceText, 'utf8');
}
function writeTemplate(sourceRoot: string, destRoot: string, contentReplacements: { from: RegExp, to: string }[], filenameReplacements: { from: RegExp, to: string }[]) {
listFilesExcludingGitignored(sourceRoot).forEach(fn => {
let sourceContent = fs.readFileSync(path.join(sourceRoot, fn));
if (isTextFile(fn)) {
sourceContent = applyContentReplacements(sourceContent, contentReplacements);
} }
// Also apply replacements in filenames // Also apply replacements in filenames
@@ -86,6 +91,22 @@ function copyRecursive(sourceRoot: string, destRoot: string, matchGlob: string)
}); });
} }
function leftPad(str: string, minLength: number, padChar: string) {
while (str.length < minLength) {
str = padChar + str;
}
return str;
}
function getBuildNumber(): string {
if (process.env.APPVEYOR_BUILD_NUMBER) {
return leftPad(process.env.APPVEYOR_BUILD_NUMBER, 6, '0');
}
// For local builds, use timestamp
return 't-' + Math.floor((new Date().valueOf() - new Date(2017, 0, 1).valueOf()) / (60*1000));
}
function buildYeomanNpmPackage(outputRoot: string) { function buildYeomanNpmPackage(outputRoot: string) {
const outputTemplatesRoot = path.join(outputRoot, 'app/templates'); const outputTemplatesRoot = path.join(outputRoot, 'app/templates');
rimraf.sync(outputTemplatesRoot); rimraf.sync(outputTemplatesRoot);
@@ -95,12 +116,11 @@ function buildYeomanNpmPackage(outputRoot: string) {
{ from: /.*\.csproj$/, to: 'tokenreplace-namePascalCase.csproj' } { from: /.*\.csproj$/, to: 'tokenreplace-namePascalCase.csproj' }
]; ];
const contentReplacements = [ const contentReplacements = [
// global.json items // Currently, there are none
{ from: /sdkVersionInjectedHere/, to: '<%= sdkVersion %>' }
]; ];
_.forEach(templates, (templateConfig, templateName) => { _.forEach(templates, (templateConfig, templateName) => {
const outputDir = path.join(outputTemplatesRoot, templateName); const outputDir = path.join(outputTemplatesRoot, templateName);
writeTemplate(templateConfig.dir, outputDir, contentReplacements, filenameReplacements, commonForceInclusionRegex); writeTemplate(templateConfig.dir, outputDir, contentReplacements, filenameReplacements);
}); });
// Also copy the generator files (that's the compiled .js files, plus all other non-.ts files) // Also copy the generator files (that's the compiled .js files, plus all other non-.ts files)
@@ -112,7 +132,17 @@ function buildYeomanNpmPackage(outputRoot: string) {
rimraf.sync(tempRoot); rimraf.sync(tempRoot);
} }
function buildDotNetNewNuGetPackage() { function buildDotNetNewNuGetPackages(outputDir: string) {
const dotNetPackageIds = _.values(dotNetPackages);
dotNetPackageIds.forEach(packageId => {
const dotNetNewNupkgPath = buildDotNetNewNuGetPackage(packageId);
// Move the .nupkg file to the output dir
fs.renameSync(dotNetNewNupkgPath, path.join(outputDir, path.basename(dotNetNewNupkgPath)));
});
}
function buildDotNetNewNuGetPackage(packageId: string) {
const outputRoot = './dist/dotnetnew'; const outputRoot = './dist/dotnetnew';
rimraf.sync(outputRoot); rimraf.sync(outputRoot);
@@ -125,8 +155,13 @@ function buildDotNetNewNuGetPackage() {
]; ];
const contentReplacements = []; const contentReplacements = [];
_.forEach(templates, (templateConfig, templateName) => { _.forEach(templates, (templateConfig, templateName) => {
// Only include templates matching the output package ID
if (templateConfig.dotNetPackageId !== packageId) {
return;
}
const templateOutputDir = path.join(outputRoot, 'Content', templateName); const templateOutputDir = path.join(outputRoot, 'Content', templateName);
writeTemplate(templateConfig.dir, templateOutputDir, contentReplacements, filenameReplacements, commonForceInclusionRegex); writeTemplate(templateConfig.dir, templateOutputDir, contentReplacements, filenameReplacements);
// Add the .template.config dir and its contents // Add the .template.config dir and its contents
const templateConfigDir = path.join(templateOutputDir, '.template.config'); const templateConfigDir = path.join(templateOutputDir, '.template.config');
@@ -134,10 +169,10 @@ function buildDotNetNewNuGetPackage() {
fs.writeFileSync(path.join(templateConfigDir, 'template.json'), JSON.stringify({ fs.writeFileSync(path.join(templateConfigDir, 'template.json'), JSON.stringify({
author: 'Microsoft', author: 'Microsoft',
classifications: ["Web", "MVC", "SPA"], classifications: ['Web', 'MVC', 'SPA'],
groupIdentity: `Microsoft.AspNetCore.SpaTemplates.${templateConfig.dotNetNewId}`, groupIdentity: `${packageId}.${templateConfig.dotNetNewId}`,
identity: `Microsoft.AspNetCore.SpaTemplates.${templateConfig.dotNetNewId}.CSharp`, identity: `${packageId}.${templateConfig.dotNetNewId}.CSharp`,
name: `MVC ASP.NET Core with ${templateConfig.displayName}`, name: `ASP.NET Core with ${templateConfig.displayName}`,
preferNameDirectory: true, preferNameDirectory: true,
primaryOutputs: [{ path: `${sourceProjectName}.csproj` }], primaryOutputs: [{ path: `${sourceProjectName}.csproj` }],
shortName: `${templateConfig.dotNetNewId.toLowerCase()}`, shortName: `${templateConfig.dotNetNewId.toLowerCase()}`,
@@ -148,46 +183,99 @@ function buildDotNetNewNuGetPackage() {
exclude: ['.template.config/**'] exclude: ['.template.config/**']
}], }],
symbols: { symbols: {
sdkVersion: { TargetFrameworkOverride: {
type: 'bind', type: 'parameter',
binding: 'dotnet-cli-version', description: 'Overrides the target framework',
replaces: 'sdkVersionInjectedHere' replaces: 'TargetFrameworkOverride',
datatype: 'string',
defaultValue: ''
}, },
Framework: { Framework: {
type: "parameter", type: 'parameter',
description: "The target framework for the project.", description: 'The target framework for the project.',
datatype: "choice", datatype: 'choice',
choices: [ choices: [
{ {
choice: "netcoreapp1.1", choice: 'netcoreapp2.0',
description: "Target netcoreapp1.1" description: 'Target netcoreapp2.0'
} }
], ],
defaultValue: "netcoreapp1.1" replaces: 'netcoreapp2.0',
defaultValue: 'netcoreapp2.0'
},
HostIdentifier: {
type: 'bind',
binding: 'HostIdentifier'
} }
}, },
tags: { language: 'C#', type: 'project' }, tags: { language: 'C#', type: 'project' },
postActions: [
/*
// Currently it doesn't appear to be possible to run `npm install` from a
// postAction, due to https://github.com/dotnet/templating/issues/849
// We will re-enable this when that bug is fixed.
{
condition: '(!skipRestore)',
description: 'Restores NPM packages required by this project.',
manualInstructions: [{ text: 'Run \'npm install\'' }],
actionId: '3A7C4B45-1F5D-4A30-959A-51B88E82B5D2',
args: { executable: 'npm', args: 'install' },
continueOnError: false
}
*/
{
// For the preview2 release, just display manual instructions instead.
// This is only applicable on the command line, because VS will restore
// NPM packages automatically by default.
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"' }]
}
],
}, null, 2)); }, null, 2));
fs.writeFileSync(path.join(templateConfigDir, 'dotnetcli.host.json'), JSON.stringify({ fs.writeFileSync(path.join(templateConfigDir, 'dotnetcli.host.json'), JSON.stringify({
symbolInfo: {} $schema: 'http://json.schemastore.org/dotnetcli.host',
symbolInfo: {
TargetFrameworkOverride: {
isHidden: 'true',
longName: 'target-framework-override',
shortName: ''
},
Framework: {
longName: 'framework'
}
}
}, null, 2)); }, null, 2));
const localisedNameId = templateConfig.localizationIdStart + 0;
const localisedDescId = templateConfig.localizationIdStart + 1;
fs.writeFileSync(path.join(templateConfigDir, 'vs-2017.3.host.json'), JSON.stringify({ fs.writeFileSync(path.join(templateConfigDir, 'vs-2017.3.host.json'), JSON.stringify({
name: { text: templateConfig.displayName }, $schema: 'http://json.schemastore.org/vs-2017.3.host',
description: { text: `Web application built with MVC ASP.NET Core and ${templateConfig.displayName}` }, name: { text: templateConfig.displayName, package: webToolsVSPackageGuid, id: localisedNameId.toString() },
order: 2000, description: { text: `A project template for creating an ASP.NET Core application with ${templateConfig.displayName}`, package: webToolsVSPackageGuid, id: localisedDescId.toString() },
order: 301,
icon: 'icon.png', icon: 'icon.png',
learnMoreLink: 'https://github.com/aspnet/JavaScriptServices', learnMoreLink: 'https://github.com/aspnet/JavaScriptServices',
uiFilters: [ 'oneaspnet' ] uiFilters: [ 'oneaspnet' ],
minFullFrameworkVersion: '4.6.1'
}, null, 2)); }, null, 2));
}); });
// Invoke NuGet to create the final package // Create the .nuspec file
const yeomanPackageVersion = JSON.parse(fs.readFileSync(path.join(yeomanGeneratorSource, 'package.json'), 'utf8')).version; const yeomanPackageVersion = JSON.parse(fs.readFileSync(path.join(yeomanGeneratorSource, 'package.json'), 'utf8')).version;
writeTemplate('./src/dotnetnew', outputRoot, [ const nuspecContentTemplate = fs.readFileSync(`./src/dotnetnew/${ packageId }.nuspec`);
{ from: /\{version\}/g, to: yeomanPackageVersion }, writeFileEnsuringDirExists(outputRoot,
], [], null); `${ packageId }.nuspec`,
applyContentReplacements(nuspecContentTemplate, [
{ from: /\{yeomanversion\}/g, to: yeomanPackageVersion },
{ from: /\{buildnumber\}/g, to: getBuildNumber() },
])
);
// Invoke NuGet to create the final package
const nugetExe = path.join(process.cwd(), './bin/NuGet.exe'); const nugetExe = path.join(process.cwd(), './bin/NuGet.exe');
const nugetStartInfo = { cwd: outputRoot, stdio: 'inherit' }; const nugetStartInfo = { cwd: outputRoot, stdio: 'inherit' };
if (isWindows) { if (isWindows) {
@@ -204,21 +292,7 @@ function buildDotNetNewNuGetPackage() {
return glob.sync(path.join(outputRoot, './*.nupkg'))[0]; return glob.sync(path.join(outputRoot, './*.nupkg'))[0];
} }
function runAllPrepublishScripts() { function runPrepublishScripts(rootDir: string, scripts: string[]) {
Object.getOwnPropertyNames(templates).forEach(templateKey => {
const templateInfo = templates[templateKey];
// First run standard prepublish steps
runScripts(templateInfo.dir, commonTemplatePrepublishSteps);
// Second, run any template-specific prepublish steps
if (templateInfo.prepublish) {
runScripts(templateInfo.dir, templateInfo.prepublish);
}
});
}
function runScripts(rootDir: string, scripts: string[]) {
console.log(`[Prepublish] In directory: ${ rootDir }`); console.log(`[Prepublish] In directory: ${ rootDir }`);
scripts.forEach(script => { scripts.forEach(script => {
console.log(`[Prepublish] Running: ${ script }`); console.log(`[Prepublish] Running: ${ script }`);
@@ -233,12 +307,8 @@ const yeomanOutputRoot = path.join(distDir, 'generator-aspnetcore-spa');
rimraf.sync(distDir); rimraf.sync(distDir);
mkdirp.sync(artifactsDir); mkdirp.sync(artifactsDir);
runAllPrepublishScripts();
buildYeomanNpmPackage(yeomanOutputRoot); buildYeomanNpmPackage(yeomanOutputRoot);
const dotNetNewNupkgPath = buildDotNetNewNuGetPackage(); buildDotNetNewNuGetPackages(artifactsDir);
// Move the .nupkg file to the artifacts dir
fs.renameSync(dotNetNewNupkgPath, path.join(artifactsDir, path.basename(dotNetNewNupkgPath)));
// Finally, create a .tar.gz file containing the built generator-aspnetcore-spa. // Finally, create a .tar.gz file containing the built generator-aspnetcore-spa.
// The CI system can treat this as the final built artifact. // The CI system can treat this as the final built artifact.

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>{version}</version> <version>{yeomanversion}</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>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Microsoft.DotNet.Web.Spa.ProjectTemplates</id>
<version>1.0.0-preview-{buildnumber}</version>
<description>Single Page Application templates for ASP.NET Core</description>
<authors>Microsoft</authors>
<language>en-US</language>
<projectUrl>https://github.com/aspnet/javascriptservices</projectUrl>
<licenseUrl>https://www.microsoft.com/web/webpi/eula/net_library_eula_enu.htm</licenseUrl>
<copyright>Copyright © Microsoft Corporation</copyright>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<packageTypes>
<packageType name="Template" />
</packageTypes>
</metadata>
</package>

View File

@@ -6,7 +6,6 @@ import * as glob from 'glob';
import * as semver from 'semver'; import * as semver from 'semver';
import * as chalk from 'chalk'; import * as chalk from 'chalk';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import npmWhich = require('npm-which');
const yosay = require('yosay'); const yosay = require('yosay');
const toPascalCase = require('to-pascal-case'); const toPascalCase = require('to-pascal-case');
const isWindows = /^win/.test(process.platform); const isWindows = /^win/.test(process.platform);
@@ -165,15 +164,6 @@ class MyGenerator extends yeoman.Base {
} }
installingDeps() { installingDeps() {
// If available, restore dependencies using Yarn instead of NPM
const yarnPath = getPathToExecutable('yarn');
if (!!yarnPath) {
this.log('Will restore NPM dependencies using \'yarn\' installed at ' + yarnPath);
this.npmInstall = (pkgs, options, cb) => {
return (this as any).runInstall(yarnPath, pkgs, options, cb);
};
}
this.installDependencies({ this.installDependencies({
npm: true, npm: true,
bower: false, bower: false,
@@ -186,14 +176,6 @@ class MyGenerator extends yeoman.Base {
} }
} }
function getPathToExecutable(executableName: string) {
try {
return npmWhich(__dirname).sync(executableName);
} catch(ex) {
return null;
}
}
function assertNpmVersionIsAtLeast(minVersion: string) { function assertNpmVersionIsAtLeast(minVersion: string) {
const runningVersion = execSync('npm -v').toString(); const runningVersion = execSync('npm -v').toString();
if (!semver.gte(runningVersion, minVersion, /* loose */ true)) { if (!semver.gte(runningVersion, minVersion, /* loose */ true)) {

View File

@@ -1,6 +1,6 @@
{ {
"name": "generator-aspnetcore-spa", "name": "generator-aspnetcore-spa",
"version": "0.9.3", "version": "0.9.4",
"description": "Single-Page App templates for ASP.NET Core", "description": "Single-Page App templates for ASP.NET Core",
"author": "Microsoft", "author": "Microsoft",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -21,7 +21,6 @@
"chalk": "^1.1.3", "chalk": "^1.1.3",
"glob": "^7.0.3", "glob": "^7.0.3",
"node-uuid": "^1.4.7", "node-uuid": "^1.4.7",
"npm-which": "^3.0.1",
"to-pascal-case": "^1.0.0", "to-pascal-case": "^1.0.0",
"yeoman-generator": "^0.20.2", "yeoman-generator": "^0.20.2",
"yeoman-option-or-prompt": "^1.0.2", "yeoman-option-or-prompt": "^1.0.2",

View File

@@ -1,7 +1,9 @@
<!-- This file may be overwritten by automation. Only values allowed here are VersionPrefix and VersionSuffix. --> <Project>
<Project>
<PropertyGroup> <PropertyGroup>
<VersionPrefix>2.0.0</VersionPrefix> <VersionPrefix>2.0.4</VersionPrefix>
<VersionSuffix>preview2</VersionSuffix> <VersionSuffix>rtm</VersionSuffix>
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' == 'rtm' ">$(VersionPrefix)</PackageVersion>
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' != 'rtm' ">$(VersionPrefix)-$(VersionSuffix)-final</PackageVersion>
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
</PropertyGroup> </PropertyGroup>
</Project> </Project>