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) {
 14     if (!isDev) {
 15         return ExtractTextPlugin.extract({
 16             fallback: "vue-style-loader",
 17             use: ["css-loader", "postcss-loader", "sass-loader",
 18                 "sass-resources-loader?resources=./src/common/style/sass-resources.scss"]
 19         });
 20     }
 21     return "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) {
 25     if (!isDev) {
 26         return ExtractTextPlugin.extract({
 27             fallback: "style-loader",
 28             use: ["css-loader", "postcss-loader"]
 29         });
 30     }
 31     return [
 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) {
 39     if (!isDev) {
 40         return ExtractTextPlugin.extract({
 41             fallback: "style-loader",
 42             use: ["css-loader", "postcss-loader", "sass-loader",
 43                 "sass-resources-loader?resources=./src/common/style/sass-resources.scss"]
 44         });
 45     }
 46     return [
 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         {
 52             loader: 'sass-resources-loader',
 53             options: {resources: './src/common/style/sass-resources.scss'}
 54         }
 55     ];
 56 }
 57 
 58 function getPlugins(isDev, plugins) {
 59     if (!isDev) {
 60         plugins.push(
 61             new ExtractTextPlugin({
 62                 filename: '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
 67                 allChunks: true,
 68             }),
 69             // Compress extracted CSS. We are using this plugin so that possible
 70             // duplicated CSS from different components can be deduped.
 71             new OptimizeCSSPlugin({
 72               assetNameRegExp: /\.css$/g,
 73               cssProcessor: require('cssnano'),
 74               cssProcessorOptions: { discardComments: {removeAll: true}},
 75               canPrint: true,
 76             }),
 77             new ParallelUglifyPlugin({
 78                 uglifyJS: {
 79                     output: {
 80                         comments:  false  //去掉注释
 81                     },
 82                     compress: {
 83                         warnings:  false,
 84                         drop_debugger:  true,
 85                         drop_console:  true
 86                     },
 87                     sourceMap: 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             // }),
 99             new CopyWebpackPlugin([
100                 {
101                     from: resolve(__dirname, 'static'),
102                     to: resolve(__dirname, `../web/static`),
103                     ignore: ['.*']  //忽视.*文件
104                 },
105                 {
106                     from: 'favicon.ico',
107                     to: resolve(__dirname, '../web/'),
108                     force: true
109                 }], {}),
110             new webpack.DefinePlugin({
111                 'process.env': {
112                     NODE_ENV: JSON.stringify('production')
113                 }
114             }),
115             new CleanWebpackPlugin([
116                 `../web/${publicPath}/chunks`,
117                 `../web/${publicPath}/assets`,
118                 `../web/static`], {
119                 root: __dirname,
120                 verbose: true,
121                 dry: false,
122                 allowExternal: true
123             }),
124         );
125     }
126     return plugins;
127 }
128 
129 module.exports = (options = {}) => ({
130     entry: {
131         vendor: [
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         ],
137         main: './src/main.ts'
138     },
139     output: {
140         path: resolve(__dirname, '../web' + publicPath),
141         filename: '[name].js',
142         chunkFilename: 'chunks/[name].[chunkhash:8].js',
143         publicPath: options.dev ? '/' : publicPath
144     },
145     resolve: {
146         extensions: ['.ts', '.tsx', '.js', '.vue', '.json'],
147         alias: {
148             'vue$': 'vue/dist/vue.esm.js',
149             '@': resolve(__dirname, 'src'),
150         }
151     },
152     module: {
153         rules: [
154             {
155                 test: /\.js$/,
156                 loader: 'babel-loader',
157                 // exclude: file => (
158                 //     /node_modules/.test(file) &&
159                 //     !/\.vue\.js/.test(file)
160                 // ),
161                 include: [
162                     resolve('src'),
163                     resolve('node_modules/vue-echarts-v3/src'),
164                     resolve('node_modules/vue-pdf/src')
165                 ]
166             },
167             {
168                 test: /\.tsx?$/,
169                 exclude: /node_modules/,
170                 enforce: 'pre',
171                 loader: 'tslint-loader'
172             },
173             {
174                 test: /\.tsx?$/,
175                 exclude: /node_modules|vue\/src/,
176                 use: [
177                     "babel-loader",
178                     {
179                         loader: "ts-loader",
180                         options: {
181                             appendTsSuffixTo: [/\.vue$/],
182                             transpileOnly: true,
183                         }
184                     }
185                 ]
186             },
187             {
188                 test: /\.vue$/,
189                 use: [{
190                     loader: 'vue-loader',
191                     options: {
192                         loaders: {
193                             js: "babel-loader",
194                             ts: "ts-loader!tslint-loader",
195                             tsx: "babel-loader!ts-loader!tslint-loader",
196                             scss: getVueStyleLoader(options.dev),
197                             i18n: "@kazupon/vue-i18n-loader"
198                         }
199                     }
200                 }]
201             },
202             {
203                 test: /\.css$/,
204                 use: getCssLoader(options.dev),
205             },
206             {
207                 test: /\.scss$/,
208                 use: getScssLoader(options.dev),
209                 exclude: /node_modules/
210             },
211             {
212                 test: /favicon\.png$/,
213                 use: [{
214                     loader: 'file-loader',
215                     options: {
216                         name: '[name].[ext]?[hash]'
217                     }
218                 }]
219             },
220             {
221                 test: /\.((woff2?|svg)(\?v=[0-9]\.[0-9]\.[0-9]))|(woff2?|svg|jpe?g|png|gif|ico)$/,
222                 exclude: /favicon\.png$/,
223                 use: [
224                     // 小于10KB的图片会自动转成dataUrl
225                     {
226                         loader: 'url-loader',
227                         options: {
228                             limit: 10240,
229                             name: "assets/image/[name].[hash:8].[ext]"
230                         }
231                     },
232                     {
233                         loader: 'image-webpack-loader',
234                         options: {
235                             query: {
236                                 mozjpeg: {
237                                     progressive: true,
238                                 },
239                                 gifsicle: {
240                                     interlaced: true,
241                                 },
242                                 optipng: {
243                                     bypassOnDebug: true,
244                                     progressive: true,
245                                     pngquant: {quality: "65-80", speed: 4}
246                                 }
247                             }
248                         }
249                     }
250                 ]
251             },
252             {
253                 test: /\.((ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9]))|(ttf|eot)$/,
254                 use: [
255                     {
256                         loader: 'url-loader',
257                         options: {
258                             limit: 10240,
259                             name: "assets/font/[name].[hash:8].[ext]"
260                         }
261                     }]
262             },
263             {
264                 test: /\.json$/,
265                 loader: 'json-loader',
266                 exclude: /node_modules/
267             }
268         ],
269         loaders: [
270             {
271                 test: require.resolve('tinymce/tinymce'),
272                 loaders: [
273                     'imports?this=>window',
274                     'exports?window.tinymce'
275                 ]
276             },
277             {
278                 test: /tinymce\/(themes|plugins)\//,
279                 loaders: [
280                     'imports?this=>window'
281                 ]
282             }]
283     },
284     plugins: getPlugins(options.dev, [
285         new 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
296         new webpack.optimize.CommonsChunkPlugin({
297             name: 'vendor',
298             minChunks(module) {
299                 // any required modules inside node_modules are extracted to vendor
300                 return (
301                     module.resource &&
302                     /\.js$/.test(module.resource) &&
303                     module.resource.indexOf(
304                         resolve(__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
311         new webpack.optimize.CommonsChunkPlugin({
312             name: 'manifest',
313             minChunks: 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
318         new webpack.optimize.CommonsChunkPlugin({
319             name: 'main',
320             async: 'common',
321             children: true,
322             minChunks: 2
323         }),
324         new HtmlWebpackPlugin({
325             template: 'src/index.html',
326             filename: options.dev ? 'index.html' : resolve(__dirname, '../web/index.html'),
327             inject: true, //注入的js文件将会被放在body标签中,当值为'head'时,将被放在head标签中
328             chunks: ["manifest", "vendor", "common", "main"],
329             hash: true,
330             minify: {  //压缩配置
331                 removeComments: true, //删除html中的注释代码
332                 collapseWhitespace: true,  //删除html中的空白符
333                 removeAttributeQuotes: true  //删除html元素中属性的引号
334             },
335             chunksSortMode: 'dependency' //按dependency的顺序引入
336         }),
337         // 该处设定的参数可在程序中访问,但必须以/开头和结尾,并且/也会是值的一部分
338         new webpack.DefinePlugin({
339             __API_PATH__: options.dev ? '/api/' : '/ /',  // 此处必须写成/ / ,必须以/开头和结尾,且中间必须有一个空格
340             __ASSETS_PATH__: options.dev ? '/' : publicPath
341         }),
342         new webpack.ProvidePlugin({
343             _: 'lodash',
344             Enumerable: 'linq'
345         })
346     ]),
347     node: {
348         // prevent webpack from injecting useless setImmediate polyfill because Vue
349         // source contains it (although only uses it if it's native).
350         setImmediate: false,
351         // prevent webpack from injecting mocks to Node native modules
352         // that does not make sense for the client
353         dgram: 'empty',
354         fs: 'empty',
355         net: 'empty',
356         tls: 'empty',
357         child_process: 'empty'
358     },
359     devServer: {
360         host: '127.0.0.1',
361         port: 8081,
362         proxy: {
363             '/api/*': {
364                 target: 'http://127.0.0.1:8080',
365                 secure: false,
366                 changeOrigin: true,
367                 pathRewrite: {
368                     '^/api': ''
369                 }
370             }
371         },
372         historyApiFallback: {
373             index: url.parse(options.dev ? '/' : publicPath).pathname
374         }
375     },
376     devtool: 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" {
 4     const value: any;
 5     export default value;
 6 }
 7 
 8 declare module "*.jpg" {
 9     const value: any;
10     export default value;
11 }
12 
13 declare module 'vue/types/vue' {
14     interface Vue {
15         $http: AxiosStatic,
16         $socket: any,
17     }
18 }

vue-shim.d.ts

1 declare module "*.vue" {
2     import Vue from "vue";
3     export 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({
61     locale: 'zh-CN', // set locale
62     messages, // set locale messages
63     silentTranslationWarn: true
64 })
65 
66 Vue.use(ElementUI, {
67     i18n: (key, value) => i18n.t(key, value)
68 })
69 
70 Vue.use(VueSocketio, 'http://127.0.0.1:9092');
71 
72 const router = new VueRouter({
73     routes
74 })
75 
76 const vm = new Vue({
77     el: "#app",
78     data: {rootid: "ac"},
79     // components: {
80     //     echarts
81     // },
82     router,
83     render: h => h(App),
84     i18n
85 })

 

posted @ 2018-06-23 15:25  ClockDotNet  阅读(1681)  评论(0编辑  收藏  举报