├── .npmrc ├── pre-typescript ├── .npmrc ├── src │ ├── User.js │ └── index.js ├── index.html ├── webpack.config.js └── package.json ├── post-typescript ├── .npmrc ├── src │ ├── User.ts │ └── index.js ├── index.html ├── tsconfig.json ├── webpack.config.js └── package.json ├── .gitignore └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | save=true 2 | save-exact=true 3 | -------------------------------------------------------------------------------- /pre-typescript/.npmrc: -------------------------------------------------------------------------------- 1 | save=true 2 | save-exact=true 3 | -------------------------------------------------------------------------------- /post-typescript/.npmrc: -------------------------------------------------------------------------------- 1 | save=true 2 | save-exact=true 3 | progress=false 4 | -------------------------------------------------------------------------------- /pre-typescript/src/User.js: -------------------------------------------------------------------------------- 1 | export class User { 2 | constructor(handle) { 3 | this.handle = handle; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /post-typescript/src/User.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | handle: string; 3 | constructor(handle) { 4 | this.handle = handle; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /post-typescript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pre-typescript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /post-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "outDir": "tsDist", 5 | "module": "es6", 6 | "target": "es6" 7 | }, 8 | "exclude": [ 9 | "node_modules", 10 | "tsDist", 11 | "dist", 12 | "webpack.config.js" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /post-typescript/src/index.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | 4 | import { User } from './User'; 5 | 6 | class MyComponent extends React.Component { 7 | render() { 8 | return
{ this.props.user.handle }
; 9 | } 10 | } 11 | 12 | let user = new User('@clayallsopp'); 13 | 14 | let node = document.getElementById('container'); 15 | ReactDOM.render(, node); 16 | -------------------------------------------------------------------------------- /post-typescript/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: "./tsDist/index.js", 3 | output: { 4 | filename: "out.js", 5 | path: __dirname + "/dist", 6 | }, 7 | module: { 8 | loaders: [ 9 | { 10 | test: /\.jsx?$/, 11 | exclude: /node_modules/, 12 | loader: "babel-loader", 13 | query: { 14 | presets: ['es2015', 'react'] 15 | } 16 | } 17 | ], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /pre-typescript/src/index.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | 4 | import { User } from './User'; 5 | 6 | class MyComponent extends React.Component { 7 | render() { 8 | return
{ this.props.user.handle }
; 9 | } 10 | } 11 | 12 | let user = new User('@clayallsopp'); 13 | 14 | let node = document.getElementById('container'); 15 | ReactDOM.render(, node); 16 | -------------------------------------------------------------------------------- /pre-typescript/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: "./src/index.js", 3 | output: { 4 | filename: "out.js", 5 | path: __dirname + "/dist", 6 | }, 7 | module: { 8 | loaders: [ 9 | { 10 | test: /\.jsx?$/, 11 | exclude: /node_modules/, 12 | loader: "babel-loader", 13 | query: { 14 | presets: ['es2015', 'react'] 15 | } 16 | } 17 | ], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /pre-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pre-typescript", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rm -rf ./dist/ && webpack && open index.html" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "babel-core": "6.4.5", 13 | "babel-loader": "6.2.2", 14 | "babel-preset-es2015": "6.3.13", 15 | "babel-preset-react": "6.3.13", 16 | "react": "0.14.7", 17 | "react-dom": "0.14.7", 18 | "webpack": "1.12.13" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /post-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pre-typescript", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rm -rf ./dist/ && rm -rf ./tsDist/ && tsc && webpack && open index.html" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "babel-core": "6.4.5", 13 | "babel-loader": "6.2.2", 14 | "babel-preset-es2015": "6.3.13", 15 | "babel-preset-react": "6.3.13", 16 | "react": "0.14.7", 17 | "react-dom": "0.14.7", 18 | "typescript": "1.8.0", 19 | "webpack": "1.12.13" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | dist/ 36 | tsDist/ 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Migrating JavaScript to TypeScript 1.8 2 | 3 | Types are a Good Idea. Computers are relentless at finding errors, humans not so much. You can get away without using types, but large projects and teams will run better with them. 4 | 5 | TypeScript is a mature project to bring types to JavaScript. Originally TypeScript was pitched as a distinct language (like CoffeeScript), but it has since committed to being [a superset of standard JavaScript](https://blogs.msdn.microsoft.com/typescript/2014/10/22/typescript-and-the-road-to-2-0/). 6 | 7 | Which sounds great, right? You already know JavaScript, you just need to learn the extra type syntax. But how do you actually start adding TypeScript to an existing (and probably large) JavaScript project? Read on to see how [TypeScript 1.8](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#including-js-files-with---allowjs) adds a key feature that makes it much easier. 8 | 9 | ## How TypeScript Works, for JavaScript Developers 10 | 11 | Coming from the React ecosystem, figuring out how TypeScript integrated into a project was unintuitive. I tried to fit it into my existing model of tooling, but it didn't quite work. Here's how I think of it now: 12 | 13 | > TypeScript is an ES6 transpiler, type-checker, and module transformer. It can be used in place of Babel, Flow, and Browserify. 14 | 15 | Out of the box, TypeScript is more on the monolith end of the spectrum and wants to be most of your toolchain - but you can configure it otherwise, which helps us integrate into an existing project. 16 | 17 | The TypeScript compiler, _tsc_, will process all of the files in a directory and spit out their compiled JavaScript variants to a destination directory (or a single bundle file). It's unlike Webpack and Browserify, which generally take a single file as an argument and emit another file. Like other tools, TypeScript's compiler can be configured via command line arguments or a _tsconfig.json_ file. 18 | 19 | Historically TypeScript only worked with _.ts_ and _.tsx_ files, which made it tough to integrate with _.js_ code. But TypeScript is moving to improve the JavaScript development experience (read up on [#4793](https://github.com/Microsoft/TypeScript/issues/4793) and [#4789](https://github.com/Microsoft/TypeScript/issues/4789)). An early step is for the TypeScript tooling to play nicely with _.js_ files, which is [shipping with TypeScript 1.8](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#including-js-files-with---allowjs). 20 | 21 | 22 | ## Migration Strategy 23 | 24 | Let's say we want to add some TypeScript to an existing React app, and our app is currently built with Webpack and Babel. 25 | 26 | What we're going to do is run all of our project's files through the TypeScript compiler, and then change our Webpack config to read from that compiled output. So instead of Webpacking our JavaScript source code directly, we'll be Webpacking the Typescript output. (wait - is _Webpacking_ a verb?) 27 | 28 | This isn't the only way to migrate your code, the whole subject is rapidly evolving, but it may spark some ideas. Let's walk through the mechanics of how this happens. 29 | 30 | ## Example 31 | 32 | We're only going to TypeScript-ify code unrelated to React. TypeScript is very handy for React component definitions (you get compile-time checks to ensure your _props_ and _state_ are valid), but it adds a bit more tooling that's beyond the scope of this article ([external type definitions](https://github.com/typings/typings)). One of the benefits of moving your app over piecemeal like this is you can avoid adding such tooling complexity until you're ready. 33 | 34 | Here's our pre-existing toy app (you can find the source [on Github](https://github.com/clayallsopp/typescript-1.8-tutorial/tree/master/pre-typescript)): 35 | 36 | ``` 37 | import * as React from 'react'; 38 | import * as ReactDOM from 'react-dom'; 39 | 40 | import { User } from './User'; 41 | 42 | class MyComponent extends React.Component { 43 | render() { 44 | return
{ this.props.user.handle }
; 45 | } 46 | } 47 | 48 | let user = new User('@clayallsopp'); 49 | 50 | let node = document.getElementById('container'); 51 | ReactDOM.render(, node); 52 | ``` 53 | 54 | ``` 55 | export class User { 56 | constructor(handle) { 57 | this.handle = handle; 58 | } 59 | } 60 | ``` 61 | 62 | Our Webpack config: 63 | 64 | ``` 65 | module.exports = { 66 | entry: "./src/index.js", 67 | output: { 68 | filename: "out.js", 69 | path: __dirname + "/dist", 70 | }, 71 | module: { 72 | loaders: [ 73 | { 74 | test: /\.jsx?$/, 75 | exclude: /node_modules/, 76 | loader: "babel-loader", 77 | query: { 78 | presets: ['es2015', 'react'] 79 | } 80 | } 81 | ], 82 | }, 83 | } 84 | ``` 85 | 86 | Finally, our build script looks like: 87 | 88 | ``` 89 | "scripts": { 90 | "build": "rm -rf ./dist/ && webpack && open index.html" 91 | } 92 | ``` 93 | 94 | To add TypeScript, we install it via NPM: 95 | 96 | ``` 97 | $ npm install typescript@1.8.0 --save 98 | ``` 99 | 100 | TypeScript's compiler configuration lives in a file called _tsconfig.json_; we create that and configure it like so: 101 | 102 | ``` 103 | { 104 | "compilerOptions": { 105 | "allowJs": true, 106 | "outDir": "tsDist", 107 | "module": "es6", 108 | "target": "es6" 109 | }, 110 | "exclude": [ 111 | "node_modules", 112 | "tsDist", 113 | "dist", 114 | "webpack.config.js" 115 | ] 116 | } 117 | ``` 118 | 119 | _allowJs_ is the option newly available in 1.8. The TypeScript compiler will run a quick sanity check on _.js_ files for syntax errors but otherwise passes them straight through to the output directory. The "_es6_" options mean that TypeScript will emit ES2015-compatible modules and code, instead of transforming them. 120 | 121 | Note that we specified a "_tsDist_" output folder (_ourDir_). We need to tell Webpack to read from this folder, not the original "_src_" version: 122 | 123 | ``` 124 | module.exports = { 125 | entry: "./tsDist/index.js", 126 | // ... 127 | }; 128 | ``` 129 | 130 | Accordingly, our "_build_" script needs to change: 131 | 132 | ``` 133 | "scripts": { 134 | "build": "rm -rf ./dist/ && rm -rf ./tsDist/ && tsc && webpack && open index.html" 135 | } 136 | ``` 137 | 138 | The folder naming conventions here aren't necessarily a recommendation you should do on your project, it's very much up to you. 139 | 140 | Cool, time to actually write some TypeScript. We begin by renaming our `User` file: 141 | 142 | ``` 143 | $ mv src/User.js src/User.ts 144 | ``` 145 | 146 | When we build, we have a problem: 147 | 148 | ``` 149 | $ npm run build 150 | ... 151 | src/User.ts(3,10): error TS2339: Property 'handle' does not exist on type 'User'. 152 | ``` 153 | 154 | It's alive! One of TypeScript's checks is that all properties of `this` must be declared. We can fix that pretty quickly: 155 | 156 | ``` 157 | export class User { 158 | handle: string; 159 | constructor(handle) { 160 | this.handle = handle; 161 | } 162 | } 163 | ``` 164 | 165 | We can now build safely: 166 | 167 | ``` 168 | $ npm run build 169 | 170 | ... 171 | 172 | Hash: 923de2cd435a90a150ce 173 | Version: webpack 1.12.13 174 | Time: 2260ms 175 | Asset Size Chunks Chunk Names 176 | out.js 679 kB 0 [emitted] main 177 | + 160 hidden modules 178 | ``` 179 | 180 | Congratulations! This is a pretty trivial example, but you can imagine how it would benefit a larger project and as you integrate type definitions for third-party libraries. 181 | 182 | ## Beyond 183 | 184 | Before TypeScript 1.8 it was a pain to migrate a codebase, but now there's a first-class path forward. 185 | 186 | It's a bummer that the JavaScript community doesn't have a consensus on typing tools. The React ecosystem is invested in [Flow](http://flowtype.org), but in my experience the community and usage of TypeScript is much more active. Microsoft is implementing deep IDE support, Angular has adopted TypeScript, and my own Palantir is a heavy user of TypeScript (check out [Plottable](http://plottablejs.org/) and [TSLint](http://palantir.github.io/tslint/)). 187 | 188 | I think the TypeScript team has the right idea to make the JavaScript migration experience better with features like _allowJs_. TypeScript is iterating incredibly fast, and I'm excited to see what happens over the next year. 189 | --------------------------------------------------------------------------------