Vue2.5 Web App 项目搭建 (TypeScript版)

犀渠玉剑良家子,白马金羁侠少年。这篇文章主要讲述Vue2.5 Web App 项目搭建 (TypeScript版)相关的知识,希望能为你提供帮助。
参考了几位同行的Blogs和StackOverflow上的许多问答,在原来的ng1加TypeScript以及Webpack的经验基础上,搭建了该项目,核心文件如下,供需要的人参考。
package.json

1 { 2"name": "app", 3"version": "1.0.0", 4"description": "App package.json from the documentation, supplemented with testing support", 5"author": "", 6"private": true, 7"scripts": { 8"dev": "webpack-dev-server -d --inline --hot --env.dev", 9"build": "rimraf dist & & webpack --progress --hide-modules" 10}, 11"dependencies": { 12"axios": "^0.18.0", 13"bootstrap": "^4.0.0", 14"bootstrap-vue": "^2.0.0-rc.2", 15"element-ui": "^2.2.2", 16"font-awesome": "^4.7.0", 17"jointjs": "^2.0.1", 18"jquery": "^3.3.1", 19"js-md5": "^0.7.3", 20"layui-src": "^2.2.5", 21"linq": "^3.0.9", 22"lodash": "^4.17.5", 23"pdfmake": "^0.1.36", 24"popper.js": "^1.14.1", 25"tinymce": "^4.7.12", 26"uuid": "^3.2.1", 27"vue": "^2.5.16", 28"vue-class-component": "^6.2.0", 29"vue-echarts-v3": "^1.0.19", 30"vue-i18n": "^7.6.0", 31"vue-i18n-extensions": "^0.1.0", 32"vue-lazyload": "^1.2.3", 33"vue-pdf": "^3.3.1", 34"vue-property-decorator": "^6.0.0", 35"vue-router": "^3.0.1", 36"vue-socket.io": "^2.1.1-b", 37"vue-tinymce": "github:lpreterite/vue-tinymce", 38"vue-video-player": "^5.0.2", 39"vuex": "^3.0.1", 40"vuex-class": "^0.3.0" 41}, 42"engines": { 43"node": "> =6.0.0", 44"npm": "> = 3.0.0" 45}, 46"browserslist": [ 47"> 1%", 48"last 2 versions", 49"not ie < = 8" 50], 51"devDependencies": { 52"@kazupon/vue-i18n-loader": "^0.3.0", 53"@types/lodash": "^4.14.106", 54"ajv": "^6.3.0", 55"autoprefixer": "^8.2.0", 56"babel-core": "^6.26.3", 57"babel-helper-vue-jsx-merge-props": "^2.0.3", 58"babel-loader": "^7.1.4", 59"babel-plugin-syntax-dynamic-import": "^6.18.0", 60"babel-plugin-syntax-jsx": "^6.18.0", 61"babel-plugin-transform-vue-jsx": "^3.7.0", 62"babel-preset-env": "^1.6.1", 63"bootstrap-loader": "^2.2.0", 64"clean-webpack-plugin": "^0.1.19", 65"compression-webpack-plugin": "^1.1.11", 66"copy-webpack-plugin": "^4.5.1", 67"css-loader": "^0.28.11", 68"cssnano": "^3.10.0", 69"extract-text-webpack-plugin": "^3.0.2", 70"file-loader": "^1.1.11", 71"html-loader": "^0.5.5", 72"html-webpack-plugin": "^3.1.0", 73"image-webpack-loader": "^4.2.0", 74"json-loader": "^0.5.7", 75"node-sass": "^4.7.2", 76"optimize-css-assets-webpack-plugin": "^3.2.0", 77"postcss-import": "^11.1.0", 78"postcss-loader": "^2.1.3", 79"postcss-url": "^7.3.2", 80"resolve-url-loader": "^2.3.0", 81"rimraf": "^2.6.2", 82"sass-loader": "^6.0.7", 83"sass-resources-loader": "^1.3.3", 84"style-loader": "^0.20.3", 85"ts-loader": "^3.1.1", 86"tslint": "^5.9.1", 87"tslint-config-standard": "^7.0.0", 88"tslint-loader": "^3.6.0", 89"typescript": "^2.8.3", 90"uglifyjs-webpack-plugin": "^1.2.5", 91"url-loader": "^1.0.1", 92"vue-loader": "^14.2.1", 93"vue-style-loader": "^4.1.0", 94"vue-template-compiler": "^2.5.16", 95"webpack": "^3.1.0", 96"webpack-dev-server": "^2.9.4", 97"webpack-parallel-uglify-plugin": "^1.1.0" 98} 99 }

webpack.config.js
1 const {resolve} = require(‘path‘); 2 const webpack = require(‘webpack‘); 3 const CopyWebpackPlugin = require(‘copy-webpack-plugin‘); 4 const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); 5 const ExtractTextPlugin = require(‘extract-text-webpack-plugin‘); 6 const OptimizeCSSPlugin = require(‘optimize-css-assets-webpack-plugin‘); 7 const ParallelUglifyPlugin=require(‘webpack-parallel-uglify-plugin‘) ; 8 const CompressionWebpackPlugin = require(‘compression-webpack-plugin‘); 9 const CleanWebpackPlugin = require(‘clean-webpack-plugin‘); 10 const url = require(‘url‘); 11 const publicPath = ‘/public/‘; 12 13 function getVueStyleLoader(isDev) { 14if (!isDev) { 15return ExtractTextPlugin.extract({ 16fallback: "vue-style-loader", 17use: ["css-loader", "postcss-loader", "sass-loader", 18"sass-resources-loader?resources=./src/common/style/sass-resources.scss"] 19}); 20} 21return "vue-style-loader!css-loader?sourceMap!postcss-loader?sourceMap!sass-loader?sourceMap!sass-resources-loader?resources=./src/common/style/sass-resources.scss"; 22 } 23 24 function getCssLoader(isDev) { 25if (!isDev) { 26return ExtractTextPlugin.extract({ 27fallback: "style-loader", 28use: ["css-loader", "postcss-loader"] 29}); 30} 31return [ 32{loader: ‘style-loader‘}, 33{loader: ‘css-loader‘, options: {sourceMap: true}}, 34{loader: ‘postcss-loader‘, options: {sourceMap: true}}, 35]; 36 } 37 38 function getScssLoader(isDev) { 39if (!isDev) { 40return ExtractTextPlugin.extract({ 41fallback: "style-loader", 42use: ["css-loader", "postcss-loader", "sass-loader", 43"sass-resources-loader?resources=./src/common/style/sass-resources.scss"] 44}); 45} 46return [ 47{loader: ‘style-loader‘}, 48{loader: ‘css-loader‘, options: {sourceMap: true}}, 49{loader: ‘postcss-loader‘, options: {sourceMap: true}}, 50{loader: ‘sass-loader‘, options: {sourceMap: true}}, 51{ 52loader: ‘sass-resources-loader‘, 53options: {resources: ‘./src/common/style/sass-resources.scss‘} 54} 55]; 56 } 57 58 function getPlugins(isDev, plugins) { 59if (!isDev) { 60plugins.push( 61new ExtractTextPlugin({ 62filename: ‘assets/css/[name].[contenthash:8].css‘, 63// Setting the following option to `false` will not extract CSS from codesplit chunks. 64// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 65// It‘s currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it‘s `false`, 66// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 67allChunks: true, 68}), 69// Compress extracted CSS. We are using this plugin so that possible 70// duplicated CSS from different components can be deduped. 71new OptimizeCSSPlugin({ 72assetNameRegExp: /.css$/g, 73cssProcessor: require(‘cssnano‘), 74cssProcessorOptions: { discardComments: {removeAll: true}}, 75canPrint: true, 76}), 77new ParallelUglifyPlugin({ 78uglifyJS: { 79output: { 80comments:false//去掉注释 81}, 82compress: { 83warnings:false, 84drop_debugger:true, 85drop_console:true 86}, 87sourceMap: false, 88} 89}), 90// new CompressionWebpackPlugin({ 91//asset: ‘[path].gz[query]‘, //目标文件名 92//algorithm: ‘gzip‘, //使用gzip压缩 93//test: new RegExp( //满足正则表达式的文件会被压缩 94//‘\.(‘ + [‘js‘, ‘css‘].join(‘|‘) + ‘)$‘ 95//), 96//threshold: 10240, //资源文件大于10240B=10kB时会被压缩 97//minRatio: 0.8 //最小压缩比达到0.8时才会被压缩 98// }), 99new CopyWebpackPlugin([ 100{ 101from: resolve(__dirname, ‘static‘), 102to: resolve(__dirname, `../web/static`), 103ignore: [‘.*‘]//忽视.*文件 104}, 105{ 106from: ‘favicon.ico‘, 107to: resolve(__dirname, ‘../web/‘), 108force: true 109}], {}), 110new webpack.DefinePlugin({ 111‘process.env‘: { 112NODE_ENV: JSON.stringify(‘production‘) 113} 114}), 115new CleanWebpackPlugin([ 116`../web/${publicPath}/chunks`, 117`../web/${publicPath}/assets`, 118`../web/static`], { 119root: __dirname, 120verbose: true, 121dry: false, 122allowExternal: true 123}), 124); 125} 126return plugins; 127 } 128 129 module.exports = (options = {}) => ({ 130entry: { 131vendor: [ 132‘./src/vendor.ts‘, 133`bootstrap-loader/lib/bootstrap.loader?${!options.dev ? ‘extractStyles‘ : ‘‘}& configFilePath=${__dirname}/.bootstraprc!bootstrap-loader/no-op.js`, 134‘lodash‘, 135‘linq‘ 136], 137main: ‘./src/main.ts‘ 138}, 139output: { 140path: resolve(__dirname, ‘../web‘ + publicPath), 141filename: ‘[name].js‘, 142chunkFilename: ‘chunks/[name].[chunkhash:8].js‘, 143publicPath: options.dev ? ‘/‘ : publicPath 144}, 145resolve: { 146extensions: [‘.ts‘, ‘.tsx‘, ‘.js‘, ‘.vue‘, ‘.json‘], 147alias: { 148‘vue$‘: ‘vue/dist/vue.esm.js‘, 149‘@‘: resolve(__dirname, ‘src‘), 150} 151}, 152module: { 153rules: [ 154{ 155test: /.js$/, 156loader: ‘babel-loader‘, 157// exclude: file => ( 158///node_modules/.test(file) & & 159//!/.vue.js/.test(file) 160// ), 161include: [ 162resolve(‘src‘), 163resolve(‘node_modules/vue-echarts-v3/src‘), 164resolve(‘node_modules/vue-pdf/src‘) 165] 166}, 167{ 168test: /.tsx?$/, 169exclude: /node_modules/, 170enforce: ‘pre‘, 171loader: ‘tslint-loader‘ 172}, 173{ 174test: /.tsx?$/, 175exclude: /node_modules|vue/src/, 176use: [ 177"babel-loader", 178{ 179loader: "ts-loader", 180options: { 181appendTsSuffixTo: [/.vue$/], 182transpileOnly: true, 183} 184} 185] 186}, 187{ 188test: /.vue$/, 189use: [{ 190loader: ‘vue-loader‘, 191options: { 192loaders: { 193js: "babel-loader", 194ts: "ts-loader!tslint-loader", 195tsx: "babel-loader!ts-loader!tslint-loader", 196scss: getVueStyleLoader(options.dev), 197i18n: "@kazupon/vue-i18n-loader" 198} 199} 200}] 201}, 202{ 203test: /.css$/, 204use: getCssLoader(options.dev), 205}, 206{ 207test: /.scss$/, 208use: getScssLoader(options.dev), 209exclude: /node_modules/ 210}, 211{ 212test: /favicon.png$/, 213use: [{ 214loader: ‘file-loader‘, 215options: { 216name: ‘[name].[ext]?[hash]‘ 217} 218}] 219}, 220{ 221test: /.((woff2?|svg)(?v=[0-9].[0-9].[0-9]))|(woff2?|svg|jpe?g|png|gif|ico)$/, 222exclude: /favicon.png$/, 223use: [ 224// 小于10KB的图片会自动转成dataUrl 225{ 226loader: ‘url-loader‘, 227options: { 228limit: 10240, 229name: "assets/image/[name].[hash:8].[ext]" 230} 231}, 232{ 233loader: ‘image-webpack-loader‘, 234options: { 235query: { 236mozjpeg: { 237progressive: true, 238}, 239gifsicle: { 240interlaced: true, 241}, 242optipng: { 243bypassOnDebug: true, 244progressive: true, 245pngquant: {quality: "65-80", speed: 4} 246} 247} 248} 249} 250] 251}, 252{ 253test: /.((ttf|eot)(?v=[0-9].[0-9].[0-9]))|(ttf|eot)$/, 254use: [ 255{ 256loader: ‘url-loader‘, 257options: { 258limit: 10240, 259name: "assets/font/[name].[hash:8].[ext]" 260} 261}] 262}, 263{ 264test: /.json$/, 265loader: ‘json-loader‘, 266exclude: /node_modules/ 267} 268], 269loaders: [ 270{ 271test: require.resolve(‘tinymce/tinymce‘), 272loaders: [ 273‘imports?this=> window‘, 274‘exports?window.tinymce‘ 275] 276}, 277{ 278test: /tinymce/(themes|plugins)//, 279loaders: [ 280‘imports?this=> window‘ 281] 282}] 283}, 284plugins: getPlugins(options.dev, [ 285new CopyWebpackPlugin([ 286{ from: ‘./node_modules/layui-src/dist/lay‘, to: ‘./chunks/lay‘ }, 287{ from: ‘./node_modules/layui-src/dist/css‘, to: ‘./chunks/css‘ }, 288{ from: ‘./node_modules/tinymce/plugins‘, to: ‘./chunks/plugins‘ }, 289{ from: ‘./node_modules/tinymce/themes‘, to: ‘./chunks/themes‘ }, 290{ from: ‘./node_modules/tinymce/skins‘, to: ‘./chunks/skins‘ }, 291// {from: ‘viewer‘, 292// to: (options.dev ? ‘/‘ : resolve(__dirname, ‘./build/public/viewer/‘)), 293// force: true} 294], {}), 295// split vendor js into its own file 296new webpack.optimize.CommonsChunkPlugin({ 297name: ‘vendor‘, 298minChunks(module) { 299// any required modules inside node_modules are extracted to vendor 300return ( 301module.resource & & 302/.js$/.test(module.resource) & & 303module.resource.indexOf( 304resolve(__dirname, ‘../node_modules‘) 305) === 0 306) 307} 308}), 309// extract webpack runtime and module manifest to its own file in order to 310// prevent vendor hash from being updated whenever app bundle is updated 311new webpack.optimize.CommonsChunkPlugin({ 312name: ‘manifest‘, 313minChunks: Infinity, 314}), 315// This instance extracts shared chunks from code splitted chunks and bundles them 316// in a separate chunk, similar to the vendor chunk 317// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 318new webpack.optimize.CommonsChunkPlugin({ 319name: ‘main‘, 320async: ‘common‘, 321children: true, 322minChunks: 2 323}), 324new HtmlWebpackPlugin({ 325template: ‘src/index.html‘, 326filename: options.dev ? ‘index.html‘ : resolve(__dirname, ‘../web/index.html‘), 327inject: true, //注入的js文件将会被放在body标签中,当值为‘head‘时,将被放在head标签中 328chunks: ["manifest", "vendor", "common", "main"], 329hash: true, 330minify: {//压缩配置 331removeComments: true, //删除html中的注释代码 332collapseWhitespace: true,//删除html中的空白符 333removeAttributeQuotes: true//删除html元素中属性的引号 334}, 335chunksSortMode: ‘dependency‘ //按dependency的顺序引入 336}), 337// 该处设定的参数可在程序中访问,但必须以/开头和结尾,并且/也会是值的一部分 338new webpack.DefinePlugin({ 339__API_PATH__: options.dev ? ‘/api/‘ : ‘/ /‘,// 此处必须写成/ / ,必须以/开头和结尾,且中间必须有一个空格 340__ASSETS_PATH__: options.dev ? ‘/‘ : publicPath 341}), 342new webpack.ProvidePlugin({ 343_: ‘lodash‘, 344Enumerable: ‘linq‘ 345}) 346]), 347node: { 348// prevent webpack from injecting useless setImmediate polyfill because Vue 349// source contains it (although only uses it if it‘s native). 350setImmediate: false, 351// prevent webpack from injecting mocks to Node native modules 352// that does not make sense for the client 353dgram: ‘empty‘, 354fs: ‘empty‘, 355net: ‘empty‘, 356tls: ‘empty‘, 357child_process: ‘empty‘ 358}, 359devServer: { 360host: ‘127.0.0.1‘, 361port: 8081, 362proxy: { 363‘/api/*‘: { 364target: ‘http://127.0.0.1:8080‘, 365secure: false, 366changeOrigin: true, 367pathRewrite: { 368‘^/api‘: ‘‘ 369} 370} 371}, 372historyApiFallback: { 373index: url.parse(options.dev ? ‘/‘ : publicPath).pathname 374} 375}, 376devtool: options.dev ? ‘#cheap-module-eval-source-map‘ : ‘#source-map‘, 377 })

tsconfig.json
1 { 2"compilerOptions": { 3// 编译输出目标 ES 版本 4"target": "es5", 5// 采用的模块系统 6"module": "esnext", 7// 如何处理模块 8"moduleResolution": "node", 9// 以严格模式解析 10"strict": false, 11// 是否包含可以用于 debug 的 sourceMap 12"sourceMap": true, 13// 允许从没有设置默认导出的模块中默认导入 14"allowSyntheticDefaultImports": true, 15// 将每个文件作为单独的模块 16"isolatedModules": false, 17// 启用装饰器 18"experimentalDecorators": true, 19// 启用设计类型元数据(用于反射) 20"emitDecoratorMetadata": true, 21"removeComments": false, 22// 在表达式和声明上有隐含的any类型时报错 23"noImplicitAny": false, 24// 不是函数的所有返回路径都有返回值时报错。 25"noImplicitReturns": true, 26// 从 tslib 导入外部帮助库: 比如__extends,__rest等 27"importHelpers": true, 28"suppressImplicitAnyIndexErrors": true, 29"noResolve": false, 30// 允许编译javascript文件 31"allowJs": true, 32// 解析非相对模块名的基准目录 33"baseUrl": "./", 34// 指定特殊模块的路径 35"paths": { 36"jquery": [ 37"node_modules/jquery/dist/jquery" 38] 39}, 40"lib": ["es2017", "dom"], 41"jsx": "preserve" 42}, 43"exclude": [ 44"node_modules" 45] 46 }

.babelrc
1 { 2"presets": ["env"], 3"plugins": [ 4"syntax-dynamic-import", 5"transform-vue-jsx" 6] 7 }

typings.d.ts
1 import {AxiosStatic} from "axios"; 2 3 declare module "*.png" { 4const value: any; 5export default value; 6 } 7 8 declare module "*.jpg" { 9const value: any; 10export default value; 11 } 12 13 declare module ‘vue/types/vue‘ { 14interface Vue { 15$http: AxiosStatic, 16$socket: any, 17} 18 }

vue-shim.d.ts
1 declare module "*.vue" { 2import Vue from "vue"; 3export default Vue; 4 }

main.ts
1 import Vue, { AsyncComponent } from ‘vue‘; 2 import Vuex from "vuex"; 3 import VueRouter from "vue-router"; 4 import VueLazyLoad from "vue-lazyload"; 5 import VueI18n from ‘vue-i18n‘ 6 import axios from "axios"; 7 8 import BootstrapVue from "bootstrap-vue"; 9 import ElementUI from "element-ui"; 10 11 import enLocaleElementUI from ‘element-ui/lib/locale/lang/en‘ 12 import zhCNLocaleElementUI from ‘element-ui/lib/locale/lang/zh-CN‘ 13 import enLocaleCommon from ‘./common/lang/en‘ 14 import zhCNLocaleCommon from ‘./common/lang/zh-CN‘ 15 import enLocaleApp from ‘./lang/en‘ 16 import zhCNLocaleApp from ‘./lang/zh-CN‘ 17 18 import VueSocketio from ‘vue-socket.io‘; 19 20 import App from "./app.vue"; 21 import routes from "./framework/routes"; 22 23 import "@/common/style/baseStyle.css"; 24 import "@/common/style/var.scss"; 25 import "@/common/style/layout.scss"; 26 27 // import VueECharts from "vue-echarts/components/ECharts.vue"; 28 // import ECharts modules manually to reduce bundle size; 29 // import "echarts/lib/chart/bar"; 30 // import "echarts/lib/component/tooltip"; 31 32 Vue.use(Vuex); 33 Vue.use(VueRouter); 34 35 Vue.use(VueLazyLoad, { 36// error:"./static/error.png", 37// loading:"./static/loading.png" 38 }) 39 40 Vue.use(VueI18n) 41 42 Vue.prototype.$http = axios; 43 44 Vue.use(BootstrapVue); 45 46 const messages = { 47"en": { 48...enLocaleElementUI, 49...enLocaleCommon, 50...enLocaleApp, 51}, 52"zh-CN": { 53...zhCNLocaleElementUI, 54...zhCNLocaleCommon, 55...zhCNLocaleApp, 56} 57 } 58 59 // Create VueI18n instance with options 60 const i18n = new VueI18n({ 61locale: ‘zh-CN‘, // set locale 62messages, // set locale messages 63silentTranslationWarn: true 64 }) 65 66 Vue.use(ElementUI, { 67i18n: (key, value) => i18n.t(key, value) 68 }) 69 70 Vue.use(VueSocketio, ‘http://127.0.0.1:9092‘); 71 72 const router = new VueRouter({ 73routes 74 }) 75 76 const vm = new Vue({ 77el: "#app", 78data: {rootid: "ac"}, 79// components: { 80//echarts 81// }, 82router, 83render: h => h(App), 84i18n 85 })

【Vue2.5 Web App 项目搭建 (TypeScript版)】 

    推荐阅读