├── src ├── pages │ ├── blog.ts │ ├── home.ts │ └── about.ts ├── index.html ├── main.spec.ts └── main.ts ├── .gitignore ├── tsconfig.json ├── README.md ├── package.json ├── karma.conf.js ├── webpack.config.js └── tslint.json /src/pages/blog.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 |

This is my blog.

3 | `; 4 | -------------------------------------------------------------------------------- /src/pages/home.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 |

This is home.

3 | `; 4 | -------------------------------------------------------------------------------- /src/pages/about.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 |

This page is about myself.

3 | `; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | src/**/*.js 4 | src/**/*.js.map 5 | typings/ 6 | dist/ 7 | coverage/ 8 | *.log 9 | package-lock.json 10 | *.lock -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | webpack-typescript-starter 4 | 5 | 6 | Home 7 | About 8 | Blog 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main.spec.ts: -------------------------------------------------------------------------------- 1 | import Home from './pages/home'; 2 | 3 | const sum = (n1: number, n2: number) => n1 + n2; 4 | 5 | describe('Application', () => { 6 | it('true should be true', () => { 7 | expect(true).toBe(true); 8 | }); 9 | 10 | it('2+2 should be 4', () => { 11 | expect(sum(2, 2)).toBe(4); 12 | }); 13 | 14 | it('home page should be defined and should be string', () => { 15 | expect(Home).toBeDefined(); 16 | expect(typeof Home).toBe('string'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | async function bootstrap(main: HTMLMainElement) { 2 | const module = await import(`./pages${location.pathname}.ts`); 3 | main.innerHTML = module.default; 4 | } 5 | 6 | document.addEventListener('DOMContentLoaded', async () => { 7 | const main: HTMLMainElement = document.querySelector('main'); 8 | const links = Array.from(document.querySelectorAll('a')); 9 | 10 | await bootstrap(main); 11 | 12 | links.forEach((link) => { 13 | link.addEventListener('click', async (e: MouseEvent) => { 14 | e.preventDefault(); 15 | 16 | const module = await import(`./pages/${link.dataset.chunk}.ts`); 17 | 18 | history.replaceState({}, `${link.dataset.title}`, `/${link.dataset.chunk}`); 19 | main.innerHTML = module.default; 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "sourceMap": true, 5 | "declaration": false, 6 | "moduleResolution": "node", 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "target": "es5", 10 | "module": "esnext", 11 | "typeRoots": [ 12 | "./node_modules/@types" 13 | ], 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "lib": [ 17 | "es2017", 18 | "es2015", 19 | "dom" 20 | ], 21 | "noImplicitAny": true, 22 | "noImplicitThis": true, 23 | "noUnusedParameters": true, 24 | "forceConsistentCasingInFileNames": true, 25 | "allowJs": true, 26 | "strictNullChecks": false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Webpack + TypeScript starter 2 | --- 3 | 4 | ![example](https://juristr.com/blog/assets/imgs/meetup-intro-angular2/transpiling.png) 5 | 6 | Это далеко не самый сложный starter kit. Цель его состоит в том, чтобы получить простейшую возможную настройку для работы с Webpack и TypeScript. Развернув себе этот репозиторий, вы сможете дополнить теми инструментами, которые вам нужны, например, SASS-компиляцию, Gzip, Brotli-компрессию, дополнить тестами свое окружение. 7 | 8 | ## Features 9 | 10 | - [x] Webpack 11 | - [x] TypeScript compilation 12 | - [x] ts-lint 13 | - [x] Webpack Development Server 14 | - [x] Karma and Jasmine test execution 15 | 16 | ## How to use 17 | 18 | Просто склонируйте репозиторий, а дальше: 19 | 20 | ```bash 21 | # Переходим в директорию с проектом 22 | $ cd 23 | 24 | # Удаляем `.git` директорию 25 | 26 | # Установка зависимостей 27 | $ npm i 28 | $ # or 29 | $ yarn 30 | 31 | # Запуск сборки приложения и веб-сервера: 32 | $ npm serve 33 | 34 | # Сборка приложения без минификации: 35 | $ npm run build 36 | 37 | # Сборка приложения с минификацией: 38 | $ npm run build:prod 39 | 40 | # Запуск тестов 41 | $ npm run test 42 | ``` 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-typescript-starter", 3 | "version": "0.0.1", 4 | "description": "A simple Webpack starter with TypeScript transpilation", 5 | "scripts": { 6 | "serve": "webpack-dev-server --progress --color", 7 | "build": "webpack -w", 8 | "build:prod": "cross-env NODE_ENV=production webpack", 9 | "test": "karma start" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "typescript" 14 | ], 15 | "author": "Ivanov Max", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "@types/jasmine": "2.8.6", 19 | "@types/karma": "1.7.3", 20 | "@types/node": "9.6.5", 21 | "cross-env": "5.1.4", 22 | "html-webpack-plugin": "3.2.0", 23 | "jasmine-core": "3.1.0", 24 | "karma": "2.0.2", 25 | "karma-chrome-launcher": "2.2.0", 26 | "karma-cli": "1.0.1", 27 | "karma-coverage": "1.1.2", 28 | "karma-jasmine": "1.1.1", 29 | "karma-sourcemap-loader": "0.3.7", 30 | "karma-webpack": "4.0.0-beta.0", 31 | "source-map-loader": "0.2.3", 32 | "ts-loader": "4.2.0", 33 | "tslint": "5.9.1", 34 | "tslint-loader": "3.6.0", 35 | "typescript": "2.8.1", 36 | "uglifyjs-webpack-plugin": "1.2.4", 37 | "webpack": "4.5.0", 38 | "webpack-cli": "2.0.14", 39 | "webpack-dev-server": "3.1.3", 40 | "webpack-merge": "4.1.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const SourceMapDevToolPlugin = require('webpack').SourceMapDevToolPlugin; 5 | 6 | const testFile = process.argv[4] || '*'; 7 | const testFilesGlob = `./src/**/${testFile}.spec.ts`; 8 | 9 | module.exports = config => { 10 | config.set({ 11 | basePath: __dirname, 12 | 13 | frameworks: ['jasmine'], 14 | 15 | files: [testFilesGlob], 16 | 17 | preprocessors: { 18 | [testFilesGlob]: ['webpack', 'sourcemap'] 19 | }, 20 | 21 | browsers: ['Chrome'], 22 | 23 | webpack: { 24 | devtool: 'inline-source-map', 25 | 26 | mode: 'production', 27 | 28 | resolve: { 29 | extensions: ['.ts'], 30 | 31 | modules: [ 32 | path.join(__dirname, 'src') 33 | ] 34 | }, 35 | 36 | module: { 37 | rules: [{ 38 | test: /\.ts$/, 39 | loader: 'ts-loader' 40 | }] 41 | }, 42 | 43 | plugins: [ 44 | new SourceMapDevToolPlugin({ 45 | test: /\.(ts|js)($|\?)/ 46 | }) 47 | ] 48 | }, 49 | 50 | webpackMiddleware: { 51 | stats: { 52 | color: true, 53 | chunks: true 54 | } 55 | }, 56 | 57 | client: { 58 | captureConsole: false 59 | }, 60 | 61 | logLevel: config.LOG_INFO, 62 | 63 | mime: { 64 | 'text/x-typescript': ['ts'] 65 | }, 66 | 67 | reporters: ['progress', 'coverage'], 68 | 69 | port: 9876, 70 | 71 | colors: true, 72 | 73 | autoWatch: false, 74 | 75 | singleRun: true, 76 | 77 | concurrency: Infinity, 78 | 79 | coverageReporter: { 80 | type: 'html', 81 | dir: 'coverage/' 82 | } 83 | }); 84 | }; 85 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const merge = require('webpack-merge'); 5 | const isProduction = process.env.NODE_ENV == 'production'; 6 | 7 | const modes = { 8 | [true]: 'production', 9 | [false]: 'development' 10 | }; 11 | 12 | const config = { 13 | context: __dirname, 14 | 15 | entry: './src/main.ts', 16 | 17 | output: { 18 | filename: '[name].[hash].js', 19 | chunkFilename: '[name].[chunkhash].chunk.js', 20 | pathinfo: true 21 | }, 22 | 23 | target: 'web', 24 | 25 | mode: modes[isProduction], 26 | 27 | resolve: { 28 | extensions: ['.js', '.ts'] 29 | }, 30 | 31 | module: { 32 | rules: [{ 33 | enforce: 'pre', 34 | test: /\.js$/, 35 | loader: 'source-map-loader' 36 | }, { 37 | enforce: 'pre', 38 | test: /\.ts$/, 39 | exclude: /node_modules/, 40 | loader: 'tslint-loader' 41 | }, { 42 | test: /\.ts$/, 43 | loader: 'ts-loader' 44 | }] 45 | }, 46 | 47 | plugins: [ 48 | new HtmlWebpackPlugin({ 49 | template: './src/index.html' 50 | }) 51 | ] 52 | }; 53 | 54 | if (isProduction) { 55 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 56 | 57 | module.exports = merge(config, { 58 | optimization: { 59 | minimize: true, 60 | 61 | minimizer: [ 62 | new UglifyJsPlugin({ 63 | parallel: require('os').cpus().length, 64 | 65 | uglifyOptions: { 66 | ie8: false, 67 | 68 | output: { 69 | ecma: 8, 70 | beautify: false, 71 | comments: false 72 | } 73 | } 74 | }) 75 | ] 76 | } 77 | }); 78 | } else { 79 | module.exports = merge(config, { 80 | devServer: { 81 | port: 4200, 82 | open: true, 83 | watchContentBase: true, 84 | historyApiFallback: true 85 | } 86 | }); 87 | } 88 | 89 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsRules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "no-duplicate-variable": true, 13 | "no-eval": true, 14 | "no-trailing-whitespace": true, 15 | "no-unsafe-finally": true, 16 | "one-line": [ 17 | true, 18 | "check-open-brace", 19 | "check-whitespace" 20 | ], 21 | "quotemark": [ 22 | true, 23 | "single" 24 | ], 25 | "semicolon": [ 26 | true, 27 | "always" 28 | ], 29 | "triple-equals": [ 30 | true, 31 | "allow-null-check" 32 | ], 33 | "variable-name": [ 34 | true, 35 | "ban-keywords" 36 | ], 37 | "whitespace": [ 38 | true, 39 | "check-branch", 40 | "check-decl", 41 | "check-operator", 42 | "check-separator", 43 | "check-type" 44 | ] 45 | }, 46 | "rules": { 47 | "class-name": true, 48 | "comment-format": [ 49 | true, 50 | "check-space" 51 | ], 52 | "indent": [ 53 | true, 54 | "spaces" 55 | ], 56 | "no-eval": true, 57 | "no-internal-module": true, 58 | "no-trailing-whitespace": true, 59 | "no-unsafe-finally": true, 60 | "no-var-keyword": false, 61 | "one-line": [ 62 | true, 63 | "check-open-brace", 64 | "check-whitespace" 65 | ], 66 | "quotemark": [ 67 | true, 68 | "single" 69 | ], 70 | "semicolon": [ 71 | true, 72 | "always" 73 | ], 74 | "triple-equals": [ 75 | true, 76 | "allow-null-check" 77 | ], 78 | "typedef-whitespace": [ 79 | true, 80 | { 81 | "call-signature": "nospace", 82 | "index-signature": "nospace", 83 | "parameter": "nospace", 84 | "property-declaration": "nospace", 85 | "variable-declaration": "nospace" 86 | } 87 | ], 88 | "variable-name": [ 89 | true, 90 | "ban-keywords" 91 | ], 92 | "whitespace": [ 93 | true, 94 | "check-branch", 95 | "check-decl", 96 | "check-operator", 97 | "check-separator", 98 | "check-type" 99 | ] 100 | } 101 | } --------------------------------------------------------------------------------