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

vue

参考了几位同行的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

回到顶部