diff --git a/.gitignore b/.gitignore index 8b0ef75..3808901 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ dist public npm-debug.log mywebsite -stats.html \ No newline at end of file +tls.key +tls.crt \ No newline at end of file diff --git a/config.template.json b/config.template.json index a987565..c8e9e20 100644 --- a/config.template.json +++ b/config.template.json @@ -1,12 +1,15 @@ { - "database": { + "Database": { "url": "", "database": "", "username": "", "password": "" }, - "api": { + "Api": { "key": "" }, - "port": 8080 + "Port": 8080, + "TLSPort": 443, + "TLSCertFile": "", + "TLSKeyFile": "" } diff --git a/metadata.js b/metadata.js index 476a068..71e8eec 100644 --- a/metadata.js +++ b/metadata.js @@ -12,6 +12,8 @@ import ncp from 'ncp'; import marked from 'marked'; import highlight from 'highlight.js'; +const debug = process.env.NODE_ENV !== 'production'; + marked.setOptions({ header: true, highlight: (code) => { @@ -32,10 +34,9 @@ function parse_dir(dir, folder_name = null){ for(let post of posts){ const stats = fs.statSync(dir + post); - if(stats.isDirectory()){ parse_dir(dir + post + '/', post); - } else if(folder_name !== null){ + } else if(folder_name !== null && dir !== './posts/extras/'){ const file = fs.readFileSync(dir+post, 'utf8'); const tokens = marked.lexer(file, null); const temp = { @@ -59,8 +60,10 @@ json.posts.sort((a, b) => { return new Date(b.date) - new Date(a.date); }); +const prettyJson = debug ? JSON.stringify(json, null, 4) : JSON.stringify(json, null, null); + //output to public path -fs.writeFile('./public/metadata.json', JSON.stringify(json,null,4), (err) => { +fs.writeFile('./public/metadata.json', prettyJson, (err) => { if (err) throw err; console.log("Saved metadata.json"); }) diff --git a/package.json b/package.json index 5245dc6..94c3a29 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,12 @@ "main": "index.js", "scripts": { "analyze": "webpack --json | webpack-bundle-size-analyzer", - "build": "NODE_ENV=production webpack -p --progress --colors && babel-node metadata.js", + "build": "NODE_ENV=production webpack -p --progress --colors && NODE_ENV=production babel-node metadata.js", "c9": "webpack-dev-server --port $PORT --host $IP --hot --content-base dist --history-api-fallback", "check-gzip-size": "gzip -9 -c ./public/client.min.js | wc -c | numfmt --to=iec-i --suffix=B --padding=10", "deploy": "npm run get_dependencies && npm run prod && ./mywebsite", "dev": "webpack-dev-server --content-base public --inline --hot --history-api-fallback", + "generate-tls": "sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./tls.key -out ./tls.crt", "get_dependencies": "go get ./server && npm install", "prod": "npm run build && go build ./server/mywebsite.go", "prod-win": "webpack -p --define process.env.NODE_ENV='\"production\"' --progress --colors && babel-node metadata.js && go build ./server/mywebsite.go", diff --git a/posts/Web Stuff/2016-10-28-webpack-build-optimization.md b/posts/Web Stuff/2016-10-28-webpack-build-optimization.md new file mode 100644 index 0000000..841b029 --- /dev/null +++ b/posts/Web Stuff/2016-10-28-webpack-build-optimization.md @@ -0,0 +1,129 @@ +# Building for production with Webpack + +I've been using webpack for almost 6 months now and I still only know a fraction of the tools it provides. Bundling my files for production has been a bit of an annoyance, but I'm at a point where everything works. View my current webpack configuration file at the bottom of the post. + +## Production vs. Development + +So the goal is to have two different build options for webpack: one for production and one for development. The development build needs to include things like source maps and logging while the production build needs JS minification. There are a few different ways to do this. + +### Multiple webpack configuration files + +Some choose to create a `webpack.prod.config.js` file, which could be useful for projects of massive scale. I found this option a bit tedious because I did not want to update two different configuration files when I found a new tool for webpack. + +### NODE_ENV=production webpack -p + +NODE_ENV is an environment variable that can be used within the code itself. I use one `webpack.config.js` file and enable certain plugins based on this condition. The environment variable can be passed to webpack with `NODE= webpack`. On Windows the command translates to the command below. For some reason Windows likes to add the space after production to the variable so that must be removed. There is a tool called [cross-env](https://github.com/kentcdodds/cross-env) that solves this issue, but I have not used it as I primarily develop on a unix system. + +```bash + set NODE_ENV=production&&webpack -p +``` + +I'm not going to discuss the specific plugins I use as they change quite often, but a few of the production plugins are below. More information about these can be found in the webpack documentation. + +```javascript + var debug = process.env.NODE_ENV !== 'production'; + + if (!debug){ + plugins = plugins.concat([ + new webpack.optimize.DedupePlugin(), + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false}) + ])} + } +``` + +This environment variable can be passed to the application this way by using the webpack environment plugin. + +```javascript + new webpack.EnvironmentPlugin([ + "NODE_ENV" + ]) +``` + +`process.env.NODE_ENV` can then be checked for within the application itself. I currently use this to enable Redux logging for development. + +```javascript + const debug = process.env.NODE_ENV !== 'production'; + + //run redux logger if we are in dev mode + const middleware = debug ? applyMiddleware(thunk, logger()) : applyMiddleware(thunk); +``` + +I was originally going to discuss build optimization here, but I think I will save that for another post. My JS bundle was almost 1mb and I got it down to a little less than 400kb. + +## My current webpack configurations + +Here is my webpack.config.js file at the time of writing this post. My current webpack config file can be found here. [webpack.config.js](https://github.com/mgerb/mywebsite/blob/master/webpack.config.js) + +```javascript + var debug = process.env.NODE_ENV !== "production"; + var webpack = require('webpack'); + var path = require('path'); + var HtmlWebpackPlugin = require('html-webpack-plugin'); + var autoprefixer = require('autoprefixer'); + var Visualizer = require('webpack-visualizer-plugin'); + + module.exports = { + devtool: debug ? "inline-sourcemap" : null, + entry: ["babel-polyfill", "./client/js/app.js"], + module: { + loaders: [ + { + test: /\.js?$/, + exclude: /(node_modules)/, + loader: 'babel-loader', + query: { + presets: ['react', 'es2015', 'stage-0'], + plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'], + } + }, + { test: /\.scss$/, loader: "style-loader!css-loader!postcss-loader!sass-loader"}, + { test: /\.css$/, loader: "style-loader!css-loader" }, + { test: /\.png$/, loader: "url-loader?limit=100000&name=images/[hash].[ext]" }, + { test: /\.jpg$/, loader: "url-loader?limit=100000&name=images/[hash].[ext]" }, + { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml&name=images/[hash].[ext]"}, + { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff&name=fonts/[hash].[ext]"}, + { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff&name=fonts/[hash].[ext]"}, + { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream&name=fonts/[hash].[ext]"}, + { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file?name=fonts/[hash].[ext]"} + ] + }, + postcss: function(){ return [autoprefixer]}, + output: { + path: __dirname + "/public/", + publicPath: "/public/", + filename: "client.min.js" + }, + plugins: getPlugins(), + externals:{ + "hljs": "hljs", + "react": "React", + "react-dom": "ReactDOM", + "react-router": "ReactRouter" + } + }; + + function getPlugins(){ + var plugins = [ + new HtmlWebpackPlugin({ + fileName: 'index.html', + template: 'index.html', + inject: 'body', + hash: true + }), + new webpack.EnvironmentPlugin([ + "NODE_ENV" + ]), + new Visualizer({ + filename: "../stats.html" + }) + ]; + if(!debug){ + plugins = plugins.concat([ + new webpack.optimize.DedupePlugin(), + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false}) + ])} + return plugins; + } +``` \ No newline at end of file diff --git a/posts/extras/stats.html b/posts/extras/stats.html new file mode 100644 index 0000000..b95a3d4 --- /dev/null +++ b/posts/extras/stats.html @@ -0,0 +1,189 @@ + + Webpack Visualizer + +
+ + + \ No newline at end of file diff --git a/posts/minimize-js-bundle.md b/posts/minimize-js-bundle.md new file mode 100644 index 0000000..f639ce3 --- /dev/null +++ b/posts/minimize-js-bundle.md @@ -0,0 +1,11 @@ +# Minimizing JS bundle size with Webpack + +I haven't been paying much attention to the bundle size of my JS files until recently I discovered it was almost 1mb (after minification)! Even after gzip the bundle was over 300kb. Although this site doesn't get much traffic this was unacceptable in my eyes. + +## Why was it so big? + +I had no idea why my bundle size was so huge because I wasn't using an large libraries or anything. I needed something like a profiler, or a way to analyze each mondule being loaded because I had no idea where to start. + +## Webpack visualizer plugin + +I came across this plugin and it is what saved me. I shows an interactive graph of each module being loaded along with sub modules. It was just what I needed! To my surprise, highlight.js was taking up almost 600kb (keep in mind webpack visualizer shows sizes before minification) \ No newline at end of file diff --git a/server/mywebsite.go b/server/mywebsite.go index edb23b2..0e233f4 100644 --- a/server/mywebsite.go +++ b/server/mywebsite.go @@ -24,5 +24,11 @@ func main(){ handle := gziphandler.GzipHandler(route.Routes()) log.Println("Starting Server...") - log.Println(http.ListenAndServe(":"+strconv.Itoa(configurations.Port), handle)) + go func(){ + log.Println(http.ListenAndServe(":"+strconv.Itoa(configurations.Port), handle)) + }() + + if configurations.TLSCertFile != "" && configurations.TLSKeyFile != "" { + log.Println(http.ListenAndServeTLS(":"+strconv.Itoa(configurations.TLSPort), configurations.TLSCertFile, configurations.TLSKeyFile, handle)) + } } diff --git a/server/utils/config.go b/server/utils/config.go index 5085ad6..6bdb181 100644 --- a/server/utils/config.go +++ b/server/utils/config.go @@ -12,9 +12,12 @@ import ( //structure for application configurations type Config struct { - Database db.DatabaseInfo `json:"database"` - Api api.ApiInfo `json:"api"` - Port int `json:"port"` + Database db.DatabaseInfo `json:"Database"` + Api api.ApiInfo `json:"Api"` + Port int `json:"Port"` + TLSPort int `json:"TLSPort"` + TLSCertFile string `json:"TLSCertFile"` + TLSKeyFile string `json:"TLSKeyFile"` } //read the config file and return JsonObject struct