AspNetCore有一套Spa模板Microsoft.AspNetCore.SpaTemplates,可以用来快速生成Spa项目。
尝试了一下其中的Vue模板,使用起来挺方便的,Vue+TypeScript作为前端,AspNetCore作为后端,能生成一个简单的Vue应用。
但是这个模板已经很久没有更新了,集成的控件是Bootstrap,想改为使用Ant Design of Vue,要费一番功夫。
准备工作
安装模板
dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
使用模板创建Vue项目
dotnet new vue
会以当前文件夹名为项目命名
修改目标框架
模板默认是基于.NetCore2.0的,已经过时,所以将将目标框架改为3.1。
修改后会提示错误,需要修改项目文件,将ItemGroup
节的PackageReference
子节点删除,等待VS还原项目
然后手动添加Microsoft.AspNetCore.SpaServices
的Nuget包
此时在Startup.cs
文件中会报warnning。需要修改Configure
方法,将env参数的类型改为IWebHostEnvironment
,然后修改ConfigureServices
方法,在services.AddMvc
中增加options.EnableEndpointRouting = false
的选项。修改后代码如下:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => options.EnableEndpointRouting = false);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//代码未变动,中间仍有一个warnning,但没办法消除
}
更新NPM包
模板自带的NPM包版本都很低,安装AntDesign后无法正常使用,需要进行更新,但直接更新为最新版会带来版本兼容性问题。经过测试,最终的package.json
文件如下
{
"name": "project-name",
"private": true,
"version": "0.0.0",
"devDependencies": {
"@types/webpack-env": "^1.15.3",
"ant-design-vue": "^1.7.2",
"aspnet-webpack": "^3.0.0",
"awesome-typescript-loader": "^5.2.1",
"css-loader": "^5.0.1",
"event-source-polyfill": "^1.0.21",
"file-loader": "^6.2.0",
"isomorphic-fetch": "^3.0.0",
"jquery": "^3.1.1",
"mini-css-extract-plugin": "^1.3.0",
"postcss": "^8.1.0",
"style-loader": "^2.0.0",
"typescript": "^3.9.7",
"url-loader": "^4.1.1",
"vue": "^2.6.12",
"vue-class-component": "^7.2.6",
"vue-loader": "^15.9.5",
"vue-property-decorator": "^9.0.2",
"vue-router": "^3.4.9",
"vue-template-compiler": "^2.6.12",
"webpack": "^4.27.0",
"webpack-cli": "^4.2.0",
"webpack-dev-middleware": "^3.0.0",
"webpack-hot-middleware": "^2.25.0"
}
}
除了版本更新外,还进行了以下更改:去除Bootstrap,将extract-text-webpack-plugin
替换为mini-css-extract-plugin
。同时,还需要修改webpack.config.js
及webpack.config.vendor.js
文件
//webpack.config.js
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const bundleOutputDir = './wwwroot/dist';
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
return [{
mode:isDevBuild?'development':'production',
stats: { modules: false },
context: __dirname,
resolve: {
extensions: ['.js', '.ts'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
entry: { 'main': './ClientApp/boot.ts' },
module: {
rules: [
{ test: /\.vue$/, include: /ClientApp/, loader: 'vue-loader', options: { loaders: { js: 'awesome-typescript-loader?silent=true' } } },
{ test: /\.ts$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
{ test: /\.css$/, use: isDevBuild ? [ 'style-loader', 'css-loader' ] : [MiniCssExtractPlugin.loader, 'css-loader?minimize' ] },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' },
{ test: /\.(ttf|eot|svg|woff|woff2)$/, use: 'url-loader' },
]
},
output: {
path: path.join(__dirname, bundleOutputDir),
filename: '[name].js',
publicPath: 'dist/'
},
plugins: [
new CheckerPlugin(),
new VueLoaderPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(isDevBuild ? 'development' : 'production')
}
}),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(bundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new MiniCssExtractPlugin('site.css')
])
}];
};
//webpack.config.vendor.js
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
return [{
stats: { modules: false },
resolve: {
extensions: ['.js'],
},
entry: {
vendor: [
'event-source-polyfill',
'isomorphic-fetch',
'vue/dist/vue.esm.js',
'vue-router'
],
},
module: {
rules: [
{ test: /\.(sa|sc|c)ss$/, use: [MiniCssExtractPlugin.loader, isDevBuild ? 'css-loader' : 'css-loader?minimize' ] },
{ test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, use: 'url-loader?limit=100000' }
]
},
output: {
path: path.join(__dirname, 'wwwroot', 'dist'),
publicPath: 'dist/',
filename: '[name].js',
library: '[name]_[hash]'
},
plugins: [
new MiniCssExtractPlugin({ filename: 'vendor.css' }),
new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), // Maps these identifiers to the jQuery package (because Bootstrap expects it to be a global variable)
new webpack.DefinePlugin({
'process.env.NODE_ENV': isDevBuild ? '"development"' : '"production"'
}),
new webpack.DllPlugin({
path: path.join(__dirname, 'wwwroot', 'dist', '[name]-manifest.json'),
name: '[name]_[hash]'
})
].concat(isDevBuild ? [] : [
new webpack.optimize.UglifyJsPlugin()
])
}];
};
最后,在修改入口文件boot.ts
。新版的vue-loader
在通过require
导入组件时需要加上.default
(参考此处),最终的boot.ts
文件为
//boot.ts
import './css/site.css';
import Vue from 'vue';
import VueRouter, { RouteConfig } from 'vue-router';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
Vue.config.productionTip = false;
Vue.use(Antd);
Vue.use(VueRouter);
const routes:RouteConfig[] = [
{ path: '/', component: require('./components/home/home.vue').default },
{ path: '/counter', component: require('./components/counter/counter.vue').default },
{ path: '/fetchdata', component: require('./components/fetchdata/fetchdata.vue').default },
new Vue({
el: '#app-root',
router: new VueRouter({ mode: 'history', routes: routes }),
render: h => h(require('./components/app/app.vue').default)
});
这样,AntDesign就能正常使用了。