├── .gitattributes ├── .gitignore ├── README.md ├── config ├── helper.js ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js ├── package-lock.json ├── package.json ├── src ├── assets │ └── img │ │ └── vue.png ├── components │ ├── about │ │ ├── About.ts │ │ ├── about.html │ │ └── index.ts │ ├── home │ │ ├── Home.ts │ │ ├── home.html │ │ └── index.ts │ ├── index.ts │ ├── list │ │ ├── List.ts │ │ ├── index.ts │ │ ├── list.html │ │ └── types.ts │ └── navbar │ │ ├── Navbar.ts │ │ ├── index.ts │ │ ├── navbar.html │ │ └── types.ts ├── index.html ├── main.ts ├── polyfills.ts ├── router.ts ├── styles │ └── style.css ├── utils │ ├── index.ts │ └── logger.ts └── vendor.ts ├── tsconfig.json └── tslint.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # JS and TS files must always use LF for tools to work 5 | *.js eol=lf 6 | *.ts eol=lf 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | dist 3 | build 4 | 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | 11 | # Dependency directories 12 | node_modules 13 | jspm_packages 14 | 15 | # Optional npm cache directory 16 | .npm 17 | 18 | # Optional REPL history 19 | .node_repl_history 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-webpack-typescript 2 | Vue.js Webpack Structure for TypeScript Example 3 | > It is a structure that separates templates without using a single file of `*.vue` extension. 4 | 5 | ## Installation 6 | ```js 7 | npm install 8 | npm run dev //= webpack-dev-server 9 | npm start //= npm run build && npm run httpd 10 | ``` 11 | Run [http://localhost:3000](http://localhost:3000) 12 | -------------------------------------------------------------------------------- /config/helper.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.resolve(__dirname, '..'); 4 | 5 | exports.isDev = !!(process.env.NODE_ENV === 'development'); 6 | exports.root = (...args) => path.join(...[rootPath].concat(args)); 7 | -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const HtmlPlugin = require('html-webpack-plugin'); 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | const { isDev, root } = require('./helper'); 4 | 5 | module.exports = { 6 | entry: { 7 | polyfills: './src/polyfills.ts', 8 | vendor: './src/vendor.ts', 9 | app: './src/main.ts' 10 | }, 11 | 12 | resolve: { 13 | extensions: ['.js', '.ts', '.html'], 14 | alias: { 15 | 'vue$': 'vue/dist/vue.esm.js', 16 | '@': root('src') 17 | } 18 | }, 19 | 20 | module: { 21 | rules: [{ 22 | test: /\.ts$/, 23 | enforce: 'pre', 24 | loader: 'tslint-loader', 25 | options: { 26 | configFile: 'tslint.json', 27 | } 28 | }, { 29 | test: /\.ts$/, 30 | loader: 'awesome-typescript-loader', 31 | options: { 32 | configFileName: 'tsconfig.json', 33 | declaration: isDev, 34 | sourceMap: isDev, 35 | inlineSources: isDev 36 | } 37 | }, { 38 | test: /\.html$/, 39 | loader: 'html-loader' 40 | }, { 41 | test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/, 42 | loader: 'file-loader?name=assets/[name].[hash].[ext]' 43 | }, { 44 | test: /\.css$/, 45 | exclude: root('src', 'components'), 46 | use: ExtractTextPlugin.extract({ 47 | fallback: 'style-loader', 48 | use: [{ 49 | loader: 'css-loader', 50 | options: { sourceMap: isDev }, 51 | }] 52 | }) 53 | }, { 54 | test: /\.css$/, 55 | include: root('src', 'components'), 56 | loader: 'raw-loader' 57 | }] 58 | }, 59 | 60 | plugins: [ 61 | new HtmlPlugin({ 62 | template: 'src/index.html' 63 | }) 64 | ] 65 | }; 66 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const webpackMerge = require('webpack-merge'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const commonConfig = require('./webpack.common.js'); 5 | const { root } = require('./helper'); 6 | 7 | module.exports = webpackMerge(commonConfig, { 8 | mode: 'development', 9 | devtool: 'eval-source-map', 10 | 11 | output: { 12 | path: root('dist'), 13 | publicPath: 'http://localhost:3000/', 14 | filename: '[name].js', 15 | chunkFilename: '[id].chunk.js' 16 | }, 17 | 18 | plugins: [ 19 | new ExtractTextPlugin('[name].css'), 20 | new webpack.EnvironmentPlugin({ 21 | NODE_ENV: 'development', 22 | DEBUG: true 23 | }) 24 | ], 25 | 26 | devServer: { 27 | historyApiFallback: true, 28 | stats: 'minimal', 29 | watchOptions: { 30 | aggregateTimeout: 300, 31 | poll: 1000 32 | } 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const webpackMerge = require('webpack-merge'); 3 | const TerserPlugin = require('terser-webpack-plugin'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const commonConfig = require('./webpack.common.js'); 6 | const { root } = require('./helper'); 7 | 8 | const ENV = process.env.NODE_ENV = process.env.ENV = 'production'; 9 | 10 | 11 | module.exports = webpackMerge(commonConfig, { 12 | mode: 'production', 13 | devtool: 'nosources-source-map', 14 | 15 | output: { 16 | path: root('dist'), 17 | publicPath: '', 18 | filename: '[name].[hash].js', 19 | chunkFilename: '[id].[hash].chunk.js' 20 | }, 21 | 22 | optimization: { 23 | minimize: true, 24 | minimizer: [ 25 | new TerserPlugin({ 26 | parallel: true, 27 | terserOptions: { 28 | ecma: 6, 29 | compress: true, 30 | output: { 31 | comments: false, 32 | beautify: false 33 | } 34 | } 35 | }) 36 | ] 37 | }, 38 | 39 | plugins: [ 40 | new webpack.NoEmitOnErrorsPlugin(), 41 | new ExtractTextPlugin('[name].[hash].css'), 42 | new webpack.EnvironmentPlugin({ 43 | NODE_ENV: 'production', 44 | DEBUG: false 45 | }) 46 | ] 47 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-webpack-typescript", 3 | "version": "0.0.1", 4 | "description": "Vue.js Webpack Structure for TypeScript Example", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "lint": "tslint --project tsconfig.json ./src/**/*.ts", 8 | "build": "NODE_ENV=production rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail", 9 | "dev": "NODE_ENV=development webpack-dev-server --config config/webpack.dev.js --inline --progress --profile --port 3000", 10 | "httpd": "lite-server --baseDir=dist", 11 | "prestart": "npm run build", 12 | "start": "npm run httpd" 13 | }, 14 | "dependencies": { 15 | "@mdi/font": "^3.8.95", 16 | "axios": "^0.19.0", 17 | "material-design-icons-iconfont": "^5.0.1", 18 | "vue": "^2.6.10", 19 | "vue-property-decorator": "^8.2.1", 20 | "vue-router": "^3.0.7", 21 | "vuetify": "^2.0.0", 22 | "vuex": "^3.1.1" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^12.6.8", 26 | "awesome-typescript-loader": "^5.2.1", 27 | "css-loader": "^3.1.0", 28 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 29 | "file-loader": "^4.1.0", 30 | "html-loader": "^0.5.5", 31 | "html-webpack-plugin": "^3.2.0", 32 | "json-loader": "^0.5.7", 33 | "lite-server": "^2.5.3", 34 | "raw-loader": "^3.1.0", 35 | "style-loader": "^0.23.1", 36 | "terser-webpack-plugin": "^1.3.0", 37 | "tslint": "^5.18.0", 38 | "tslint-loader": "^3.5.4", 39 | "tslint-microsoft-contrib": "^6.2.0", 40 | "typescript": "^3.5.3", 41 | "webpack": "^4.37.0", 42 | "webpack-cli": "^3.3.6", 43 | "webpack-dev-server": "^3.7.2", 44 | "webpack-merge": "^4.2.1" 45 | }, 46 | "repository": { 47 | "type": "git", 48 | "url": "https://github.com/CatsMiaow/vue-webpack-typescript.git" 49 | }, 50 | "keywords": [ 51 | "TypeScript", 52 | "Vue", 53 | "Vuejs", 54 | "Webpack" 55 | ], 56 | "homepage": "http://tested.co.kr", 57 | "author": "infiltrator@naver.com", 58 | "license": "MIT" 59 | } 60 | -------------------------------------------------------------------------------- /src/assets/img/vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CatsMiaow/vue-webpack-typescript/812b839df2c027cb8c386a961f4c775024c44ba9/src/assets/img/vue.png -------------------------------------------------------------------------------- /src/components/about/About.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Component } from 'vue-property-decorator'; 3 | 4 | import { logger } from '@/utils/logger'; 5 | 6 | /** 7 | * AboutComponent 8 | */ 9 | @Component({ 10 | template: require('./about.html') 11 | }) 12 | export class About extends Vue { 13 | public created(): void { 14 | logger.info('About created'); 15 | this.$emit('loading'); 16 | } 17 | 18 | public mounted(): void { 19 | logger.info('About mounted'); 20 | this.$nextTick(() => this.$emit('ready')); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/about/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | About 8 | Blablablabla... 9 | 10 | 11 | List arrow_forward 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/about/index.ts: -------------------------------------------------------------------------------- 1 | export * from './About'; 2 | -------------------------------------------------------------------------------- /src/components/home/Home.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Component } from 'vue-property-decorator'; 3 | 4 | import { logger } from '@/utils/logger'; 5 | 6 | /** 7 | * HomeComponent 8 | */ 9 | @Component({ 10 | template: require('./home.html') 11 | }) 12 | export class Home extends Vue { 13 | public created(): void { 14 | logger.info('Home created'); 15 | this.$emit('loading'); 16 | } 17 | 18 | public mounted(): void { 19 | logger.info('Home mounted'); 20 | this.$nextTick(() => this.$emit('ready')); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/home/home.html: -------------------------------------------------------------------------------- 1 | 2 | Home 3 | Blablablabla... 4 | 5 | 6 | About arrow_forward 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/home/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Home'; 2 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './about'; 2 | export * from './home'; 3 | export * from './list'; 4 | export * from './navbar'; 5 | -------------------------------------------------------------------------------- /src/components/list/List.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Component } from 'vue-property-decorator'; 3 | 4 | import Axios, { AxiosInstance, AxiosResponse } from 'axios'; 5 | 6 | import { IUser } from '@/components/list/types.ts'; 7 | import { logger } from '@/utils/logger'; 8 | 9 | // https://router.vuejs.org/guide/advanced/data-fetching.html 10 | // https://github.com/vuejs/vue-class-component#adding-custom-hooks 11 | 12 | /** 13 | * HomeComponent 14 | */ 15 | @Component({ 16 | template: require('./list.html') 17 | }) 18 | export class List extends Vue { 19 | public items: IUser[] = []; 20 | private readonly url: string = 'https://jsonplaceholder.typicode.com/users'; 21 | private readonly axios: AxiosInstance; 22 | 23 | constructor() { 24 | super(); 25 | this.axios = Axios; 26 | } 27 | 28 | public async created(): Promise { 29 | logger.info('List created'); 30 | this.$emit('loading'); 31 | await this.fetchData(); 32 | } 33 | 34 | public mounted(): void { 35 | logger.info('List mounted'); 36 | this.$nextTick(() => this.$emit('ready')); 37 | } 38 | 39 | private async fetchData(): Promise { 40 | if (this.items.length) { 41 | return; 42 | } 43 | 44 | try { 45 | const response: AxiosResponse = await this.axios.get(this.url); 46 | this.items = response.data; 47 | } catch (err) { 48 | logger.error(err); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/list/index.ts: -------------------------------------------------------------------------------- 1 | export * from './List'; 2 | -------------------------------------------------------------------------------- /src/components/list/list.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | Id 7 | Name 8 | Username 9 | Email 10 | Address 11 | Phone 12 | Website 13 | Company 14 | 15 | 16 | 17 | 18 | {{item.id}} 19 | {{item.name}} 20 | {{item.username}} 21 | {{item.email}} 22 | {{item.address.street}}, {{item.address.suite}}, {{item.address.city}} 23 | {{item.phone}} 24 | {{item.website}} 25 | {{item.company.name}} 26 | 27 | 28 | 29 | 30 | 31 | 32 | arrow_back Home 33 | 34 | 35 | 36 |
37 | -------------------------------------------------------------------------------- /src/components/list/types.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | id: string; 3 | name: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/navbar/Navbar.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Component } from 'vue-property-decorator'; 3 | import { Route } from 'vue-router'; 4 | 5 | import { ILink } from '@/components/navbar/types.ts'; 6 | import { logger } from '@/utils/logger'; 7 | 8 | /** 9 | * NavbarComponent 10 | */ 11 | @Component({ 12 | template: require('./navbar.html'), 13 | watch: { 14 | $route(current: Route, previous: Route): void { 15 | logger.info(`Changed current path to: ${this.$route.path}`); 16 | } 17 | } 18 | }) 19 | export class Navbar extends Vue { 20 | public links: ILink[] = [ 21 | { name: 'Home', path: '/' }, 22 | { name: 'About', path: '/about' }, 23 | { name: 'List', path: '/list' } 24 | ]; 25 | 26 | public mounted(): void { 27 | this.$nextTick(() => logger.info('Navbar mounted')); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/navbar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Navbar'; 2 | -------------------------------------------------------------------------------- /src/components/navbar/navbar.html: -------------------------------------------------------------------------------- 1 | 2 | Vue-Webpack-TypeScript 3 | 4 | 5 | 6 | 7 | {{link.name}} 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/navbar/types.ts: -------------------------------------------------------------------------------- 1 | export interface ILink { 2 | name: string; 3 | path: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vue.js Webpack for TypeScript 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Footer 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuetify from 'vuetify'; 3 | 4 | import { Navbar } from '@/components/navbar'; 5 | import { router } from '@/router'; 6 | import { logger } from '@/utils'; 7 | 8 | Vue.use(Vuetify); 9 | 10 | // tslint:disable-next-line: no-unused-expression 11 | new Vue({ 12 | el: '#app-main', 13 | // https://github.com/vuetifyjs/vuetify/issues/6895#issuecomment-510640903 14 | vuetify: new Vuetify(), 15 | router, 16 | data: { loading: true }, 17 | components: { 18 | navbar: Navbar 19 | }, 20 | created(): void { 21 | logger.info('Created'); 22 | }, 23 | mounted(): void { 24 | logger.info('Mounted'); 25 | }, 26 | methods: {} 27 | }); 28 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills 2 | 3 | // tslint:disable-next-line: no-console 4 | console.log('polyfills'); 5 | -------------------------------------------------------------------------------- /src/router.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | 4 | import * as component from '@/components'; 5 | 6 | Vue.use(VueRouter); 7 | 8 | export const router: VueRouter = new VueRouter({ 9 | routes: [ 10 | { path: '/', component: component.Home }, 11 | { path: '/about', component: component.About }, 12 | { path: '/list', component: component.List } 13 | ] 14 | }); 15 | -------------------------------------------------------------------------------- /src/styles/style.css: -------------------------------------------------------------------------------- 1 | /*#region Vuetify*/ 2 | .theme--light .list, 3 | .theme--light .card, 4 | .theme--light .table, 5 | .theme--light .input-group.input-group--solo { 6 | background-color: #FAFAFA; 7 | } 8 | /*#endregion Vuetify*/ 9 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './logger'; 2 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Logs 3 | */ 4 | export class Logger { 5 | // tslint:disable:no-console 6 | public info(message: string): void { 7 | console.info(message); 8 | } 9 | 10 | public warn(message: string): void { 11 | console.warn(message); 12 | } 13 | 14 | public error(message: string): void { 15 | console.error(message); 16 | } 17 | // tslint:enable:no-console 18 | } 19 | 20 | export const logger: Logger = new Logger(); 21 | -------------------------------------------------------------------------------- /src/vendor.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-import-side-effect 2 | 3 | import '@mdi/font/css/materialdesignicons.css'; 4 | import 'material-design-icons-iconfont/dist/material-design-icons.css'; 5 | import 'vuetify/dist/vuetify.min.css'; 6 | 7 | import '@/styles/style.css'; 8 | 9 | // tslint:enable:no-import-side-effect 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "*": ["typings/*"], 6 | "@/*": ["src/*"] 7 | }, 8 | "target": "es5", 9 | "outDir": "dist", 10 | "module": "es2015", 11 | "moduleResolution": "node", 12 | "declaration": true, 13 | "strict": true, // noImplicitAny, noImplicitThis, alwaysStrict, strictBindCallApply, strictNullChecks, strictFunctionTypes, strictPropertyInitialization 14 | "noImplicitReturns": true, 15 | "noUnusedLocals": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "removeComments": true, 18 | "sourceMap": true, 19 | "inlineSources": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "experimentalDecorators": true, 22 | "emitDecoratorMetadata": true 23 | }, 24 | "include": [ 25 | "src/**/*", 26 | "typings/*.d.ts" 27 | ], 28 | "exclude": [ 29 | "node_modules" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-microsoft-contrib", 3 | "rules": { 4 | "no-parameter-properties": false, 5 | "no-unsafe-any": false, 6 | "no-void-expression": [true, "ignore-arrow-function-shorthand"], 7 | 8 | // import { ... } from '@/...'; 9 | "no-implicit-dependencies": [true, ["@"]], 10 | "no-submodule-imports": false, 11 | 12 | // template: require('./template.html') 13 | "no-require-imports": false, 14 | 15 | // watch: $route() 16 | "function-name": false, 17 | 18 | "import-name": false, 19 | "strict-boolean-expressions": false 20 | } 21 | } 22 | --------------------------------------------------------------------------------