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 })
以上是 Vue2.5 Web App 项目搭建 (TypeScript版) 的全部内容, 来源链接: utcz.com/z/377519.html