mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-23 01:58:29 +00:00
Compare commits
53 Commits
example-us
...
581-isomor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c9892ab18 | ||
|
|
f8714e9653 | ||
|
|
9d5006107f | ||
|
|
c3316d0caa | ||
|
|
38441c6598 | ||
|
|
f8f42a53b2 | ||
|
|
a93f59c660 | ||
|
|
c377e6cd28 | ||
|
|
2a1435d9c1 | ||
|
|
6789dbc102 | ||
|
|
59526ff16a | ||
|
|
8c456aa0fc | ||
|
|
49bece54c2 | ||
|
|
0907379da8 | ||
|
|
fbb8054c2e | ||
|
|
70f59fc8ec | ||
|
|
e6cfe93e13 | ||
|
|
dad095b749 | ||
|
|
f31b96fe1e | ||
|
|
312885acde | ||
|
|
0763f1062b | ||
|
|
dcb819f644 | ||
|
|
f722dcf98d | ||
|
|
153c9848ca | ||
|
|
aefa578e04 | ||
|
|
953f370336 | ||
|
|
200d80627c | ||
|
|
994653a277 | ||
|
|
17b107bb42 | ||
|
|
67cd5f6093 | ||
|
|
06ccad4344 | ||
|
|
4fd19b4293 | ||
|
|
f34eb582ad | ||
|
|
4580412f73 | ||
|
|
a76b6e25d4 | ||
|
|
2135d77189 | ||
|
|
62b676174e | ||
|
|
67c2cfd84e | ||
|
|
6decb30681 | ||
|
|
33ed333f92 | ||
|
|
6c16ba3da7 | ||
|
|
495c95d227 | ||
|
|
4dd91a9b78 | ||
|
|
06335eeee1 | ||
|
|
eb98067e86 | ||
|
|
a25d1f4731 | ||
|
|
4c3834361e | ||
|
|
4cc61d79e2 | ||
|
|
1c4682e50d | ||
|
|
6545e11bf2 | ||
|
|
8d1cb3a537 | ||
|
|
8705a4b353 | ||
|
|
7c685c1354 |
@@ -1,5 +1,7 @@
|
||||
# JavaScriptServices
|
||||
|
||||
AppVeyor: [](https://ci.appveyor.com/project/aspnetci/javascriptservices/branch/dev)
|
||||
|
||||
This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
|
||||
|
||||
## What is this?
|
||||
|
||||
41
appveyor.yml
41
appveyor.yml
@@ -1,7 +1,44 @@
|
||||
init:
|
||||
- git config --global core.autocrlf true
|
||||
install:
|
||||
- ps: Install-Product node 6.9.2 x64
|
||||
# .NET Core SDK binaries
|
||||
# Download .NET Core SDK and add to PATH
|
||||
- ps: $urlCurrent = "https://go.microsoft.com/fwlink/?LinkID=834991"
|
||||
- ps: $urlPreview = "https://dotnetcli.blob.core.windows.net/dotnet/Sdk/rel-1.0.0/dotnet-dev-win-x64.latest.zip"
|
||||
- ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetsdk"
|
||||
- ps: mkdir $env:DOTNET_INSTALL_DIR -Force | Out-Null
|
||||
- ps: $tempFileCurrent = [System.IO.Path]::GetTempFileName()
|
||||
- ps: $tempFilePreview = [System.IO.Path]::GetTempFileName()
|
||||
- ps: (New-Object System.Net.WebClient).DownloadFile($urlCurrent, $tempFileCurrent)
|
||||
- ps: (New-Object System.Net.WebClient).DownloadFile($urlPreview, $tempFilePreview)
|
||||
- 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($tempFilePreview, $env:DOTNET_INSTALL_DIR + "preview")
|
||||
- ps: Copy-Item "${env:DOTNET_INSTALL_DIR}preview\*" "${env:DOTNET_INSTALL_DIR}" -Force -Recurse
|
||||
- ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path"
|
||||
build_script:
|
||||
- build.cmd verify
|
||||
- ps: Push-Location
|
||||
- cd templates/package-builder
|
||||
- npm install
|
||||
- npm run build
|
||||
- ps: Pop-Location
|
||||
artifacts:
|
||||
- path: templates\package-builder\dist\artifacts\generator-aspnetcore-spa.tar.gz
|
||||
name: generator-aspnetcore-spa
|
||||
# - build.cmd verify
|
||||
clone_depth: 1
|
||||
test: off
|
||||
test_script:
|
||||
- dotnet restore ./src
|
||||
- npm install -g selenium-standalone
|
||||
- selenium-standalone install
|
||||
# The nosys flag is needed for selenium to work on Appveyor
|
||||
- ps: Start-Process selenium-standalone 'start','--','-Djna.nosys=true'
|
||||
- ps: Push-Location
|
||||
- cd test
|
||||
- npm install
|
||||
- npm test
|
||||
on_finish :
|
||||
- ps: Pop-Location
|
||||
# After running tests, upload results to Appveyor
|
||||
- ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\test\tmp\junit\*.xml))
|
||||
deploy: off
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"projects": ["src"]
|
||||
"projects": ["src"],
|
||||
"sdk": { "version": "1.0.0-preview2-1-003177" }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
var VERSION='0.1'
|
||||
var FULL_VERSION='0.1'
|
||||
var AUTHORS='Microsoft Open Technologies, Inc.'
|
||||
var SAMPLES_PROJECT_GLOB='templates/*/project.json'
|
||||
var TEST_PROJECT_GLOB='templates/*/project.json'
|
||||
var SAMPLES_PROJECT_GLOB='samples/misc/*/project.json'
|
||||
|
||||
@{
|
||||
// The build quality values are set in each of the project.json files
|
||||
BuildQuality = "";
|
||||
}
|
||||
|
||||
use-standard-lifecycle
|
||||
k-standard-goals
|
||||
|
||||
@@ -38,7 +38,8 @@ namespace Webpack.ActionResults
|
||||
request.GetEncodedUrl(),
|
||||
request.Path + request.QueryString.Value,
|
||||
_dataToSupply,
|
||||
/* timeoutMilliseconds */ 30000
|
||||
/* timeoutMilliseconds */ 30000,
|
||||
/* requestPathBase */ "/"
|
||||
);
|
||||
|
||||
response.ContentType = "text/html";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer } from 'redux';
|
||||
import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer, Store as ReduxStore } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import { routerReducer } from 'react-router-redux';
|
||||
import * as Store from './store';
|
||||
@@ -16,7 +16,7 @@ export default function configureStore(initialState?: Store.ApplicationState) {
|
||||
|
||||
// Combine all reducers and instantiate the app-wide store instance
|
||||
const allReducers = buildRootReducer(Store.reducers);
|
||||
const store = createStoreWithMiddleware(allReducers, initialState) as Redux.Store<Store.ApplicationState>;
|
||||
const store = createStoreWithMiddleware(allReducers, initialState) as ReduxStore<Store.ApplicationState>;
|
||||
|
||||
// Enable Webpack hot module replacement for reducers
|
||||
if (module.hot) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "music-store",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@types/history": "^2.0.0",
|
||||
"@types/react": "^0.14.29",
|
||||
"@types/react-bootstrap": "^0.0.35",
|
||||
"@types/react-dom": "^0.14.14",
|
||||
@@ -41,7 +42,7 @@
|
||||
"redux-typed": "^2.0.0",
|
||||
"style-loader": "^0.13.0",
|
||||
"ts-loader": "^0.8.1",
|
||||
"typescript": "2.0.3",
|
||||
"typescript": "^2.0.3",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.13.2",
|
||||
"webpack-hot-middleware": "^2.12.2",
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "node",
|
||||
"target": "es6",
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"types": [ "webpack-env", "whatwg-fetch" ]
|
||||
"types": [ "webpack-env", "whatwg-fetch" ],
|
||||
"paths": {
|
||||
// Fix "Duplicate identifier" errors caused by multiple dependencies fetching their own copies of type definitions.
|
||||
// We tell TypeScript which type definitions module to treat as the canonical one (instead of combining all of them).
|
||||
"history": ["./node_modules/@types/history/index"],
|
||||
"redux": ["./node_modules/@types/redux/index"],
|
||||
"react": ["./node_modules/@types/react/index"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
/node_modules/
|
||||
/dist/
|
||||
/bundles/
|
||||
@@ -1,4 +0,0 @@
|
||||
/src/
|
||||
/tsconfig.json
|
||||
/build.js
|
||||
/typings/
|
||||
@@ -1,33 +0,0 @@
|
||||
// -------------
|
||||
// No need to invoke this directly. To run a build, execute:
|
||||
// npm run prepublish
|
||||
// -------------
|
||||
|
||||
var Builder = require('systemjs-builder');
|
||||
var builder = new Builder('./');
|
||||
builder.config({
|
||||
defaultJSExtensions: true,
|
||||
paths: {
|
||||
'angular2-aspnet': 'dist/Exports',
|
||||
'angular2-aspnet/*': 'dist/*'
|
||||
},
|
||||
meta: {
|
||||
'angular2/*': { build: false },
|
||||
'rxjs/*': { build: false }
|
||||
}
|
||||
});
|
||||
|
||||
var entryPoint = 'dist/Exports';
|
||||
var tasks = [
|
||||
builder.bundle(entryPoint, './bundles/angular2-aspnet.js'),
|
||||
builder.bundle(entryPoint, './bundles/angular2-aspnet.min.js', { minify: true })
|
||||
];
|
||||
|
||||
Promise.all(tasks)
|
||||
.then(function() {
|
||||
console.log('Build complete');
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Build error');
|
||||
console.error(err);
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "angular2-aspnet",
|
||||
"version": "0.0.6",
|
||||
"description": "Helpers for Angular 2 apps built on ASP.NET",
|
||||
"main": "./dist/Exports",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"prepublish": "rimraf *.d.ts dist/*.d.ts && tsc && node build.js"
|
||||
},
|
||||
"typings": "dist/Exports",
|
||||
"author": "Microsoft",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/aspnet/JavaScriptServices/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aspnet/JavaScriptServices.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"angular2": "2.0.0-beta.13",
|
||||
"reflect-metadata": "0.1.2",
|
||||
"rxjs": "5.0.0-beta.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"es6-shim": "^0.35.0",
|
||||
"rimraf": "^2.5.4",
|
||||
"systemjs-builder": "^0.14.11",
|
||||
"typescript": "^2.0.0",
|
||||
"zone.js": "^0.6.10"
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
If you just want to use this package, then you *don't have to build it*. Instead, just grab the prebuilt package from NPM:
|
||||
|
||||
npm install angular2-aspnet
|
||||
|
||||
The rest of this file is notes for anyone contributing to this package itself.
|
||||
|
||||
## How to build
|
||||
|
||||
Run the following:
|
||||
|
||||
npm install
|
||||
npm run prepublish
|
||||
|
||||
Requirements:
|
||||
|
||||
* Node, NPM
|
||||
* `tsc` installed globally (via `npm install -g typescript`)
|
||||
|
||||
## Project structure
|
||||
|
||||
This package is intended to be consumable both on the server in Node.js, and on the client. Also, it's written in TypeScript,
|
||||
which neither of those environments knows natively, but the TypeScript type definitions need to get delivered with the package
|
||||
so that developers get a good IDE experience when consuming it.
|
||||
|
||||
The build process is therefore:
|
||||
|
||||
1. Compile the TypeScript to produce the development-time (.d.ts) and server-side (.js) artifacts
|
||||
|
||||
`tsc` reads `tsconfig.json` and is instructed to compile all the `.ts` files in `src/`. It produces a corresponding
|
||||
structure of `.js` and `.d.ts` files in `dist/`.
|
||||
|
||||
When a developer consumes the resulting package (via `npm install angular2-aspnet`),
|
||||
|
||||
- No additional copy of `angular2` will be installed, because this package's dependency on it is declared as a
|
||||
`peerDependency`. This means it will work with whatever (compatible) version of `angular2` is already installed.
|
||||
- At runtime inside Node.js, the `main` configuration in `package.json` means the developer can use a standard
|
||||
`import` statement to consume this package (i.e., `import * from 'angular2-aspnet';` in either JS or TS files).
|
||||
- At development time inside an IDE such as Visual Studio Code, the `typings` configuration in `package.json` means
|
||||
the IDE will use the corresponding `.d.ts` file as type metadata for the variable imported that way.
|
||||
|
||||
2. Use the SystemJS builder to produce the client-side artifacts
|
||||
|
||||
`build.js` uses the SystemJS Builder API to combine files in `dist/` into `.js` files ready for use in client-side
|
||||
SystemJS environments, and puts them in `bundles/`. The bundle files contain `System.register` calls so that any
|
||||
other part of your client-side code that tries to import `angular2-aspnet` via SystemJS will get that module at runtime.
|
||||
|
||||
To make it work in an application:
|
||||
- Set up some build step that copies your chosen bundle file from `bundles/` to some location where it will
|
||||
be served to the client
|
||||
- Below your `<script>` tag that loads SystemJS itself, and above the `<script>` tag that makes the first call to
|
||||
`System.import`, have a `<script>` tag that loads the desired `angular2-aspnet.js` bundle file
|
||||
|
||||
For an example, see https://github.com/aspnet/NodeServices/tree/master/samples/angular/MusicStore
|
||||
|
||||
Of course, you can also bundle the `angular2-aspnet.js` file into a larger SystemJS bundle if you want to combine
|
||||
it with the rest of the code in your application.
|
||||
|
||||
Currently, this build system does *not* attempt to send sourcemaps of the original TypeScript to the client. This
|
||||
could be added if a strong need emerges.
|
||||
@@ -1,59 +0,0 @@
|
||||
import { provide, Injectable, Provider } from 'angular2/core';
|
||||
import { Connection, ConnectionBackend, Http, XHRBackend, RequestOptions, Request, RequestMethod, Response, ResponseOptions, ReadyState } from 'angular2/http';
|
||||
|
||||
@Injectable()
|
||||
export class CachePrimedConnectionBackend extends ConnectionBackend {
|
||||
private _preCachedResponses: PreCachedResponses;
|
||||
|
||||
constructor(private _underlyingBackend: ConnectionBackend, private _baseResponseOptions: ResponseOptions) {
|
||||
super();
|
||||
this._preCachedResponses = (<any>window).__preCachedResponses || {};
|
||||
}
|
||||
|
||||
public createConnection(request: Request): Connection {
|
||||
let cacheKey = request.url;
|
||||
if (request.method === RequestMethod.Get && this._preCachedResponses.hasOwnProperty(cacheKey)) {
|
||||
return new CacheHitConnection(request, this._preCachedResponses[cacheKey], this._baseResponseOptions);
|
||||
} else {
|
||||
return this._underlyingBackend.createConnection(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CacheHitConnection implements Connection {
|
||||
readyState: ReadyState;
|
||||
request: Request;
|
||||
response: any;
|
||||
|
||||
constructor (req: Request, cachedResponse: PreCachedResponse, baseResponseOptions: ResponseOptions) {
|
||||
this.request = req;
|
||||
this.readyState = ReadyState.Done;
|
||||
|
||||
// Workaround for difficulty consuming CommonJS default exports in TypeScript. Note that it has to be a dynamic
|
||||
// 'require', and not an 'import' statement, because the module isn't available on the server.
|
||||
let obsCtor: any = require('rxjs/Observable').Observable;
|
||||
this.response = new obsCtor(responseObserver => {
|
||||
let response = new Response(new ResponseOptions({ body: cachedResponse.body, status: cachedResponse.statusCode }));
|
||||
responseObserver.next(response);
|
||||
responseObserver.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare var require: any; // Part of the workaround mentioned below. Can remove this after updating Angular.
|
||||
|
||||
interface PreCachedResponses {
|
||||
[url: string]: PreCachedResponse;
|
||||
}
|
||||
|
||||
interface PreCachedResponse {
|
||||
statusCode: number;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export const CACHE_PRIMED_HTTP_PROVIDERS = [
|
||||
provide(Http, {
|
||||
useFactory: (xhrBackend, requestOptions, responseOptions) => new Http(new CachePrimedConnectionBackend(xhrBackend, responseOptions), requestOptions),
|
||||
deps: [XHRBackend, RequestOptions, ResponseOptions]
|
||||
}),
|
||||
];
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './CachePrimedHttp';
|
||||
export * from './Validation';
|
||||
@@ -1,34 +0,0 @@
|
||||
import { ControlGroup } from 'angular2/common';
|
||||
import { Response } from 'angular2/http';
|
||||
|
||||
export class Validation {
|
||||
|
||||
public static showValidationErrors(response: ValidationErrorResult | Response, controlGroup: ControlGroup): void {
|
||||
if (response instanceof Response) {
|
||||
var httpResponse = <Response>response;
|
||||
response = <ValidationErrorResult>(httpResponse.json());
|
||||
}
|
||||
|
||||
// It's not yet clear whether this is a legitimate and supported use of the ng.ControlGroup API.
|
||||
// Need feedback from the Angular 2 team on whether there's a better way.
|
||||
var errors = <ValidationErrorResult>response;
|
||||
Object.keys(errors || {}).forEach(key => {
|
||||
errors[key].forEach(errorMessage => {
|
||||
// If there's a specific control for this key, then use it. Otherwise associate the error
|
||||
// with the whole control group.
|
||||
var control = controlGroup.controls[key] || controlGroup;
|
||||
|
||||
// This is rough. Need to find out if there's a better way, or if this is even supported.
|
||||
if (!control.errors) {
|
||||
(<any>control)._errors = {};
|
||||
}
|
||||
|
||||
control.errors[errorMessage] = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface ValidationErrorResult {
|
||||
[propertyName: string]: string[];
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"sourceMap": false,
|
||||
"declaration": true,
|
||||
"experimentalDecorators": true,
|
||||
"noLib": false,
|
||||
"outDir": "./dist",
|
||||
"lib": ["es2015", "dom"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"description": "Helpers for building Angular 2 applications on ASP.NET Core.",
|
||||
"version": "1.1.0-*",
|
||||
"version": "1.1.0-beta2-*",
|
||||
"packOptions": {
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: AssemblyMetadata("Serviceable", "True")]
|
||||
[assembly: NeutralResourcesLanguage("en-us")]
|
||||
[assembly: AssemblyCompany("Microsoft Corporation.")]
|
||||
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: AssemblyProduct("Microsoft ASP.NET Core")]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"description": "Socket-based RPC for Microsoft.AspNetCore.NodeServices",
|
||||
"version": "1.1.0-*",
|
||||
"version": "1.1.0-beta2-*",
|
||||
"packOptions": {
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
// Signal to the NodeServices base class that we're ready to accept invocations
|
||||
console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
|
||||
});
|
||||
ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid));
|
||||
ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid), /* ignoreSigint */ true);
|
||||
function readRequestBodyAsJson(request, callback) {
|
||||
var requestBodyAsString = '';
|
||||
request.on('data', function (chunk) { requestBodyAsString += chunk; });
|
||||
@@ -255,7 +255,7 @@
|
||||
*/
|
||||
"use strict";
|
||||
var pollIntervalMs = 1000;
|
||||
function exitWhenParentExits(parentPid) {
|
||||
function exitWhenParentExits(parentPid, ignoreSigint) {
|
||||
setInterval(function () {
|
||||
if (!processExists(parentPid)) {
|
||||
// Can't log anything at this point, because out stdout was connected to the parent,
|
||||
@@ -263,6 +263,24 @@
|
||||
process.exit();
|
||||
}
|
||||
}, pollIntervalMs);
|
||||
if (ignoreSigint) {
|
||||
// Pressing ctrl+c in the terminal sends a SIGINT to all processes in the foreground process tree.
|
||||
// By default, the Node process would then exit before the .NET process, because ASP.NET implements
|
||||
// a delayed shutdown to allow ongoing requests to complete.
|
||||
//
|
||||
// This is problematic, because if Node exits first, the CopyToAsync code in ConditionalProxyMiddleware
|
||||
// will experience a read fault, and logs a huge load of errors. Fortunately, since the Node process is
|
||||
// already set up to shut itself down if it detects the .NET process is terminated, all we have to do is
|
||||
// ignore the SIGINT. The Node process will then terminate automatically after the .NET process does.
|
||||
//
|
||||
// A better solution would be to have WebpackDevMiddleware listen for SIGINT and gracefully close any
|
||||
// ongoing EventSource connections before letting the Node process exit, independently of the .NET
|
||||
// process exiting. However, doing this well in general is very nontrivial (see all the discussion at
|
||||
// https://github.com/nodejs/node/issues/2642).
|
||||
process.on('SIGINT', function () {
|
||||
console.log('Received SIGINT. Waiting for .NET process to exit...');
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.exitWhenParentExits = exitWhenParentExits;
|
||||
function processExists(pid) {
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
||||
|
||||
case "application/octet-stream":
|
||||
// Streamed responses have to be received as System.IO.Stream instances
|
||||
if (typeof(T) != typeof(Stream))
|
||||
if (typeof(T) != typeof(Stream) && typeof(T) != typeof(object))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Node module responded with binary stream. This cannot be converted to the requested generic type: " +
|
||||
@@ -136,4 +136,4 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ If you haven't yet installed node-inspector, you can do so as follows:
|
||||
}
|
||||
else
|
||||
{
|
||||
debuggingArgs = string.Empty;
|
||||
debuggingArgs = string.Empty;
|
||||
}
|
||||
|
||||
var thisProcessPid = Process.GetCurrentProcess().Id;
|
||||
@@ -325,17 +325,26 @@ If you haven't yet installed node-inspector, you can do so as follows:
|
||||
|
||||
private static Process LaunchNodeProcess(ProcessStartInfo startInfo)
|
||||
{
|
||||
var process = Process.Start(startInfo);
|
||||
try {
|
||||
var process = Process.Start(startInfo);
|
||||
|
||||
// On Mac at least, a killed child process is left open as a zombie until the parent
|
||||
// captures its exit code. We don't need the exit code for this process, and don't want
|
||||
// to use process.WaitForExit() explicitly (we'd have to block the thread until it really
|
||||
// has exited), but we don't want to leave zombies lying around either. It's sufficient
|
||||
// to use process.EnableRaisingEvents so that .NET will grab the exit code and let the
|
||||
// zombie be cleaned away without having to block our thread.
|
||||
process.EnableRaisingEvents = true;
|
||||
// On Mac at least, a killed child process is left open as a zombie until the parent
|
||||
// captures its exit code. We don't need the exit code for this process, and don't want
|
||||
// to use process.WaitForExit() explicitly (we'd have to block the thread until it really
|
||||
// has exited), but we don't want to leave zombies lying around either. It's sufficient
|
||||
// to use process.EnableRaisingEvents so that .NET will grab the exit code and let the
|
||||
// zombie be cleaned away without having to block our thread.
|
||||
process.EnableRaisingEvents = true;
|
||||
|
||||
return process;
|
||||
return process;
|
||||
} catch (Exception ex) {
|
||||
var message = "Failed to start Node process. To resolve this:.\n\n"
|
||||
+ "[1] Ensure that Node.js is installed and can be found in one of the PATH directories.\n"
|
||||
+ $" Current PATH enviroment variable is: { Environment.GetEnvironmentVariable("PATH") }\n"
|
||||
+ " Make sure the Node executable is in one of those directories, or update your PATH.\n\n"
|
||||
+ "[2] See the InnerException for further details of the cause.";
|
||||
throw new InvalidOperationException(message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static string UnencodeNewlines(string str)
|
||||
|
||||
@@ -67,7 +67,7 @@ public async Task<IActionResult> MyAction([FromServices] INodeServices nodeServi
|
||||
}
|
||||
```
|
||||
|
||||
Of course, you also need to supply the Node.js code you want to invoke. Create a file called `addNumber.js` at the root of your ASP.NET Core application, and add the following code:
|
||||
Of course, you also need to supply the Node.js code you want to invoke. Create a file called `addNumbers.js` at the root of your ASP.NET Core application, and add the following code:
|
||||
|
||||
```javascript
|
||||
module.exports = function (callback, first, second) {
|
||||
@@ -333,25 +333,23 @@ People have asked about using [VroomJS](https://github.com/fogzot/vroomjs) as a
|
||||
|
||||
### Built-in hosting models
|
||||
|
||||
Normally, you can just use the default hosting model, and not worry about it. But if you have some special requirements, select a hosting model by setting the `HostingModel` property on the `options` object in `AddNodeServices`. Example:
|
||||
Normally, you can just use the default hosting model, and not worry about it. But if you have some special requirements, you can write your own hosting model, or reference a package that supplies one.
|
||||
|
||||
For example, you could use the 'socket' hosting model. It performs RPC between .NET and Node.js using a fast, low-level binary channel rather than the default HTTP transport. To do this, first install the NuGet package `Microsoft.AspNetCore.NodeServices.Sockets`. Then, at the top of your `Startup.cs` file, add:
|
||||
|
||||
```csharp
|
||||
services.AddNodeServices(options =>
|
||||
{
|
||||
options.HostingModel = NodeHostingModel.Socket;
|
||||
using Microsoft.AspNetCore.NodeServices.Sockets;
|
||||
```
|
||||
|
||||
...then in your `Startup.cs` file's `ConfigureServices` method, you can configure:
|
||||
|
||||
```csharp
|
||||
services.AddNodeServices(options => {
|
||||
options.UseSocketHosting();
|
||||
});
|
||||
```
|
||||
|
||||
**Available hosting models**
|
||||
|
||||
* `Socket`
|
||||
* Launches Node as a separate process, and communicates with it using named pipes (on Windows) or domain sockets (on Linux / OS X).
|
||||
* This is faster than `Http` because it uses a low-level binary protocol with very low overhead. It retains one continuous connection for the whole lifetime of the Node instance, so it doesn't have to keep waiting for new connections to open.
|
||||
* `Http` (default)
|
||||
* Launches Node as a separate process, and communicates with it by making HTTP requests.
|
||||
* This primarily exists because it was implemented before `Socket`, but there's no particular reason to use it now that `Socket` is available. It could theoretically be useful if you wanted to run Node instances on separate servers (though there isn't currently any built-in API for configuring that).
|
||||
|
||||
The default transport may change from `Http` to `Socket` in the near future, because it's faster.
|
||||
Now when you run your application, it will use the socket-based hosting and transport mechanism. In the past, the socket transport was faster than HTTP, but since .NET Core 1.1 improved the performance of `HttpClient` there isn't really any speed difference any more, so there's no longer any significant advantage to using `Microsoft.AspNetCore.NodeServices.Sockets`.
|
||||
|
||||
### Custom hosting models
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ server.listen(requestedPortOrZero, 'localhost', function () {
|
||||
console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
|
||||
});
|
||||
|
||||
exitWhenParentExits(parseInt(parsedArgs.parentPid));
|
||||
exitWhenParentExits(parseInt(parsedArgs.parentPid), /* ignoreSigint */ true);
|
||||
|
||||
function readRequestBodyAsJson(request, callback) {
|
||||
let requestBodyAsString = '';
|
||||
|
||||
@@ -34,7 +34,7 @@ to check whether the parent PID is still running. So that's what we do here.
|
||||
|
||||
const pollIntervalMs = 1000;
|
||||
|
||||
export function exitWhenParentExits(parentPid: number) {
|
||||
export function exitWhenParentExits(parentPid: number, ignoreSigint: boolean) {
|
||||
setInterval(() => {
|
||||
if (!processExists(parentPid)) {
|
||||
// Can't log anything at this point, because out stdout was connected to the parent,
|
||||
@@ -42,6 +42,25 @@ export function exitWhenParentExits(parentPid: number) {
|
||||
process.exit();
|
||||
}
|
||||
}, pollIntervalMs);
|
||||
|
||||
if (ignoreSigint) {
|
||||
// Pressing ctrl+c in the terminal sends a SIGINT to all processes in the foreground process tree.
|
||||
// By default, the Node process would then exit before the .NET process, because ASP.NET implements
|
||||
// a delayed shutdown to allow ongoing requests to complete.
|
||||
//
|
||||
// This is problematic, because if Node exits first, the CopyToAsync code in ConditionalProxyMiddleware
|
||||
// will experience a read fault, and logs a huge load of errors. Fortunately, since the Node process is
|
||||
// already set up to shut itself down if it detects the .NET process is terminated, all we have to do is
|
||||
// ignore the SIGINT. The Node process will then terminate automatically after the .NET process does.
|
||||
//
|
||||
// A better solution would be to have WebpackDevMiddleware listen for SIGINT and gracefully close any
|
||||
// ongoing EventSource connections before letting the Node process exit, independently of the .NET
|
||||
// process exiting. However, doing this well in general is very nontrivial (see all the discussion at
|
||||
// https://github.com/nodejs/node/issues/2642).
|
||||
process.on('SIGINT', () => {
|
||||
console.log('Received SIGINT. Waiting for .NET process to exit...');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function processExists(pid: number) {
|
||||
@@ -56,7 +75,7 @@ function processExists(pid: number) {
|
||||
if (ex.code === 'EPERM') {
|
||||
throw new Error(`Attempted to check whether process ${pid} was running, but got a permissions error.`);
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"description": "Invoke Node.js modules at runtime in ASP.NET Core applications.",
|
||||
"version": "1.1.0-*",
|
||||
"version": "1.1.0-rc1-*",
|
||||
"packOptions": {
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
/node_modules/
|
||||
/*.js
|
||||
/*.d.ts
|
||||
@@ -1,3 +0,0 @@
|
||||
!/*.js
|
||||
!/*.d.ts
|
||||
/typings/
|
||||
@@ -1,12 +0,0 @@
|
||||
Copyright (c) .NET Foundation. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
these files except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
@@ -1 +0,0 @@
|
||||
TODO
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "redux-typed",
|
||||
"version": "2.0.0",
|
||||
"description": "Helpers for building React+Redux apps with strong TypeScript type checking everywhere",
|
||||
"main": "main.js",
|
||||
"typings": "main.d.ts",
|
||||
"scripts": {
|
||||
"prepublish": "rimraf *.d.ts && tsc && echo 'Finished building NPM package \"redux-typed\"'",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Microsoft",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/aspnet/JavaScriptServices/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aspnet/JavaScriptServices.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^0.14.38",
|
||||
"@types/react-redux": "^4.4.32",
|
||||
"@types/redux": "^3.5.30",
|
||||
"rimraf": "^2.5.4",
|
||||
"typescript": "^2.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
|
||||
if (typeof (<any>Object).assign != 'function') {
|
||||
(function () {
|
||||
(<any>Object).assign = function (target) {
|
||||
'use strict';
|
||||
if (target === undefined || target === null) {
|
||||
throw new TypeError('Cannot convert undefined or null to object');
|
||||
}
|
||||
|
||||
var output = Object(target);
|
||||
for (var index = 1; index < arguments.length; index++) {
|
||||
var source = arguments[index];
|
||||
if (source !== undefined && source !== null) {
|
||||
for (var nextKey in source) {
|
||||
if (source.hasOwnProperty(nextKey)) {
|
||||
output[nextKey] = source[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
})();
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Credits for the type detection technique: http://www.bluewire-technologies.com/2015/redux-actions-for-typescript/
|
||||
import * as React from 'react';
|
||||
import { Dispatch } from 'redux';
|
||||
import './ObjectAssignPolyfill';
|
||||
|
||||
export interface ActionClass<T extends Action> {
|
||||
prototype: T;
|
||||
}
|
||||
|
||||
export function typeName(name: string) {
|
||||
return function<T extends Action>(actionClass: ActionClass<T>) {
|
||||
// Although we could determine the type name using actionClass.prototype.constructor.name,
|
||||
// it's dangerous to do that because minifiers may interfere with it, and then serialized state
|
||||
// might not have the expected meaning after a recompile. So we explicitly ask for a name string.
|
||||
actionClass.prototype.type = name;
|
||||
}
|
||||
}
|
||||
|
||||
export function isActionType<T extends Action>(action: Action, actionClass: ActionClass<T>): action is T {
|
||||
return action.type == actionClass.prototype.type;
|
||||
}
|
||||
|
||||
// Middleware for transforming Typed Actions into plain actions
|
||||
export const typedToPlain = (store: any) => (next: any) => (action: any) => {
|
||||
next((<any>Object).assign({}, action));
|
||||
};
|
||||
|
||||
export abstract class Action {
|
||||
type: string;
|
||||
constructor() {
|
||||
// Make it an own-property (not a prototype property) so that it's included when JSON-serializing
|
||||
this.type = this.type;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Reducer<TState> extends Function {
|
||||
(state: TState, action: Action): TState;
|
||||
}
|
||||
|
||||
export interface ActionCreatorGeneric<TState> extends Function {
|
||||
(dispatch: Dispatch<TState>, getState: () => TState): any;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { connect as nativeConnect } from 'react-redux';
|
||||
|
||||
export type ReactComponentClass<T, S> = new(props: T) => React.Component<T, S>;
|
||||
export class ComponentBuilder<TOwnProps, TActions, TExternalProps> {
|
||||
constructor(private stateToProps: (appState: any) => TOwnProps, private actionCreators: TActions) {
|
||||
}
|
||||
|
||||
public withExternalProps<TAddExternalProps>() {
|
||||
return this as any as ComponentBuilder<TOwnProps, TActions, TAddExternalProps>;
|
||||
}
|
||||
|
||||
public get allProps(): TOwnProps & TActions & TExternalProps { return null; }
|
||||
|
||||
public connect<TState>(componentClass: ReactComponentClass<TOwnProps & TActions & TExternalProps, TState>): ReactComponentClass<TExternalProps, TState> {
|
||||
return nativeConnect(this.stateToProps, this.actionCreators as any)(componentClass) as any;
|
||||
}
|
||||
}
|
||||
|
||||
export function provide<TOwnProps, TActions>(stateToProps: (appState: any) => TOwnProps, actionCreators: TActions) {
|
||||
return new ComponentBuilder<TOwnProps, TActions, {}>(stateToProps, actionCreators);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './StrongActions';
|
||||
export * from './StrongProvide';
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"declaration": true,
|
||||
"outDir": ".",
|
||||
"lib": ["es2015", "dom"]
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"description": "Helpers for building React applications on ASP.NET Core.",
|
||||
"version": "1.1.0-*",
|
||||
"version": "1.1.0-beta2-*",
|
||||
"packOptions": {
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -538,7 +538,7 @@ module.exports = {
|
||||
|
||||
output: {
|
||||
path: path.join(__dirname, 'wwwroot', 'dist'),
|
||||
publicPath: '/dist',
|
||||
publicPath: '/dist/',
|
||||
filename: '[name].js'
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "aspnet-webpack",
|
||||
"version": "1.0.25",
|
||||
"version": "1.0.26",
|
||||
"description": "Helpers for using Webpack in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -108,7 +108,7 @@ function attachWebpackDevMiddleware(app: any, webpackConfig: webpack.Configurati
|
||||
// file on disk wouldn't match the file served to the browser, and the source map line numbers wouldn't
|
||||
// match up. Breakpoints would either not be hit, or would hit the wrong lines.
|
||||
(compiler as any).plugin('done', stats => {
|
||||
copyRecursiveToRealFsSync(compiler.outputFileSystem, '/', [/\.hot-update\.(js|json)$/]);
|
||||
copyRecursiveToRealFsSync(compiler.outputFileSystem, '/', [/\.hot-update\.(js|json|js\.map)$/]);
|
||||
});
|
||||
|
||||
if (enableHotModuleReplacement) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"description": "Helpers for building single-page applications on ASP.NET MVC Core",
|
||||
"version": "1.1.0-*",
|
||||
"version": "1.1.0-rc1-*",
|
||||
"packOptions": {
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
2
templates/Angular2Spa/.deployment
Normal file
2
templates/Angular2Spa/.deployment
Normal file
@@ -0,0 +1,2 @@
|
||||
[config]
|
||||
SCM_SCRIPT_GENERATOR_ARGS=--aspNetCore project.json
|
||||
90
templates/Angular2Spa/Angular2Spa.csproj
Normal file
90
templates/Angular2Spa/Angular2Spa.csproj
Normal file
@@ -0,0 +1,90 @@
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
|
||||
<GlobalExclude>$(GlobalExclude);bin\**;obj\**;node_modules\**;**\*.user;**\*.suo;**\*.*proj;.git\**</GlobalExclude>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="**\*" Exclude="$(GlobalExclude)"/>
|
||||
<Compile Include="**\*.cs" Exclude="$(GlobalExclude)" />
|
||||
<EmbeddedResource Include="**\*.resx" Exclude="$(GlobalExclude)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.App">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Web">
|
||||
<Version>1.0.0-alpha-20161104-2-112</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Tools">
|
||||
<Version>1.1.0-preview4-final</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices">
|
||||
<Version>1.1.0-*</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
<!-- Files not to show in IDE -->
|
||||
<None Remove=".bowerrc" />
|
||||
<None Remove="yarn.lock" />
|
||||
|
||||
<!-- Files not to publish (note that the 'dist' subfolders are re-added below) -->
|
||||
<Content Remove="ClientApp\**" />
|
||||
</ItemGroup>
|
||||
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">
|
||||
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
|
||||
<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 --env.prod" />
|
||||
|
||||
<!-- Include the newly-built files in the publish output -->
|
||||
<ItemGroup>
|
||||
<DistFiles Include="wwwroot\dist\**; ClientApp\dist\**" />
|
||||
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>%(DistFiles.Identity)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</ResolvedFileToPublish>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -5,7 +5,7 @@ import { AppComponent } from './components/app/app.component'
|
||||
import { NavMenuComponent } from './components/navmenu/navmenu.component';
|
||||
import { HomeComponent } from './components/home/home.component';
|
||||
import { FetchDataComponent } from './components/fetchdata/fetchdata.component';
|
||||
import { CounterComponent } from 'ClientApp/app/components/counter/counter.component';
|
||||
import { CounterComponent } from './components/counter/counter.component';
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [ AppComponent ],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<h2>Counter</h2>
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p>This is a simple example of an Angular 2 component.</p>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('Counter component', () => {
|
||||
});
|
||||
|
||||
it('should display a title', async(() => {
|
||||
const titleText = fixture.nativeElement.querySelector('h2').textContent;
|
||||
const titleText = fixture.nativeElement.querySelector('h1').textContent;
|
||||
expect(titleText).toEqual('Counter');
|
||||
}));
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ module.exports = function (config) {
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
mime: { 'application/javascript': ['ts','tsx'] },
|
||||
singleRun: false,
|
||||
webpack: require('../../webpack.config.js').filter(config => config.target !== 'node'), // Test against client bundle, because tests run in a browser
|
||||
webpackMiddleware: { stats: 'errors-only' }
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"es6-shim": "^0.35.1",
|
||||
"event-source-polyfill": "^0.0.7",
|
||||
"expose-loader": "^0.7.1",
|
||||
"extract-text-webpack-plugin": "^2.0.0-beta",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"file-loader": "^0.9.0",
|
||||
"html-loader": "^0.4.4",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
@@ -39,10 +39,10 @@
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
"style-loader": "^0.13.1",
|
||||
"to-string-loader": "^1.1.5",
|
||||
"ts-loader": "^1.3.0",
|
||||
"typescript": "^2.1.0",
|
||||
"ts-loader": "^0.8.2",
|
||||
"typescript": "^2.0.3",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^2.1.0-beta",
|
||||
"webpack": "^1.13.2",
|
||||
"webpack-hot-middleware": "^2.12.2",
|
||||
"webpack-merge": "^0.14.1",
|
||||
"zone.js": "^0.6.25"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
------------------------------------------------------------------
|
||||
Don't delete this file. Do include it in your source control repo.
|
||||
------------------------------------------------------------------
|
||||
|
||||
This file exists as a workaround for https://github.com/dotnet/cli/issues/1396
|
||||
('dotnet publish' does not publish any directories that didn't exist or were
|
||||
empty before the publish script started, which means it's not enough just to
|
||||
run 'npm install' during publishing: you need to ensure node_modules already
|
||||
existed at least).
|
||||
|
||||
Hopefully, this can be removed after the move to the new MSBuild.
|
||||
@@ -1,11 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"moduleResolution": "node",
|
||||
"target": "es5",
|
||||
"sourceMap": true,
|
||||
|
||||
@@ -6,13 +6,7 @@ var merge = require('webpack-merge');
|
||||
// Configuration in common to both client-side and server-side bundles
|
||||
var sharedConfig = {
|
||||
context: __dirname,
|
||||
resolve: {
|
||||
extensions: [ '.js', '.ts' ],
|
||||
modules: [
|
||||
'node_modules',
|
||||
'.'
|
||||
]
|
||||
},
|
||||
resolve: { extensions: [ '', '.js', '.ts' ] },
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
|
||||
@@ -22,7 +16,8 @@ var sharedConfig = {
|
||||
{ test: /\.ts$/, include: /ClientApp/, loaders: ['ts-loader?silent=true', 'angular2-template-loader'] },
|
||||
{ test: /\.html$/, loader: 'html-loader?minimize=false' },
|
||||
{ test: /\.css$/, loader: 'to-string-loader!css-loader' },
|
||||
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url-loader', query: { limit: 25000 } }
|
||||
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url-loader', query: { limit: 25000 } },
|
||||
{ test: /\.json$/, loader: 'json-loader' }
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -52,7 +47,7 @@ var clientBundleConfig = merge(sharedConfig, {
|
||||
|
||||
// Configuration for server-side (prerendering) bundle suitable for running in Node
|
||||
var serverBundleConfig = merge(sharedConfig, {
|
||||
resolve: { mainFields: ['main'] },
|
||||
resolve: { packageMains: ['main'] },
|
||||
entry: { 'main-server': './ClientApp/boot-server.ts' },
|
||||
plugins: [
|
||||
new webpack.DllReferencePlugin({
|
||||
|
||||
@@ -6,7 +6,7 @@ var merge = require('webpack-merge');
|
||||
var extractCSS = new ExtractTextPlugin('vendor.css');
|
||||
|
||||
var sharedConfig = {
|
||||
resolve: { extensions: [ '.js' ] },
|
||||
resolve: { extensions: [ '', '.js' ] },
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /\.json$/, loader: require.resolve('json-loader') },
|
||||
@@ -68,7 +68,7 @@ var clientBundleConfig = merge(sharedConfig, {
|
||||
|
||||
var serverBundleConfig = merge(sharedConfig, {
|
||||
target: 'node',
|
||||
resolve: { mainFields: ['main'] },
|
||||
resolve: { packageMains: ['main'] },
|
||||
output: {
|
||||
path: path.join(__dirname, 'ClientApp', 'dist'),
|
||||
libraryTarget: 'commonjs2',
|
||||
|
||||
2
templates/AureliaSpa/.deployment
Normal file
2
templates/AureliaSpa/.deployment
Normal file
@@ -0,0 +1,2 @@
|
||||
[config]
|
||||
SCM_SCRIPT_GENERATOR_ARGS=--aspNetCore project.json
|
||||
87
templates/AureliaSpa/Aurelia.csproj
Normal file
87
templates/AureliaSpa/Aurelia.csproj
Normal file
@@ -0,0 +1,87 @@
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
|
||||
<GlobalExclude>$(GlobalExclude);bin\**;obj\**;node_modules\**;**\*.user;**\*.suo;**\*.*proj;.git\**</GlobalExclude>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="**\*" Exclude="$(GlobalExclude)"/>
|
||||
<Compile Include="**\*.cs" Exclude="$(GlobalExclude)" />
|
||||
<EmbeddedResource Include="**\*.resx" Exclude="$(GlobalExclude)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.App">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Web">
|
||||
<Version>1.0.0-alpha-20161104-2-112</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Tools">
|
||||
<Version>1.1.0-preview4-final</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices">
|
||||
<Version>1.1.0-*</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
<!-- Files not to show in IDE -->
|
||||
<None Remove=".bowerrc" />
|
||||
<None Remove="yarn.lock" />
|
||||
</ItemGroup>
|
||||
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">
|
||||
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
|
||||
<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 --env.prod" />
|
||||
|
||||
<!-- Include the newly-built files in the publish output -->
|
||||
<ItemGroup>
|
||||
<DistFiles Include="wwwroot\dist\**" />
|
||||
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>%(DistFiles.Identity)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</ResolvedFileToPublish>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<h2>Counter</h2>
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p>This is a simple example of an Aurelia component.</p>
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"file-loader": "^0.9.0",
|
||||
"html-loader": "^0.4.4",
|
||||
"html-webpack-plugin": "^2.22.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"raw-loader": "^0.5.1",
|
||||
"style-loader": "^0.13.1",
|
||||
"to-string-loader": "^1.1.5",
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
------------------------------------------------------------------
|
||||
Don't delete this file. Do include it in your source control repo.
|
||||
------------------------------------------------------------------
|
||||
|
||||
This file exists as a workaround for https://github.com/dotnet/cli/issues/1396
|
||||
('dotnet publish' does not publish any directories that didn't exist or were
|
||||
empty before the publish script started, which means it's not enough just to
|
||||
run 'npm install' during publishing: you need to ensure node_modules already
|
||||
existed at least).
|
||||
|
||||
Hopefully, this can be removed after the move to the new MSBuild.
|
||||
@@ -17,7 +17,8 @@ module.exports = {
|
||||
{ test: /\.ts$/, include: /ClientApp/, loader: 'ts-loader', query: { silent: true } },
|
||||
{ test: /\.html$/, loader: 'html-loader' },
|
||||
{ test: /\.css$/, loaders: [ 'style-loader', 'css-loader' ] },
|
||||
{ test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }
|
||||
{ test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' },
|
||||
{ test: /\.json$/, loader: 'json-loader' }
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
|
||||
2
templates/KnockoutSpa/.deployment
Normal file
2
templates/KnockoutSpa/.deployment
Normal file
@@ -0,0 +1,2 @@
|
||||
[config]
|
||||
SCM_SCRIPT_GENERATOR_ARGS=--aspNetCore project.json
|
||||
@@ -1,4 +1,4 @@
|
||||
<h2>Counter</h2>
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p>This is a simple example of a Knockout component.</p>
|
||||
|
||||
|
||||
87
templates/KnockoutSpa/KnockoutSpa.csproj
Normal file
87
templates/KnockoutSpa/KnockoutSpa.csproj
Normal file
@@ -0,0 +1,87 @@
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
|
||||
<GlobalExclude>$(GlobalExclude);bin\**;obj\**;node_modules\**;**\*.user;**\*.suo;**\*.*proj;.git\**</GlobalExclude>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="**\*" Exclude="$(GlobalExclude)"/>
|
||||
<Compile Include="**\*.cs" Exclude="$(GlobalExclude)" />
|
||||
<EmbeddedResource Include="**\*.resx" Exclude="$(GlobalExclude)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.App">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Web">
|
||||
<Version>1.0.0-alpha-20161104-2-112</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Tools">
|
||||
<Version>1.1.0-preview4-final</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices">
|
||||
<Version>1.1.0-*</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
<!-- Files not to show in IDE -->
|
||||
<None Remove=".bowerrc" />
|
||||
<None Remove="yarn.lock" />
|
||||
</ItemGroup>
|
||||
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">
|
||||
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
|
||||
<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 --env.prod" />
|
||||
|
||||
<!-- Include the newly-built files in the publish output -->
|
||||
<ItemGroup>
|
||||
<DistFiles Include="wwwroot\dist\**" />
|
||||
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>%(DistFiles.Identity)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</ResolvedFileToPublish>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -23,6 +23,7 @@
|
||||
"history": "^4.3.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"jquery": "^2.2.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"knockout": "^3.4.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"style-loader": "^0.13.1",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
This file exists as a workaround for https://github.com/dotnet/cli/issues/1396
|
||||
('dotnet publish' does not publish any directories that didn't exist or were
|
||||
empty before the publish script started, which means it's not enough just to
|
||||
run 'npm install' during publishing: you need to ensure node_modules already
|
||||
existed at least).
|
||||
|
||||
Hopefully, this can be removed after the move to the new MSBuild.
|
||||
@@ -17,7 +17,8 @@ module.exports = {
|
||||
{ test: /\.ts$/, include: /ClientApp/, loader: 'ts-loader', query: { silent: true } },
|
||||
{ test: /\.html$/, loader: 'raw-loader' },
|
||||
{ test: /\.css$/, loader: isDevBuild ? 'style-loader!css-loader' : ExtractTextPlugin.extract(['css-loader']) },
|
||||
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url-loader', query: { limit: 25000 } }
|
||||
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url-loader', query: { limit: 25000 } },
|
||||
{ test: /\.json$/, loader: 'json-loader' }
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
|
||||
2
templates/ReactReduxSpa/.deployment
Normal file
2
templates/ReactReduxSpa/.deployment
Normal file
@@ -0,0 +1,2 @@
|
||||
[config]
|
||||
SCM_SCRIPT_GENERATOR_ARGS=--aspNetCore project.json
|
||||
@@ -8,6 +8,15 @@ import { syncHistoryWithStore } from 'react-router-redux';
|
||||
import routes from './routes';
|
||||
import configureStore from './configureStore';
|
||||
import { ApplicationState } from './store';
|
||||
import cookie from 'react-cookie';
|
||||
|
||||
// If the server supplied any edits to cookies, apply them on the client
|
||||
const cookieDataFromServer = window['cookieData'];
|
||||
if (cookieDataFromServer) {
|
||||
Object.getOwnPropertyNames(cookieDataFromServer).forEach(name => {
|
||||
cookie.save(name, cookieDataFromServer[name]);
|
||||
});
|
||||
}
|
||||
|
||||
// Get the application-wide store instance, prepopulating with state from the server where available.
|
||||
const initialState = (window as any).initialReduxState as ApplicationState;
|
||||
|
||||
@@ -6,9 +6,28 @@ import createMemoryHistory from 'history/lib/createMemoryHistory';
|
||||
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
|
||||
import routes from './routes';
|
||||
import configureStore from './configureStore';
|
||||
import cookieUtil from 'cookie';
|
||||
import cookie from 'react-cookie';
|
||||
|
||||
function plugInCookiesFromDotNet(cookieData: { key: string, value: string }[], res) {
|
||||
const formattedData = {};
|
||||
cookieData.forEach(keyValuePair => {
|
||||
formattedData[keyValuePair.key] = keyValuePair.value;
|
||||
});
|
||||
cookie.plugToRequest({ cookies: formattedData }, res);
|
||||
}
|
||||
|
||||
export default createServerRenderer(params => {
|
||||
return new Promise<RenderResult>((resolve, reject) => {
|
||||
const cookiesModifiedOnServer = {};
|
||||
if (params.data.cookies) {
|
||||
// If we received some cookie data, use that to prepopulate 'react-cookie'
|
||||
plugInCookiesFromDotNet(params.data.cookies, {
|
||||
// Also track any cookies written on the server
|
||||
cookie: (name, val) => { cookiesModifiedOnServer[name] = val; }
|
||||
})
|
||||
}
|
||||
|
||||
// Match the incoming request against the list of client-side routes
|
||||
match({ routes, location: params.location }, (error, redirectLocation, renderProps: any) => {
|
||||
if (error) {
|
||||
@@ -42,7 +61,17 @@ export default createServerRenderer(params => {
|
||||
params.domainTasks.then(() => {
|
||||
resolve({
|
||||
html: renderToString(app),
|
||||
globals: { initialReduxState: store.getState() }
|
||||
globals: {
|
||||
initialReduxState: store.getState(),
|
||||
|
||||
// Send any cookies written during server-side prerendering to the client.
|
||||
// WARNING: Do not pass any security-sensitive cookies this way, because they will become
|
||||
// readable in the HTML source. If your goal is to use this approach to manage authentication
|
||||
// cookies, then be sure *not* to use 'globals' to send them to the client - instead, invoke
|
||||
// Microsoft.AspNetCore.SpaServices.Prerendering.Prerender directly from your .NET code and
|
||||
// only send the 'html' part back to the client (or at least, not all of the 'globals').
|
||||
cookieData: cookiesModifiedOnServer
|
||||
}
|
||||
});
|
||||
}, reject); // Also propagate any errors back into the host application
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Action, Reducer, ThunkAction } from 'redux';
|
||||
import cookie from 'react-cookie';
|
||||
|
||||
// -----------------
|
||||
// STATE - This defines the type of data maintained in the Redux store.
|
||||
@@ -30,13 +31,20 @@ export const actionCreators = {
|
||||
|
||||
// ----------------
|
||||
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
|
||||
const cookieKey = 'counterValue';
|
||||
|
||||
function modifiedCount(state: CounterState, delta: number): CounterState {
|
||||
const newCount = state.count + delta;
|
||||
cookie.save(cookieKey, newCount); // Ideally, don't do this here: have something that watches the store instead of having a side-effect from a reducer
|
||||
return { count: newCount };
|
||||
}
|
||||
|
||||
export const reducer: Reducer<CounterState> = (state: CounterState, action: KnownAction) => {
|
||||
switch (action.type) {
|
||||
case 'INCREMENT_COUNT':
|
||||
return { count: state.count + 1 };
|
||||
return modifiedCount(state, +1);
|
||||
case 'DECREMENT_COUNT':
|
||||
return { count: state.count - 1 };
|
||||
return modifiedCount(state, -1);
|
||||
default:
|
||||
// The following line guarantees that every action in the KnownAction union has been covered by a case above
|
||||
const exhaustiveCheck: never = action;
|
||||
@@ -44,5 +52,6 @@ export const reducer: Reducer<CounterState> = (state: CounterState, action: Know
|
||||
|
||||
// For unrecognized actions (or in cases where actions have no effect), must return the existing state
|
||||
// (or default initial state if none was supplied)
|
||||
return state || { count: 0 };
|
||||
const prevCountFromCookie = parseInt(cookie.load(cookieKey) || '0');
|
||||
return state || { count: prevCountFromCookie };
|
||||
};
|
||||
|
||||
90
templates/ReactReduxSpa/ReactReduxSpa.csproj
Normal file
90
templates/ReactReduxSpa/ReactReduxSpa.csproj
Normal file
@@ -0,0 +1,90 @@
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
|
||||
<GlobalExclude>$(GlobalExclude);bin\**;obj\**;node_modules\**;**\*.user;**\*.suo;**\*.*proj;.git\**</GlobalExclude>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="**\*" Exclude="$(GlobalExclude)"/>
|
||||
<Compile Include="**\*.cs" Exclude="$(GlobalExclude)" />
|
||||
<EmbeddedResource Include="**\*.resx" Exclude="$(GlobalExclude)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.App">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Web">
|
||||
<Version>1.0.0-alpha-20161104-2-112</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Tools">
|
||||
<Version>1.1.0-preview4-final</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices">
|
||||
<Version>1.1.0-*</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
<!-- Files not to show in IDE -->
|
||||
<None Remove=".bowerrc" />
|
||||
<None Remove="yarn.lock" />
|
||||
|
||||
<!-- Files not to publish (note that the 'dist' subfolders are re-added below) -->
|
||||
<Content Remove="ClientApp\**" />
|
||||
</ItemGroup>
|
||||
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">
|
||||
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
|
||||
<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 --env.prod" />
|
||||
|
||||
<!-- Include the newly-built files in the publish output -->
|
||||
<ItemGroup>
|
||||
<DistFiles Include="wwwroot\dist\**; ClientApp\dist\**" />
|
||||
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>%(DistFiles.Identity)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</ResolvedFileToPublish>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -2,7 +2,8 @@
|
||||
ViewData["Title"] = "Home Page";
|
||||
}
|
||||
|
||||
<div id="react-app" asp-prerender-module="ClientApp/dist/main-server">Loading...</div>
|
||||
<div id="react-app" asp-prerender-module="ClientApp/dist/main-server"
|
||||
asp-prerender-data="new { cookies = ViewContext.HttpContext.Request.Cookies }">Loading...</div>
|
||||
|
||||
@section scripts {
|
||||
<script src="~/dist/main-client.js" asp-append-version="true"></script>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "WebApplicationBasic",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@types/history": "^2.0.0",
|
||||
"@types/react": "^0.14.29",
|
||||
"@types/react-dom": "^0.14.14",
|
||||
"@types/react-redux": "^4.4.29",
|
||||
@@ -31,6 +32,7 @@
|
||||
"json-loader": "^0.5.4",
|
||||
"node-noop": "^1.0.0",
|
||||
"react": "^15.3.2",
|
||||
"react-cookie": "^1.0.4",
|
||||
"react-dom": "^15.3.2",
|
||||
"react-redux": "^4.4.5",
|
||||
"react-router": "^2.8.1",
|
||||
@@ -39,7 +41,7 @@
|
||||
"redux-thunk": "^2.1.0",
|
||||
"style-loader": "^0.13.0",
|
||||
"ts-loader": "^0.8.1",
|
||||
"typescript": "2.0.3",
|
||||
"typescript": "^2.0.3",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.13.2",
|
||||
"webpack-hot-middleware": "^2.12.2",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
This file exists as a workaround for https://github.com/dotnet/cli/issues/1396
|
||||
('dotnet publish' does not publish any directories that didn't exist or were
|
||||
empty before the publish script started, which means it's not enough just to
|
||||
run 'npm install' during publishing: you need to ensure node_modules already
|
||||
existed at least).
|
||||
|
||||
Hopefully, this can be removed after the move to the new MSBuild.
|
||||
@@ -10,8 +10,11 @@
|
||||
"lib": ["es6", "dom"],
|
||||
"types": [ "webpack-env", "whatwg-fetch" ],
|
||||
"paths": {
|
||||
// Fixes "Duplicate identifier 'Redux'" error when packages were installed via Yarn (by specifying which .d.ts file is authoritative)
|
||||
"redux": ["./node_modules/@types/redux/index"]
|
||||
// Fix "Duplicate identifier" errors caused by multiple dependencies fetching their own copies of type definitions.
|
||||
// We tell TypeScript which type definitions module to treat as the canonical one (instead of combining all of them).
|
||||
"history": ["./node_modules/@types/history/index"],
|
||||
"redux": ["./node_modules/@types/redux/index"],
|
||||
"react": ["./node_modules/@types/react/index"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
@@ -14,7 +14,8 @@ var sharedConfig = () => ({
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /\.tsx?$/, include: /ClientApp/, loader: 'babel-loader' },
|
||||
{ test: /\.tsx?$/, include: /ClientApp/, loader: 'ts-loader', query: { silent: true } }
|
||||
{ test: /\.tsx?$/, include: /ClientApp/, loader: 'ts-loader', query: { silent: true } },
|
||||
{ test: /\.json$/, loader: 'json-loader' }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
2
templates/ReactSpa/.deployment
Normal file
2
templates/ReactSpa/.deployment
Normal file
@@ -0,0 +1,2 @@
|
||||
[config]
|
||||
SCM_SCRIPT_GENERATOR_ARGS=--aspNetCore project.json
|
||||
87
templates/ReactSpa/ReactSpa.csproj
Normal file
87
templates/ReactSpa/ReactSpa.csproj
Normal file
@@ -0,0 +1,87 @@
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
|
||||
<GlobalExclude>$(GlobalExclude);bin\**;obj\**;node_modules\**;**\*.user;**\*.suo;**\*.*proj;.git\**</GlobalExclude>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="**\*" Exclude="$(GlobalExclude)"/>
|
||||
<Compile Include="**\*.cs" Exclude="$(GlobalExclude)" />
|
||||
<EmbeddedResource Include="**\*.resx" Exclude="$(GlobalExclude)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.App">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Web">
|
||||
<Version>1.0.0-alpha-20161104-2-112</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Tools">
|
||||
<Version>1.1.0-preview4-final</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices">
|
||||
<Version>1.1.0-*</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
<!-- Files not to show in IDE -->
|
||||
<None Remove=".bowerrc" />
|
||||
<None Remove="yarn.lock" />
|
||||
</ItemGroup>
|
||||
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">
|
||||
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
|
||||
<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 --env.prod" />
|
||||
|
||||
<!-- Include the newly-built files in the publish output -->
|
||||
<ItemGroup>
|
||||
<DistFiles Include="wwwroot\dist\**" />
|
||||
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>%(DistFiles.Identity)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</ResolvedFileToPublish>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -20,6 +20,7 @@
|
||||
"file-loader": "^0.9.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"jquery": "^2.2.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"react": "^15.3.2",
|
||||
"react-dom": "^15.3.2",
|
||||
"react-router": "^2.8.1",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
This file exists as a workaround for https://github.com/dotnet/cli/issues/1396
|
||||
('dotnet publish' does not publish any directories that didn't exist or were
|
||||
empty before the publish script started, which means it's not enough just to
|
||||
run 'npm install' during publishing: you need to ensure node_modules already
|
||||
existed at least).
|
||||
|
||||
Hopefully, this can be removed after the move to the new MSBuild.
|
||||
@@ -1,10 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "node",
|
||||
"target": "es6",
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"skipDefaultLibCheck": true
|
||||
"skipDefaultLibCheck": true,
|
||||
"paths": {
|
||||
// Fix "Duplicate identifier" errors caused by multiple dependencies fetching their own copies of type definitions.
|
||||
// We tell TypeScript which type definitions module to treat as the canonical one (instead of combining all of them).
|
||||
"history": ["./node_modules/@types/history/index"],
|
||||
"react": ["./node_modules/@types/react/index"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"bin",
|
||||
|
||||
@@ -18,7 +18,8 @@ module.exports = {
|
||||
{ test: /\.ts(x?)$/, include: /ClientApp/, loader: 'babel-loader' },
|
||||
{ test: /\.tsx?$/, include: /ClientApp/, loader: 'ts-loader', query: { silent: true } },
|
||||
{ test: /\.css$/, loader: isDevBuild ? 'style-loader!css-loader' : ExtractTextPlugin.extract(['css-loader']) },
|
||||
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url-loader', query: { limit: 25000 } }
|
||||
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url-loader', query: { limit: 25000 } },
|
||||
{ test: /\.json$/, loader: 'json-loader' }
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
This file exists as a workaround for https://github.com/dotnet/cli/issues/1396
|
||||
('dotnet publish' does not publish any directories that didn't exist or were
|
||||
empty before the publish script started, which means it's not enough just to
|
||||
run 'npm install' during publishing: you need to ensure node_modules already
|
||||
existed at least).
|
||||
|
||||
Hopefully, this can be removed after the move to the new MSBuild.
|
||||
@@ -10,6 +10,7 @@
|
||||
"author": "Microsoft",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/chalk": "^0.4.31",
|
||||
"@types/semver": "^5.3.30",
|
||||
"diff": "^2.2.2",
|
||||
"gitignore-parser": "0.0.2",
|
||||
@@ -17,7 +18,8 @@
|
||||
"lodash": "^4.11.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "^2.5.2",
|
||||
"semver": "^5.3.0"
|
||||
"semver": "^5.3.0",
|
||||
"tar.gz": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/glob": "^5.0.30",
|
||||
|
||||
@@ -6,13 +6,23 @@ import * as _ from 'lodash';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import * as rimraf from 'rimraf';
|
||||
import * as childProcess from 'child_process';
|
||||
import * as targz from 'tar.gz';
|
||||
|
||||
const isWindows = /^win/.test(process.platform);
|
||||
const textFileExtensions = ['.gitignore', 'template_gitignore', '.config', '.cs', '.cshtml', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.nuspec', '.ts', '.tsx', '.xproj'];
|
||||
const textFileExtensions = ['.gitignore', 'template_gitignore', '.config', '.cs', '.cshtml', '.csproj', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.nuspec', '.ts', '.tsx', '.xproj'];
|
||||
const yeomanGeneratorSource = './src/yeoman';
|
||||
|
||||
const templates: { [key: string]: { dir: string, dotNetNewId: string, displayName: string, forceInclusion?: RegExp } } = {
|
||||
'angular-2': { dir: '../../templates/Angular2Spa/', dotNetNewId: 'Angular', displayName: 'Angular 2', forceInclusion: /^(wwwroot|ClientApp)\/dist\// },
|
||||
// For the Angular 2 template, we want to bundle prebuilt dist dev-mode files, because the VS template can't auto-run
|
||||
// webpack on project creation. Note that these script entries are *not* the same as the project's usual prepublish
|
||||
// scripts, because here we want dev-mode builds (e.g., to support HMR), not prod-mode builds.
|
||||
const runWebpackInDevModeScripts = [
|
||||
'npm install',
|
||||
'node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js',
|
||||
'node node_modules/webpack/bin/webpack.js'
|
||||
];
|
||||
|
||||
const templates: { [key: string]: { dir: string, dotNetNewId: string, displayName: string, prepublish?: string[], forceInclusion?: RegExp } } = {
|
||||
'angular-2': { dir: '../../templates/Angular2Spa/', dotNetNewId: 'Angular', displayName: 'Angular 2', prepublish: runWebpackInDevModeScripts, forceInclusion: /^(wwwroot|ClientApp)\/dist\// },
|
||||
'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' },
|
||||
@@ -72,21 +82,25 @@ function copyRecursive(sourceRoot: string, destRoot: string, matchGlob: string)
|
||||
});
|
||||
}
|
||||
|
||||
function buildYeomanNpmPackage() {
|
||||
const outputRoot = './dist/generator-aspnetcore-spa';
|
||||
function buildYeomanNpmPackage(outputRoot: string) {
|
||||
const outputTemplatesRoot = path.join(outputRoot, 'app/templates');
|
||||
rimraf.sync(outputTemplatesRoot);
|
||||
|
||||
// Copy template files
|
||||
const filenameReplacements = [
|
||||
{ from: /.*\.xproj$/, to: 'tokenreplace-namePascalCase.xproj' }
|
||||
{ from: /.*\.xproj$/, to: 'tokenreplace-namePascalCase.xproj' },
|
||||
{ from: /.*\.csproj$/, to: 'tokenreplace-namePascalCase.csproj' }
|
||||
];
|
||||
const contentReplacements = [
|
||||
// .xproj items
|
||||
{ from: /\bWebApplicationBasic\b/g, to: '<%= namePascalCase %>' },
|
||||
{ from: /<ProjectGuid>[0-9a-f\-]{36}<\/ProjectGuid>/g, to: '<ProjectGuid><%= projectGuid %></ProjectGuid>' },
|
||||
{ from: /<RootNamespace>.*?<\/RootNamespace>/g, to: '<RootNamespace><%= namePascalCase %></RootNamespace>'},
|
||||
{ from: /\s*<BaseIntermediateOutputPath.*?<\/BaseIntermediateOutputPath>/g, to: '' },
|
||||
{ from: /\s*<OutputPath.*?<\/OutputPath>/g, to: '' },
|
||||
|
||||
// global.json items
|
||||
{ from: /1\.0\.0-preview2-1-003177/, to: '<%= sdkVersion %>' }
|
||||
];
|
||||
_.forEach(templates, (templateConfig, templateName) => {
|
||||
const outputDir = path.join(outputTemplatesRoot, templateName);
|
||||
@@ -110,12 +124,9 @@ function buildDotNetNewNuGetPackage() {
|
||||
const sourceProjectName = 'WebApplicationBasic';
|
||||
const projectGuid = '00000000-0000-0000-0000-000000000000';
|
||||
const filenameReplacements = [
|
||||
// TODO: For dotnetnew templates, switch to csproj. No need for SDK choice as it can be Preview3+ only.
|
||||
{ from: /.*\.xproj$/, to: `${sourceProjectName}.xproj` },
|
||||
{ from: /\btemplate_gitignore$/, to: '.gitignore' },
|
||||
|
||||
// Workaround for https://github.com/aspnet/JavaScriptServices/issues/235
|
||||
// For details, see the comment in ../yeoman/app/index.ts
|
||||
{ from: /\btemplate_nodemodules_placeholder.txt$/, to: 'node_modules/_placeholder.txt' }
|
||||
{ from: /\btemplate_gitignore$/, to: '.gitignore' }
|
||||
];
|
||||
const contentReplacements = [
|
||||
{ from: /<ProjectGuid>[0-9a-f\-]{36}<\/ProjectGuid>/g, to: `<ProjectGuid>${projectGuid}</ProjectGuid>` },
|
||||
@@ -161,21 +172,37 @@ function buildDotNetNewNuGetPackage() {
|
||||
rimraf.sync('./tmp');
|
||||
}
|
||||
|
||||
// TODO: Instead of just showing this warning, improve build script so it actually does build them
|
||||
// in the correct format. Can do this once we've moved away from using ASPNETCORE_ENVIRONMENT to
|
||||
// control the build output mode. The templates we warn about here are the ones where we ship some
|
||||
// files that wouldn't normally be under source control (e.g., /wwwroot/dist/*).
|
||||
const templatesWithForceIncludes = Object.getOwnPropertyNames(templates)
|
||||
.filter(templateName => !!templates[templateName].forceInclusion);
|
||||
if (templatesWithForceIncludes.length > 0) {
|
||||
console.warn(`
|
||||
---
|
||||
WARNING: Ensure that the following templates are already built in the configuration desired for publishing.
|
||||
For example, build the dist files in debug mode.
|
||||
TEMPLATES: ${templatesWithForceIncludes.join(', ')}
|
||||
---
|
||||
`);
|
||||
function runAllPrepublishScripts() {
|
||||
Object.getOwnPropertyNames(templates).forEach(templateKey => {
|
||||
const templateInfo = templates[templateKey];
|
||||
if (templateInfo.prepublish) {
|
||||
runScripts(templateInfo.dir, templateInfo.prepublish);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
buildYeomanNpmPackage();
|
||||
function runScripts(rootDir: string, scripts: string[]) {
|
||||
console.log(`[Prepublish] In directory: ${ rootDir }`);
|
||||
scripts.forEach(script => {
|
||||
console.log(`[Prepublish] Running: ${ script }`);
|
||||
childProcess.execSync(script, { cwd: rootDir, stdio: 'inherit' });
|
||||
});
|
||||
console.log(`[Prepublish] Done`)
|
||||
}
|
||||
|
||||
const distDir = './dist';
|
||||
const artifactsDir = path.join(distDir, 'artifacts');
|
||||
const yeomanOutputRoot = path.join(distDir, 'generator-aspnetcore-spa');
|
||||
|
||||
rimraf.sync(distDir);
|
||||
mkdirp.sync(artifactsDir);
|
||||
runAllPrepublishScripts();
|
||||
buildYeomanNpmPackage(yeomanOutputRoot);
|
||||
buildDotNetNewNuGetPackage();
|
||||
|
||||
// Finally, create a .tar.gz file containing the built generator-aspnetcore-spa.
|
||||
// The CI system can treat this as the final built artifact.
|
||||
// Note that the targz APIs only come in async flavor.
|
||||
targz().compress(yeomanOutputRoot, path.join(artifactsDir, 'generator-aspnetcore-spa.tar.gz'), err => {
|
||||
if (err) { throw err; }
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ Generates ASP.NET Core projects for single-page applications.
|
||||
Can generate projects with:
|
||||
|
||||
* Angular 2
|
||||
* Aurelia
|
||||
* Knockout
|
||||
* React
|
||||
* React with Redux
|
||||
|
||||
@@ -4,11 +4,13 @@ import * as yeoman from 'yeoman-generator';
|
||||
import * as uuid from 'node-uuid';
|
||||
import * as glob from 'glob';
|
||||
import * as semver from 'semver';
|
||||
import * as chalk from 'chalk';
|
||||
import { execSync } from 'child_process';
|
||||
import npmWhich = require('npm-which');
|
||||
const yosay = require('yosay');
|
||||
const toPascalCase = require('to-pascal-case');
|
||||
const isWindows = /^win/.test(process.platform);
|
||||
const generatorPackageJson = require(path.resolve(__dirname, '../package.json'));
|
||||
|
||||
// Paths matching these regexes will only be included if the user wants tests
|
||||
const testSpecificPaths = [
|
||||
@@ -41,6 +43,20 @@ const templates = [
|
||||
{ value: 'react-redux', name: 'React with Redux', tests: false }
|
||||
];
|
||||
|
||||
// Once everyone is on .csproj-compatible tooling, we might be able to remove the global.json files and eliminate
|
||||
// this SDK choice altogether. That would be good because then it would work with whatever SDK version you have
|
||||
// installed. For now, we need to specify an SDK version explicitly, because there's no support for wildcards, and
|
||||
// preview3+ tooling doesn't support project.json at all.
|
||||
const sdkChoices = [{
|
||||
value: '1.0.0-preview2-1-003177', // Current released version
|
||||
name: 'project.json' + chalk.gray(' (compatible with .NET Core tooling preview 2 and Visual Studio 2015)'),
|
||||
includeFiles: [/^project.json$/, /\.xproj$/, /_placeholder.txt$/, /\.deployment$/]
|
||||
}, {
|
||||
value: '1.0.0-preview3-004056', // Version that ships with VS2017RC
|
||||
name: '.csproj' + chalk.gray(' (compatible with .NET Core tooling preview 3 and Visual Studio 2017)'),
|
||||
includeFiles: [/\.csproj$/]
|
||||
}];
|
||||
|
||||
class MyGenerator extends yeoman.Base {
|
||||
private _answers: any;
|
||||
private _optionOrPrompt: YeomanPrompt;
|
||||
@@ -48,7 +64,7 @@ class MyGenerator extends yeoman.Base {
|
||||
constructor(args: string | string[], options: any) {
|
||||
super(args, options);
|
||||
this._optionOrPrompt = optionOrPrompt;
|
||||
this.log(yosay('Welcome to the ASP.NET Core Single-Page App generator!'));
|
||||
this.log(yosay('Welcome to the ASP.NET Core Single-Page App generator!\n\nVersion: ' + generatorPackageJson.version));
|
||||
|
||||
if (isWindows) {
|
||||
assertNpmVersionIsAtLeast('3.0.0');
|
||||
@@ -64,8 +80,13 @@ class MyGenerator extends yeoman.Base {
|
||||
name: 'framework',
|
||||
message: 'Framework',
|
||||
choices: templates
|
||||
}], frameworkAnswer => {
|
||||
const frameworkChoice = templates.filter(t => t.value === frameworkAnswer.framework)[0];
|
||||
}, {
|
||||
type: 'list',
|
||||
name: 'sdkVersion',
|
||||
message: 'What type of project do you want to create?',
|
||||
choices: sdkChoices
|
||||
}], firstAnswers => {
|
||||
const frameworkChoice = templates.filter(t => t.value === firstAnswers.framework)[0];
|
||||
const furtherQuestions = [{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
@@ -83,9 +104,10 @@ class MyGenerator extends yeoman.Base {
|
||||
}
|
||||
|
||||
this._optionOrPrompt(furtherQuestions, answers => {
|
||||
answers.framework = frameworkAnswer.framework;
|
||||
answers.framework = firstAnswers.framework;
|
||||
this._answers = answers;
|
||||
this._answers.framework = frameworkAnswer.framework;
|
||||
this._answers.framework = firstAnswers.framework;
|
||||
this._answers.sdkVersion = firstAnswers.sdkVersion;
|
||||
this._answers.namePascalCase = toPascalCase(answers.name);
|
||||
this._answers.projectGuid = this.options['projectguid'] || uuid.v4();
|
||||
done();
|
||||
@@ -94,7 +116,8 @@ class MyGenerator extends yeoman.Base {
|
||||
}
|
||||
|
||||
writing() {
|
||||
var templateRoot = this.templatePath(this._answers.framework);
|
||||
const templateRoot = this.templatePath(this._answers.framework);
|
||||
const chosenSdk = sdkChoices.filter(sdk => sdk.value === this._answers.sdkVersion)[0];
|
||||
glob.sync('**/*', { cwd: templateRoot, dot: true, nodir: true }).forEach(fn => {
|
||||
// Token replacement in filenames
|
||||
let outputFn = fn.replace(/tokenreplace\-([^\.\/]*)/g, (substr, token) => this._answers[token]);
|
||||
@@ -104,23 +127,17 @@ class MyGenerator extends yeoman.Base {
|
||||
outputFn = path.join(path.dirname(fn), '.gitignore');
|
||||
}
|
||||
|
||||
// Likewise, output template_nodemodules_placeholder.txt as node_modules/_placeholder.txt
|
||||
// This is a workaround for https://github.com/aspnet/JavaScriptServices/issues/235. We need the new project
|
||||
// to have a nonempty node_modules dir as far as *source control* is concerned. So, there's a gitignore
|
||||
// rule that explicitly causes node_modules/_placeholder.txt to be tracked in source control. But how
|
||||
// does that file get there in the first place? It's not enough for such a file to exist when the
|
||||
// generator-aspnetcore-spa NPM package is published, because NPM doesn't allow any directories called
|
||||
// node_modules to exist in the package. So we have a file with at a different location, and move it
|
||||
// to node_modules as part of executing the template.
|
||||
if (path.basename(fn) === 'template_nodemodules_placeholder.txt') {
|
||||
outputFn = path.join(path.dirname(fn), 'node_modules', '_placeholder.txt');
|
||||
}
|
||||
|
||||
// Exclude test-specific files (unless the user has said they want tests)
|
||||
// Decide whether to emit this file
|
||||
const isTestSpecificFile = testSpecificPaths.some(regex => regex.test(outputFn));
|
||||
if (this._answers.tests || !isTestSpecificFile) {
|
||||
const isSdkSpecificFile = sdkChoices.some(sdk => sdk.includeFiles.some(regex => regex.test(outputFn)));
|
||||
const matchesChosenSdk = chosenSdk.includeFiles.some(regex => regex.test(outputFn));
|
||||
const emitFile = (matchesChosenSdk || !isSdkSpecificFile)
|
||||
&& (this._answers.tests || !isTestSpecificFile);
|
||||
|
||||
if (emitFile) {
|
||||
let inputFullPath = path.join(templateRoot, fn);
|
||||
let destinationFullPath = this.destinationPath(outputFn);
|
||||
let deleteInputFileAfter = false;
|
||||
if (path.basename(fn) === 'package.json') {
|
||||
// Special handling for package.json, because we rewrite it dynamically
|
||||
const tempPath = destinationFullPath + '.tmp';
|
||||
@@ -131,6 +148,7 @@ class MyGenerator extends yeoman.Base {
|
||||
/* space */ 2
|
||||
);
|
||||
inputFullPath = tempPath;
|
||||
deleteInputFileAfter = true;
|
||||
}
|
||||
|
||||
const outputDirBasename = path.basename(path.dirname(destinationFullPath));
|
||||
@@ -148,6 +166,10 @@ class MyGenerator extends yeoman.Base {
|
||||
this._answers
|
||||
);
|
||||
}
|
||||
|
||||
if (deleteInputFileAfter) {
|
||||
this.fs.delete(inputFullPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -186,7 +208,7 @@ function assertNpmVersionIsAtLeast(minVersion: string) {
|
||||
const runningVersion = execSync('npm -v').toString();
|
||||
if (!semver.gte(runningVersion, minVersion, /* loose */ true)) {
|
||||
console.error(`This generator requires NPM version ${minVersion} or later. You are running NPM version ${runningVersion}`);
|
||||
process.exit(0);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "generator-aspnetcore-spa",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.3",
|
||||
"description": "Single-Page App templates for ASP.NET Core",
|
||||
"author": "Microsoft",
|
||||
"license": "Apache-2.0",
|
||||
@@ -18,6 +18,7 @@
|
||||
"url": "https://github.com/aspnet/JavaScriptServices.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^1.1.3",
|
||||
"glob": "^7.0.3",
|
||||
"node-uuid": "^1.4.7",
|
||||
"npm-which": "^3.0.1",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"lib": ["es6"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules",
|
||||
"dist"
|
||||
]
|
||||
|
||||
3
test/.gitignore
vendored
Normal file
3
test/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/node_modules/
|
||||
/tmp/
|
||||
/yarn.lock
|
||||
34
test/package.json
Normal file
34
test/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"description": "Integration tests for the templates in JavaScriptServices. This is not really an NPM package and will not be published.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "tsc && wdio"
|
||||
},
|
||||
"author": "Microsoft",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/chai": "^3.4.34",
|
||||
"@types/mkdirp": "^0.3.29",
|
||||
"@types/mocha": "^2.2.33",
|
||||
"@types/node": "^6.0.52",
|
||||
"@types/rimraf": "^0.0.28",
|
||||
"@types/webdriverio": "^4.0.32",
|
||||
"chai": "^3.5.0",
|
||||
"cross-spawn": "^5.0.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"portastic": "^1.0.1",
|
||||
"rimraf": "^2.5.4",
|
||||
"selenium-standalone": "^5.9.0",
|
||||
"tree-kill": "^1.1.0",
|
||||
"typescript": "^2.1.4",
|
||||
"webdriverio": "^4.5.0",
|
||||
"yo": "^1.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wdio-junit-reporter": "^0.2.0",
|
||||
"wdio-mocha-framework": "^0.5.7",
|
||||
"wdio-selenium-standalone-service": "0.0.7"
|
||||
}
|
||||
}
|
||||
106
test/templates/angular.spec.ts
Normal file
106
test/templates/angular.spec.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { expect } from 'chai';
|
||||
import { generateProjectSync } from './util/yeoman';
|
||||
import { AspNetProcess, AspNetCoreEnviroment, defaultUrl, publishProjectSync } from './util/aspnet';
|
||||
import { getValue, getCssPropertyValue } from './util/webdriverio';
|
||||
|
||||
// Currently we test both 'csproj' and 'project.json' project types. Eventually we'll only need csproj.
|
||||
['csproj', 'projectjson'].forEach(toolingType => {
|
||||
// First, generate a new project using the locally-built generator-aspnetcore-spa
|
||||
// Do this outside the Mocha fixture, otherwise Mocha will time out
|
||||
const appDir = path.resolve(__dirname, '../generated/angular', toolingType);
|
||||
const publishedAppDir = path.resolve(appDir, './bin/Release/published');
|
||||
if (!process.env.SKIP_PROJECT_GENERATION) {
|
||||
generateProjectSync(appDir, {
|
||||
framework: 'angular-2',
|
||||
name: 'Test App',
|
||||
sdkVersion: toolingType === 'projectjson' ? '1.0.0-preview2-1-003177' : '1.0.0-preview3-004056',
|
||||
tests: false
|
||||
});
|
||||
publishProjectSync(appDir, publishedAppDir);
|
||||
}
|
||||
|
||||
function testBasicNavigation() {
|
||||
describe('Basic navigation', () => {
|
||||
beforeEach(() => browser.url(defaultUrl));
|
||||
|
||||
it('should initially display the home page', () => {
|
||||
expect(browser.getText('h1')).to.eq('Hello, world!');
|
||||
expect(browser.getText('li a[href="https://angular.io/"]')).to.eq('Angular 2');
|
||||
});
|
||||
|
||||
it('should be able to show the counter page', () => {
|
||||
browser.click('a[href="/counter"]');
|
||||
expect(browser.getText('h1')).to.eq('Counter');
|
||||
|
||||
// Test clicking the 'increment' button
|
||||
expect(browser.getText('counter strong')).to.eq('0');
|
||||
browser.click('counter button');
|
||||
expect(browser.getText('counter strong')).to.eq('1');
|
||||
});
|
||||
|
||||
it('should be able to show the fetchdata page', () => {
|
||||
browser.click('a[href="/fetch-data"]');
|
||||
expect(browser.getText('h1')).to.eq('Weather forecast');
|
||||
|
||||
browser.waitForExist('fetchdata table');
|
||||
expect(getValue(browser.elements('fetchdata table tbody tr')).length).to.eq(5);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testHotModuleReplacement() {
|
||||
describe('Hot module replacement', () => {
|
||||
beforeEach(() => browser.url(defaultUrl));
|
||||
|
||||
it('should update when HTML is changed', () => {
|
||||
expect(browser.getText('h1')).to.eq('Hello, world!');
|
||||
|
||||
const filePath = path.resolve(appDir, './ClientApp/app/components/home/home.component.html');
|
||||
const origFileContents = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
try {
|
||||
const newFileContents = origFileContents.replace('<h1>Hello, world!</h1>', '<h1>HMR is working</h1>');
|
||||
fs.writeFileSync(filePath, newFileContents, { encoding: 'utf8' });
|
||||
|
||||
browser.waitUntil(() => browser.getText('h1').toString() === 'HMR is working');
|
||||
} finally {
|
||||
// Restore old contents so that other tests don't have to account for this
|
||||
fs.writeFileSync(filePath, origFileContents, { encoding: 'utf8' });
|
||||
}
|
||||
});
|
||||
|
||||
it('should update when CSS is changed', () => {
|
||||
expect(getCssPropertyValue(browser, 'li.link-active a', 'color')).to.eq('rgba(255,255,255,1)');
|
||||
|
||||
const filePath = path.resolve(appDir, './ClientApp/app/components/navmenu/navmenu.component.css');
|
||||
const origFileContents = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
try {
|
||||
const newFileContents = origFileContents.replace('color: white;', 'color: purple;');
|
||||
fs.writeFileSync(filePath, newFileContents, { encoding: 'utf8' });
|
||||
|
||||
browser.waitUntil(() => getCssPropertyValue(browser, 'li.link-active a', 'color') === 'rgba(128,0,128,1)');
|
||||
} finally {
|
||||
// Restore old contents so that other tests don't have to account for this
|
||||
fs.writeFileSync(filePath, origFileContents, { encoding: 'utf8' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Now launch dotnet and use selenium to perform tests
|
||||
describe('Angular template: dev mode', () => {
|
||||
AspNetProcess.RunInMochaContext(appDir, AspNetCoreEnviroment.development);
|
||||
testBasicNavigation();
|
||||
testHotModuleReplacement();
|
||||
});
|
||||
|
||||
describe('Angular template: production mode', () => {
|
||||
// csproj tooling takes the assembly name from <name>.csproj, whereas project.json takes it from the directory name
|
||||
const assemblyName = toolingType === 'csproj' ? 'TestApp.dll' : 'projectjson.dll';
|
||||
AspNetProcess.RunInMochaContext(publishedAppDir, AspNetCoreEnviroment.production, assemblyName);
|
||||
testBasicNavigation();
|
||||
});
|
||||
});
|
||||
123
test/templates/util/aspnet.ts
Normal file
123
test/templates/util/aspnet.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import * as childProcess from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as readline from 'readline';
|
||||
import { waitUntilPortState } from './ports';
|
||||
const treeKill = require('tree-kill');
|
||||
const crossSpawn: typeof childProcess.spawn = require('cross-spawn');
|
||||
const defaultPort = 5000;
|
||||
const defaultInterface = 'localhost';
|
||||
|
||||
export const defaultUrl = `http://localhost:${ defaultPort }`;
|
||||
|
||||
export enum AspNetCoreEnviroment {
|
||||
development,
|
||||
production
|
||||
}
|
||||
|
||||
export class AspNetProcess {
|
||||
public static RunInMochaContext(cwd: string, mode: AspNetCoreEnviroment, dllToRun?: string) {
|
||||
// Set up mocha before/after callbacks so that a 'dotnet run' process exists
|
||||
// for the same duration as the context this is called inside
|
||||
let aspNetProcess: AspNetProcess;
|
||||
before(() => {
|
||||
aspNetProcess = new AspNetProcess(cwd, mode, dllToRun);
|
||||
return aspNetProcess.waitUntilListening();
|
||||
});
|
||||
after(() => aspNetProcess.dispose());
|
||||
}
|
||||
|
||||
private _process: childProcess.ChildProcess;
|
||||
private _processHasExited: boolean;
|
||||
private _stdoutReader: readline.ReadLine;
|
||||
|
||||
constructor(cwd: string, mode: AspNetCoreEnviroment, dllToRun?: string) {
|
||||
try {
|
||||
// Prepare env for child process. Note that it doesn't inherit parent's env vars automatically,
|
||||
// hence cloning process.env.
|
||||
const childProcessEnv = Object.assign({}, process.env);
|
||||
childProcessEnv.ASPNETCORE_ENVIRONMENT = mode === AspNetCoreEnviroment.development ? 'Development' : 'Production';
|
||||
|
||||
const verbOrAssembly = dllToRun || 'run';
|
||||
console.log(`Running 'dotnet ${ verbOrAssembly }' in ${ cwd }`);
|
||||
this._process = crossSpawn('dotnet', [verbOrAssembly], { cwd: cwd, stdio: 'pipe', env: childProcessEnv });
|
||||
this._stdoutReader = readline.createInterface(this._process.stdout, null);
|
||||
|
||||
// Echo stdout to the test process's own stdout
|
||||
this._stdoutReader.on('line', line => {
|
||||
console.log(`[dotnet] ${ line.toString() }`);
|
||||
});
|
||||
|
||||
// Also echo stderr
|
||||
this._process.stderr.on('data', chunk => {
|
||||
console.log(`[dotnet ERROR] ${ chunk.toString() }`);
|
||||
});
|
||||
|
||||
// Ensure the process isn't orphaned even if Node crashes before we're disposed
|
||||
process.on('exit', () => this._killAspNetProcess());
|
||||
|
||||
// Also track whether it exited on its own already
|
||||
this._process.on('exit', () => {
|
||||
this._processHasExited = true;
|
||||
});
|
||||
} catch(ex) {
|
||||
console.log('ERROR: ' + ex.toString());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public waitUntilListening(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._stdoutReader.on('line', (line: string) => {
|
||||
if (line.startsWith('Now listening on:')) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._killAspNetProcess(err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _killAspNetProcess(callback?: (err: any) => void) {
|
||||
callback = callback || (() => {});
|
||||
if (!this._processHasExited) {
|
||||
// It's important to kill the whole tree, because 'dotnet run' launches a separate 'dotnet exec'
|
||||
// child process that would otherwise be left running
|
||||
treeKill(this._process.pid, 'SIGINT', err => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
// It's not enough just to send a SIGINT to ASP.NET. It will stay open for a moment, completing
|
||||
// any outstanding requests. We have to wait for it really to be gone before continuing, otherwise
|
||||
// the next test might be unable to start because of the port still being in use.
|
||||
console.log(`Waiting until port ${ defaultPort } is closed...`);
|
||||
waitUntilPortState(defaultPort, defaultInterface, /* isListening */ false, /* timeoutMs */ 15000, err => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
console.log(`Port ${ defaultPort } is now closed`);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function publishProjectSync(sourceDir: string, outputDir: string) {
|
||||
childProcess.execSync(`dotnet publish -c Release -o ${ outputDir }`, {
|
||||
cwd: sourceDir,
|
||||
stdio: 'inherit',
|
||||
encoding: 'utf8'
|
||||
});
|
||||
}
|
||||
24
test/templates/util/ports.ts
Normal file
24
test/templates/util/ports.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as portastic from 'portastic';
|
||||
const pollInterval = 500;
|
||||
|
||||
export function waitUntilPortState(port: number, iface: string, isListening: boolean, timeoutMs: number, callback: (err: any) => void) {
|
||||
if (!(timeoutMs > 0)) {
|
||||
throw new Error(`Timed out waiting for port ${ port } to become ${ isListening ? 'in use' : 'free' }`);
|
||||
}
|
||||
|
||||
portastic.test(port, iface).then(
|
||||
actuallyIsAvailable => {
|
||||
const actuallyIsListening = !actuallyIsAvailable;
|
||||
if (actuallyIsListening === isListening) {
|
||||
// Desired state is reached
|
||||
callback(null);
|
||||
} else {
|
||||
// Wait longer
|
||||
setTimeout(() => {
|
||||
waitUntilPortState(port, iface, isListening, timeoutMs - pollInterval, callback);
|
||||
}, pollInterval);
|
||||
}
|
||||
},
|
||||
callback
|
||||
)
|
||||
}
|
||||
12
test/templates/util/webdriverio.ts
Normal file
12
test/templates/util/webdriverio.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// Workaround for missing '.value' property on WebdriverIO.Client<RawResult<T>> that should be of type T
|
||||
// Can't notify TypeScript that the property exists directly, because the interface merging feature doesn't
|
||||
// appear to support pattern matching in such a way that WebdriverIO.Client<T> is extended only when T
|
||||
// itself extends RawResult<U> for some U.
|
||||
export function getValue<T>(client: WebdriverIO.Client<WebdriverIO.RawResult<T>>): T {
|
||||
return (client as any).value;
|
||||
}
|
||||
|
||||
// The official type declarations for getCssProperty are completely wrong. This function matches runtime behaviour.
|
||||
export function getCssPropertyValue<T>(client: WebdriverIO.Client<T>, selector: string, cssProperty: string): string {
|
||||
return (client.getCssProperty(selector, cssProperty) as any).value;
|
||||
}
|
||||
53
test/templates/util/yeoman.ts
Normal file
53
test/templates/util/yeoman.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as childProcess from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as rimraf from 'rimraf';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
|
||||
const generatorDirRelative = '../templates/package-builder/dist/generator-aspnetcore-spa';
|
||||
const yoPackageDirAbsolute = path.resolve('./node_modules/yo');
|
||||
|
||||
export interface GeneratorOptions {
|
||||
framework: string;
|
||||
name: string;
|
||||
sdkVersion?: string;
|
||||
tests?: boolean;
|
||||
}
|
||||
|
||||
export function generateProjectSync(targetDir: string, generatorOptions: GeneratorOptions) {
|
||||
const generatorDirAbsolute = path.resolve(generatorDirRelative);
|
||||
console.log(`Running NPM install to prepare Yeoman generator at ${ generatorDirAbsolute }`);
|
||||
childProcess.execSync(`npm install`, { stdio: 'inherit', cwd: generatorDirAbsolute });
|
||||
|
||||
console.log(`Ensuring empty output directory at ${ targetDir }`);
|
||||
rimraf.sync(targetDir);
|
||||
mkdirp.sync(targetDir);
|
||||
|
||||
const yoExecutableAbsolute = findYeomanCliScript();
|
||||
console.log(`Will invoke Yeoman at ${ yoExecutableAbsolute } to generate application in ${ targetDir } with options:`);
|
||||
console.log(JSON.stringify(generatorOptions, null, 2));
|
||||
const command = `node "${ yoExecutableAbsolute }" "${ path.resolve(generatorDirAbsolute, './app/index.js') }"`;
|
||||
const args = makeYeomanCommandLineArgs(generatorOptions);
|
||||
childProcess.execSync(`${ command } ${ args }`, {
|
||||
stdio: 'inherit',
|
||||
cwd: targetDir
|
||||
});
|
||||
}
|
||||
|
||||
function findYeomanCliScript() {
|
||||
// On Windows, you can't invoke ./node_modules/.bin/yo from the shell for some reason.
|
||||
// So instead, we'll locate the CLI entrypoint that yeoman would expose if it was installed globally.
|
||||
const yeomanPackageJsonPath = path.join(yoPackageDirAbsolute, './package.json');
|
||||
const yeomanPackageJson = require(yeomanPackageJsonPath);
|
||||
const yeomanCliScriptRelative = yeomanPackageJson.bin.yo;
|
||||
if (!yeomanCliScriptRelative) {
|
||||
throw new Error(`Could not find Yeoman CLI script. Looked for a bin/yo entry in ${ yeomanPackageJsonPath }`);
|
||||
}
|
||||
|
||||
return path.join(yoPackageDirAbsolute, yeomanCliScriptRelative);
|
||||
}
|
||||
|
||||
function makeYeomanCommandLineArgs(generatorOptions: GeneratorOptions) {
|
||||
return Object.getOwnPropertyNames(generatorOptions)
|
||||
.map(key => `--${ key }="${ generatorOptions[key] }"`)
|
||||
.join(' ');
|
||||
}
|
||||
15
test/tsconfig.json
Normal file
15
test/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"target": "es5",
|
||||
"rootDir": ".",
|
||||
"outDir": "tmp",
|
||||
"sourceMap": false,
|
||||
"lib": ["es6", "dom"]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules",
|
||||
"tmp"
|
||||
]
|
||||
}
|
||||
204
test/wdio.conf.js
Normal file
204
test/wdio.conf.js
Normal file
@@ -0,0 +1,204 @@
|
||||
exports.config = {
|
||||
|
||||
//
|
||||
// ==================
|
||||
// Specify Test Files
|
||||
// ==================
|
||||
// Define which test specs should run. The pattern is relative to the directory
|
||||
// from which `wdio` was called. Notice that, if you are calling `wdio` from an
|
||||
// NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
|
||||
// directory is where your package.json resides, so `wdio` will be called from there.
|
||||
//
|
||||
specs: [
|
||||
'./tmp/templates/**/*.spec.js'
|
||||
],
|
||||
// Patterns to exclude.
|
||||
exclude: [
|
||||
// 'path/to/excluded/files'
|
||||
],
|
||||
//
|
||||
// ============
|
||||
// Capabilities
|
||||
// ============
|
||||
// Define your capabilities here. WebdriverIO can run multiple capabilities at the same
|
||||
// time. Depending on the number of capabilities, WebdriverIO launches several test
|
||||
// sessions. Within your capabilities you can overwrite the spec and exclude options in
|
||||
// order to group specific specs to a specific capability.
|
||||
//
|
||||
// First, you can define how many instances should be started at the same time. Let's
|
||||
// say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
|
||||
// set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
|
||||
// files and you set maxInstances to 10, all spec files will get tested at the same time
|
||||
// and 30 processes will get spawned. The property handles how many capabilities
|
||||
// from the same test should run tests.
|
||||
//
|
||||
maxInstances: 10,
|
||||
//
|
||||
// If you have trouble getting all important capabilities together, check out the
|
||||
// Sauce Labs platform configurator - a great tool to configure your capabilities:
|
||||
// https://docs.saucelabs.com/reference/platforms-configurator
|
||||
//
|
||||
capabilities: [{
|
||||
// maxInstances can get overwritten per capability. So if you have an in-house Selenium
|
||||
// grid with only 5 firefox instances available you can make sure that not more than
|
||||
// 5 instances get started at a time.
|
||||
maxInstances: 5,
|
||||
//
|
||||
browserName: 'chrome'
|
||||
}],
|
||||
//
|
||||
// ===================
|
||||
// Test Configurations
|
||||
// ===================
|
||||
// Define all options that are relevant for the WebdriverIO instance here
|
||||
//
|
||||
// By default WebdriverIO commands are executed in a synchronous way using
|
||||
// the wdio-sync package. If you still want to run your tests in an async way
|
||||
// e.g. using promises you can set the sync option to false.
|
||||
sync: true,
|
||||
//
|
||||
// Level of logging verbosity: silent | verbose | command | data | result | error
|
||||
logLevel: 'silent',
|
||||
//
|
||||
// Enables colors for log output.
|
||||
coloredLogs: true,
|
||||
//
|
||||
// If you only want to run your tests until a specific amount of tests have failed use
|
||||
// bail (default is 0 - don't bail, run all tests).
|
||||
bail: 0,
|
||||
//
|
||||
// Saves a screenshot to a given path if a command fails.
|
||||
screenshotPath: './tmp/errorShots/',
|
||||
//
|
||||
// Set a base URL in order to shorten url command calls. If your url parameter starts
|
||||
// with "/", then the base url gets prepended.
|
||||
baseUrl: 'http://localhost:5000',
|
||||
//
|
||||
// Default timeout for all waitFor* commands.
|
||||
waitforTimeout: 10000,
|
||||
//
|
||||
// Default timeout in milliseconds for request
|
||||
// if Selenium Grid doesn't send response
|
||||
connectionRetryTimeout: 90000,
|
||||
//
|
||||
// Default request retries count
|
||||
connectionRetryCount: 3,
|
||||
//
|
||||
// Initialize the browser instance with a WebdriverIO plugin. The object should have the
|
||||
// plugin name as key and the desired plugin options as properties. Make sure you have
|
||||
// the plugin installed before running any tests. The following plugins are currently
|
||||
// available:
|
||||
// WebdriverCSS: https://github.com/webdriverio/webdrivercss
|
||||
// WebdriverRTC: https://github.com/webdriverio/webdriverrtc
|
||||
// Browserevent: https://github.com/webdriverio/browserevent
|
||||
// plugins: {
|
||||
// webdrivercss: {
|
||||
// screenshotRoot: 'my-shots',
|
||||
// failedComparisonsRoot: 'diffs',
|
||||
// misMatchTolerance: 0.05,
|
||||
// screenWidth: [320,480,640,1024]
|
||||
// },
|
||||
// webdriverrtc: {},
|
||||
// browserevent: {}
|
||||
// },
|
||||
//
|
||||
// Test runner services
|
||||
// Services take over a specific job you don't want to take care of. They enhance
|
||||
// your test setup with almost no effort. Unlike plugins, they don't add new
|
||||
// commands. Instead, they hook themselves up into the test process.
|
||||
// services: ['selenium-standalone'],
|
||||
//
|
||||
// Framework you want to run your specs with.
|
||||
// The following are supported: Mocha, Jasmine, and Cucumber
|
||||
// see also: http://webdriver.io/guide/testrunner/frameworks.html
|
||||
//
|
||||
// Make sure you have the wdio adapter package for the specific framework installed
|
||||
// before running any tests.
|
||||
framework: 'mocha',
|
||||
//
|
||||
// Test reporter for stdout.
|
||||
// The only one supported by default is 'dot'
|
||||
// see also: http://webdriver.io/guide/testrunner/reporters.html
|
||||
reporters: ['junit'],
|
||||
reporterOptions: {
|
||||
outputDir: './tmp/junit'
|
||||
},
|
||||
|
||||
//
|
||||
// Options to be passed to Mocha.
|
||||
// See the full list at http://mochajs.org/
|
||||
mochaOpts: {
|
||||
ui: 'bdd',
|
||||
timeout: 60000
|
||||
},
|
||||
//
|
||||
// =====
|
||||
// Hooks
|
||||
// =====
|
||||
// WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
|
||||
// it and to build services around it. You can either apply a single function or an array of
|
||||
// methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
|
||||
// resolved to continue.
|
||||
//
|
||||
// Gets executed once before all workers get launched.
|
||||
// onPrepare: function (config, capabilities) {
|
||||
// },
|
||||
//
|
||||
// Gets executed just before initialising the webdriver session and test framework. It allows you
|
||||
// to manipulate configurations depending on the capability or spec.
|
||||
// beforeSession: function (config, capabilities, specs) {
|
||||
// },
|
||||
//
|
||||
// Gets executed before test execution begins. At this point you can access all global
|
||||
// variables, such as `browser`. It is the perfect place to define custom commands.
|
||||
// before: function (capabilities, specs) {
|
||||
// },
|
||||
//
|
||||
// Hook that gets executed before the suite starts
|
||||
// beforeSuite: function (suite) {
|
||||
// },
|
||||
//
|
||||
// Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
|
||||
// beforeEach in Mocha)
|
||||
// beforeHook: function () {
|
||||
// },
|
||||
//
|
||||
// Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
|
||||
// afterEach in Mocha)
|
||||
// afterHook: function () {
|
||||
// },
|
||||
//
|
||||
// Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
|
||||
// beforeTest: function (test) {
|
||||
// },
|
||||
//
|
||||
// Runs before a WebdriverIO command gets executed.
|
||||
// beforeCommand: function (commandName, args) {
|
||||
// },
|
||||
//
|
||||
// Runs after a WebdriverIO command gets executed
|
||||
// afterCommand: function (commandName, args, result, error) {
|
||||
// },
|
||||
//
|
||||
// Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
|
||||
// afterTest: function (test) {
|
||||
// },
|
||||
//
|
||||
// Hook that gets executed after the suite has ended
|
||||
// afterSuite: function (suite) {
|
||||
// },
|
||||
//
|
||||
// Gets executed after all tests are done. You still have access to all global variables from
|
||||
// the test.
|
||||
// after: function (result, capabilities, specs) {
|
||||
// },
|
||||
//
|
||||
// Gets executed right after terminating the webdriver session.
|
||||
// afterSession: function (config, capabilities, specs) {
|
||||
// },
|
||||
//
|
||||
// Gets executed after all workers got shut down and the process is about to exit. It is not
|
||||
// possible to defer the end of the process using a promise.
|
||||
// onComplete: function(exitCode) {
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user