1"use strict";(self.webpackChunkjohnnyreilly_com=self.webpackChunkjohnnyreilly_com||[]).push([["20648"],{99514(e,n,s){s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>p,frontMatter:()=>i,metadata:()=>t,toc:()=>c});var t=s(28390),o=s(74848),a=s(28453);let i={slug:"using-ts-loader-with-webpack-2",title:"Using ts-loader with webpack 2",authors:"johnnyreilly",date:new Date("2016-12-19T00:00:00.000Z"),tags:["ts-loader","webpack"],hide_table_of_contents:!1,description:"TypeScript loader ts-loader has made its loader compatible with webpack 2. The update allows greater compatibility between the two applications.",atUri:"at://did:plc:yy3apqjlms24kso7ahn7lbmb/site.standard.document/3mr5bsuni3q2o"},r,l={authorsImageUrls:[void 0]},c=[{value:"Porting our example",id:"porting-our-example",level:2},{value:"<code>webpack.config.js</code>",id:"webpackconfigjs",level:2},{value:"<code>plugins</code>",id:"plugins",level:2},{value:"<code>LoaderOptionsPlugin</code> we hardly new ya",id:"loaderoptionsplugin-we-hardly-new-ya",level:2},{value:"<code>karma.conf.js</code>",id:"karmaconfjs",level:2},{value:"Compare and contrast",id:"compare-and-contrast",level:2}];function d(e){let n={a:"a",code:"code",em:"em",h2:"h2",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsxs)(n.p,{children:["Hands up, despite being one of the maintainers of ",(0,o.jsx)(n.a,{href:"https://github.com/TypeStrong/ts-loader",children:"ts-loader"})," (a TypeScript loader for webpack) I have not been tracking webpack v2. My reasons? Well, I'm keen on cutting edge but bleeding edge is often not a ton of fun as dealing with regularly breaking changes is frustrating. I'm generally happy to wait for things to settle down a bit before leaping aboard. However, ",(0,o.jsx)(n.a,{href:"https://github.com/webpack/webpack/releases/tag/v2.2.0-rc.0",children:"webpack 2 RC'd last week"})," and so it's time to take a look!"]}),"\n",(0,o.jsx)(n.h2,{id:"porting-our-example",children:"Porting our example"}),"\n",(0,o.jsxs)(n.p,{children:["Let's take ",(0,o.jsx)(n.a,{href:"https://github.com/TypeStrong/ts-loader/tree/master/examples/webpack1-gulp-react-flux-babel-karma",children:"ts-loader's webpack 1 example"})," and try and port it to webpack 2. Will it work? Probably; I'm aware of other people using ts-loader with webpack 2. It'll be a voyage of discovery. Like Darwin on the Beagle, I shall document our voyage for a couple of reasons:"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"I'm probably going to get some stuff wrong. That's fine; one of the best ways to learn is to make mistakes. So do let me know where I go wrong."}),"\n",(0,o.jsxs)(n.li,{children:["I'm doing this based on what I've read in the new docs; they're very much a work in progress and the mistakes I make here may lead to those docs improving even more. That matters; ",(0,o.jsx)(n.strong,{children:"documentation matters"}),". I'll be leaning heavily on the ",(0,o.jsx)(n.a,{href:"https://webpack.js.org/guides/migrating/",children:"Migrating from v1 to v2"})," guide."]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"So here we go. Our example is one which uses TypeScript for static typing and uses Babel to transpile from ES-super-modern (yes - it's a thing) to ES-older-than-that. Our example also uses React; but that's somewhat incidental. It only uses webpack for typescript / javascript and karma. It uses gulp to perform various other tasks; so if you're reliant on webpack for less / sass compilation etc then I have no idea whether that works."}),"\n",(0,o.jsx)(n.p,{children:"First of all, let's install the latest RC of webpack:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-ts",children:"npm install [email protected] --save-dev\n"})}),"\n",(0,o.jsx)(n.h2,{id:"webpackconfigjs",children:(0,o.jsx)(n.code,{children:"webpack.config.js"})}),"\n",(0,o.jsxs)(n.p,{children:["Let's look at our existing ",(0,o.jsx)(n.code,{children:"webpack.config.js"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-js",children:"'use strict';\n\nvar path = require('path');\n\nmodule.exports = {\n cache: true,\n entry: {\n main: './src/main.tsx',\n vendor: ['babel-polyfill', 'fbemitter', 'flux', 'react', 'react-dom'],\n },\n output: {\n path: path.resolve(__dirname, './dist/scripts'),\n filename: '[name].js',\n chunkFilename: '[chunkhash].js',\n },\n module: {\n loaders: [\n {\n test: /\\.ts(x?)$/,\n exclude: /node_modules/,\n loader:\n 'babel-loader?presets[]=es2016&presets[]=es2015&presets[]=react!ts-loader',\n },\n {\n test: /\\.js$/,\n exclude: /node_modules/,\n loader: 'babel',\n query: {\n presets: ['es2016', 'es2015', 'react'],\n },\n },\n ],\n },\n plugins: [],\n resolve: {\n extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'],\n },\n};\n"})}),"\n",(0,o.jsxs)(n.p,{children:["There's a number of things we need to do here. First of all, we can get rid of the empty extension under resolve; I understand that's unnecessary now. Also, I'm going to get rid of ",(0,o.jsx)(n.code,{children:"'.webpack.js'"})," and ",(0,o.jsx)(n.code,{children:"'.web.js'"}),"; I never used them anyway. Also, just having ",(0,o.jsx)(n.code,{children:"'babel'"})," as a loader won't fly anymore. We need that suffix as well."]}),"\n",(0,o.jsxs)(n.p,{children:["Now I could start renaming ",(0,o.jsx)(n.code,{children:"loaders"})," to ",(0,o.jsx)(n.code,{children:"rules"})," as the terminology is changing. But I'd like to deal with that later since I know the old school names are still supported at present. More interestingly, I seem to remember hearing that one of the super exciting things about webpack is that it supports modules directly now. (I think that's supposed to be good for tree-shaking but I'm not totally certain.)"]}),"\n",(0,o.jsxs)(n.p,{children:["Initially I thought I was supposed to switch to a custom babel preset called ",(0,o.jsx)(n.a,{href:"https://www.npmjs.com/package/babel-preset-es2015-webpack",children:(0,o.jsx)(n.code,{children:"babel-preset-es2015-webpack"})}),'. However it has a big "DEPRECATED" mark at the top and it says I should just use ',(0,o.jsx)(n.code,{children:"babel-preset-es2015"})," (which I already am) with the following option specified:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-js",children:'{\n "presets": [\n [\n "es2015",\n {\n "modules": false\n }\n ]\n ]\n}\n'})}),"\n",(0,o.jsxs)(n.p,{children:["Looking at our existing config you'll note that for ",(0,o.jsx)(n.code,{children:"js"})," files we're using ",(0,o.jsx)(n.code,{children:"query"})," (",(0,o.jsx)(n.code,{children:"options"})," in the new world I understand) to configure babel usage. We're using ",(0,o.jsx)(n.a,{href:"https://webpack.github.io/docs/using-loaders.html#query-parameters",children:"query parameters"})," for ",(0,o.jsx)(n.code,{children:"ts"})," files. I have ",(0,o.jsx)(n.em,{children:"zero"})," idea how to configure preset options using query parameters. Fiddling with ",(0,o.jsx)(n.code,{children:"query"})," / ",(0,o.jsx)(n.code,{children:"options"})," didn't seem to work. So, I've decided to abandon using query entirely and drop in a ",(0,o.jsx)(n.a,{href:"http://babeljs.io/docs/usage/babelrc/",children:(0,o.jsx)(n.code,{children:".babelrc"})})," file using our presets combined with the ",(0,o.jsx)(n.a,{href:"https://babeljs.io/docs/plugins/#plugin-preset-options",children:(0,o.jsx)(n.code,{children:"modules"})})," setting:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-js",children:'{\n "presets": [\n "react",\n [\n "es2015",\n {\n "modules": false\n }\n ],\n "es2016"\n ]\n}\n'})}),"\n",(0,o.jsxs)(n.p,{children:["As an aside; apparently these are applied in reverse order. So ",(0,o.jsx)(n.code,{children:"es2016"})," is applied first, ",(0,o.jsx)(n.code,{children:"es2015"})," second and ",(0,o.jsx)(n.code,{children:"react"})," third. I'm not totally certain this is correct; the ",(0,o.jsx)(n.code,{children:'<a href="http://babeljs.io/docs/usage/babelrc/">.babelrc</a> docs'})," are a little unclear."]}),"\n",(0,o.jsxs)(n.p,{children:["With our query options extracted we're down to a simpler ",(0,o.jsx)(n.code,{children:"webpack.config.js"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-js",children:"'use strict';\n\nvar path = require('path');\n\nmodule.exports = {\n cache: true,\n entry: {\n main: './src/main.tsx',\n vendor: ['babel-polyfill', 'fbemitter', 'flux', 'react', 'react-dom'],\n },\n output: {\n path: path.resolve(__dirname, './dist/scripts'),\n filename: '[name].js',\n chunkFilename: '[chunkhash].js',\n },\n module: {\n loaders: [\n {\n test: /\\.ts(x?)$/,\n exclude: /node_modules/,\n loader: 'babel-loader!ts-loader',\n },\n {\n test: /\\.js$/,\n exclude: /node_modules/,\n loader: 'babel-loader',\n },\n ],\n },\n plugins: [],\n resolve: {\n extensions: ['.ts', '.tsx', '.js'],\n },\n};\n"})}),"\n",(0,o.jsx)(n.h2,{id:"plugins",children:(0,o.jsx)(n.code,{children:"plugins"})}),"\n",(0,o.jsxs)(n.p,{children:["In our example the ",(0,o.jsx)(n.code,{children:"plugins"})," section of our ",(0,o.jsx)(n.code,{children:"webpack.config.js"})," is extended in a separate process. Whilst we're developing we also set the ",(0,o.jsx)(n.code,{children:"debug"})," flag to be ",(0,o.jsx)(n.code,{children:"true"}),". ",(0,o.jsxs)(n.a,{href:"https://webpack.js.org/guides/migrating/#debug",children:["It seems we need to introduce a ",(0,o.jsx)(n.code,{children:"LoaderOptionsPlugin"})," to do this for us."]})]}),"\n",(0,o.jsxs)(n.p,{children:["As we introduce our ",(0,o.jsx)(n.code,{children:"LoaderOptionsPlugin"})," we also need to make sure that we provide it with ",(0,o.jsx)(n.code,{children:"options"}),". How do I know this? Well ",(0,o.jsx)(n.a,{href:"https://github.com/TypeStrong/ts-loader/issues/283",children:"someone raised an issue against ts-loader"}),". I don't think this is actually an issue with ts-loader; I think it's just a webpack 2 thing. I could be wrong; answers on a postcard please."]}),"\n",(0,o.jsxs)(n.p,{children:["Either way, to get up and running we just need the ",(0,o.jsx)(n.code,{children:"LoaderOptionsPlugin"})," in play. Consequently, most of what follows in our ",(0,o.jsx)(n.code,{children:"webpack.js"})," file is unchanged:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-js",children:"// .....\n\nvar webpackConfig = require('../webpack.config.js');\nvar packageJson = require('../package.json');\n\n// .....\n\nfunction buildProduction(done) {\n // .....\n\n myProdConfig.plugins = myProdConfig.plugins.concat(\n // .....\n\n // new webpack.optimize.DedupePlugin(), Not a thing anymore apparently\n new webpack.optimize.UglifyJsPlugin(),\n\n // I understand this here matters...\n // but it doesn't seem to make any difference; perhaps I'm missing something?\n new webpack.LoaderOptionsPlugin({\n minimize: true,\n debug: false,\n }),\n\n failPlugin,\n );\n\n // .....\n}\n\nfunction createDevCompiler() {\n var myDevConfig = webpackConfig;\n myDevConfig.devtool = 'inline-source-map';\n // myDevConfig.debug = true; - not allowed in webpack 2\n\n myDevConfig.plugins = myDevConfig.plugins.concat(\n new webpack.optimize.CommonsChunkPlugin({\n name: 'vendor',\n filename: 'vendor.js',\n }),\n new WebpackNotifierPlugin({\n title: 'webpack build',\n excludeWarnings: true,\n }),\n\n // this is the webpack 2 hotness!\n new webpack.LoaderOptionsPlugin({\n debug: true,\n options: myDevConfig,\n }),\n // it ends here - there wasn't much really....\n );\n\n // create a single instance of the compiler to allow caching\n return webpack(myDevConfig);\n}\n\n// .....\n"})}),"\n",(0,o.jsxs)(n.h2,{id:"loaderoptionsplugin-we-hardly-new-ya",children:[(0,o.jsx)(n.code,{children:"LoaderOptionsPlugin"})," we hardly new ya"]}),"\n",(0,o.jsxs)(n.p,{children:["After a little more experimentation it seems that the ",(0,o.jsx)(n.code,{children:"LoaderOptionsPlugin"})," is not necessary at all for our own use case. In fact it's probably not best practice to get used to using it as it's only intended to live a short while whilst people move from webpack 1 to webpack 2. In that vein let'
1s tweak our ",(0,o.jsx)(n.code,{children:"webpack.js"})," file once more:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-js",children:"function buildProduction(done) {\n // .....\n\n myProdConfig.plugins = myProdConfig.plugins.concat(\n // .....\n\n new webpack.optimize.UglifyJsPlugin({\n compress: {\n warnings: true,\n },\n }),\n\n failPlugin,\n );\n\n // .....\n}\n\nfunction createDevCompiler() {\n var myDevConfig = webpackConfig;\n myDevConfig.devtool = 'inline-source-map';\n\n myDevConfig.plugins = myDevConfig.plugins.concat(\n new webpack.optimize.CommonsChunkPlugin({\n name: 'vendor',\n filename: 'vendor.js',\n }),\n new WebpackNotifierPlugin({\n title: 'webpack build',\n excludeWarnings: true,\n }),\n );\n\n // create a single instance of the compiler to allow caching\n return webpack(myDevConfig);\n}\n\n// .....\n"})}),"\n",(0,o.jsx)(n.h2,{id:"karmaconfjs",children:(0,o.jsx)(n.code,{children:"karma.conf.js"})}),"\n",(0,o.jsxs)(n.p,{children:["Finally Karma. Our ",(0,o.jsx)(n.code,{children:"karma.conf.js"})," with webpack 1 looked like this:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-js",children:"/* eslint-disable no-var, strict */\n'use strict';\n\nvar webpackConfig = require('./webpack.config.js');\n\nmodule.exports = function (config) {\n // Documentation: https://karma-runner.github.io/0.13/config/configuration-file.html\n config.set({\n browsers: ['PhantomJS'],\n\n files: [\n // This ensures we have the es6 shims in place and then loads all the tests\n 'test/main.js',\n ],\n\n port: 9876,\n\n frameworks: ['jasmine'],\n\n logLevel: config.LOG_INFO, //config.LOG_DEBUG\n\n preprocessors: {\n 'test/main.js': ['webpack', 'sourcemap'],\n },\n\n webpack: {\n devtool: 'inline-source-map',\n debug: true,\n module: webpackConfig.module,\n resolve: webpackConfig.resolve,\n },\n\n webpackMiddleware: {\n quiet: true,\n stats: {\n colors: true,\n },\n },\n\n // reporter options\n mochaReporter: {\n colors: {\n success: 'bgGreen',\n info: 'cyan',\n warning: 'bgBlue',\n error: 'bgRed',\n },\n },\n });\n};\n"})}),"\n",(0,o.jsxs)(n.p,{children:["We just need to chop out the ",(0,o.jsx)(n.code,{children:"debug"})," statement from the ",(0,o.jsx)(n.code,{children:"webpack"})," section like so:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-js",children:"module.exports = function(config) {\n\n // .....\n\n webpack: {\n devtool: 'inline-source-map',\n module: webpackConfig.module,\n resolve: webpackConfig.resolve\n },\n\n // .....\n\n });\n};\n"})}),"\n",(0,o.jsx)(n.h2,{id:"compare-and-contrast",children:"Compare and contrast"}),"\n",(0,o.jsxs)(n.p,{children:["We now have a repo that works with webpack 2 rc 1. Yay! If you'd like to see it then take a look ",(0,o.jsx)(n.a,{href:"https://github.com/TypeStrong/ts-loader/tree/master/examples/webpack2-gulp-react-flux-babel-karma",children:"here"}),"."]}),"\n",(0,o.jsx)(n.p,{children:"I thought I'd compare performance / output size of compiling with webpack 1 to webpack 2. First of all in debug / development mode:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-ts",children:"// webpack 1\n\nVersion: webpack 1.14.0\nTime: 5063ms\n Asset Size Chunks Chunk Names\n main.js 37.2 kB 0 [emitted] main\nvendor.js 2.65 MB 1 [emitted] vendor\n\n// webpack 2\n\nVersion: webpack 2.2.0-rc.1\nTime: 5820ms\n Asset Size Chunks Chunk Names\n main.js 38.7 kB 0 [emitted] main\nvendor.js 2.63 MB 1 [emitted] [big] vendor\n"})}),"\n",(0,o.jsx)(n.p,{children:"Size and compilation time is not massively different from webpack 1 to webpack 2. It's all about the same. I'm not sure if that's to be expected or not.... Though I've a feeling in production mode I'm supposed to feel the benefits of tree shaking so let's have a go:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-ts",children:"// webpack 1\n\nVersion: webpack 1.14.0\nTime: 5788ms\n Asset Size Chunks Chunk Names\n main.269c66e1bc13b7426cee.js 10.5 kB 0 [emitted] main\nvendor.269c66e1bc13b7426cee.js 231 kB 1 [emitted] vendor\n\n// webpack 2\n\nVersion: webpack 2.2.0-rc.1\nTime: 5659ms\n Asset Size Chunks Chunk Names\n main.33e0d70eeec29206e9b6.js 9.22 kB 0 [emitted] main\nvendor.33e0d70eeec29206e9b6.js 233
1kB 1 [emitted] vendor\n"})}),"\n",(0,o.jsx)(n.p,{children:"To my surprise this looks pretty much unchanged before and after as well. This may be a sign I have missed something crucial out. Or maybe that's to be expected. Do give me a heads up if I've missed something..."})]})}function p(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>i,x:()=>r});var t=s(96540);let o={},a=t.createContext(o);function i(e){let n=t.useContext(a);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),t.createElement(a.Provider,{value:n},e.children)}},28390(e){e.exports=JSON.parse('{"permalink":"/using-ts-loader-with-webpack-2","editUrl":"https://github.com/johnnyreilly/blog.johnnyreilly.com/edit/main/blog-website/blog/2016-12-19-using-ts-loader-with-webpack-2/index.md","source":"@site/blog/2016-12-19-using-ts-loader-with-webpack-2/index.md","title":"Using ts-loader with webpack 2","description":"TypeScript loader ts-loader has made its loader compatible with webpack 2. The update allows greater compatibility between the two applications.","date":"2016-12-19T00:00:00.000Z","tags":[{"inline":false,"label":"ts-loader","permalink":"/tags/ts-loader","description":"The TypeScript loader for webpack."},{"inline":false,"label":"webpack","permalink":"/tags/webpack","description":"The webpack module bundler."}],"readingTime":7.83,"hasTruncateMarker":true,"authors":[{"name":"John Reilly","title":"OSS Engineer - TypeScript, Azure, React, Node.js, .NET","url":"https://johnnyreilly.com/about","imageURL":"https://johnnyreilly.com/img/profile-2025.jpg","key":"johnnyreilly","page":null}],"frontMatter":{"slug":"using-ts-loader-with-webpack-2","title":"Using ts-loader with webpack 2","authors":"johnnyreilly","date":"2016-12-19T00:00:00.000Z","tags":["ts-loader","webpack"],"hide_table_of_contents":false,"description":"TypeScript loader ts-loader has made its loader compatible with webpack 2. The update allows greater compatibility between the two applications.","atUri":"at://did:plc:yy3apqjlms24kso7ahn7lbmb/site.standard.document/3mr5bsuni3q2o"},"unlisted":false,"prevItem":{"title":"webpack: configuring a loader with query / options","permalink":"/webpack-configuring-loader-with-query"},"nextItem":{"title":"webpack: syncing the enhanced-resolve","permalink":"/webpack-syncing-enhanced-resolve"}}')}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.