├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── action.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── LICENSE.txt ├── README.md ├── build.ps1 ├── samples ├── QuasarCliSample │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── ClientApp │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── .postcssrc.js │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── quasar.conf.js │ │ ├── src │ │ │ ├── App.vue │ │ │ ├── assets │ │ │ │ ├── quasar-logo-full.svg │ │ │ │ └── sad.svg │ │ │ ├── boot │ │ │ │ └── .gitkeep │ │ │ ├── components │ │ │ │ └── .gitkeep │ │ │ ├── css │ │ │ │ ├── app.styl │ │ │ │ └── quasar.variables.styl │ │ │ ├── index.template.html │ │ │ ├── layouts │ │ │ │ └── MyLayout.vue │ │ │ ├── models │ │ │ │ └── IWeatherForecast.ts │ │ │ ├── pages │ │ │ │ ├── Error404.vue │ │ │ │ └── Index.vue │ │ │ ├── router │ │ │ │ ├── index.ts │ │ │ │ └── routes.ts │ │ │ └── statics │ │ │ │ ├── app-logo-128x128.png │ │ │ │ └── icons │ │ │ │ ├── apple-icon-120x120.png │ │ │ │ ├── apple-icon-152x152.png │ │ │ │ ├── apple-icon-167x167.png │ │ │ │ ├── apple-icon-180x180.png │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ ├── favicon-96x96.png │ │ │ │ ├── favicon.ico │ │ │ │ ├── icon-128x128.png │ │ │ │ ├── icon-192x192.png │ │ │ │ ├── icon-256x256.png │ │ │ │ ├── icon-384x384.png │ │ │ │ ├── icon-512x512.png │ │ │ │ ├── ms-icon-144x144.png │ │ │ │ └── safari-pinned-tab.svg │ │ ├── tsconfig.json │ │ └── tslint.json │ ├── Controllers │ │ └── WeatherForecastController.cs │ ├── Models │ │ └── WeatherForecast.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── QuasarCliSample.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ └── .gitkeep ├── Vue3 │ ├── ClientApp │ │ ├── .gitignore │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── index.html │ │ └── src │ │ │ ├── App.vue │ │ │ ├── assets │ │ │ └── logo.png │ │ │ ├── components │ │ │ └── HelloWorld.vue │ │ │ └── main.js │ ├── Controllers │ │ └── WeatherForecastController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── Vue3Sample.csproj │ ├── WeatherForecast.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ └── .gitkeep └── VueCliSample │ ├── ClientApp │ ├── .browserslistrc │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── main.ts │ │ ├── models │ │ │ └── IWeatherForecast.ts │ │ ├── router.ts │ │ ├── shims-tsx.d.ts │ │ ├── shims-vue.d.ts │ │ └── views │ │ │ ├── About.vue │ │ │ └── Home.vue │ ├── tsconfig.json │ ├── tslint.json │ └── vue.config.js │ ├── Controllers │ └── WeatherForecastController.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Startup.cs │ ├── VueCliSample.csproj │ ├── WeatherForecast.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ └── .gitkeep └── src ├── VueCliMiddleware.Tests ├── EventedStreamReaderTests.cs ├── PidUtilsTests.cs └── VueCliMiddleware.Tests.csproj ├── VueCliMiddleware.sln └── VueCliMiddleware ├── Util ├── Internals.cs ├── KillPort.cs ├── ScriptRunner.cs └── ScriptRunnerType.cs ├── VueCliMiddleware.csproj ├── VueDevelopmentServerMiddleware.cs └── VueDevelopmentServerMiddlewareExtensions.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto !eol -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [EEParker] 2 | -------------------------------------------------------------------------------- /.github/workflows/action.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | configuration: [Debug, Release] 14 | runs-on: windows-latest 15 | env: 16 | solution_name: ./src/VueCliMiddleware.sln 17 | artifact_path: ./src/nupkgs 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 0 23 | - name: Setup .NET 24 | uses: actions/setup-dotnet@v1 25 | with: 26 | dotnet-version: 6.0.x 27 | - name: Restore dependencies 28 | run: dotnet restore $env:solution_name 29 | env: 30 | Configuration: ${{ matrix.configuration }} 31 | - name: Build 32 | run: dotnet build $env:solution_name -c $env:Configuration --no-restore 33 | env: 34 | Configuration: ${{ matrix.configuration }} 35 | # - name: Test 36 | # run: dotnet test $env:solution_name -c $env:Configuration --no-build --verbosity normal 37 | # env: 38 | # Configuration: ${{ matrix.configuration }} 39 | - name: Pack 40 | run: dotnet pack $env:solution_name -c Release --no-restore --output $env:artifact_path 41 | - name: Artifact 42 | uses: actions/upload-artifact@v2 43 | with: 44 | name: nuget-package 45 | path: $env:artifact_path/*.nupkg 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #Ignore thumbnails created by Windows 3 | Thumbs.db 4 | #Ignore .idea 5 | *.idea 6 | #Ignore files built by Visual Studio 7 | *.obj 8 | *.exe 9 | *.pdb 10 | *.user 11 | *.aps 12 | *.pch 13 | *.vspscc 14 | *_i.c 15 | *_p.c 16 | *.ncb 17 | *.suo 18 | *.tlb 19 | *.tlh 20 | *.bak 21 | *.cache 22 | *.ilk 23 | *.log 24 | [Bb]in 25 | [Dd]ebug*/ 26 | *.lib 27 | *.sbr 28 | obj/ 29 | [Rr]elease*/ 30 | _ReSharper*/ 31 | [Tt]est[Rr]esult* 32 | .vs/ 33 | #Nuget packages folder 34 | packages/ 35 | /src/nupkgs/* 36 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/VueCliMiddleware/bin/Debug/netcoreapp2.2/VueCliMiddleware.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/VueCliMiddleware", 16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window 17 | "console": "internalConsole", 18 | "stopAtEntry": false, 19 | "internalConsoleOptions": "openOnSessionStart" 20 | }, 21 | { 22 | "name": ".NET Core Attach", 23 | "type": "coreclr", 24 | "request": "attach", 25 | "processId": "${command:pickProcess}" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dotnet-test-explorer.testProjectPath": "**/*.Tests/*.csproj" 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/VueCliMiddleware/VueCliMiddleware.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | }, 14 | { 15 | "label": "test", 16 | "command": "dotnet", 17 | "type": "process", 18 | "group": "test", 19 | "args": [ 20 | "test", 21 | "${workspaceFolder}/VueCliMiddleware.Tests/VueCliMiddleware.Tests.csproj" 22 | ], 23 | "problemMatcher": "$msCompile" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation Contributors 2 | 3 | All rights reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 6 | this file except in compliance with the License. You may obtain a copy of the 7 | License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software distributed 12 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | specific language governing permissions and limitations under the License. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VueCliMiddleware - Supporting Vue Cli and Quasar Cli 2 | 3 | [![](https://img.shields.io/nuget/v/VueCliMiddleware.svg)](https://www.nuget.org/packages/VueCliMiddleware/) 4 | 5 | This is a stand-alone module to add Vue Cli and Quasar Cli support to AspNet Core. 6 | 7 | See the examples here: [https://github.com/EEParker/aspnetcore-vueclimiddleware/tree/master/samples](https://github.com/EEParker/aspnetcore-vueclimiddleware/tree/master/samples) 8 | 9 | ## ASP.NET 3.X Endpoint Routing 10 | First, be sure to switch Vue Cli or Quasar Cli to output distribution files to wwwroot directly (not dist). 11 | 12 | * Quasar CLI: regex: "Compiled successfully" 13 | * Vue CLI: regex: "Compiled successfully" or "running at" or "Starting development server" depending on version 14 | >the reason for **`Starting development server`** ,the npm-script running checkpoint: 15 | Although the dev server may eventually tell us the URL it's listening on, 16 | it doesn't do so until it's finished compiling, and even then only if there were 17 | no compiler warnings. So instead of waiting for that, consider it ready as soon 18 | as it starts listening for requests.[see the codes](https://github.com/EEParker/aspnetcore-vueclimiddleware/blob/master/src/VueCliMiddleware/VueDevelopmentServerMiddleware.cs#L91) 19 | 20 | ## Configuration Options 21 | When using the `MapToVueCliProxy` or `UseVueCli` you can customize the behavior based on your npm script runner or compiler. 22 | 23 | | Parameter | Type | Description | Default | 24 | |-----------|------|-------------|---------| 25 | |`npmScript`|string|The name of the script in your package.json file that launches the vue-cli, quasar cli or other web server.|| 26 | |`spaOptions`|[SpaOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.spaservices.spaoptions?view=aspnetcore-5.0)|Set the folder of the app to be proxied.|| 27 | |`port`|int|Specify the vue cli server port number. This is also used by the force-kill option to discover processes utilizing the port.|8080| 28 | |`https`|bool|Set proxy to use https|false| 29 | |`runner`|`enum { Npm, Yarn }`|Specify the runner, Npm and Yarn are valid options. Yarn support is experimental.|Npm| 30 | |`regex`|string|**IMPORTANT** Text to search in npm log that indicates web server is running. This **MUST BE SET** for vue-cli, quasar and quasar v2. (e.g. `running at`, `READY`, `APP Url`)|`running at`| 31 | |`forceKill`|bool|Attempt to kill the npm process when stopping debugging.|false| 32 | |`wsl`|bool|Set to true if you are using WSL on windows. For other operating systems it will be ignored. This changes the executed process name to `wsl` instead of `cmd`.|false| 33 | 34 | 35 | 36 | See [Migrating Asp.Net 2.2 to 3.0 Endpoint Routing](https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#update-routing-startup-code) 37 | ```csharp 38 | public class Startup { 39 | 40 | public Startup(IConfiguration configuration) 41 | { 42 | Configuration = configuration; 43 | } 44 | 45 | public IConfiguration Configuration { get; } 46 | 47 | // This method gets called by the runtime. Use this method to add services to the container. 48 | public void ConfigureServices(IServiceCollection services) 49 | { 50 | // NOTE: PRODUCTION Ensure this is the same path that is specified in your webpack output 51 | services.AddSpaStaticFiles(opt => opt.RootPath = "ClientApp/dist"); 52 | services.AddControllers(); 53 | } 54 | 55 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 56 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 57 | { 58 | // optional base path for IIS virtual app/directory 59 | app.UsePathBase("/optionalpath"); 60 | 61 | // PRODUCTION uses webpack static files 62 | app.UseSpaStaticFiles(); 63 | 64 | // Routing 65 | app.UseRouting(); 66 | app.UserAuthorization(); 67 | app.UseEndpoints(endpoints => 68 | { 69 | endpoints.MapControllers(); 70 | 71 | endpoints.MapToVueCliProxy( 72 | "{*path}", 73 | new SpaOptions { SourcePath = "ClientApp" }, 74 | npmScript: (System.Diagnostics.Debugger.IsAttached) ? "serve" : null, 75 | regex: "Compiled successfully", 76 | forceKill: true, 77 | wsl: false // Set to true if you are using WSL on windows. For other operating systems it will be ignored 78 | ); 79 | }); 80 | } 81 | } 82 | ``` 83 | 84 | 85 | ## ASP.NET 2.2 Usage Example 86 | ```csharp 87 | using VueCliMiddleware; 88 | 89 | public class Startup 90 | { 91 | public Startup(IConfiguration configuration) 92 | { 93 | Configuration = configuration; 94 | } 95 | 96 | public IConfiguration Configuration { get; } 97 | 98 | public virtual void ConfigureServices(IServiceCollection services) 99 | { 100 | services.AddMvc(); // etc 101 | 102 | // Need to register ISpaStaticFileProvider for UseSpaStaticFiles middleware to work 103 | services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; }); 104 | } 105 | 106 | public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env) 107 | { 108 | // your config opts... 109 | // optional basepath 110 | // app.UsePathBase("/myapp"); 111 | 112 | // add static files from SPA (/dist) 113 | app.UseSpaStaticFiles(); 114 | 115 | app.UseMvc(routes => /* configure*/ ); 116 | 117 | app.UseSpa(spa => 118 | { 119 | spa.Options.SourcePath = "ClientApp"; 120 | #if DEBUG 121 | if (env.IsDevelopment()) 122 | { 123 | spa.UseVueCli(npmScript: "serve", port: 8080); // optional port 124 | } 125 | #endif 126 | }); 127 | } 128 | } 129 | ``` 130 | 131 | ## CSPROJ Configuration 132 | You may also need to add the following tasks to your csproj file. This are similar to what are found in the default ASPNETSPA templates. 133 | 134 | ```project.csproj 135 | 136 | 137 | ClientApp\ 138 | $(DefaultItemExcludes);$(SpaRoot)node_modules\** 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | %(DistFiles.Identity) 172 | PreserveNewest 173 | 174 | 175 | 176 | 177 | ``` 178 | 179 | ## History 180 | 181 | Due to the discussion [here](https://github.com/aspnet/JavaScriptServices/pull/1726), it was decided to not be included in the Microsoft owned package. 182 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | dotnet pack .\src\VueCliMiddleware.sln --output .\src\nupkgs -------------------------------------------------------------------------------- /samples/QuasarCliSample/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.0/QuasarCliSample.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser 18 | "serverReadyAction": { 19 | "action": "openExternally", 20 | "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach", 33 | "processId": "${command:pickProcess}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /samples/QuasarCliSample/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/QuasarCliSample.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/QuasarCliSample.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/QuasarCliSample.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/.gitignore: -------------------------------------------------------------------------------- 1 | .quasar 2 | .DS_Store 3 | .thumbs.db 4 | node_modules 5 | /dist 6 | /src-cordova/node_modules 7 | /src-cordova/platforms 8 | /src-cordova/plugins 9 | /src-cordova/www 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: [ 5 | // to edit target browsers: use "browserslist" field in package.json 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/README.md: -------------------------------------------------------------------------------- 1 | # quasar-cli-sample (quasar-cli-sample) 2 | 3 | Quasar Cli Sample 4 | 5 | This project was created `quasar create` with default options and no plugins. NPM is used as the package manager. 6 | 7 | ## Install the dependencies 8 | ```bash 9 | npm install 10 | ``` 11 | 12 | ### Start the app in development mode (hot-code reloading, error reporting, etc.) 13 | ```bash 14 | quasar dev 15 | ``` 16 | 17 | 18 | ### Build the app for production 19 | ```bash 20 | quasar build 21 | ``` 22 | 23 | ### Customize the configuration 24 | See [Configuring quasar.conf.js](https://quasar.dev/quasar-cli/quasar-conf-js). 25 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@quasar/babel-preset-app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasar-cli-sample", 3 | "version": "0.0.1", 4 | "description": "Quasar Cli Sample", 5 | "productName": "quasar-cli-sample", 6 | "cordovaId": "org.cordova.quasar.app", 7 | "author": "EEparker ", 8 | "private": true, 9 | "scripts": { 10 | "serve": "quasar dev", 11 | "build": "quasar build", 12 | "build:pwa": "quasar build -m pwa" 13 | }, 14 | "dependencies": { 15 | "@quasar/extras": "^1.9.13", 16 | "axios": "^0.21.1", 17 | "quasar": "^1.15.0", 18 | "vue-class-component": "^7.2.6", 19 | "vue-property-decorator": "^8.5.1", 20 | "vuex": "^3.6.0", 21 | "vuex-class": "^0.3.2", 22 | "vuex-module-decorators": "^0.11.0" 23 | }, 24 | "devDependencies": { 25 | "@quasar/app": "^1.9.6", 26 | "@types/node": "^13.13.39", 27 | "pug": "^2.0.4", 28 | "pug-plain-loader": "^1.1.0", 29 | "ts-loader": "^6.2.2", 30 | "typescript": "^3.9.7" 31 | }, 32 | "engines": { 33 | "node": ">= 8.9.0", 34 | "npm": ">= 5.6.0", 35 | "yarn": ">= 1.6.0" 36 | }, 37 | "browserslist": [ 38 | "last 1 version, not dead, ie >= 11" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/quasar.conf.js: -------------------------------------------------------------------------------- 1 | // Configuration for your app 2 | // https://quasar.dev/quasar-cli/quasar-conf-js 3 | 4 | const path = require('path') 5 | 6 | // Configuration for your app 7 | function extendWebpackAliases(cfg) { 8 | cfg.resolve.alias['~'] = __dirname; 9 | cfg.resolve.alias['@'] = path.resolve(__dirname, 'src'); 10 | } 11 | 12 | function extendWebpackTypescript(cfg) { 13 | // added the type-script support 14 | cfg.resolve.extensions.push('.ts'); 15 | 16 | cfg.module.rules.push({ 17 | test: /\.(tsx?)$/, 18 | loader: 'ts-loader', 19 | options: { appendTsSuffixTo: [/\.vue$/] } 20 | }); 21 | }; 22 | 23 | function extendWebpackPug(cfg) { 24 | cfg.module.rules.push({ 25 | test: /\.pug$/, 26 | loader: 'pug-plain-loader' 27 | }); 28 | } 29 | 30 | module.exports = function (ctx) { 31 | return { 32 | // app boot file (/src/boot) 33 | // --> boot files are part of "main.js" 34 | boot: [ 35 | ], 36 | 37 | css: [ 38 | 'app.styl' 39 | ], 40 | 41 | extras: [ 42 | // 'ionicons-v4', 43 | // 'mdi-v3', 44 | // 'fontawesome-v5', 45 | // 'eva-icons', 46 | // 'themify', 47 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! 48 | 49 | 'roboto-font', // optional, you are not bound to it 50 | 'material-icons' // optional, you are not bound to it 51 | ], 52 | 53 | framework: { 54 | // iconSet: 'ionicons-v4', 55 | // lang: 'de', // Quasar language 56 | 57 | // all: true, // --- includes everything; for dev only! 58 | 59 | components: [ 60 | 'QLayout', 61 | 'QHeader', 62 | 'QDrawer', 63 | 'QPageContainer', 64 | 'QPage', 65 | 'QToolbar', 66 | 'QToolbarTitle', 67 | 'QBtn', 68 | 'QIcon', 69 | 'QList', 70 | 'QItem', 71 | 'QItemSection', 72 | 'QItemLabel', 73 | 'QTable', 74 | 'QTh', 75 | 'QTr', 76 | 'QTd' 77 | ], 78 | 79 | directives: [ 80 | 'Ripple' 81 | ], 82 | 83 | // Quasar plugins 84 | plugins: [ 85 | 'Notify' 86 | ] 87 | }, 88 | 89 | supportIE: false, 90 | 91 | build: { 92 | scopeHoisting: true, 93 | distDir: './dist', // used by dotnet project 94 | vueRouterMode: 'history', 95 | // vueCompiler: true, 96 | // gzip: true, 97 | // analyze: true, 98 | extractCSS: true, 99 | extendWebpack (cfg) { 100 | extendWebpackAliases(cfg); 101 | extendWebpackTypescript(cfg); 102 | extendWebpackPug(cfg); 103 | } 104 | }, 105 | 106 | devServer: { 107 | // https: true, 108 | // port: 8080, 109 | open: true // opens browser window automatically 110 | }, 111 | 112 | // animations: 'all', // --- includes all animations 113 | animations: [], 114 | 115 | ssr: { 116 | pwa: false 117 | }, 118 | 119 | pwa: { 120 | // workboxPluginMode: 'InjectManifest', 121 | // workboxOptions: {}, // only for NON InjectManifest 122 | manifest: { 123 | // name: 'quasar-cli-sample', 124 | // short_name: 'quasar-cli-sample', 125 | // description: 'Quasar Cli Sample', 126 | display: 'standalone', 127 | orientation: 'portrait', 128 | background_color: '#ffffff', 129 | theme_color: '#027be3', 130 | icons: [ 131 | { 132 | 'src': 'statics/icons/icon-128x128.png', 133 | 'sizes': '128x128', 134 | 'type': 'image/png' 135 | }, 136 | { 137 | 'src': 'statics/icons/icon-192x192.png', 138 | 'sizes': '192x192', 139 | 'type': 'image/png' 140 | }, 141 | { 142 | 'src': 'statics/icons/icon-256x256.png', 143 | 'sizes': '256x256', 144 | 'type': 'image/png' 145 | }, 146 | { 147 | 'src': 'statics/icons/icon-384x384.png', 148 | 'sizes': '384x384', 149 | 'type': 'image/png' 150 | }, 151 | { 152 | 'src': 'statics/icons/icon-512x512.png', 153 | 'sizes': '512x512', 154 | 'type': 'image/png' 155 | } 156 | ] 157 | } 158 | }, 159 | 160 | cordova: { 161 | // id: 'org.cordova.quasar.app', 162 | // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing 163 | }, 164 | 165 | electron: { 166 | // bundler: 'builder', // or 'packager' 167 | 168 | extendWebpack (cfg) { 169 | // do something with Electron main process Webpack cfg 170 | // chainWebpack also available besides this extendWebpack 171 | }, 172 | 173 | packager: { 174 | // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options 175 | 176 | // OS X / Mac App Store 177 | // appBundleId: '', 178 | // appCategoryType: '', 179 | // osxSign: '', 180 | // protocol: 'myapp://path', 181 | 182 | // Windows only 183 | // win32metadata: { ... } 184 | }, 185 | 186 | builder: { 187 | // https://www.electron.build/configuration/configuration 188 | 189 | // appId: 'quasar-cli-sample' 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/assets/quasar-logo-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 66 | 69 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 104 | 105 | 106 | 107 | 113 | 118 | 126 | 133 | 142 | 151 | 160 | 169 | 178 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/assets/sad.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/boot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/boot/.gitkeep -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/components/.gitkeep -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/css/app.styl: -------------------------------------------------------------------------------- 1 | // app global css 2 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/css/quasar.variables.styl: -------------------------------------------------------------------------------- 1 | // Quasar Stylus Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Stylus variables found in Quasar's source Stylus files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // It's highly recommended to change the default colors 9 | // to match your app's branding. 10 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 11 | 12 | $primary = #027BE3 13 | $secondary = #26A69A 14 | $accent = #9C27B0 15 | 16 | $positive = #21BA45 17 | $negative = #C10015 18 | $info = #31CCEC 19 | $warning = #F2C037 20 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= htmlWebpackPlugin.options.productName %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/layouts/MyLayout.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 63 | 64 | 66 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/models/IWeatherForecast.ts: -------------------------------------------------------------------------------- 1 | export interface IWeatherForecast { 2 | temperatureC: number; 3 | temperatureF: number; 4 | summary: string; 5 | date: Date; 6 | } 7 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/pages/Error404.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 36 | 37 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter, { RouterOptions } from 'vue-router' 3 | 4 | import routes from './routes' 5 | 6 | Vue.use(VueRouter) 7 | 8 | /* 9 | * If not building with SSR mode, you can 10 | * directly export the Router instantiation 11 | */ 12 | const router = new VueRouter({ 13 | scrollBehavior: () => ({ x: 0, y: 0 }), 14 | routes, 15 | 16 | // Leave these as is and change from quasar.conf.js instead! 17 | // quasar.conf.js -> build -> vueRouterMode 18 | // quasar.conf.js -> build -> publicPath 19 | mode: process.env.VUE_ROUTER_MODE, 20 | base: process.env.VUE_ROUTER_BASE 21 | } as RouterOptions); 22 | 23 | export default router; 24 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/router/routes.ts: -------------------------------------------------------------------------------- 1 | import { RouteConfig} from 'vue-router'; 2 | 3 | const routes: Array = [ 4 | { 5 | path: '/', 6 | component: () => import('@/layouts/MyLayout.vue'), 7 | children: [ 8 | { path: '', component: () => import('@/pages/Index.vue') } 9 | ] 10 | } 11 | ]; 12 | 13 | // Always leave this as last one 14 | if (process.env.MODE !== 'ssr') { 15 | routes.push({ 16 | path: '*', 17 | component: () => import('@/pages/Error404.vue') 18 | }) 19 | } 20 | 21 | export default routes 22 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/app-logo-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/app-logo-128x128.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/apple-icon-120x120.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/apple-icon-152x152.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/apple-icon-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/apple-icon-167x167.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/apple-icon-180x180.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/favicon-16x16.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/favicon-32x32.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/favicon-96x96.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/favicon.ico -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/icon-128x128.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/icon-192x192.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/icon-256x256.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/icon-384x384.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/icon-512x512.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/ClientApp/src/statics/icons/ms-icon-144x144.png -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/src/statics/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "es5", 5 | "strict": true, 6 | "experimentalDecorators": true, 7 | "esModuleInterop": true, 8 | "module": "esNext", 9 | "jsx": "preserve", 10 | "moduleResolution": "node", 11 | "emitDecoratorMetadata": true, 12 | "allowSyntheticDefaultImports": true, 13 | "baseUrl": ".", 14 | "paths": { 15 | "~/*": ["./*"], 16 | "@/*": [ "src/*" ] 17 | } 18 | }, 19 | "include": [ 20 | "src/**/*.ts", 21 | "src/**/*.tsx", 22 | "src/**/*.vue", 23 | ], 24 | "exclude": [ 25 | ".quasar", 26 | "dist", 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/ClientApp/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ "tslint:recommended" ], 4 | "jsRules": {}, 5 | "rules": { 6 | "quotemark": [ true, "single" ], 7 | "indent": [ true ], 8 | "interface-name": [ false ], 9 | "arrow-parens": false, 10 | // Pending fix for shorthand property names. 11 | "object-literal-sort-keys": false 12 | }, 13 | "rulesDirectory": [] 14 | } 15 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | using QuasarCliSample.Models; 8 | 9 | namespace QuasarCliSample.Controllers 10 | { 11 | [ApiController] 12 | [Route("api/[controller]")] 13 | public class WeatherForecastController : ControllerBase 14 | { 15 | private static readonly string[] Summaries = new[] 16 | { 17 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 18 | }; 19 | 20 | private readonly ILogger _logger; 21 | 22 | public WeatherForecastController(ILogger logger) 23 | { 24 | _logger = logger; 25 | } 26 | 27 | [HttpGet] 28 | public IEnumerable Get() 29 | { 30 | var rng = new Random(); 31 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 32 | { 33 | Date = DateTime.Now.AddDays(index), 34 | TemperatureC = rng.Next(-20, 55), 35 | Summary = Summaries[rng.Next(Summaries.Length)] 36 | }) 37 | .ToArray(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/Models/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QuasarCliSample.Models 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace QuasarCliSample 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:58786", 7 | "sslPort": 0 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "QuasarCliSample": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /samples/QuasarCliSample/QuasarCliSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ClientApp\ 13 | $(DefaultItemExcludes);$(SpaRoot)node_modules\** 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | %(DistFiles.Identity) 54 | PreserveNewest 55 | True 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.SpaServices; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.FileProviders; 13 | using Microsoft.Extensions.Hosting; 14 | using Microsoft.Extensions.Logging; 15 | using VueCliMiddleware; 16 | 17 | namespace QuasarCliSample 18 | { 19 | public class Startup 20 | { 21 | public Startup(IConfiguration configuration) 22 | { 23 | Configuration = configuration; 24 | } 25 | 26 | public IConfiguration Configuration { get; } 27 | 28 | // This method gets called by the runtime. Use this method to add services to the container. 29 | public void ConfigureServices(IServiceCollection services) 30 | { 31 | // NOTE: PRODUCTION Ensure this is the same path that is specified in your webpack output 32 | services.AddSpaStaticFiles(opt => opt.RootPath = "ClientApp/dist"); 33 | services.AddControllers(); 34 | } 35 | 36 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 37 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 38 | { 39 | if (env.IsDevelopment()) 40 | { 41 | app.UseDeveloperExceptionPage(); 42 | } 43 | 44 | // NOTE: this is optional, it adds HTTPS to Kestrel 45 | app.UseHttpsRedirection(); 46 | 47 | // NOTE: PRODUCTION uses webpack static files 48 | app.UseSpaStaticFiles(); 49 | 50 | app.UseRouting(); 51 | 52 | app.UseAuthorization(); 53 | 54 | app.UseEndpoints(endpoints => 55 | { 56 | endpoints.MapControllers(); 57 | 58 | // NOTE: VueCliProxy is meant for developement and hot module reload 59 | // NOTE: SSR has not been tested 60 | // Production systems should only need the UseSpaStaticFiles() (above) 61 | // You could wrap this proxy in either 62 | // if (System.Diagnostics.Debugger.IsAttached) 63 | // or a preprocessor such as #if DEBUG 64 | endpoints.MapToVueCliProxy( 65 | "{*path}", 66 | new SpaOptions { SourcePath = "ClientApp" }, 67 | npmScript: (System.Diagnostics.Debugger.IsAttached) ? "serve" : null, 68 | forceKill: true); 69 | 70 | }); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /samples/QuasarCliSample/wwwroot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/QuasarCliSample/wwwroot/.gitkeep -------------------------------------------------------------------------------- /samples/Vue3/ClientApp/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /samples/Vue3/ClientApp/README.md: -------------------------------------------------------------------------------- 1 | # sample 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /samples/Vue3/ClientApp/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /samples/Vue3/ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "vue": "^3.0.0" 13 | }, 14 | "devDependencies": { 15 | "@vue/cli-plugin-babel": "~4.5.0", 16 | "@vue/cli-plugin-eslint": "~4.5.0", 17 | "@vue/cli-service": "~4.5.0", 18 | "@vue/compiler-sfc": "^3.0.0", 19 | "babel-eslint": "^10.1.0", 20 | "eslint": "^6.7.2", 21 | "eslint-plugin-vue": "^7.0.0-0" 22 | }, 23 | "eslintConfig": { 24 | "root": true, 25 | "env": { 26 | "node": true 27 | }, 28 | "extends": [ 29 | "plugin:vue/vue3-essential", 30 | "eslint:recommended" 31 | ], 32 | "parserOptions": { 33 | "parser": "babel-eslint" 34 | }, 35 | "rules": {} 36 | }, 37 | "browserslist": [ 38 | "> 1%", 39 | "last 2 versions", 40 | "not dead" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /samples/Vue3/ClientApp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/Vue3/ClientApp/public/favicon.ico -------------------------------------------------------------------------------- /samples/Vue3/ClientApp/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/Vue3/ClientApp/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | 27 | -------------------------------------------------------------------------------- /samples/Vue3/ClientApp/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/Vue3/ClientApp/src/assets/logo.png -------------------------------------------------------------------------------- /samples/Vue3/ClientApp/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /samples/Vue3/ClientApp/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /samples/Vue3/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace VueCliSample.Controllers 9 | { 10 | [ApiController] 11 | [Route("api/[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private static readonly string[] Summaries = new[] 15 | { 16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 17 | }; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public WeatherForecastController(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | [HttpGet] 27 | public IEnumerable Get() 28 | { 29 | var rng = new Random(); 30 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 31 | { 32 | Date = DateTime.Now.AddDays(index), 33 | TemperatureC = rng.Next(-20, 55), 34 | Summary = Summaries[rng.Next(Summaries.Length)] 35 | }) 36 | .ToArray(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/Vue3/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace VueCliSample 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/Vue3/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "https://localhost:44323/", 7 | "sslPort": 44323 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "VueCliSample": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /samples/Vue3/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.SpaServices; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Hosting; 13 | using Microsoft.Extensions.Logging; 14 | using VueCliMiddleware; 15 | 16 | namespace VueCliSample 17 | { 18 | public class Startup 19 | { 20 | public Startup(IConfiguration configuration) 21 | { 22 | Configuration = configuration; 23 | } 24 | 25 | public IConfiguration Configuration { get; } 26 | 27 | // This method gets called by the runtime. Use this method to add services to the container. 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | // NOTE: PRODUCTION Ensure this is the same path that is specified in your webpack output 31 | services.AddSpaStaticFiles(opt => opt.RootPath = "ClientApp/dist"); 32 | services.AddControllers(); 33 | } 34 | 35 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 36 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 37 | { 38 | if (env.IsDevelopment()) 39 | { 40 | app.UseDeveloperExceptionPage(); 41 | } 42 | 43 | // NOTE: this is optional, it adds HTTPS to Kestrel 44 | app.UseHttpsRedirection(); 45 | 46 | // NOTE: PRODUCTION uses webpack static files 47 | app.UseSpaStaticFiles(); 48 | 49 | app.UseRouting(); 50 | 51 | app.UseAuthorization(); 52 | 53 | app.UseEndpoints(endpoints => 54 | { 55 | endpoints.MapControllers(); 56 | 57 | // NOTE: VueCliProxy is meant for developement and hot module reload 58 | // NOTE: SSR has not been tested 59 | // Production systems should only need the UseSpaStaticFiles() (above) 60 | // You could wrap this proxy in either 61 | // if (System.Diagnostics.Debugger.IsAttached) 62 | // or a preprocessor such as #if DEBUG 63 | endpoints.MapToVueCliProxy( 64 | "{*path}", 65 | new SpaOptions { SourcePath = "ClientApp" }, 66 | npmScript: (System.Diagnostics.Debugger.IsAttached) ? "serve" : null, 67 | regex: "Compiled successfully", 68 | forceKill: true 69 | ); 70 | }); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /samples/Vue3/Vue3Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ClientApp\ 13 | $(DefaultItemExcludes);$(SpaRoot)node_modules\** 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | %(DistFiles.Identity) 54 | PreserveNewest 55 | True 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /samples/Vue3/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VueCliSample 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/Vue3/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/Vue3/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /samples/Vue3/wwwroot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/Vue3/wwwroot/.gitkeep -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/README.md: -------------------------------------------------------------------------------- 1 | # clientapp 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clientapp", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.21.1", 12 | "core-js": "^3.8.2", 13 | "vue": "^2.6.12", 14 | "vue-class-component": "^7.2.6", 15 | "vue-property-decorator": "^8.5.1", 16 | "vue-router": "^3.4.9" 17 | }, 18 | "devDependencies": { 19 | "@vue/cli-plugin-babel": "^4.5.10", 20 | "@vue/cli-plugin-typescript": "^4.5.10", 21 | "@vue/cli-service": "^4.5.10", 22 | "typescript": "^3.9.7", 23 | "vue-template-compiler": "^2.6.12" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/VueCliSample/ClientApp/public/favicon.ico -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | clientapp 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/VueCliSample/ClientApp/src/assets/logo.png -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | 5 | Vue.config.productionTip = false; 6 | 7 | new Vue({ 8 | router, 9 | render: (h) => h(App), 10 | }).$mount('#app'); 11 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/src/models/IWeatherForecast.ts: -------------------------------------------------------------------------------- 1 | export interface IWeatherForecast { 2 | date: Date; 3 | temperatureC: number; 4 | temperatureF: number; 5 | summary: string; 6 | } 7 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/src/router.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import Home from './views/Home.vue'; 4 | 5 | Vue.use(Router); 6 | 7 | export default new Router({ 8 | mode: 'history', 9 | base: process.env.BASE_URL, 10 | routes: [ 11 | { 12 | path: '/', 13 | name: 'home', 14 | component: Home, 15 | }, 16 | { 17 | path: '/about', 18 | name: 'about', 19 | // route level code-splitting 20 | // this generates a separate chunk (about.[hash].js) for this route 21 | // which is lazy-loaded when the route is visited. 22 | component: () => import(/* webpackChunkName: "about" */ './views/About.vue'), 23 | }, 24 | ], 25 | }); 26 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue'; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 56 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "linterOptions": { 7 | "exclude": [ 8 | "node_modules/**" 9 | ] 10 | }, 11 | "rules": { 12 | "indent": [true, "spaces", 2], 13 | "interface-name": false, 14 | "no-consecutive-blank-lines": false, 15 | "object-literal-sort-keys": false, 16 | "ordered-imports": false, 17 | "quotemark": [true, "single"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/VueCliSample/ClientApp/vue.config.js: -------------------------------------------------------------------------------- 1 | // vue.config.js 2 | module.exports = { 3 | devServer: { 4 | progress: false 5 | } 6 | } -------------------------------------------------------------------------------- /samples/VueCliSample/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace VueCliSample.Controllers 9 | { 10 | [ApiController] 11 | [Route("api/[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private static readonly string[] Summaries = new[] 15 | { 16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 17 | }; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public WeatherForecastController(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | [HttpGet] 27 | public IEnumerable Get() 28 | { 29 | var rng = new Random(); 30 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 31 | { 32 | Date = DateTime.Now.AddDays(index), 33 | TemperatureC = rng.Next(-20, 55), 34 | Summary = Summaries[rng.Next(Summaries.Length)] 35 | }) 36 | .ToArray(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/VueCliSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace VueCliSample 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/VueCliSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "https://localhost:44323/", 7 | "sslPort": 44323 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "VueCliSample": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /samples/VueCliSample/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.SpaServices; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Hosting; 13 | using Microsoft.Extensions.Logging; 14 | using VueCliMiddleware; 15 | 16 | namespace VueCliSample 17 | { 18 | public class Startup 19 | { 20 | public Startup(IConfiguration configuration) 21 | { 22 | Configuration = configuration; 23 | } 24 | 25 | public IConfiguration Configuration { get; } 26 | 27 | // This method gets called by the runtime. Use this method to add services to the container. 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | // NOTE: PRODUCTION Ensure this is the same path that is specified in your webpack output 31 | services.AddSpaStaticFiles(opt => opt.RootPath = "ClientApp/dist"); 32 | services.AddControllers(); 33 | } 34 | 35 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 36 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 37 | { 38 | if (env.IsDevelopment()) 39 | { 40 | app.UseDeveloperExceptionPage(); 41 | } 42 | 43 | // NOTE: this is optional, it adds HTTPS to Kestrel 44 | app.UseHttpsRedirection(); 45 | 46 | // NOTE: PRODUCTION uses webpack static files 47 | app.UseSpaStaticFiles(); 48 | 49 | app.UseRouting(); 50 | 51 | app.UseAuthorization(); 52 | 53 | app.UseEndpoints(endpoints => 54 | { 55 | endpoints.MapControllers(); 56 | 57 | // NOTE: VueCliProxy is meant for developement and hot module reload 58 | // NOTE: SSR has not been tested 59 | // Production systems should only need the UseSpaStaticFiles() (above) 60 | // You could wrap this proxy in either 61 | // if (System.Diagnostics.Debugger.IsAttached) 62 | // or a preprocessor such as #if DEBUG 63 | endpoints.MapToVueCliProxy( 64 | "{*path}", 65 | new SpaOptions { SourcePath = "ClientApp" }, 66 | npmScript: (System.Diagnostics.Debugger.IsAttached) ? "serve" : null, 67 | regex: "Compiled successfully", 68 | forceKill: true 69 | ); 70 | }); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /samples/VueCliSample/VueCliSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ClientApp\ 13 | $(DefaultItemExcludes);$(SpaRoot)node_modules\** 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | %(DistFiles.Identity) 54 | PreserveNewest 55 | True 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /samples/VueCliSample/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VueCliSample 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/VueCliSample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/VueCliSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /samples/VueCliSample/wwwroot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EEParker/aspnetcore-vueclimiddleware/3ddb5cac717bbf39642b34f9b04b1828616fc5e5/samples/VueCliSample/wwwroot/.gitkeep -------------------------------------------------------------------------------- /src/VueCliMiddleware.Tests/EventedStreamReaderTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace VueCliMiddleware.Tests 8 | { 9 | [TestClass] 10 | public class EventedStreamReaderTests 11 | { 12 | [TestMethod] 13 | public async Task ReadChunks_MultipleNewLines_OnCompleteLineFiresEachNewline() 14 | { 15 | 16 | string testMessage = "this \nis \na \ntest \nof \nmultiple \nnewlines\n trailing data"; 17 | int numNewLines = testMessage.Split('\n').Length - 1; 18 | int chunksReceived = 0; 19 | int linesReceived = 0; 20 | 21 | using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(testMessage))) 22 | { 23 | var streamReader = new StreamReader(ms); 24 | 25 | var esr = new EventedStreamReader(streamReader); 26 | esr.OnReceivedChunk += (e) => Interlocked.Increment(ref chunksReceived); 27 | esr.OnReceivedLine += (e) => Interlocked.Increment(ref linesReceived); 28 | await Task.Delay(200); 29 | } 30 | 31 | Assert.AreEqual(numNewLines, linesReceived); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/VueCliMiddleware.Tests/PidUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace VueCliMiddleware.Tests 4 | { 5 | [TestClass] 6 | public class PidUtilsTests 7 | { 8 | [TestMethod] 9 | public void KillPort_8080_KillsVueServe() 10 | { 11 | bool success = PidUtils.KillPort(8080, true, true); 12 | Assert.IsTrue(success); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/VueCliMiddleware.Tests/VueCliMiddleware.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net5.0;net6.0 5 | false 6 | VueCliMiddleware 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/VueCliMiddleware.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VueCliMiddleware", "VueCliMiddleware\VueCliMiddleware.csproj", "{6EF79200-935A-4EBB-BB10-EB233A360210}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VueCliMiddleware.Tests", "VueCliMiddleware.Tests\VueCliMiddleware.Tests.csproj", "{36DA789C-1C87-48A3-9A9E-804E9BA5C141}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6E3830FF-A9BD-4D14-8F65-1FBE28F81E05}" 11 | ProjectSection(SolutionItems) = preProject 12 | ..\CHANGELOG.md = ..\CHANGELOG.md 13 | ..\README.md = ..\README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuasarCliSample", "..\samples\QuasarCliSample\QuasarCliSample.csproj", "{9C672A02-413F-45CB-ACC7-E067561A40E0}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{E0AC4E37-9F26-4CAD-A3F9-10420CB0F9E7}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VueCliSample", "..\samples\VueCliSample\VueCliSample.csproj", "{FBE4604B-BCF1-4220-8292-135193C95967}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vue3Sample", "..\samples\Vue3\Vue3Sample.csproj", "{1DB93D9A-B5F1-4467-8263-7FC10BEC1EA3}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {6EF79200-935A-4EBB-BB10-EB233A360210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {6EF79200-935A-4EBB-BB10-EB233A360210}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {6EF79200-935A-4EBB-BB10-EB233A360210}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {6EF79200-935A-4EBB-BB10-EB233A360210}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {36DA789C-1C87-48A3-9A9E-804E9BA5C141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {36DA789C-1C87-48A3-9A9E-804E9BA5C141}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {36DA789C-1C87-48A3-9A9E-804E9BA5C141}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {36DA789C-1C87-48A3-9A9E-804E9BA5C141}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {9C672A02-413F-45CB-ACC7-E067561A40E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {9C672A02-413F-45CB-ACC7-E067561A40E0}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {FBE4604B-BCF1-4220-8292-135193C95967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {FBE4604B-BCF1-4220-8292-135193C95967}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {1DB93D9A-B5F1-4467-8263-7FC10BEC1EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {1DB93D9A-B5F1-4467-8263-7FC10BEC1EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(NestedProjects) = preSolution 49 | {9C672A02-413F-45CB-ACC7-E067561A40E0} = {E0AC4E37-9F26-4CAD-A3F9-10420CB0F9E7} 50 | {FBE4604B-BCF1-4220-8292-135193C95967} = {E0AC4E37-9F26-4CAD-A3F9-10420CB0F9E7} 51 | {1DB93D9A-B5F1-4467-8263-7FC10BEC1EA3} = {E0AC4E37-9F26-4CAD-A3F9-10420CB0F9E7} 52 | EndGlobalSection 53 | GlobalSection(ExtensibilityGlobals) = postSolution 54 | SolutionGuid = {A9280CDC-FB96-4CF4-B48A-048A3355168A} 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /src/VueCliMiddleware/Util/Internals.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation Contributors. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Original Source: https://github.com/aspnet/JavaScriptServices 4 | 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using System; 9 | using System.Threading.Tasks; 10 | using System.Net; 11 | using System.Net.Sockets; 12 | using System.IO; 13 | using System.Text; 14 | using System.Text.RegularExpressions; 15 | 16 | 17 | [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("VueCliMiddleware.Tests")] 18 | namespace VueCliMiddleware 19 | { 20 | internal static class LoggerFinder 21 | { 22 | public static ILogger GetOrCreateLogger( 23 | IApplicationBuilder appBuilder, 24 | string logCategoryName) 25 | { 26 | // If the DI system gives us a logger, use it. Otherwise, set up a default one. 27 | var loggerFactory = appBuilder.ApplicationServices.GetService(); 28 | if (loggerFactory == null) 29 | { 30 | loggerFactory = LoggerFactory.Create(builder => 31 | { 32 | builder.AddConsole(); 33 | }); 34 | } 35 | 36 | return loggerFactory.CreateLogger(logCategoryName); 37 | } 38 | } 39 | 40 | internal static class TaskTimeoutExtensions 41 | { 42 | public static async Task WithTimeout(this Task task, TimeSpan timeoutDelay, string message) 43 | { 44 | if (task == await Task.WhenAny(task, Task.Delay(timeoutDelay))) 45 | { 46 | task.Wait(); // Allow any errors to propagate 47 | } 48 | else 49 | { 50 | throw new TimeoutException(message); 51 | } 52 | } 53 | 54 | public static async Task WithTimeout(this Task task, TimeSpan timeoutDelay, string message) 55 | { 56 | if (task == await Task.WhenAny(task, Task.Delay(timeoutDelay))) 57 | { 58 | return task.Result; 59 | } 60 | else 61 | { 62 | throw new TimeoutException(message); 63 | } 64 | } 65 | } 66 | 67 | internal static class TcpPortFinder 68 | { 69 | public static int FindAvailablePort() 70 | { 71 | var listener = new TcpListener(IPAddress.Loopback, 0); 72 | listener.Start(); 73 | try 74 | { 75 | return ((IPEndPoint)listener.LocalEndpoint).Port; 76 | } 77 | finally 78 | { 79 | listener.Stop(); 80 | } 81 | } 82 | } 83 | 84 | /// 85 | /// Wraps a to expose an evented API, issuing notifications 86 | /// when the stream emits partial lines, completed lines, or finally closes. 87 | /// 88 | internal class EventedStreamReader 89 | { 90 | public delegate void OnReceivedChunkHandler(ArraySegment chunk); 91 | public delegate void OnReceivedLineHandler(string line); 92 | public delegate void OnStreamClosedHandler(); 93 | 94 | public event OnReceivedChunkHandler OnReceivedChunk; 95 | public event OnReceivedLineHandler OnReceivedLine; 96 | public event OnStreamClosedHandler OnStreamClosed; 97 | 98 | private readonly StreamReader _streamReader; 99 | private readonly StringBuilder _linesBuffer; 100 | 101 | public EventedStreamReader(StreamReader streamReader) 102 | { 103 | _streamReader = streamReader ?? throw new ArgumentNullException(nameof(streamReader)); 104 | _linesBuffer = new StringBuilder(); 105 | Task.Factory.StartNew(Run); 106 | } 107 | 108 | public Task WaitForMatch(Regex regex) 109 | { 110 | var tcs = new TaskCompletionSource(); 111 | var completionLock = new object(); 112 | 113 | OnReceivedLineHandler onReceivedLineHandler = null; 114 | OnStreamClosedHandler onStreamClosedHandler = null; 115 | 116 | void ResolveIfStillPending(Action applyResolution) 117 | { 118 | lock (completionLock) 119 | { 120 | if (!tcs.Task.IsCompleted) 121 | { 122 | OnReceivedLine -= onReceivedLineHandler; 123 | OnStreamClosed -= onStreamClosedHandler; 124 | applyResolution(); 125 | } 126 | } 127 | } 128 | 129 | onReceivedLineHandler = line => 130 | { 131 | var match = regex.Match(line); 132 | if (match.Success) 133 | { 134 | ResolveIfStillPending(() => tcs.SetResult(match)); 135 | } 136 | }; 137 | 138 | onStreamClosedHandler = () => 139 | { 140 | ResolveIfStillPending(() => tcs.SetException(new EndOfStreamException())); 141 | }; 142 | 143 | OnReceivedLine += onReceivedLineHandler; 144 | OnStreamClosed += onStreamClosedHandler; 145 | 146 | return tcs.Task; 147 | } 148 | 149 | private async Task Run() 150 | { 151 | var buf = new char[8 * 1024]; 152 | while (true) 153 | { 154 | var chunkLength = await _streamReader.ReadAsync(buf, 0, buf.Length); 155 | if (chunkLength == 0) 156 | { 157 | OnClosed(); 158 | break; 159 | } 160 | 161 | OnChunk(new ArraySegment(buf, 0, chunkLength)); 162 | 163 | int lineBreakPos = -1; 164 | int startPos = 0; 165 | 166 | // get all the newlines 167 | while ((lineBreakPos = Array.IndexOf(buf, '\n', startPos, chunkLength - startPos)) >= 0 && startPos < chunkLength) 168 | { 169 | var length = lineBreakPos - startPos; 170 | _linesBuffer.Append(buf, startPos, length); 171 | OnCompleteLine(_linesBuffer.ToString()); 172 | _linesBuffer.Clear(); 173 | startPos = lineBreakPos + 1; 174 | } 175 | 176 | // get the rest 177 | if (lineBreakPos < 0 && startPos < chunkLength) 178 | { 179 | _linesBuffer.Append(buf, startPos, chunkLength - startPos); 180 | } 181 | } 182 | } 183 | 184 | private void OnChunk(ArraySegment chunk) 185 | { 186 | var dlg = OnReceivedChunk; 187 | dlg?.Invoke(chunk); 188 | } 189 | 190 | private void OnCompleteLine(string line) 191 | { 192 | var dlg = OnReceivedLine; 193 | dlg?.Invoke(line); 194 | } 195 | 196 | private void OnClosed() 197 | { 198 | var dlg = OnStreamClosed; 199 | dlg?.Invoke(); 200 | } 201 | } 202 | 203 | /// 204 | /// Captures the completed-line notifications from a , 205 | /// combining the data into a single . 206 | /// 207 | internal class EventedStreamStringReader : IDisposable 208 | { 209 | private EventedStreamReader _eventedStreamReader; 210 | private bool _isDisposed; 211 | private StringBuilder _stringBuilder = new StringBuilder(); 212 | 213 | public EventedStreamStringReader(EventedStreamReader eventedStreamReader) 214 | { 215 | _eventedStreamReader = eventedStreamReader 216 | ?? throw new ArgumentNullException(nameof(eventedStreamReader)); 217 | _eventedStreamReader.OnReceivedLine += OnReceivedLine; 218 | } 219 | 220 | public string ReadAsString() => _stringBuilder.ToString(); 221 | 222 | private void OnReceivedLine(string line) => _stringBuilder.AppendLine(line); 223 | 224 | public void Dispose() 225 | { 226 | if (!_isDisposed) 227 | { 228 | _eventedStreamReader.OnReceivedLine -= OnReceivedLine; 229 | _isDisposed = true; 230 | } 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/VueCliMiddleware/Util/KillPort.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Text.RegularExpressions; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace VueCliMiddleware 9 | { 10 | public static class PidUtils 11 | { 12 | 13 | const string ssPidRegex = @"(?:^|"",|"",pid=)(\d+)"; 14 | const string portRegex = @"[^]*[.:](\\d+)$"; 15 | 16 | public static int GetPortPid(ushort port) 17 | { 18 | int pidOut = -1; 19 | 20 | int portColumn = 1; // windows 21 | int pidColumn = 4; // windows 22 | string pidRegex = null; 23 | 24 | List results = null; 25 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 26 | { 27 | results = RunProcessReturnOutputSplit("netstat", "-anv -p tcp"); 28 | results.AddRange(RunProcessReturnOutputSplit("netstat", "-anv -p udp")); 29 | portColumn = 3; 30 | pidColumn = 8; 31 | } 32 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 33 | { 34 | results = RunProcessReturnOutputSplit("ss", "-tunlp"); 35 | portColumn = 4; 36 | pidColumn = 6; 37 | pidRegex = ssPidRegex; 38 | } 39 | else 40 | { 41 | results = RunProcessReturnOutputSplit("netstat", "-ano"); 42 | } 43 | 44 | 45 | foreach (var line in results) 46 | { 47 | if (line.Length <= portColumn || line.Length <= pidColumn) continue; 48 | try 49 | { 50 | // split lines to words 51 | var portMatch = Regex.Match(line[portColumn], $"[.:]({port})"); 52 | if (portMatch.Success) 53 | { 54 | int portValue = int.Parse(portMatch.Groups[1].Value); 55 | 56 | if (pidRegex == null) 57 | { 58 | pidOut = int.Parse(line[pidColumn]); 59 | return pidOut; 60 | } 61 | else 62 | { 63 | var pidMatch = Regex.Match(line[pidColumn], pidRegex); 64 | if (pidMatch.Success) 65 | { 66 | pidOut = int.Parse(pidMatch.Groups[1].Value); 67 | } 68 | } 69 | } 70 | } 71 | catch (Exception ex) 72 | { 73 | // ignore line error 74 | } 75 | } 76 | 77 | return pidOut; 78 | } 79 | 80 | private static List RunProcessReturnOutputSplit(string fileName, string arguments) 81 | { 82 | string result = RunProcessReturnOutput(fileName, arguments); 83 | if (result == null) return new List(); 84 | 85 | string[] lines = result.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); 86 | var lineWords = new List(); 87 | foreach (var line in lines) 88 | { 89 | lineWords.Add(line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); 90 | } 91 | return lineWords; 92 | } 93 | 94 | private static string RunProcessReturnOutput(string fileName, string arguments) 95 | { 96 | Process process = null; 97 | try 98 | { 99 | var si = new ProcessStartInfo(fileName, arguments) 100 | { 101 | UseShellExecute = false, 102 | RedirectStandardOutput = true, 103 | RedirectStandardError = true, 104 | CreateNoWindow = true 105 | }; 106 | 107 | process = Process.Start(si); 108 | var stdOutT = process.StandardOutput.ReadToEndAsync(); 109 | var stdErrorT = process.StandardError.ReadToEndAsync(); 110 | if (!process.WaitForExit(10000)) 111 | { 112 | try { process?.Kill(); } catch { } 113 | } 114 | 115 | if (Task.WaitAll(new Task[] { stdOutT, stdErrorT }, 10000)) 116 | { 117 | // if success, return data 118 | return (stdOutT.Result + Environment.NewLine + stdErrorT.Result).Trim(); 119 | } 120 | return null; 121 | } 122 | catch (Exception ex) 123 | { 124 | return null; 125 | } 126 | finally 127 | { 128 | process?.Close(); 129 | } 130 | } 131 | 132 | public static bool Kill(string process, bool ignoreCase = true, bool force = false, bool tree = true) 133 | { 134 | var args = new List(); 135 | try 136 | { 137 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 138 | { 139 | if (force) { args.Add("-9"); } 140 | if (ignoreCase) { args.Add("-i"); } 141 | args.Add(process); 142 | RunProcessReturnOutput("pkill", string.Join(" ", args)); 143 | } 144 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 145 | { 146 | if (force) { args.Add("-9"); } 147 | if (ignoreCase) { args.Add("-I"); } 148 | args.Add(process); 149 | RunProcessReturnOutput("killall", string.Join(" ", args)); 150 | } 151 | else 152 | { 153 | if (force) { args.Add("/f"); } 154 | if (tree) { args.Add("/T"); } 155 | args.Add("/im"); 156 | args.Add(process); 157 | return RunProcessReturnOutput("taskkill", string.Join(" ", args))?.StartsWith("SUCCESS") ?? false; 158 | } 159 | return true; 160 | } 161 | catch (Exception) 162 | { 163 | 164 | } 165 | return false; 166 | } 167 | 168 | public static bool Kill(int pid, bool force = false, bool tree = true) 169 | { 170 | if (pid == -1) { return false; } 171 | 172 | var args = new List(); 173 | try 174 | { 175 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 176 | { 177 | if (force) { args.Add("-9"); } 178 | args.Add(pid.ToString()); 179 | RunProcessReturnOutput("kill", string.Join(" ", args)); 180 | } 181 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 182 | { 183 | if (force) { args.Add("-9"); } 184 | args.Add(pid.ToString()); 185 | RunProcessReturnOutput("kill", string.Join(" ", args)); 186 | } 187 | else 188 | { 189 | if (force) { args.Add("/f"); } 190 | if (tree) { args.Add("/T"); } 191 | args.Add("/PID"); 192 | args.Add(pid.ToString()); 193 | return RunProcessReturnOutput("taskkill", string.Join(" ", args))?.StartsWith("SUCCESS") ?? false; 194 | } 195 | return true; 196 | } 197 | catch (Exception ex) 198 | { 199 | } 200 | return false; 201 | } 202 | 203 | public static bool KillPort(ushort port, bool force = false, bool tree = true) => Kill(GetPortPid(port), force: force, tree: tree); 204 | 205 | } 206 | 207 | 208 | } -------------------------------------------------------------------------------- /src/VueCliMiddleware/Util/ScriptRunner.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation Contributors. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Original Source: https://github.com/aspnet/JavaScriptServices 4 | 5 | using Microsoft.Extensions.Logging; 6 | using System; 7 | using System.Diagnostics; 8 | using System.Runtime.InteropServices; 9 | using System.Text.RegularExpressions; 10 | using System.Collections.Generic; 11 | using System.Text; 12 | 13 | namespace VueCliMiddleware 14 | { 15 | /// 16 | /// Executes the script entries defined in a package.json file, 17 | /// capturing any output written to stdio. 18 | /// 19 | internal class ScriptRunner 20 | { 21 | public EventedStreamReader StdOut { get; } 22 | public EventedStreamReader StdErr { get; } 23 | public Process RunnerProcess { get; } 24 | 25 | public ScriptRunnerType Runner { get; } 26 | 27 | private string GetExeName() 28 | { 29 | switch (Runner) 30 | { 31 | case ScriptRunnerType.Npm: 32 | return "npm"; 33 | case ScriptRunnerType.Yarn: 34 | return "yarn"; 35 | case ScriptRunnerType.Npx: 36 | return "npx"; 37 | case ScriptRunnerType.Pnpm: 38 | return "pnpm"; 39 | default: 40 | return "npm"; 41 | } 42 | } 43 | 44 | private static string BuildCommand(ScriptRunnerType runner, string scriptName, string arguments) 45 | { 46 | var command = new StringBuilder(); 47 | 48 | if (runner == ScriptRunnerType.Npm) { command.Append("run "); } 49 | 50 | command.Append(scriptName); 51 | command.Append(' '); 52 | 53 | if (runner == ScriptRunnerType.Npm) { command.Append("-- "); } 54 | 55 | if (!string.IsNullOrWhiteSpace(arguments)) { command.Append(arguments); } 56 | return command.ToString(); 57 | } 58 | 59 | private static Regex AnsiColorRegex = new Regex("\x001b\\[[0-9;]*m", RegexOptions.None, TimeSpan.FromSeconds(1)); 60 | 61 | 62 | 63 | public void Kill() 64 | { 65 | try { RunnerProcess?.Kill(); } catch { } 66 | try { RunnerProcess?.WaitForExit(); } catch { } 67 | } 68 | 69 | public ScriptRunner(string workingDirectory, string scriptName, string arguments, IDictionary envVars, ScriptRunnerType runner, bool wsl) 70 | { 71 | if (string.IsNullOrEmpty(workingDirectory)) 72 | { 73 | throw new ArgumentException("Cannot be null or empty.", nameof(workingDirectory)); 74 | } 75 | 76 | if (string.IsNullOrEmpty(scriptName)) 77 | { 78 | throw new ArgumentException("Cannot be null or empty.", nameof(scriptName)); 79 | } 80 | 81 | Runner = runner; 82 | 83 | var exeName = GetExeName(); 84 | var completeArguments = BuildCommand(runner, scriptName, arguments); 85 | 86 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 87 | { 88 | if (wsl) 89 | { 90 | completeArguments = $"{exeName} {completeArguments}"; 91 | exeName = "wsl"; 92 | } 93 | else 94 | { 95 | // On Windows, the NPM executable is a .cmd file, so it can't be executed 96 | // directly (except with UseShellExecute=true, but that's no good, because 97 | // it prevents capturing stdio). So we need to invoke it via "cmd /c". 98 | completeArguments = $"/c {exeName} {completeArguments}"; 99 | exeName = "cmd"; 100 | } 101 | } 102 | 103 | var processStartInfo = new ProcessStartInfo(exeName) 104 | { 105 | Arguments = completeArguments, 106 | UseShellExecute = false, 107 | RedirectStandardInput = true, 108 | RedirectStandardOutput = true, 109 | RedirectStandardError = true, 110 | WorkingDirectory = workingDirectory 111 | }; 112 | 113 | if (envVars != null) 114 | { 115 | foreach (var keyValuePair in envVars) 116 | { 117 | processStartInfo.Environment[keyValuePair.Key] = keyValuePair.Value; 118 | } 119 | } 120 | 121 | RunnerProcess = LaunchNodeProcess(processStartInfo); 122 | 123 | StdOut = new EventedStreamReader(RunnerProcess.StandardOutput); 124 | StdErr = new EventedStreamReader(RunnerProcess.StandardError); 125 | } 126 | 127 | public void AttachToLogger(ILogger logger) 128 | { 129 | // When the NPM task emits complete lines, pass them through to the real logger 130 | StdOut.OnReceivedLine += line => 131 | { 132 | if (!string.IsNullOrWhiteSpace(line)) 133 | { 134 | // NPM tasks commonly emit ANSI colors, but it wouldn't make sense to forward 135 | // those to loggers (because a logger isn't necessarily any kind of terminal) 136 | // making this console for debug purpose 137 | if (line.StartsWith("")) 138 | { 139 | line = line.Substring(3); 140 | } 141 | 142 | if (logger == null) 143 | { 144 | Console.Error.WriteLine(line); 145 | } 146 | else 147 | { 148 | logger.LogInformation(StripAnsiColors(line).TrimEnd('\n')); 149 | } 150 | } 151 | }; 152 | 153 | StdErr.OnReceivedLine += line => 154 | { 155 | if (!string.IsNullOrWhiteSpace(line)) 156 | { 157 | // making this console for debug purpose 158 | if (line.StartsWith("")) 159 | { 160 | line = line.Substring(3); 161 | } 162 | 163 | if (logger == null) 164 | { 165 | Console.Error.WriteLine(line); 166 | } 167 | else 168 | { 169 | logger.LogError(StripAnsiColors(line).TrimEnd('\n')); 170 | } 171 | } 172 | }; 173 | 174 | // But when it emits incomplete lines, assume this is progress information and 175 | // hence just pass it through to StdOut regardless of logger config. 176 | StdErr.OnReceivedChunk += chunk => 177 | { 178 | var containsNewline = Array.IndexOf(chunk.Array, '\n', chunk.Offset, chunk.Count) >= 0; 179 | 180 | if (!containsNewline) 181 | { 182 | Console.Write(chunk.Array, chunk.Offset, chunk.Count); 183 | } 184 | }; 185 | } 186 | 187 | private static string StripAnsiColors(string line) 188 | => AnsiColorRegex.Replace(line, string.Empty); 189 | 190 | private static Process LaunchNodeProcess(ProcessStartInfo startInfo) 191 | { 192 | try 193 | { 194 | var process = Process.Start(startInfo); 195 | 196 | // See equivalent comment in OutOfProcessNodeInstance.cs for why 197 | process.EnableRaisingEvents = true; 198 | 199 | return process; 200 | } 201 | catch (Exception ex) 202 | { 203 | var message = $"Failed to start '{startInfo.FileName}'. To resolve this:.\n\n" 204 | + $"[1] Ensure that '{startInfo.FileName}' is installed and can be found in one of the PATH directories.\n" 205 | + $" Current PATH enviroment variable is: { Environment.GetEnvironmentVariable("PATH") }\n" 206 | + " Make sure the executable is in one of those directories, or update your PATH.\n\n" 207 | + "[2] See the InnerException for further details of the cause."; 208 | throw new InvalidOperationException(message, ex); 209 | } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/VueCliMiddleware/Util/ScriptRunnerType.cs: -------------------------------------------------------------------------------- 1 | namespace VueCliMiddleware 2 | { 3 | public enum ScriptRunnerType 4 | { 5 | Npm, 6 | Yarn, 7 | Npx, 8 | Pnpm 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/VueCliMiddleware/VueCliMiddleware.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Helpers for building single-page applications on ASP.NET MVC Core using Vue Cli. 5 | netcoreapp3.1;net5.0;net6.0 6 | 7 | VueCliMiddleware 8 | 6.0.0 9 | 10 | Latest 11 | EEParker 12 | true 13 | https://github.com/EEParker/aspnetcore-vueclimiddleware.git 14 | https://github.com/EEParker/aspnetcore-vueclimiddleware.git 15 | LICENSE.txt 16 | true 17 | true 18 | true 19 | snupkg 20 | 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/VueCliMiddleware/VueDevelopmentServerMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.IO; 5 | using System.Collections.Generic; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.SpaServices; 9 | 10 | namespace VueCliMiddleware 11 | { 12 | internal static class VueCliMiddleware 13 | { 14 | private const string LogCategoryName = "VueCliMiddleware"; 15 | internal const string DefaultRegex = "running at"; 16 | 17 | private static TimeSpan RegexMatchTimeout = TimeSpan.FromMinutes(5); // This is a development-time only feature, so a very long timeout is fine 18 | 19 | public static void Attach( 20 | ISpaBuilder spaBuilder, 21 | string scriptName, 22 | int port = 8080, 23 | bool https = false, 24 | ScriptRunnerType runner = ScriptRunnerType.Npm, 25 | string regex = DefaultRegex, 26 | bool forceKill = false, 27 | bool wsl = false) 28 | { 29 | var sourcePath = spaBuilder.Options.SourcePath; 30 | if (string.IsNullOrEmpty(sourcePath)) 31 | { 32 | throw new ArgumentException("Cannot be null or empty", nameof(sourcePath)); 33 | } 34 | 35 | if (string.IsNullOrEmpty(scriptName)) 36 | { 37 | throw new ArgumentException("Cannot be null or empty", nameof(scriptName)); 38 | } 39 | 40 | // Start vue-cli and attach to middleware pipeline 41 | var appBuilder = spaBuilder.ApplicationBuilder; 42 | var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); 43 | var portTask = StartVueCliServerAsync(sourcePath, scriptName, logger, port, runner, regex, forceKill, wsl); 44 | 45 | // Everything we proxy is hardcoded to target localhost because: 46 | // - the requests are always from the local machine (we're not accepting remote 47 | // requests that go directly to the vue-cli server) 48 | var targetUriTask = portTask.ContinueWith( 49 | task => new UriBuilder(https ? "https" : "http", "127.0.0.1", task.Result).Uri); 50 | 51 | SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () => 52 | { 53 | // On each request, we create a separate startup task with its own timeout. That way, even if 54 | // the first request times out, subsequent requests could still work. 55 | var timeout = spaBuilder.Options.StartupTimeout; 56 | return targetUriTask.WithTimeout(timeout, 57 | $"The vue-cli server did not start listening for requests " + 58 | $"within the timeout period of {timeout.Seconds} seconds. " + 59 | $"Check the log output for error information."); 60 | }); 61 | } 62 | 63 | private static async Task StartVueCliServerAsync( 64 | string sourcePath, 65 | string npmScriptName, 66 | ILogger logger, 67 | int portNumber, 68 | ScriptRunnerType runner, 69 | string regex, 70 | bool forceKill = false, 71 | bool wsl = false) 72 | { 73 | if (portNumber < 80) 74 | { 75 | portNumber = TcpPortFinder.FindAvailablePort(); 76 | } 77 | else 78 | { 79 | // if the port we want to use is occupied, terminate the process utilizing that port. 80 | // this occurs when "stop" is used from the debugger and the middleware does not have the opportunity to kill the process 81 | PidUtils.KillPort((ushort)portNumber, forceKill); 82 | } 83 | logger.LogInformation($"Starting server on port {portNumber}..."); 84 | 85 | var envVars = new Dictionary 86 | { 87 | { "PORT", portNumber.ToString() }, 88 | { "DEV_SERVER_PORT", portNumber.ToString() }, // vue cli 3 uses --port {number}, included below 89 | { "BROWSER", "none" }, // We don't want vue-cli to open its own extra browser window pointing to the internal dev server port 90 | { "CODESANDBOX_SSE", true.ToString() }, // this will make vue cli use client side HMR inference 91 | }; 92 | 93 | var npmScriptRunner = new ScriptRunner(sourcePath, npmScriptName, $"--port {portNumber:0}", envVars, runner: runner, wsl: wsl); 94 | AppDomain.CurrentDomain.DomainUnload += (s, e) => npmScriptRunner?.Kill(); 95 | AppDomain.CurrentDomain.ProcessExit += (s, e) => npmScriptRunner?.Kill(); 96 | AppDomain.CurrentDomain.UnhandledException += (s, e) => npmScriptRunner?.Kill(); 97 | npmScriptRunner.AttachToLogger(logger); 98 | 99 | using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr)) 100 | { 101 | try 102 | { 103 | // Although the Vue dev server may eventually tell us the URL it's listening on, 104 | // it doesn't do so until it's finished compiling, and even then only if there were 105 | // no compiler warnings. So instead of waiting for that, consider it ready as soon 106 | // as it starts listening for requests. 107 | await npmScriptRunner.StdOut.WaitForMatch(new Regex(!string.IsNullOrWhiteSpace(regex) ? regex : DefaultRegex, RegexOptions.None, RegexMatchTimeout)); 108 | } 109 | catch (EndOfStreamException ex) 110 | { 111 | throw new InvalidOperationException( 112 | $"The NPM script '{npmScriptName}' exited without indicating that the " + 113 | $"server was listening for requests. The error output was: " + 114 | $"{stdErrReader.ReadAsString()}", ex); 115 | } 116 | } 117 | 118 | return portNumber; 119 | } 120 | } 121 | 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/VueCliMiddleware/VueDevelopmentServerMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Routing; 4 | using Microsoft.AspNetCore.SpaServices; 5 | using System; 6 | 7 | namespace VueCliMiddleware 8 | { 9 | /// 10 | /// Extension methods for enabling Vue development server middleware support. 11 | /// 12 | public static class VueCliMiddlewareExtensions 13 | { 14 | /// 15 | /// Handles requests by passing them through to an instance of the vue-cli server. 16 | /// This means you can always serve up-to-date CLI-built resources without having 17 | /// to run the vue-cli server manually. 18 | /// 19 | /// This feature should only be used in development. For production deployments, be 20 | /// sure not to enable the vue-cli server. 21 | /// 22 | /// The . 23 | /// The name of the script in your package.json file that launches the vue-cli server. 24 | /// Specify vue cli server port number. If < 80, uses random port. 25 | /// Specify vue cli server schema 26 | /// Specify the runner, Npm and Yarn are valid options. Yarn support is HIGHLY experimental. 27 | /// Specify a custom regex string to search for in the log indicating proxied server is running. VueCli: "running at", QuasarCli: "Compiled successfully" 28 | /// 29 | /// 30 | public static void UseVueCli( 31 | this ISpaBuilder spaBuilder, 32 | string npmScript = "serve", 33 | int port = 8080, 34 | bool https = false, 35 | ScriptRunnerType runner = ScriptRunnerType.Npm, 36 | string regex = VueCliMiddleware.DefaultRegex, 37 | bool forceKill = false, 38 | bool wsl = false) 39 | { 40 | if (spaBuilder == null) 41 | { 42 | throw new ArgumentNullException(nameof(spaBuilder)); 43 | } 44 | 45 | var spaOptions = spaBuilder.Options; 46 | 47 | if (string.IsNullOrEmpty(spaOptions.SourcePath)) 48 | { 49 | throw new InvalidOperationException($"To use {nameof(UseVueCli)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}."); 50 | } 51 | 52 | VueCliMiddleware.Attach(spaBuilder, npmScript, port, https: https, runner: runner, regex: regex, forceKill: forceKill, wsl: wsl); 53 | } 54 | 55 | 56 | public static IEndpointConventionBuilder MapToVueCliProxy( 57 | this IEndpointRouteBuilder endpoints, 58 | string pattern, 59 | SpaOptions options, 60 | string npmScript = "serve", 61 | int port = 8080, 62 | bool https = false, 63 | ScriptRunnerType runner = ScriptRunnerType.Npm, 64 | string regex = VueCliMiddleware.DefaultRegex, 65 | bool forceKill = false, 66 | bool wsl = false) 67 | { 68 | if (pattern == null) { throw new ArgumentNullException(nameof(pattern)); } 69 | return endpoints.MapFallback(pattern, CreateProxyRequestDelegate(endpoints, options, npmScript, port, https, runner, regex, forceKill, wsl)); 70 | } 71 | 72 | public static IEndpointConventionBuilder MapToVueCliProxy( 73 | this IEndpointRouteBuilder endpoints, 74 | string pattern, 75 | string sourcePath, 76 | string npmScript = "serve", 77 | int port = 8080, 78 | bool https = false, 79 | ScriptRunnerType runner = ScriptRunnerType.Npm, 80 | string regex = VueCliMiddleware.DefaultRegex, 81 | bool forceKill = false, 82 | bool wsl = false) 83 | { 84 | if (pattern == null) { throw new ArgumentNullException(nameof(pattern)); } 85 | if (sourcePath == null) { throw new ArgumentNullException(nameof(sourcePath)); } 86 | return endpoints.MapFallback(pattern, CreateProxyRequestDelegate(endpoints, new SpaOptions { SourcePath = sourcePath }, npmScript, port, https, runner, regex, forceKill, wsl)); 87 | } 88 | 89 | public static IEndpointConventionBuilder MapToVueCliProxy( 90 | this IEndpointRouteBuilder endpoints, 91 | SpaOptions options, 92 | string npmScript = "serve", 93 | int port = 8080, 94 | bool https = false, 95 | ScriptRunnerType runner = ScriptRunnerType.Npm, 96 | string regex = VueCliMiddleware.DefaultRegex, 97 | bool forceKill = false, 98 | bool wsl = false) 99 | { 100 | return endpoints.MapFallback("{*path}", CreateProxyRequestDelegate(endpoints, options, npmScript, port, https, runner, regex, forceKill, wsl)); 101 | } 102 | 103 | public static IEndpointConventionBuilder MapToVueCliProxy( 104 | this IEndpointRouteBuilder endpoints, 105 | string sourcePath, 106 | string npmScript = "serve", 107 | int port = 8080, 108 | bool https = false, 109 | ScriptRunnerType runner = ScriptRunnerType.Npm, 110 | string regex = VueCliMiddleware.DefaultRegex, 111 | bool forceKill = false, 112 | bool wsl = false) 113 | { 114 | if (sourcePath == null) { throw new ArgumentNullException(nameof(sourcePath)); } 115 | return endpoints.MapFallback("{*path}", CreateProxyRequestDelegate(endpoints, new SpaOptions { SourcePath = sourcePath }, npmScript, port, https, runner, regex, forceKill, wsl)); 116 | } 117 | 118 | 119 | 120 | private static RequestDelegate CreateProxyRequestDelegate( 121 | IEndpointRouteBuilder endpoints, 122 | SpaOptions options, 123 | string npmScript = "serve", 124 | int port = 8080, 125 | bool https = false, 126 | ScriptRunnerType runner = ScriptRunnerType.Npm, 127 | string regex = VueCliMiddleware.DefaultRegex, 128 | bool forceKill = false, 129 | bool wsl = false) 130 | { 131 | // based on CreateRequestDelegate() https://github.com/aspnet/AspNetCore/blob/master/src/Middleware/StaticFiles/src/StaticFilesEndpointRouteBuilderExtensions.cs#L194 132 | 133 | if (endpoints == null) { throw new ArgumentNullException(nameof(endpoints)); } 134 | if (options == null) { throw new ArgumentNullException(nameof(options)); } 135 | //if (npmScript == null) { throw new ArgumentNullException(nameof(npmScript)); } 136 | 137 | var app = endpoints.CreateApplicationBuilder(); 138 | app.Use(next => context => 139 | { 140 | // Set endpoint to null so the SPA middleware will handle the request. 141 | context.SetEndpoint(null); 142 | return next(context); 143 | }); 144 | 145 | app.UseSpa(opt => 146 | { 147 | if (options != null) 148 | { 149 | opt.Options.DefaultPage = options.DefaultPage; 150 | opt.Options.DefaultPageStaticFileOptions = options.DefaultPageStaticFileOptions; 151 | opt.Options.SourcePath = options.SourcePath; 152 | opt.Options.StartupTimeout = options.StartupTimeout; 153 | } 154 | 155 | if (!string.IsNullOrWhiteSpace(npmScript)) 156 | { 157 | opt.UseVueCli(npmScript, port, https, runner, regex, forceKill, wsl); 158 | } 159 | }); 160 | 161 | return app.Build(); 162 | } 163 | } 164 | } 165 | --------------------------------------------------------------------------------