Vue2.5 Web App 项目搭建 (TypeScript版)
参考了几位同行的Blogs和StackOverflow上的许多问答,在原来的ng1加TypeScript以及Webpack的经验基础上,搭建了该项目,核心文件如下,供需要的人参考。
package.json
{
"name": "app",
"version": "1.0.0",
"description": "App package.json from the documentation, supplemented with testing support",
"author": "",
"private": true,
"scripts": {
"dev": "webpack-dev-server -d --inline --hot --env.dev",
"build": "rimraf dist && webpack --progress --hide-modules"
},
"dependencies": {
"axios": "^0.18.0",
"bootstrap": "^4.0.0",
"bootstrap-vue": "^2.0.0-rc.2",
"element-ui": "^2.2.2",
"font-awesome": "^4.7.0",
"jointjs": "^2.0.1",
"jquery": "^3.3.1",
"js-md5": "^0.7.3",
"layui-src": "^2.2.5",
"linq": "^3.0.9",
"lodash": "^4.17.5",
"pdfmake": "^0.1.36",
"popper.js": "^1.14.1",
"tinymce": "^4.7.12",
"uuid": "^3.2.1",
"vue": "^2.5.16",
"vue-class-component": "^6.2.0",
"vue-echarts-v3": "^1.0.19",
"vue-i18n": "^7.6.0",
"vue-i18n-extensions": "^0.1.0",
"vue-lazyload": "^1.2.3",
"vue-pdf": "^3.3.1",
"vue-property-decorator": "^6.0.0",
"vue-router": "^3.0.1",
"vue-socket.io": "^2.1.1-b",
"vue-tinymce": "github:lpreterite/vue-tinymce",
"vue-video-player": "^5.0.2",
"vuex": "^3.0.1",
"vuex-class": "^0.3.0"
},
"engines": {
"node": ">=6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"devDependencies": {
"@kazupon/vue-i18n-loader": "^0.3.0",
"@types/lodash": "^4.14.106",
"ajv": "^6.3.0",
"autoprefixer": "^8.2.0",
"babel-core": "^6.26.3",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^7.1.4",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-vue-jsx": "^3.7.0",
"babel-preset-env": "^1.6.1",
"bootstrap-loader": "^2.2.0",
"clean-webpack-plugin": "^0.1.19",
"compression-webpack-plugin": "^1.1.11",
"copy-webpack-plugin": "^4.5.1",
"css-loader": "^0.28.11",
"cssnano": "^3.10.0",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.11",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.1.0",
"image-webpack-loader": "^4.2.0",
"json-loader": "^0.5.7",
"node-sass": "^4.7.2",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"postcss-import": "^11.1.0",
"postcss-loader": "^2.1.3",
"postcss-url": "^7.3.2",
"resolve-url-loader": "^2.3.0",
"rimraf": "^2.6.2",
"sass-loader": "^6.0.7",
"sass-resources-loader": "^1.3.3",
"style-loader": "^0.20.3",
"ts-loader": "^3.1.1",
"tslint": "^5.9.1",
"tslint-config-standard": "^7.0.0",
"tslint-loader": "^3.6.0",
"typescript": "^2.8.3",
"uglifyjs-webpack-plugin": "^1.2.5",
"url-loader": "^1.0.1",
"vue-loader": "^14.2.1",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"webpack": "^3.1.0",
"webpack-dev-server": "^2.9.4",
"webpack-parallel-uglify-plugin": "^1.1.0"
}
}
webpack.config.js
const {resolve} = require('path');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');
const ParallelUglifyPlugin=require('webpack-parallel-uglify-plugin') ;
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const url = require('url');
const publicPath = '/public/'; function getVueStyleLoader(isDev) {
if (!isDev) {
return ExtractTextPlugin.extract({
fallback: "vue-style-loader",
use: ["css-loader", "postcss-loader", "sass-loader",
"sass-resources-loader?resources=./src/common/style/sass-resources.scss"]
});
}
return "vue-style-loader!css-loader?sourceMap!postcss-loader?sourceMap!sass-loader?sourceMap!sass-resources-loader?resources=./src/common/style/sass-resources.scss";
} function getCssLoader(isDev) {
if (!isDev) {
return ExtractTextPlugin.extract({
fallback: "style-loader",
use: ["css-loader", "postcss-loader"]
});
}
return [
{loader: 'style-loader'},
{loader: 'css-loader', options: {sourceMap: true}},
{loader: 'postcss-loader', options: {sourceMap: true}},
];
} function getScssLoader(isDev) {
if (!isDev) {
return ExtractTextPlugin.extract({
fallback: "style-loader",
use: ["css-loader", "postcss-loader", "sass-loader",
"sass-resources-loader?resources=./src/common/style/sass-resources.scss"]
});
}
return [
{loader: 'style-loader'},
{loader: 'css-loader', options: {sourceMap: true}},
{loader: 'postcss-loader', options: {sourceMap: true}},
{loader: 'sass-loader', options: {sourceMap: true}},
{
loader: 'sass-resources-loader',
options: {resources: './src/common/style/sass-resources.scss'}
}
];
} function getPlugins(isDev, plugins) {
if (!isDev) {
plugins.push(
new ExtractTextPlugin({
filename: 'assets/css/[name].[contenthash:8].css',
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true,
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorOptions: { discardComments: {removeAll: true}},
canPrint: true,
}),
new ParallelUglifyPlugin({
uglifyJS: {
output: {
comments: false //去掉注释
},
compress: {
warnings: false,
drop_debugger: true,
drop_console: true
},
sourceMap: false,
}
}),
// new CompressionWebpackPlugin({
// asset: '[path].gz[query]', //目标文件名
// algorithm: 'gzip', //使用gzip压缩
// test: new RegExp( //满足正则表达式的文件会被压缩
// '\\.(' + ['js', 'css'].join('|') + ')$'
// ),
// threshold: 10240, //资源文件大于10240B=10kB时会被压缩
// minRatio: 0.8 //最小压缩比达到0.8时才会被压缩
// }),
new CopyWebpackPlugin([
{
from: resolve(__dirname, 'static'),
to: resolve(__dirname, `../web/static`),
ignore: ['.*'] //忽视.*文件
},
{
from: 'favicon.ico',
to: resolve(__dirname, '../web/'),
force: true
}], {}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new CleanWebpackPlugin([
`../web/${publicPath}/chunks`,
`../web/${publicPath}/assets`,
`../web/static`], {
root: __dirname,
verbose: true,
dry: false,
allowExternal: true
}),
);
}
return plugins;
} module.exports = (options = {}) => ({
entry: {
vendor: [
'./src/vendor.ts',
`bootstrap-loader/lib/bootstrap.loader?${!options.dev ? 'extractStyles' : ''}&configFilePath=${__dirname}/.bootstraprc!bootstrap-loader/no-op.js`,
'lodash',
'linq'
],
main: './src/main.ts'
},
output: {
path: resolve(__dirname, '../web' + publicPath),
filename: '[name].js',
chunkFilename: 'chunks/[name].[chunkhash:8].js',
publicPath: options.dev ? '/' : publicPath
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve(__dirname, 'src'),
}
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
// exclude: file => (
// /node_modules/.test(file) &&
// !/\.vue\.js/.test(file)
// ),
include: [
resolve('src'),
resolve('node_modules/vue-echarts-v3/src'),
resolve('node_modules/vue-pdf/src')
]
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
enforce: 'pre',
loader: 'tslint-loader'
},
{
test: /\.tsx?$/,
exclude: /node_modules|vue\/src/,
use: [
"babel-loader",
{
loader: "ts-loader",
options: {
appendTsSuffixTo: [/\.vue$/],
transpileOnly: true,
}
}
]
},
{
test: /\.vue$/,
use: [{
loader: 'vue-loader',
options: {
loaders: {
js: "babel-loader",
ts: "ts-loader!tslint-loader",
tsx: "babel-loader!ts-loader!tslint-loader",
scss: getVueStyleLoader(options.dev),
i18n: "@kazupon/vue-i18n-loader"
}
}
}]
},
{
test: /\.css$/,
use: getCssLoader(options.dev),
},
{
test: /\.scss$/,
use: getScssLoader(options.dev),
exclude: /node_modules/
},
{
test: /favicon\.png$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}]
},
{
test: /\.((woff2?|svg)(\?v=[0-9]\.[0-9]\.[0-9]))|(woff2?|svg|jpe?g|png|gif|ico)$/,
exclude: /favicon\.png$/,
use: [
// 小于10KB的图片会自动转成dataUrl
{
loader: 'url-loader',
options: {
limit: 10240,
name: "assets/image/[name].[hash:8].[ext]"
}
},
{
loader: 'image-webpack-loader',
options: {
query: {
mozjpeg: {
progressive: true,
},
gifsicle: {
interlaced: true,
},
optipng: {
bypassOnDebug: true,
progressive: true,
pngquant: {quality: "65-80", speed: 4}
}
}
}
}
]
},
{
test: /\.((ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9]))|(ttf|eot)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
name: "assets/font/[name].[hash:8].[ext]"
}
}]
},
{
test: /\.json$/,
loader: 'json-loader',
exclude: /node_modules/
}
],
loaders: [
{
test: require.resolve('tinymce/tinymce'),
loaders: [
'imports?this=>window',
'exports?window.tinymce'
]
},
{
test: /tinymce\/(themes|plugins)\//,
loaders: [
'imports?this=>window'
]
}]
},
plugins: getPlugins(options.dev, [
new CopyWebpackPlugin([
{ from: './node_modules/layui-src/dist/lay', to: './chunks/lay' },
{ from: './node_modules/layui-src/dist/css', to: './chunks/css' },
{ from: './node_modules/tinymce/plugins', to: './chunks/plugins' },
{ from: './node_modules/tinymce/themes', to: './chunks/themes' },
{ from: './node_modules/tinymce/skins', to: './chunks/skins' },
// {from: 'viewer',
// to: (options.dev ? '/' : resolve(__dirname, './build/public/viewer/')),
// force: true}
], {}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks(module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
resolve(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity,
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'main',
async: 'common',
children: true,
minChunks: 2
}),
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: options.dev ? 'index.html' : resolve(__dirname, '../web/index.html'),
inject: true, //注入的js文件将会被放在body标签中,当值为'head'时,将被放在head标签中
chunks: ["manifest", "vendor", "common", "main"],
hash: true,
minify: { //压缩配置
removeComments: true, //删除html中的注释代码
collapseWhitespace: true, //删除html中的空白符
removeAttributeQuotes: true //删除html元素中属性的引号
},
chunksSortMode: 'dependency' //按dependency的顺序引入
}),
// 该处设定的参数可在程序中访问,但必须以/开头和结尾,并且/也会是值的一部分
new webpack.DefinePlugin({
__API_PATH__: options.dev ? '/api/' : '/ /', // 此处必须写成/ / ,必须以/开头和结尾,且中间必须有一个空格
__ASSETS_PATH__: options.dev ? '/' : publicPath
}),
new webpack.ProvidePlugin({
_: 'lodash',
Enumerable: 'linq'
})
]),
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
},
devServer: {
host: '127.0.0.1',
port: 8081,
proxy: {
'/api/*': {
target: 'http://127.0.0.1:8080',
secure: false,
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},
historyApiFallback: {
index: url.parse(options.dev ? '/' : publicPath).pathname
}
},
devtool: options.dev ? '#cheap-module-eval-source-map' : '#source-map',
})
tsconfig.json
{
"compilerOptions": {
// 编译输出目标 ES 版本
"target": "es5",
// 采用的模块系统
"module": "esnext",
// 如何处理模块
"moduleResolution": "node",
// 以严格模式解析
"strict": false,
// 是否包含可以用于 debug 的 sourceMap
"sourceMap": true,
// 允许从没有设置默认导出的模块中默认导入
"allowSyntheticDefaultImports": true,
// 将每个文件作为单独的模块
"isolatedModules": false,
// 启用装饰器
"experimentalDecorators": true,
// 启用设计类型元数据(用于反射)
"emitDecoratorMetadata": true,
"removeComments": false,
// 在表达式和声明上有隐含的any类型时报错
"noImplicitAny": false,
// 不是函数的所有返回路径都有返回值时报错。
"noImplicitReturns": true,
// 从 tslib 导入外部帮助库: 比如__extends,__rest等
"importHelpers": true,
"suppressImplicitAnyIndexErrors": true,
"noResolve": false,
// 允许编译javascript文件
"allowJs": true,
// 解析非相对模块名的基准目录
"baseUrl": "./",
// 指定特殊模块的路径
"paths": {
"jquery": [
"node_modules/jquery/dist/jquery"
]
},
"lib": ["es2017", "dom"],
"jsx": "preserve"
},
"exclude": [
"node_modules"
]
}
.babelrc
{
"presets": ["env"],
"plugins": [
"syntax-dynamic-import",
"transform-vue-jsx"
]
}
typings.d.ts
import {AxiosStatic} from "axios"; declare module "*.png" {
const value: any;
export default value;
} declare module "*.jpg" {
const value: any;
export default value;
} declare module 'vue/types/vue' {
interface Vue {
$http: AxiosStatic,
$socket: any,
}
}
vue-shim.d.ts
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
main.ts
import Vue, { AsyncComponent } from 'vue';
import Vuex from "vuex";
import VueRouter from "vue-router";
import VueLazyLoad from "vue-lazyload";
import VueI18n from 'vue-i18n'
import axios from "axios"; import BootstrapVue from "bootstrap-vue";
import ElementUI from "element-ui"; import enLocaleElementUI from 'element-ui/lib/locale/lang/en'
import zhCNLocaleElementUI from 'element-ui/lib/locale/lang/zh-CN'
import enLocaleCommon from './common/lang/en'
import zhCNLocaleCommon from './common/lang/zh-CN'
import enLocaleApp from './lang/en'
import zhCNLocaleApp from './lang/zh-CN' import VueSocketio from 'vue-socket.io'; import App from "./app.vue";
import routes from "./framework/routes"; import "@/common/style/baseStyle.css";
import "@/common/style/var.scss";
import "@/common/style/layout.scss"; // import VueECharts from "vue-echarts/components/ECharts.vue";
// import ECharts modules manually to reduce bundle size;
// import "echarts/lib/chart/bar";
// import "echarts/lib/component/tooltip"; Vue.use(Vuex);
Vue.use(VueRouter); Vue.use(VueLazyLoad, {
// error:"./static/error.png",
// loading:"./static/loading.png"
}) Vue.use(VueI18n) Vue.prototype.$http = axios; Vue.use(BootstrapVue); const messages = {
"en": {
...enLocaleElementUI,
...enLocaleCommon,
...enLocaleApp,
},
"zh-CN": {
...zhCNLocaleElementUI,
...zhCNLocaleCommon,
...zhCNLocaleApp,
}
} // Create VueI18n instance with options
const i18n = new VueI18n({
locale: 'zh-CN', // set locale
messages, // set locale messages
silentTranslationWarn: true
}) Vue.use(ElementUI, {
i18n: (key, value) => i18n.t(key, value)
}) Vue.use(VueSocketio, 'http://127.0.0.1:9092'); const router = new VueRouter({
routes
}) const vm = new Vue({
el: "#app",
data: {rootid: "ac"},
// components: {
// echarts
// },
router,
render: h => h(App),
i18n
})
Vue2.5 Web App 项目搭建 (TypeScript版)的更多相关文章
- 【饿了么】—— Vue2.0高仿饿了么核心模块&移动端Web App项目爬坑(三)
前言:接着上一篇项目总结,这一篇是学习过程记录的最后一篇,这里会梳理:评论组件.商家组件.优化.打包.相关资料链接.项目github地址:https://github.com/66Web/ljq_el ...
- .Net Core 3.1浏览器后端服务(一) Web API项目搭建
一.前言 基于CefSharp开发的浏览器项目已有一段时间,考虑到后期数据维护需要Server端来管理,故开启新篇章搭建浏览器后端服务.该项目前期以梳理服务端知识为主,后期将配合CefSharp浏览器 ...
- 第一次,触碰Web App项目,栽过的那些坑。
此项目是一个IPad上的Web App项目,页面的滚动用了最新的IScroll 5.0 插件, 确实是挺潮的. 项目用时 1个月 完成的, 准备今天晚上上线. 这是年前的最后一篇文章了,与众位博友分享 ...
- Web自动化测试项目搭建目录
Web自动化测试项目搭建(一) 需求与设计 Web自动化测试项目(二)BasePage实现 Web自动化测试项目(三)用例的组织与运行 Web自动化测试项目(四)测试报告 Web自动化测试项目(五)测 ...
- django学习笔记二:一个项目多个App项目搭建
django充许在一个项目中存在多个app,如一个大门户网站中可以包含论坛,新闻等内容,其中每一个模块称之为一个App,也可以理解为一个个独立的小型项目最终集成在一个门户网站中最终呈现给用户 本次测试 ...
- 一个项目多个App项目搭建
在testDjango项目中找到testDjango文件夹,打开urls.py路由配置文件并添加以下配置 from django.conf.urls import url,includefrom dj ...
- Maven项目搭建-Eclipse版
一.Maven简单介绍 Maven是基于Java平台的项目构建(mvn clean install).依赖管理(中央仓库,Nexus)和项目信息管理的项目管理工具. Maven是基于项目对象模型(PO ...
- vue2.0 安装及项目搭建(一)
基本环境安装 1.安装node:从node.js官网下载并安装node.测试:win+R(打开命令行)-------输入cmd-------敲入node -v.如果出现相应版本号,即安装成功: 2.测 ...
- 学习笔记:flutter项目搭建(mac版)
什么是flutter Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的 ...
随机推荐
- frm和ibd恢复sql文件的操作
情况:有mysql中data文件(仅仅一个数据库) 操作:frm和ibd恢复sql文件的操作 1.创建相同名字的库xxx 2.把ibdata1替换成原来的 3.把数据库xxx内内容全部替换为原来的 完 ...
- hdoj1003 DP
Max Sum Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Su ...
- vs2013和vs2010的配置
win10下vs2013和vs2010的相关配置 ------made by siwuxie095 主要是推荐一些vs2013和vs2010的好用的插件和配色方案,下面主要是以vs2013示例 1.首 ...
- 动态调用WebService方法
好像很多人做WebService的时候都是直接添加引用的方式,然后调用服务端的方法.这样就个问题,就是每次我服务端添加了方法或者修改了方法后都要更新Web引用,这样比较麻烦.下面给一个不用添加引用 ...
- [leetcode]131. Palindrome Partitioning字符串分割成回文子串
Given a string s, partition s such that every substring of the partition is a palindrome. Return all ...
- js this pointer 指针
this JavaScript的函数内部如果调用了this,那么这个this到底指向谁? 答案是,视情况而定! 如果以对象的方法形式调用,比如xiaoming.age(),该函数的this指向被调用的 ...
- win10下zip安装mysql5.7的一些问题
一.MySQL下载地址: http://dev.mysql.com/downloads/mysql/ 我们下载zip版本的 二.解压zip文件 三.复制一份里面的my-default.ini为 ...
- webkit开源项目
WebKitOpen Source Web Browser Engine Blog Downloads Feature Status Reporting Bugs Contribute Getting ...
- PAT 1065 单身狗(25)(STL-map+思路+测试点分析)
1065 单身狗(25 分) "单身狗"是中文对于单身人士的一种爱称.本题请你从上万人的大型派对中找出落单的客人,以便给予特殊关爱. 输入格式: 输入第一行给出一个正整数 N(≤ ...
- Luogu 3959 [NOIP2017] 宝藏- 状压dp
题解 真的想不到这题状压的做法...听说还有跑的飞快的模拟退火,要是现场做绝对滚粗QAQ. 不考虑深度,先预处理出 $pt_{i, S}$ 表示让一个不属于 集合 $S$ 的 点$i$ 与点集 $S$ ...