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正在被越来越多的 ...
随机推荐
- jquery读取本地文件,Windows上报错。XMLHttpRequest cannot load xxx. Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource.k.cors.a.c
问题: 测试报告,使用本地的json.txt文件,结果文件读取失败,报错如下: XMLHttpRequest cannot load xxx. Cross origin requests are on ...
- debug、release
1.区别 Debug 和 Release 并没有本质的区别,他们只是VC预定义提供的两组编译选项的集合,编译器只是按照预定的选项行动.如果我们愿意,我们完全可以把Debug和Release的行为完全颠 ...
- 判断UNITY版本号
代码示例: #if (UNITY_5_3 || UNITY_5_4 || UNITY_5_5 || UNITY_5_6 || UNITY_5_7 || UNITY_5_8 || UNITY_5_9)u ...
- Drying
Drying http://poj.org/problem?id=3104 Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 2 ...
- TZOJ 1545 Hurdles of 110m(01背包dp)
描述 In the year 2008, the 29th Olympic Games will be held in Beijing. This will signify the prosperit ...
- 迭代器&生成器&yield异步
迭代器 #迭代器是访问集合元素的一种形式,迭代器从集合的第一个元素开始访问,直到所有的元素被访问# 结束才结束,迭代器只能往前访问,不能往后访问,比如你先访问1,在访问2,在访问3.如果已经# 访问到 ...
- maven不存在jar包解决
win7环境 下载:https://maven.apache.org/download.cgi 提取文件,并cmd 转到bin目录 假设要添加的jar包是jbarcode-0.2.8.jar, 可执行 ...
- PHP资源列表(转)
一个PHP资源列表,内容包括:库.框架.模板.安全.代码分析.日志.第三方库.配置工具.Web 工具.书籍.电子书.经典博文等等. 初始翻译信息来自:<推荐!国外程序员整理的 PHP 资源大全& ...
- PAT 1010 一元多项式求导 (25)(STL-map+思路)
1010 一元多项式求导 (25)(25 分)提问 设计函数求一元多项式的导数.(注:x^n^(n为整数)的一阶导数为n*x^n-1^.) 输入格式:以指数递降方式输入多项式非零项系数和指数(绝对值均 ...
- jstl标签详解 (转载)
JSLT标签库,是日常开发经常使用的,也是众多标签中性能最好的.把常用的内容,放在这里备份一份,随用随查.尽量做到不用查,就可以随手就可以写出来.这算是Java程序员的基本功吧,一定要扎实. JSTL ...