├── .gitignore ├── templates └── index.html ├── webpack.config.js ├── app.py ├── src └── my.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | env/* 2 | node_modules/* 3 | static/bundle.js 4 | *.pyc 5 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Jo

4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/my.js', 5 | output: { 6 | filename: 'bundle.js', 7 | path: path.resolve(__dirname, 'static') 8 | } 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def main(): 7 | return render_template('index.html') 8 | 9 | 10 | if __name__=="__main__": 11 | app.run(debug=True) 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/my.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | function component() { 4 | var element = document.createElement('div') 5 | element.innerHTML = _.join(['Hello','world','!!'], ' '); 6 | 7 | return element; 8 | } 9 | 10 | document.body.appendChild(component()); 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cleaner", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack", 9 | "watch": "webpack --watch" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "webpack": "^3.6.0" 16 | }, 17 | "dependencies": { 18 | "lodash": "^4.17.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask webpack Tutorial 2 | 3 | How to easily develop flask-based python web applications that use (external) javascript 4 | libraries? Adding direct links to your templates is a natural option but in the 5 | long term, the maintenance is pretty overwhelming. Secondly, as in my case the 6 | application might not work properly if deployed without internet or behind 7 | a strict firewall. Lastly, javascript underwent a lots of changes in the recent 8 | years and using the newest language features across all browsers, require 9 | more complex toolchains (e.g. using of babel). 10 | 11 | In this tutorial I will show a solution to that problem which uses `npm` (node package 12 | manager) and `webpack` tool. The tutorial should give you basic understanding of the 13 | tools rather than full deep dive into the aforementioned technologies. 14 | 15 | Prerequisites: 16 | * understanding of `Flask`, `Python`, and `javascript` 17 | * availability of `npm` 18 | 19 | ## Project structure 20 | Let us start with a simple directory structure for our application. We will create 21 | separate directories for templates and static content as required by Flask. We also 22 | add a directory to write our javascript code. 23 | ``` 24 | mkdir -p flaskpack/templates 25 | mkdir -p flaskpack/static 26 | mkdir -p flaskpack/src 27 | ``` 28 | 29 | ## Simple Flask Application 30 | I suggest to use virtual environment for programming with flask but this is not 31 | strictly required. Anyways you can just cd in to the project directory and create 32 | application code: 33 | ``` 34 | cd flaskpack 35 | vi app.py 36 | ``` 37 | 38 | ```python 39 | from flask import Flask, render_template 40 | 41 | app = Flask(__name__) 42 | 43 | @app.route('/') 44 | def index(): 45 | return render_template('index.html') 46 | 47 | if __name__ == "__main__": 48 | app.run() 49 | ``` 50 | 51 | As you see the application has just one entry point i.e. it reacts only on requests 52 | on root url. In this case a template (`index.html`) is rendered and sent back to the user. 53 | Now we have to create the template. Although it is possible to define the location of 54 | the template directory we use the default flask option which seeks for templates in 55 | `/template/` directory. 56 | 57 | ```bash 58 | vim /template/index.html 59 | ``` 60 | 61 | ```html 62 | 63 | 64 |

Simple web app

65 | 66 | 67 | 68 | 69 | ``` 70 | 71 | There is not much happening here now. We have a simple web page with a heading. 72 | We also include a javascript file `bundle.js` from static directory. This file should 73 | incorporate additional logic of our application that we would like to outsource 74 | to the client. The problem is that the file does not exist yet. You can start the 75 | Flask application and it should work and serve all the static (i.e. not javascript-based) 76 | to the user: 77 | ``` 78 | python app.py 79 | ``` 80 | 81 | and use browser to go to `localhost:5000` to see the result. 82 | 83 | ## Javascript part 84 | Now we would like to extend our simple application with some javascript. To this 85 | end we have to create and edit `src/my.js` file. Please note that we don't create 86 | `bundle.js` now. This file will be created automatically later. Our javascript 87 | will look like that. 88 | 89 | ``` 90 | vim /src/my.js 91 | ``` 92 | 93 | ```javascript 94 | import _ from 'lodash'; 95 | 96 | function component() { 97 | var element = document.createElement('div') 98 | element.innerHTML = _.join(['Hello','world','!!'], ' '); 99 | 100 | return element; 101 | } 102 | 103 | document.body.appendChild(component()); 104 | ``` 105 | 106 | We use an external library (`lodash`) to dynamically generate additional content 107 | of our page. 108 | 109 | Let us assume that our coding ends here, and we would like to test and ship the 110 | functionality. Two remaining problems are how to get the dependencies (i.e. `lodash`) 111 | and generate `bundle.js` with our code required by the previously created template. 112 | 113 | ## Npm package manager 114 | 115 | For managing the dependencies we will use `npm` (node package manager). Firstly, we 116 | need to initialize the tool. 117 | 118 | ```shell 119 | npm init -y 120 | ``` 121 | 122 | This will create a manifest file `package.json` and also a directory `node_modules` 123 | where external libraries will be installed. At this point you can review the 124 | `package.json` file and edit things like project name, version, or description. 125 | 126 | The package manager can also install javascript libraries in the directory. We 127 | will require `lodash` library. To install it we have to issue following command: 128 | 129 | ```shell 130 | npm install lodash --save 131 | ``` 132 | 133 | The `--save` argument is required to add this dependency to the `package.json` file. 134 | You can check it now to see that a new line was added: 135 | 136 | ```javascript 137 | "dependencies": { 138 | "lodash": "^4.17.4" 139 | } 140 | ``` 141 | 142 | Although it would be possible to make the node_modules accessible to flask and thus 143 | visible from its the templates, this is probably not the best solution. Instead 144 | we will use `webpack` tool to manage the resources. 145 | 146 | ## Webpack 147 | 148 | First we have to install `webpack`. It is important to notice that `webpack` is not 149 | really required for our application to work (unlike `lodash`). The `npm` differentiate 150 | between dependencies and devDependencies. The later are used to manage tools 151 | required for development and deployment of the application but not during the 152 | runtime of it. To install `webpack` use `npm` like: 153 | 154 | ```shell 155 | npm install --save-dev webpack 156 | ``` 157 | 158 | Now you can run the `webpack` to finally create `bundle.js` file required by our 159 | application, one way of doing this is to call `webpack` directly: 160 | 161 | ```shell 162 | ./node_modules/.bin/webpack src/my.js static/bundle.js 163 | ``` 164 | 165 | After this step a new file `bundle.js` should appear in the static directory of 166 | your application and after starting Flask app again you should be able to see a webpage 167 | with dynamically generated content. 168 | 169 | A much more elegant way of working with `webpack` is to create a file called `webpack.config.js` 170 | which will describe how the `bundle.js` is created. Let us do it now: 171 | 172 | ```shell 173 | vi webpack.config.js 174 | ``` 175 | 176 | ```javascript 177 | const path = require('path'); 178 | 179 | module.exports = { 180 | entry: './src/my.js', 181 | output: { 182 | filename: 'bundle.js', 183 | path: path.resolve(__dirname, 'static') 184 | } 185 | }; 186 | ``` 187 | 188 | We can also extend `packages.json` to make the work with `webpack` easier. The `npm` 189 | offers the possibility to define scripts, you have to add following line to your 190 | project file: 191 | ```shell 192 | vi packages.json 193 | ``` 194 | 195 | ```javascript 196 | "scripts": { 197 | "build": "webpack" 198 | }, 199 | ``` 200 | 201 | Now we can generate new version of `bundle.js` by just issuing: 202 | 203 | ```shell 204 | npm run build 205 | ``` 206 | 207 | For the development it is useful to use `webpack` function called watch. This 208 | creates a process which reacts to changes in the javascript files and automatically 209 | regenerates `bundle.js`. To use this feature add one more script to the `package.json` file 210 | ```shell 211 | vim packages.json 212 | ``` 213 | 214 | ```javascript 215 | "scripts": { 216 | "build": "webpack", 217 | "watch": "webpack --watch" 218 | }, 219 | ``` 220 | 221 | you can start it now by: 222 | ```shell 223 | npm run watch 224 | ``` 225 | 226 | ## Links 227 | 228 | 229 | --------------------------------------------------------------------------------