├── .gitignore ├── test ├── data │ ├── entry.js │ └── index.html └── suites │ └── serverTest.js ├── client ├── page.jade ├── live.html ├── package.json ├── webpack.config.js ├── style.css ├── index.js ├── live.js └── npm-debug.log ├── gulpfile.js ├── package.json ├── README.md ├── src ├── pluginutil.js └── plugin.js └── npm-debug.log /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /test/data/entry.js: -------------------------------------------------------------------------------- 1 | console.log("test"); -------------------------------------------------------------------------------- /client/page.jade: -------------------------------------------------------------------------------- 1 | .header 2 | span#okness 3 | = " " 4 | span#status 5 | pre#errors 6 | #warnings 7 | iframe#iframe(src="javascript:;") -------------------------------------------------------------------------------- /test/data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/live.html: -------------------------------------------------------------------------------- 1 | webpack dev server -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpackdevserverclient", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "robert", 10 | "license": "ISC", 11 | "dependencies": { 12 | "jquery": "^2.1.3", 13 | "socket.io-client": "^1.3.3" 14 | }, 15 | "devDependencies": { 16 | "css-loader": "^0.9.1", 17 | "jade": "^1.9.1", 18 | "jade-loader": "^0.7.1", 19 | "style-loader": "^0.8.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var gutil = require("gulp-util"); 3 | var mocha = require("gulp-mocha"); 4 | var connect = require('gulp-connect'); 5 | 6 | // The development server (the recommended option for development) 7 | gulp.task("default", ["test"]); 8 | 9 | // perform macha tests 10 | gulp.task('test', function () { 11 | return gulp.src('./test/suites/**/*.js', { 12 | read: false 13 | }) 14 | .pipe(mocha({ 15 | reporter: 'nyan' 16 | })); 17 | }); 18 | 19 | gulp.task('mocha-watch', function () { 20 | gulp.watch(['./**/*.js','!./testWebPackApp/**/*'], ['test']); 21 | }); -------------------------------------------------------------------------------- /client/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | 4 | module.exports = { 5 | entry: { 6 | index: "./index", 7 | live: "./live", 8 | }, 9 | output: { 10 | path: path.join(__dirname, ""), 11 | filename: "[name].bundle.js", 12 | chunkFilename: "[chunkhash].[id].js" 13 | }, 14 | module: { 15 | loaders: [{ 16 | test: /\.css/, 17 | loader: "style-loader!css-loader" 18 | }, 19 | { 20 | test: /\.jade$/, 21 | loader: "jade?self" 22 | } 23 | ] 24 | } 25 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-webpack-dev-plugin", 3 | "version": "0.1.1", 4 | "description": "a plugin to integrate the web-pack-dev-server as a hapi plugin into a hapi server instead of using express", 5 | "main": "src/plugin.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "hapi", 11 | "webpack", 12 | "devserver", 13 | "server", 14 | "dev" 15 | ], 16 | "author": "atroo (info@atroo.de)", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/atroo/hapi-webpack-dev-server-plugin" 20 | }, 21 | "license": "ISC", 22 | "devDependencies": { 23 | "chai": "^1.9.2", 24 | "chai-spies": "^0.5.1", 25 | "gulp": "^3.8.10", 26 | "gulp-connect": "^2.2.0", 27 | "gulp-mocha": "^1.1.1", 28 | "gulp-util": "^3.0.1", 29 | "hapi": "8.x.x", 30 | "webpack": "^1.4.12", 31 | "wreck": "^5.0.1" 32 | }, 33 | "dependencies": { 34 | "boom": "^2.6.0", 35 | "jquery": "^2.1.1", 36 | "memory-fs": "^0.1.0", 37 | "mime": "^1.2.11", 38 | "socket.io": "^1.3.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client/style.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | -webkit-box-sizing: border-box; 5 | -moz-box-sizing: border-box; 6 | box-sizing: border-box; 7 | } 8 | 9 | body, 10 | html { 11 | margin: 0; 12 | padding: 0; 13 | height: 100%; 14 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 15 | } 16 | 17 | .header { 18 | width: 100%; 19 | height: 30px; 20 | padding: 0 10px; 21 | border-left: 10px solid #a3be8c; 22 | font-size: 12px; 23 | line-height: 30px; 24 | color: #eff1f5; 25 | background: #343d46; 26 | overflow: hidden; 27 | } 28 | 29 | #iframe { 30 | position: absolute; 31 | top: 30px; 32 | right: 0; 33 | bottom: 0; 34 | left: 0; 35 | width: 100%; 36 | height: -webkit-calc(100% - 30px); 37 | height: -moz-calc(100% - 30px); 38 | height: -ms-calc(100% - 30px); 39 | height: -o-calc(100% - 30px); 40 | height: calc(100% - 30px); 41 | border: 0; 42 | } 43 | 44 | #errors { 45 | width: 100%; 46 | margin: 0; 47 | padding: 10px; 48 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 49 | font-size: 14px; 50 | line-height: 1.4; 51 | color: #eff1f5; 52 | background: #bf616a; 53 | } 54 | 55 | #okness { 56 | font-weight: bold; 57 | } 58 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | var io = require("socket.io-client"); 2 | var scriptElements = document.getElementsByTagName("script"); 3 | io = io.connect(typeof __resourceQuery === "string" && __resourceQuery ? 4 | __resourceQuery.substr(1) : 5 | scriptElements[scriptElements.length-1].getAttribute("src").replace(/\/[^\/]+$/, "") 6 | ); 7 | 8 | var hot = false; 9 | var initial = true; 10 | var currentHash = ""; 11 | 12 | io.on("hot", function() { 13 | hot = true; 14 | console.log("[WDS] Hot Module Replacement enabled."); 15 | }); 16 | 17 | io.on("invalid", function() { 18 | console.log("[WDS] App updated. Recompiling..."); 19 | }); 20 | 21 | io.on("hash", function(hash) { 22 | currentHash = hash; 23 | }); 24 | 25 | io.on("ok", function() { 26 | if(initial) return initial = false; 27 | reloadApp(); 28 | }); 29 | 30 | io.on("warnings", function(warnings) { 31 | console.log("[WDS] Warnings while compiling."); 32 | for(var i = 0; i < warnings.length; i++) 33 | console.warn(warnings[i]); 34 | if(initial) return initial = false; 35 | reloadApp(); 36 | }); 37 | 38 | io.on("errors", function(errors) { 39 | console.log("[WDS] Errors while compiling."); 40 | for(var i = 0; i < errors.length; i++) 41 | console.error(errors[i]); 42 | if(initial) return initial = false; 43 | reloadApp(); 44 | }); 45 | 46 | io.on("proxy-error", function(errors) { 47 | console.log("[WDS] Proxy error."); 48 | for(var i = 0; i < errors.length; i++) 49 | console.error(errors[i]); 50 | if(initial) return initial = false; 51 | reloadApp(); 52 | }); 53 | 54 | io.on("disconnect", function() { 55 | console.error("[WDS] Disconnected!"); 56 | }); 57 | 58 | function reloadApp() { 59 | if(hot) { 60 | console.log("[WDS] App hot update..."); 61 | window.postMessage("webpackHotUpdate" + currentHash, "*"); 62 | } else { 63 | console.log("[WDS] App updated. Reloading..."); 64 | window.location.reload(); 65 | } 66 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hapi-webpack-dev-server-plugin 2 | ============================== 3 | 4 | This is a plugin for hapijs providing the necessary routes to function as a webpack-dev-server for the [awesome webpack project](http://webpack.github.io/) directly in hapijs. The actual [Webpack-dev-server](https://github.com/webpack/webpack-dev-server) is implemented with express.js and since we are heavily using the fabulous [hapijs](http://hapijs.com/) as our main backend framework we found it very useful to have everything right there. This avoids tweaking SOP related things just for dev purposes. 5 | 6 | Just like the original webpack-dev-server we also want to give out a quick warning: 7 | 8 | *DON'T USE THIS PLUGIN IN PRODUCTION!* 9 | 10 | Requirements: hapijs ^8.x.x 11 | 12 | State: In Progress (meaning not hot code replacement at the moment, but auto relaoding works just fine) 13 | 14 | Basic Usage 15 | ===== 16 | 17 | we tend to structure our apps like 18 | ```javascript 19 | - server_plugin 20 | index.js 21 | - webpack_frontend 22 | - src 23 | main.js 24 | webpack.config.js 25 | server.js 26 | ``` 27 | 28 | with that structure in mind you'd setup the dev-server-plugin in the server.js as follows 29 | 30 | ```javascript 31 | var Webpack = require('webpack'); 32 | var Hapi = require('hapi'); 33 | 34 | //basic webpack config, see webpack api docs for all options 35 | var webpackConf = require('./webpack_frontend/webpack.config.js'); 36 | webpackConf.entry = { 37 | app: './webpack_frontend/src/main.js' //this is needed to have the correct relative paths for the webpack compiler which now runs from the base dir rather than from webpack_frontend 38 | }; 39 | webpackConf.devtool = 'source-map'; 40 | 41 | //create the webpack compiler 42 | compiler = Webpack(webPackConfig); 43 | 44 | //create hapi server 45 | server = new Hapi.Server(); 46 | server.connection({ 47 | port: port, 48 | host: address 49 | }); 50 | 51 | //start server and register dev server as plugin 52 | server.start(function () { 53 | server.register({ 54 | register: require('hapi-webpack-dev-plugin'), 55 | options: { 56 | compiler: compiler, 57 | //no loginfo 58 | quiet: true, 59 | //where is the index.html located 60 | devIndex: ".", //not needed if devView is configured 61 | devView: { //allows to configure a view with whatever engine hapi has been configured to induce e.e. session information on startup 62 | name: 'main.html', 63 | data: function (request) { 64 | var tplData = { 65 | "entrypoint": "dist/app.js" 66 | }; 67 | return tplData; 68 | } 69 | } 70 | /* 71 | ,watchDelay: 200 72 | ,noInfo: false 73 | ,headers: { 74 | x-dynamic-content: true 75 | } 76 | */ 77 | } 78 | }, function (err) { 79 | console.log(err); 80 | }); 81 | }); 82 | 83 | ``` 84 | 85 | Config Options 86 | ============== 87 | 88 | 93 | 94 | ```javascript 95 | { 96 | name: "the name of the view" // mandatory, elsewise the devIndex will be used 97 | data: "tplData" // an object, a function which gets passed in the request, or empty - data is supposed to determine tplData for the view 98 | } 99 | ``` 100 | 101 | 108 | 109 | 110 | Questions? 111 | ============== 112 | 113 | Feel free to ask questions if anything is badly described! 114 | 115 | info@atroo.de or an Issue! 116 | 117 | 118 | -------------------------------------------------------------------------------- /client/live.js: -------------------------------------------------------------------------------- 1 | var $ = require("jquery"); 2 | var socketIO = require("socket.io-client"); 3 | require("./style.css"); 4 | 5 | $(function () { 6 | var body = $("body").html(require("./page.jade")()); 7 | var status = $("#status"); 8 | var okness = $("#okness"); 9 | var $errors = $("#errors"); 10 | var iframe = $("#iframe"); 11 | var header = $(".header"); 12 | var hot = false; 13 | var currentHash = ""; 14 | 15 | var contentPage = window.location.pathname.substr("/webpack-dev-server".length) + window.location.search; 16 | 17 | status.text("Connecting to socket.io server..."); 18 | $errors.hide(); 19 | iframe.hide(); 20 | header.css({ 21 | borderColor: "#96b5b4" 22 | }); 23 | io = socketIO('//', { 24 | path: '/webpackdevserversocket', 25 | transports: ['websocket'] 26 | }); 27 | 28 | io.on("hot", function () { 29 | hot = true; 30 | iframe.attr("src", contentPage + window.location.hash); 31 | }); 32 | 33 | io.on("invalid", function () { 34 | okness.text(""); 35 | status.text("App updated. Recompiling..."); 36 | header.css({ 37 | borderColor: "#96b5b4" 38 | }); 39 | $errors.hide(); 40 | if (!hot) iframe.hide(); 41 | }); 42 | 43 | io.on("hash", function (hash) { 44 | currentHash = hash; 45 | }); 46 | 47 | io.on("ok", function () { 48 | okness.text(""); 49 | $errors.hide(); 50 | reloadApp(); 51 | }); 52 | 53 | io.on("warnings", function (warnings) { 54 | okness.text("Warnings while compiling."); 55 | $errors.hide(); 56 | reloadApp(); 57 | }); 58 | 59 | io.on("errors", function (errors) { 60 | status.text("App updated with errors. No reload!"); 61 | okness.text("Errors while compiling."); 62 | $errors.text("\n" + errors.join("\n\n\n") + "\n\n"); 63 | header.css({ 64 | borderColor: "#ebcb8b" 65 | }); 66 | $errors.show(); 67 | iframe.hide(); 68 | }); 69 | 70 | io.on("proxy-error", function (errors) { 71 | status.text("Could not proxy to content base target!"); 72 | okness.text("Proxy error."); 73 | $errors.text("\n" + errors.join("\n\n\n") + "\n\n"); 74 | header.css({ 75 | borderColor: "#ebcb8b" 76 | }); 77 | $errors.show(); 78 | iframe.hide(); 79 | }); 80 | 81 | io.on("disconnect", function () { 82 | status.text(""); 83 | okness.text("Disconnected."); 84 | $errors.text("\n\n\n Lost connection to webpack-dev-server.\n Please restart the server to reestablish connection...\n\n\n\n"); 85 | header.css({ 86 | borderColor: "#ebcb8b" 87 | }); 88 | $errors.show(); 89 | iframe.hide(); 90 | }); 91 | 92 | iframe.load(function () { 93 | status.text("App ready."); 94 | header.css({ 95 | borderColor: "" 96 | }); 97 | iframe.show(); 98 | }); 99 | 100 | function reloadApp() { 101 | if (hot) { 102 | status.text("App hot update."); 103 | try { 104 | iframe[0].contentWindow.postMessage("webpackHotUpdate" + currentHash, "*"); 105 | } catch (e) { 106 | console.warn(e); 107 | } 108 | iframe.show(); 109 | } else { 110 | status.text("App updated. Reloading app..."); 111 | header.css({ 112 | borderColor: "#96b5b4" 113 | }); 114 | try { 115 | var old = iframe[0].contentWindow.location + ""; 116 | if (old.indexOf("about") == 0) old = null; 117 | iframe.attr("src", old || (contentPage + window.location.hash)); 118 | old && iframe[0].contentWindow.location.reload(); 119 | } catch (e) { 120 | iframe.attr("src", contentPage + window.location.hash); 121 | } 122 | } 123 | } 124 | 125 | }); -------------------------------------------------------------------------------- /client/npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ 'D:\\Programmierung\\tools\\nodejs\\\\node.exe', 3 | 1 verbose cli 'D:\\Programmierung\\tools\\nodejs\\node_modules\\npm\\bin\\npm-cli.js', 4 | 1 verbose cli 'install', 5 | 1 verbose cli '--save-dev', 6 | 1 verbose cli 'jade-laoder' ] 7 | 2 info using npm@1.4.28 8 | 3 info using node@v0.10.35 9 | 4 verbose node symlink D:\Programmierung\tools\nodejs\\node.exe 10 | 5 warn package.json webpackdevserverclient@1.0.0 No description 11 | 6 warn package.json webpackdevserverclient@1.0.0 No repository field. 12 | 7 warn package.json webpackdevserverclient@1.0.0 No README data 13 | 8 verbose readDependencies using package.json deps 14 | 9 verbose cache add [ 'jade-laoder', null ] 15 | 10 verbose cache add name=undefined spec="jade-laoder" args=["jade-laoder",null] 16 | 11 verbose parsed url { protocol: null, 17 | 11 verbose parsed url slashes: null, 18 | 11 verbose parsed url auth: null, 19 | 11 verbose parsed url host: null, 20 | 11 verbose parsed url port: null, 21 | 11 verbose parsed url hostname: null, 22 | 11 verbose parsed url hash: null, 23 | 11 verbose parsed url search: null, 24 | 11 verbose parsed url query: null, 25 | 11 verbose parsed url pathname: 'jade-laoder', 26 | 11 verbose parsed url path: 'jade-laoder', 27 | 11 verbose parsed url href: 'jade-laoder' } 28 | 12 silly lockFile e7e97227-jade-laoder jade-laoder 29 | 13 verbose lock jade-laoder C:\Users\Robert\AppData\Roaming\npm-cache\e7e97227-jade-laoder.lock 30 | 14 silly lockFile e7e97227-jade-laoder jade-laoder 31 | 15 silly lockFile e7e97227-jade-laoder jade-laoder 32 | 16 verbose addNamed [ 'jade-laoder', '' ] 33 | 17 verbose addNamed [ null, '*' ] 34 | 18 silly lockFile 7f984613-jade-laoder jade-laoder@ 35 | 19 verbose lock jade-laoder@ C:\Users\Robert\AppData\Roaming\npm-cache\7f984613-jade-laoder.lock 36 | 20 silly addNameRange { name: 'jade-laoder', range: '*', hasData: false } 37 | 21 verbose request where is /jade-laoder 38 | 22 verbose request registry https://registry.npmjs.org/ 39 | 23 verbose request id 6fbdd129f65d35eb 40 | 24 verbose url raw /jade-laoder 41 | 25 verbose url resolving [ 'https://registry.npmjs.org/', './jade-laoder' ] 42 | 26 verbose url resolved https://registry.npmjs.org/jade-laoder 43 | 27 verbose request where is https://registry.npmjs.org/jade-laoder 44 | 28 info trying registry request attempt 1 at 16:54:11 45 | 29 http GET https://registry.npmjs.org/jade-laoder 46 | 30 http 404 https://registry.npmjs.org/jade-laoder 47 | 31 verbose headers { date: 'Wed, 04 Feb 2015 15:54:10 GMT', 48 | 31 verbose headers server: 'CouchDB/1.5.0 (Erlang OTP/R16B03)', 49 | 31 verbose headers 'content-type': 'application/json', 50 | 31 verbose headers 'cache-control': 'max-age=0', 51 | 31 verbose headers 'content-length': '52', 52 | 31 verbose headers 'accept-ranges': 'bytes', 53 | 31 verbose headers via: '1.1 varnish', 54 | 31 verbose headers 'x-served-by': 'cache-lcy1134-LCY', 55 | 31 verbose headers 'x-cache': 'MISS', 56 | 31 verbose headers 'x-cache-hits': '0', 57 | 31 verbose headers 'x-timer': 'S1423065249.972725,VS0,VE159', 58 | 31 verbose headers 'keep-alive': 'timeout=10, max=50', 59 | 31 verbose headers connection: 'Keep-Alive' } 60 | 32 silly registry.get cb [ 404, 61 | 32 silly registry.get { date: 'Wed, 04 Feb 2015 15:54:10 GMT', 62 | 32 silly registry.get server: 'CouchDB/1.5.0 (Erlang OTP/R16B03)', 63 | 32 silly registry.get 'content-type': 'application/json', 64 | 32 silly registry.get 'cache-control': 'max-age=0', 65 | 32 silly registry.get 'content-length': '52', 66 | 32 silly registry.get 'accept-ranges': 'bytes', 67 | 32 silly registry.get via: '1.1 varnish', 68 | 32 silly registry.get 'x-served-by': 'cache-lcy1134-LCY', 69 | 32 silly registry.get 'x-cache': 'MISS', 70 | 32 silly registry.get 'x-cache-hits': '0', 71 | 32 silly registry.get 'x-timer': 'S1423065249.972725,VS0,VE159', 72 | 32 silly registry.get 'keep-alive': 'timeout=10, max=50', 73 | 32 silly registry.get connection: 'Keep-Alive' } ] 74 | 33 silly lockFile 7f984613-jade-laoder jade-laoder@ 75 | 34 silly lockFile 7f984613-jade-laoder jade-laoder@ 76 | 35 error 404 404 Not Found: jade-laoder 77 | 35 error 404 78 | 35 error 404 'jade-laoder' is not in the npm registry. 79 | 35 error 404 You should bug the author to publish it 80 | 35 error 404 It was specified as a dependency of 'webpackdevserverclient' 81 | 35 error 404 82 | 35 error 404 Note that you can also install from a 83 | 35 error 404 tarball, folder, or http url, or git url. 84 | 36 error System Windows_NT 6.2.9200 85 | 37 error command "D:\\Programmierung\\tools\\nodejs\\\\node.exe" "D:\\Programmierung\\tools\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "install" "--save-dev" "jade-laoder" 86 | 38 error cwd D:\Programmierung\workspaces\brackets\hapi-webpack-dev-server-plugin\client\new 87 | 39 error node -v v0.10.35 88 | 40 error npm -v 1.4.28 89 | 41 error code E404 90 | 42 verbose exit [ 1, true ] 91 | -------------------------------------------------------------------------------- /test/suites/serverTest.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var chai = require('chai'); 3 | var spies = require('chai-spies'); 4 | chai.use(spies); 5 | var Hapi = require('hapi'); 6 | var Wreck = require('wreck'); 7 | var path = require("path"); 8 | var Webpack = require('webpack'); 9 | 10 | var expect = chai.expect; 11 | var gutil = require('gulp-util'); 12 | 13 | describe("Hapi Weback-Dev Plugin Test", function () { 14 | var server; 15 | var port = 8337; 16 | var address = 'localhost'; 17 | var currDone = null; 18 | var compiler; 19 | 20 | var url = function (path) { 21 | return ["http://", address, ":", port, path].join("") 22 | }; 23 | 24 | 25 | // /////////////////////////////////////// 26 | beforeEach(function (done) { 27 | server = new Hapi.Server({ 28 | debug: { 29 | //request: ["error"] 30 | } 31 | }); 32 | server.connection({ 33 | port: port, 34 | host: address 35 | }); 36 | 37 | server.route({ 38 | method: "*", 39 | path: "/", 40 | handler: function (request, reply) { 41 | reply("ok").code(200); 42 | } 43 | }); 44 | 45 | //configure the webpack compiler to work with our dummy test app 46 | var webPackConfig = { 47 | cache: true, 48 | devtool: "eval", 49 | debug: true, 50 | entry: { 51 | entry: "./test/data/entry.js" 52 | }, 53 | output: { 54 | path: __dirname + "/dist", //the basic build directory 55 | publicPath: "/dist", //the route which the dev server listens for 56 | filename: "[name].js", 57 | chunkFilename: "[chunkhash].[id].js" 58 | } 59 | }; 60 | 61 | compiler = Webpack(webPackConfig); 62 | 63 | 64 | server.start(function () { 65 | server.register({ 66 | register: require('./../../src/plugin'), 67 | options: { 68 | compiler: compiler, 69 | //no loginfo 70 | quiet: true, 71 | devIndex: "./test/data" 72 | /* 73 | ,watchDelay: 200 74 | ,noInfo: false 75 | ,headers: { 76 | x-dynamic-content: true 77 | } 78 | */ 79 | } 80 | }, function (err) { 81 | expect(err).to.not.exist; 82 | done(); 83 | }); 84 | }); 85 | 86 | var apiTestPlugin = function (plugin, options, next) { 87 | plugin.route({ 88 | method: '*', 89 | path: '/api/test', 90 | handler: function (request, reply) { 91 | reply(options.message); 92 | } 93 | }); 94 | next(); 95 | }; 96 | 97 | apiTestPlugin.attributes = { 98 | version: "0.0.0", 99 | name: "test" 100 | }; 101 | 102 | server.register({ 103 | register: apiTestPlugin, 104 | options: { 105 | message: 'testApi Response' 106 | } 107 | }, function (err) { 108 | if (err) { 109 | console.log('Failed loading plugin'); 110 | } 111 | }); 112 | }); 113 | 114 | afterEach(function (done) { 115 | server.stop(function () { 116 | var err = fs.writeFileSync("./test/data/entry.js",'console.log("test");'); 117 | if(err) { 118 | console-log("clean up failed"); 119 | } 120 | done(); 121 | }); 122 | }); 123 | // /////////////////////////////////////// 124 | 125 | 126 | // /////////////////////////////////////// 127 | it("should intercept requests", function (done) { 128 | Wreck.get(url("/dist/entry.js"), function (err, res, payload) { 129 | expect(res.headers["x-hapi-webpack-dev"]).to.exist; 130 | expect(res.headers["x-hapi-webpack-dev"]).to.equal("true"); 131 | done(); 132 | }); 133 | }); 134 | 135 | 136 | it("should notice a change in webpack relevant files", function (done) { 137 | compiler.plugin("done", function () { 138 | done(); 139 | }); 140 | fs.appendFile("./test/data/entry.js", "console.log('test');", function (err) { 141 | if (err) { 142 | throw "could not edit entry.js file" + err; 143 | } 144 | }); 145 | }); 146 | 147 | it("should deliver root html", function (done) { 148 | Wreck.get(url("/index.html"), function (err, res, payload) { 149 | expect(res.statusCode).to.equal(200); 150 | done(); 151 | }); 152 | }); 153 | 154 | it("should redirect to host app", function (done) { 155 | Wreck.get(url("/webpack-dev-server"), function (err, res, payload) { 156 | expect(res.statusCode).to.equal(302); 157 | done(); 158 | }); 159 | }); 160 | 161 | it("should serve the bundle js file", function (done) { 162 | Wreck.get(url("/__webpack_dev_server__/live.bundle.js"), function (err, res, payload) { 163 | expect(res.statusCode).to.equal(200); 164 | done(); 165 | }); 166 | }); 167 | 168 | it("should serve the webpackdevserver frontend js file", function (done) { 169 | Wreck.get(url("/webpack-dev-server.js"), function (err, res, payload) { 170 | expect(res.statusCode).to.equal(200); 171 | done(); 172 | }); 173 | }); 174 | 175 | it("should serve dynamic content from the memory filesystem", function (done) { 176 | Wreck.get(url("/dist/entry.js"), function (err, res, payload) { 177 | expect(res.statusCode).to.equal(200); 178 | done(); 179 | }); 180 | }); 181 | 182 | it("should serve an error for unknown files", function (done) { 183 | Wreck.get(url("/dist/entry_undefined.js"), function (err, res, payload) { 184 | expect(res.statusCode).to.equal(404); 185 | done(); 186 | }); 187 | }); 188 | 189 | it("should handle routes correctly", function (done) { 190 | Wreck.get(url("/api/test"), function (err, res, payload) { 191 | expect(res.statusCode).to.equal(200); 192 | Wreck.post(url("/api/test"), function (err, res, payload) { 193 | expect(res.statusCode).to.equal(200); 194 | done(); 195 | }); 196 | }); 197 | }); 198 | // /////////////////////////////////////// 199 | }); -------------------------------------------------------------------------------- /src/pluginutil.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | 5 | This is an adaptation of sokras great webpack-dev-middle-ware for webpacks compiler state managment as well as default routing working for express as a middleware 6 | This adapation will do a comparable thing for Hapi in context of beeing integrated in a hapi plugin 7 | 8 | Author Robert Krüger +de.robat 9 | */ 10 | 11 | var path = require("path"); 12 | var MemoryFileSystem = require("memory-fs"); 13 | var mime = require("mime"); 14 | var Boom = require("boom"); 15 | 16 | // constructor for the middlewa 17 | module.exports = function (options) { 18 | var compiler = options.compiler; 19 | if (options.watchDelay === undefined) options.watchDelay = 200; 20 | if (typeof options.stats === "undefined") options.stats = {}; 21 | if (!options.stats.context) options.stats.context = process.cwd(); 22 | 23 | // store our files in memory 24 | var files = {}; 25 | var fs = compiler.outputFileSystem = new MemoryFileSystem(); 26 | 27 | compiler.plugin("done", function (stats) { 28 | // We are now on valid state 29 | state = true; 30 | // Do the stuff in nextTick, because bundle may be invalidated 31 | // if a change happend while compiling 32 | process.nextTick(function () { 33 | // check if still in valid state 34 | if (!state) return; 35 | // print webpack output 36 | var displayStats = (!options.quiet && options.stats !== false); 37 | if (displayStats && 38 | !(stats.hasErrors() || stats.hasWarnings()) && 39 | options.noInfo) 40 | displayStats = false; 41 | if (displayStats) { 42 | console.log(stats.toString(options.stats)); 43 | } 44 | if (!options.noInfo && !options.quiet) 45 | console.info("webpack: bundle is now VALID."); 46 | 47 | // execute callback that are delayed 48 | var cbs = callbacks; 49 | callbacks = []; 50 | cbs.forEach(function continueBecauseBundleAvailible(cb) { 51 | cb(); 52 | }); 53 | }); 54 | 55 | if (forceRebuild) { 56 | forceRebuild = false; 57 | rebuild(); 58 | } 59 | }); 60 | 61 | // on compiling 62 | function invalidPlugin() { 63 | if (state && (!options.noInfo && !options.quiet)) 64 | console.info("webpack: bundle is now INVALID."); 65 | // We are now in invalid state 66 | state = false; 67 | } 68 | compiler.plugin("invalid", invalidPlugin); 69 | compiler.plugin("compile", invalidPlugin); 70 | 71 | // the state, false: bundle invalid, true: bundle valid 72 | var state = false; 73 | 74 | var forceRebuild = false; 75 | 76 | // delayed callback 77 | var callbacks = []; 78 | 79 | // wait for bundle valid 80 | function ready(fn, url) { 81 | if (state) return fn(); 82 | if (!options.noInfo && !options.quiet) 83 | console.log("webpack: wait until bundle finished: " + url); 84 | callbacks.push(fn); 85 | } 86 | 87 | var watching = compiler.watch(options.watchDelay, function (err) { 88 | if (err) throw err; 89 | }); 90 | 91 | function rebuild() { 92 | if (state) { 93 | state = false; 94 | compiler.run(function (err) { 95 | if (err) throw err; 96 | }); 97 | } else { 98 | forceRebuild = true; 99 | } 100 | } 101 | 102 | function pathJoin(a, b) { 103 | return a == "/" ? "/" + b : (a || "") + "/" + b 104 | } 105 | 106 | function getFilenameFromUrl(url) { 107 | // publicPrefix is the folder our bundle should be in 108 | var localPrefix = compiler.options.output.publicPath || "/"; 109 | if(localPrefix[0] != "/"){ 110 | //the route always starts with a slash even when the publicPath doesnt 111 | localPrefix = "/" + localPrefix; 112 | } 113 | if (url.indexOf(localPrefix) !== 0) { 114 | if (/^(https?:)?\/\//.test(localPrefix)) { 115 | localPrefix = "/" + localPrefix.replace(/^(https?:)?\/\/[^\/]+\//, ""); 116 | // fast exit if another directory requested 117 | if (url.indexOf(localPrefix) !== 0) return false; 118 | } else return false; 119 | } 120 | // get filename from request 121 | var filename = url.substr(localPrefix.length); 122 | if (filename.indexOf("?") >= 0) { 123 | filename = filename.substr(0, filename.indexOf("?")); 124 | } 125 | return filename ? pathJoin(compiler.outputPath, filename) : compiler.outputPath; 126 | } 127 | 128 | function beforeRequest(req, reply) { 129 | var filename = getFilenameFromUrl(req.url.path); 130 | if (filename === false) return reply.continue(); 131 | 132 | // delay the request until we have a vaild bundle 133 | ready(function () { 134 | try { 135 | var stat = fs.statSync(filename); 136 | if (!stat.isFile()) { 137 | if (stat.isDirectory()) { 138 | filename = path.join(filename, "index.html"); 139 | stat = fs.statSync(filename); 140 | if (!stat.isFile()) throw "next"; 141 | } else { 142 | throw "next"; 143 | } 144 | } 145 | } catch (e) { 146 | return reply.continue(); 147 | } 148 | 149 | //reply with a file from the mem fs 150 | var content = fs.readFileSync(filename); 151 | var res = reply(content); 152 | 153 | res.header("Access-Control-Allow-Origin", "*"); // To support XHR, etc. 154 | res.header("x-hapi-webpack-dev", "true"); // allow for unittests 155 | res.header("Content-Type", mime.lookup(filename)); 156 | 157 | if (options.headers) { 158 | for (var name in options.headers) { 159 | res.header(name, options.headers[name]); 160 | } 161 | }; 162 | }, filename); 163 | }; 164 | 165 | return { 166 | beforeRequest: beforeRequest, 167 | getFilenameFromUrl: getFilenameFromUrl, 168 | invalidate: function () { 169 | if (watching) watching.invalidate(); 170 | }, 171 | close: function (callback) { 172 | callback = callback || function () {}; 173 | if (watching) watching.close(callback); 174 | else callback(); 175 | }, 176 | fileSystem: fs 177 | }; 178 | } -------------------------------------------------------------------------------- /src/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * http://opensource.org/licenses/mit-license.php 3 | * Author Tobias koppers 4 | * 5 | * This is an adaptation of the great work of Tobias Koppers to allow for a HapiJS Server to work as a webpack-dev-server by the help of this plugin as well 6 | * 7 | * Author Robert Krüger +de.robat www.atroo.de 8 | */ 9 | 10 | var fs = require("fs"); 11 | var path = require("path"); 12 | var PluginUtil = require("./pluginutil"); 13 | var mime = require("mime"); 14 | var Boom = require("boom"); 15 | 16 | var compiler, 17 | io, 18 | pluginUtil, 19 | pluginDevOptions, 20 | options, 21 | _stats, 22 | devIndex; 23 | 24 | //before each request make sure that the webpack compiler is in a acceptable state (at least if we have to server files) 25 | function runPreHandlerInterceptor(req, reply) { 26 | try { 27 | pluginUtil.beforeRequest(req, reply); 28 | } catch (e) { 29 | console.error("PreHandler Fault: ", e); 30 | reply(e); 31 | } 32 | }; 33 | 34 | function sendStats(socket, stats, force) { 35 | if (!force && stats && stats.assets && stats.assets.every(function (asset) { 36 | return !asset.emitted; 37 | })) return; 38 | socket.emit("hash", stats.hash); 39 | if (stats.errors.length > 0) 40 | socket.emit("errors", stats.errors); 41 | else if (stats.warnings.length > 0) 42 | socket.emit("warnings", stats.warnings); 43 | else 44 | socket.emit("ok"); 45 | } 46 | 47 | function serveMagicHtml(req, reply) { 48 | var _path = req.path; 49 | try { 50 | if (!pluginUtil.fileSystem.statSync(pluginUtil.getFilenameFromUrl(_path + ".js")).isFile()) 51 | return false; 52 | // Serve a page that executes the javascript 53 | var res = '