{"id":7098,"date":"2019-05-29T21:43:55","date_gmt":"2019-05-29T21:43:55","guid":{"rendered":"http:\/\/putridparrot.com\/blog\/?p=7098"},"modified":"2019-05-29T21:43:55","modified_gmt":"2019-05-29T21:43:55","slug":"zero-to-web-with-typescript-webpack-and-more","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/zero-to-web-with-typescript-webpack-and-more\/","title":{"rendered":"Zero to web with TypeScript, webpack and more"},"content":{"rendered":"<p>This post is a little long but the aim is to cover creating a simple TypeScript\/HTML website without using any frameworks but using a whole bunch of standard tools. <\/p>\n<p>The website will be pathetically unimpressive because I want to solely concentrate on just getting the various technologies working together.<\/p>\n<p>We&#8217;ll be using Visual Code as the editor. We&#8217;ll be using TypeScript, ESLint and Jest (as per previous posts on this topics) and we&#8217;ll be using webpack to both create a distribution of our code and to host our code.<\/p>\n<p><strong>Creating the basics<\/strong><\/p>\n<ul>\n<li>Create the project&#8217;s folder, mine&#8217;s zerotoweb<\/li>\n<li>Open Visual code against the new folder<\/li>\n<li>Use the key combination (on mine it&#8217;s CTRL + &#8216; on Windows) to open the terminal within Visual Code or if you prefer open a command prompt and navigate to the the folder you created<\/li>\n<li>run <em>yarn init &#8211;yes<\/em> within the terminal. This will create the package.json file<\/li>\n<li>run <em>tsc &#8211;init<\/em> within the terminal to create the tsconfig.json file<\/li>\n<li>Change tsconfig.json to the following contents\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n{\r\n  &quot;compilerOptions&quot;: {\r\n    &quot;target&quot;: &quot;es6&quot;,\r\n    &quot;outDir&quot;: &quot;.\/public&quot;,\r\n    &quot;rootDir&quot;: &quot;.\/src&quot;,\r\n    &quot;allowJs&quot;: true,\r\n    &quot;skipLibCheck&quot;: true,\r\n    &quot;allowSyntheticDefaultImports&quot;: true,\r\n    &quot;strict&quot;: true,\r\n    &quot;forceConsistentCasingInFileNames&quot;: true,\r\n    &quot;module&quot;: &quot;esnext&quot;,\r\n    &quot;moduleResolution&quot;: &quot;node&quot;,\r\n    &quot;resolveJsonModule&quot;: true,\r\n    &quot;noImplicitAny&quot;: false\r\n  },\r\n  &quot;include&quot;: &#x5B;\r\n    &quot;src&quot;\r\n  ],\r\n  &quot;exclude&quot;: &#x5B;\r\n    &quot;node_modules&quot;\r\n  ]\r\n}\r\n<\/pre>\n<\/li>\n<li>Next create the <em>src<\/em> folder off of the root<\/li>\n<li>Add a file named <em>index.ts<\/em> to <em>src<\/em> folder and add the following code\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nclass App {\r\n    getSalutation() {\r\n        return &quot;Hello World&quot;\r\n    } \r\n}\r\n\r\nlet app = new App();\r\nconsole.log(app.getSalutation());\r\n<\/pre>\n<\/li>\n<li>We&#8217;ll want to run from node, which currently doesn&#8217;t support es6 so we&#8217;ll need to add babel, so run\n<ul>\n<li>yarn add babel-cli -D<\/li>\n<li>yarn add babel-preset-es2015 -D<\/li>\n<li>yarn add babel-preset-env -D<\/li>\n<\/ul>\n<\/li>\n<li>Let&#8217;s now add a couple of scripts to package.json\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&quot;scripts&quot;: \r\n{\r\n   &quot;build&quot;: &quot;tsc --watch&quot;, \r\n   &quot;start&quot;: &quot;babel-node --presets es2015 .\/public\/index.js&quot;\r\n}\r\n<\/pre>\n<\/li>\n<li>Now we need to add a configuration file for babel, so add a .babelrc file to the root folder and put the following within it\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n{\r\n   &quot;presets&quot;: &#x5B;&quot;env&quot;]\r\n}\r\n<\/pre>\n<\/li>\n<li>Next we want to add ESLint (this section is a duplication of the previous post but is here for completeness), so run the following\n<ul>\n<li>yarn add eslint -D<\/li>\n<li>yarn add @typescript-eslint\/parser -D<\/li>\n<li>yarn add @typescript-eslint\/eslint-plugin -D<\/li>\n<li>Add a .eslintrc.js file to the root folder, place the following into the file\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nmodule.exports = {\r\n    &quot;parser&quot;: '@typescript-eslint\/parser',\r\n    &quot;plugins&quot;: &#x5B;'@typescript-eslint'],\r\n    &quot;extends&quot;: &#x5B;'plugin:@typescript-eslint\/recommended'],\r\n    &quot;rules&quot;: {\r\n        &quot;@typescript-eslint\/no-parameter-properties&quot;: &quot;off&quot;,\r\n        &quot;@typescript-eslint\/no-explicit-any&quot;: &quot;off&quot;\r\n    }\r\n};\r\n<\/pre>\n<\/li>\n<li>Now add the following <em>&#8220;lint&#8221;: &#8220;eslint .\/src\/*.ts&#8221;<\/em> to the scripts section of the package.json<\/li>\n<\/ul>\n<\/li>\n<li>Now run <em>yarn build<\/em><\/li>\n<li>Lets see some output, so run <em>yarn start<\/em>. If all went well you should see <em>Hello World<\/em> output.<\/li>\n<\/ul>\n<p><strong>Adding tests<\/strong><\/p>\n<p>I&#8217;ve covered installing Jest for testing React applications previously but let&#8217;s see the steps to take to get everything installed for our non-React world<\/p>\n<ul>\n<li>yarn add jest -D<\/li>\n<li>yarn add @types\/jest -D<\/li>\n<li>yarn add ts-jest -D<\/li>\n<li>yarn add @types\/node -D (not sure about this one)<\/li>\n<li>Add __tests__ under the src folder<\/li>\n<li>Add TypeScript tests to the __tests__ folder, convention suggests {name}.test.ts for the filenames<\/li>\n<li>Add &#8220;test&#8221;: &#8220;jest&#8221; to the scripts section of packages.json if you want to run the tests yourself (i.e. no plugin or watch)<\/li>\n<li>Finally in the root folder add the file jest.config.js with the following code\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nmodule.exports = {\r\n    roots: &#x5B;'.\/src'],\r\n    transform: {\r\n      '^.+\\\\.tsx?$': 'ts-jest',\r\n    },\r\n    testRegex: '(\/__tests__\/.*|(\\\\.|\/)(test|spec))\\\\.tsx?$',\r\n    moduleFileExtensions: &#x5B;'ts', 'tsx', 'js', 'jsx', 'json', 'node'],\r\n  }\r\n<\/pre>\n<\/li>\n<\/ul>\n<p>In Visual Code the plugin for Jest (Use Facebok&#8217;s Jest With Pleasure) from Orta is very useful to have.<\/p>\n<p><strong>Test coverage<\/strong><\/p>\n<p>Jest includes an option for running code coverage, simply change your packages.json script to<\/p>\n<ul>\n<li>&#8220;test&#8221;: &#8220;jest &#8211;coverage&#8221;<\/li>\n<\/ul>\n<p><strong>Adding webpack<\/strong><\/p>\n<p>Next up we&#8217;re going to create our basic web site, extending on what we&#8217;ve already covered.<\/p>\n<ul>\n<li>Create folder name <em>public<\/em> off of the root folder<\/li>\n<li>Add a file named index.html to public folder, here&#8217;s the HTML\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;html&gt;    \r\n    &lt;body&gt;\r\n        &lt;script src=&quot;.\/index.js&quot;&gt;&lt;\/script&gt;\r\n    &lt;\/body&gt;\r\n&lt;\/html&gt;\r\n<\/pre>\n<\/li>\n<li>Run the following commands\n<ul>\n<li>yarn add webpack -D<\/li>\n<li>yarn add webpack-cli -D<\/li>\n<li>yarn add webpack-dev-server -D<\/li>\n<li>yarn add babel-loader@7 -D <em>(@7 was required for bable-core)<\/em><\/li>\n<li>yarn add babel-core -D<\/li>\n<\/ul>\n<\/li>\n<li>Create a webpack.config.js in the root folder here&#8217;s mine\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nvar path = require(&quot;path&quot;);\r\nmodule.exports = {\r\n   entry: {\r\n     app: &#x5B;&quot;.\/public\/index.js&quot;]\r\n   },\r\n  output: {\r\n    path: path.resolve(__dirname, &quot;build&quot;),\r\n    filename: &quot;bundle.js&quot;\r\n  },\r\n  devServer: {\r\n    port: 9000,\r\n    contentBase: &quot;.\/public&quot;\r\n  },\r\n  module: {\r\n    rules: &#x5B;\r\n      {\r\n        test: \/\\.js$\/,\r\n        exclude: \/(node_modules)\/,\r\n        use: {\r\n          loader: &quot;babel-loader&quot;,\r\n          options: {\r\n            presets: &#x5B;&quot;babel-preset-env&quot;]\r\n          }\r\n        }\r\n      }\r\n    ]\r\n  }\r\n};\r\n<\/pre>\n<\/li>\n<li>Now replace the console.log line within the index.ts file with the following\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\ndocument.body.innerHTML = app.getSalutation();\r\n<\/pre>\n<\/li>\n<li>Replace the previously created &#8220;start&#8221; script with <em>&#8220;start&#8221;: &#8220;webpack-dev-server &#8211;open&#8221;<\/em> in packages.json &#8220;scripts&#8221; section<\/li>\n<li>Let&#8217;s run <em>yarn start<\/em> and if all went well you should see a browser window open and display our HTML page with the JavaScript created from our TypeScript file displayed<\/li>\n<\/ul>\n<p><strong>Time to create bundles<\/strong><\/p>\n<p>We&#8217;re going to use webpack to create some distribution bundles<\/p>\n<ul>\n<li>Run the following commands\n<ul>\n<li>yarn add express -D<\/li>\n<li>yarn add webpack-dev-middleware -D<\/li>\n<li>yarn add html-webpack-plugin -D<\/li>\n<li>yarn add clean-webpack-plugin -D<\/li>\n<\/ul>\n<\/li>\n<li>Change the webpack.config.json to\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst path = require('path');\r\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\r\nconst CleanWebpackPlugin = require('clean-webpack-plugin');\r\n\r\nmodule.exports = {\r\n  mode: 'development',\r\n  entry: {\r\n    app: '.\/public\/index.js'\r\n  },\r\n  devtool: 'inline-source-map',\r\n  devServer: {\r\n    port: 9000,\r\n    contentBase: '.\/dist'\r\n  },\r\n  plugins: &#x5B;\r\n    new CleanWebpackPlugin(),\r\n    new HtmlWebpackPlugin({\r\n      title: 'Demo'\r\n    })\r\n  ],\r\n  output: {\r\n    filename: '&#x5B;name].bundle.js',\r\n    path: path.resolve(__dirname, 'dist'),\r\n    publicPath: '\/'\r\n  }\r\n};\r\n<\/pre>\n<\/li>\n<li>Add <em>&#8220;bundle&#8221;: &#8220;webpack&#8221;<\/em> to the scripts section of the packages.json file. This will create a <em>dist<\/em> folder with, both the HTML file and the JS files we&#8217;ve created and generated<\/li>\n<\/ul>\n<p><strong>Minifying<\/strong><\/p>\n<p>You&#8217;ll notice that the JavaScript file generated in the previous steps is not exactly small, containing comments, whitespace etc. So we&#8217;re going to minify it for distribution.<\/p>\n<ul>\n<li>Run <em>yarn add babel-minify-webpack-plugin -D<\/em><\/li>\n<li>Within the <em>webpack.config.js<\/em> file, comment out <em>devtool: &#8216;inline-source-map&#8217;,<\/em><\/li>\n<li>Add this line to the top of the file\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst MinifyPlugin = require(&quot;babel-minify-webpack-plugin&quot;);\r\n<\/pre>\n<\/li>\n<li>Add new <em>MinifyPlugin<\/em> to the file, so it looks like this\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nplugins: &#x5B;\r\n   new CleanWebpackPlugin(),\r\n   new HtmlWebpackPlugin({\r\n      title: 'Demo'\r\n   }),\r\n   new MinifyPlugin()\r\n],\r\n<\/pre>\n<\/li>\n<li>Finally, run our script <em>yarn bundle<\/em><\/li>\n<\/ul>\n<p><strong>Trying an alternate minify<\/strong><\/p>\n<p>Let&#8217;s try terser<\/p>\n<ul>\n<li>Run <em>yarn add terser-webpack-plugin -D<\/em><\/li>\n<li>Add this line to the top of webpack.config.js\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst TerserPlugin = require('terser-webpack-plugin');\r\n<\/pre>\n<\/li>\n<li>Add the following to webpack.config.js\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\noptimization: {\r\n    minimizer: &#x5B;new TerserPlugin({\r\n      extractComments: true,\r\n      test: \/\\.js(\\?.*)?$\/i,\r\n    })],\r\n  },\r\n<\/pre>\n<\/li>\n<\/ul>\n<p>The optimization will need to be run webpack in production mode, i.e. webpack  &#8211;mode=production or webpack-dev-server &#8211;open &#8211;mode=production or in webpack.config.js set mode: &#8216;production&#8217;.<\/p>\n<p>Hence using yarn we can run <em>yarn bundle &#8211;mode=production<\/em> to see the bundle.js has been minified using terse. Obviously you can remove the <em>new MinifyPlugin()<\/em> also at this point if using TerserPlugin.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post is a little long but the aim is to cover creating a simple TypeScript\/HTML website without using any frameworks but using a whole bunch of standard tools. The website will be pathetically unimpressive because I want to solely concentrate on just getting the various technologies working together. We&#8217;ll be using Visual Code as [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[45,46],"tags":[],"class_list":["post-7098","post","type-post","status-publish","format-standard","hentry","category-javascript","category-typescript"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/7098","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=7098"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/7098\/revisions"}],"predecessor-version":[{"id":7121,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/7098\/revisions\/7121"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=7098"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=7098"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=7098"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}