├── test
├── data
│ ├── entry.js
│ └── index.html
└── suites
│ └── serverTest.js
├── .gitignore
├── 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
/test/data/entry.js:
--------------------------------------------------------------------------------
1 | console.log("test");
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 |
--------------------------------------------------------------------------------
/client/page.jade:
--------------------------------------------------------------------------------
1 | .header
2 | span#okness
3 | = " "
4 | span#status
5 | pre#errors
6 | #warnings
7 | iframe#iframe(src="javascript:;" allowfullscreen)
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hapi-webpack-dev-plugin",
3 | "version": "1.2.0",
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 | "peerDependencies": {
23 | "hapi": ">=14.x.x"
24 | },
25 | "devDependencies": {
26 | "chai": "^1.9.2",
27 | "chai-spies": "^0.5.1",
28 | "gulp": "^3.8.10",
29 | "gulp-connect": "^2.2.0",
30 | "gulp-mocha": "^1.1.1",
31 | "gulp-util": "^3.0.1",
32 | "hapi": "8.x.x",
33 | "webpack": "^1.4.12",
34 | "wreck": "^5.0.1"
35 | },
36 | "dependencies": {
37 | "boom": "^2.6.0",
38 | "jquery": "^2.1.1",
39 | "memory-fs": "^0.1.0",
40 | "mime": "^1.2.11",
41 | "socket.io": "^1.3.3"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/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 | ## >= v1.2.0 -> Requirements: hapijs >=14.x.x
11 | ## >= v1.1.2 -> Requirements: hapijs ^14.x.x
12 | ## < v1.1.2 -> Requirements: hapijs ^8.x.x
13 |
14 | State: In Progress (meaning not hot code replacement at the moment, but auto relaoding works just fine)
15 |
16 | Basic Usage
17 | =====
18 |
19 | we tend to structure our apps like
20 | ```javascript
21 | - server_plugin
22 | index.js
23 | - webpack_frontend
24 | - src
25 | main.js
26 | webpack.config.js
27 | server.js
28 | ```
29 |
30 | with that structure in mind you'd setup the dev-server-plugin in the server.js as follows
31 |
32 | ```javascript
33 | var Webpack = require('webpack');
34 | var Hapi = require('hapi');
35 |
36 | //basic webpack config, see webpack api docs for all options
37 | var webpackConf = require('./webpack_frontend/webpack.config.js');
38 | webpackConf.entry = {
39 | 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
40 | };
41 | webpackConf.devtool = 'source-map';
42 |
43 | //create the webpack compiler
44 | compiler = Webpack(webPackConfig);
45 |
46 | //create hapi server
47 | server = new Hapi.Server();
48 | server.connection({
49 | port: port,
50 | host: address
51 | });
52 |
53 | //start server and register dev server as plugin
54 | server.start(function () {
55 | server.register({
56 | register: require('hapi-webpack-dev-plugin'),
57 | options: {
58 | compiler: compiler,
59 | //no loginfo
60 | quiet: true,
61 | //where is the index.html located
62 | devIndex: ".", //not needed if devView is configured
63 | devView: { //allows to configure a view with whatever engine hapi has been configured to induce e.e. session information on startup
64 | name: 'main.html',
65 | data: function (request) {
66 | var tplData = {
67 | "entrypoint": "dist/app.js"
68 | };
69 | return tplData;
70 | }
71 | }
72 | /*
73 | ,watchDelay: 200
74 | ,noInfo: false
75 | ,headers: {
76 | x-dynamic-content: true
77 | }
78 | */
79 | }
80 | }, function (err) {
81 | console.log(err);
82 | });
83 | });
84 |
85 | ```
86 |
87 | Config Options
88 | ==============
89 |
90 |
91 | - compiler - the webpack compiler
92 | - devIndex - path to the dev index.html file. Is ignored if devView is configured. defaults to index.html in the cwd
93 | - devView
94 |
95 |
96 | ```javascript
97 | {
98 | name: "the name of the view" // mandatory, elsewise the devIndex will be used
99 | data: "tplData" // an object, a function which gets passed in the request, or empty - data is supposed to determine tplData for the view
100 | }
101 | ```
102 |
103 |
104 | -
105 | quiet - turns off webpack compiler logging
106 |
107 | - watchDelay - determines how frequently file changes are monitored
108 | - headers - hash of headers to add to webpack-dev-server-plugin served files
109 |
110 |
111 |
112 | Questions?
113 | ==============
114 |
115 | Feel free to ask questions if anything is badly described!
116 |
117 | info@atroo.de or an Issue!
118 |
119 |
120 |
--------------------------------------------------------------------------------
/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 = '';
58 | reply(res);
59 | return true;
60 | } catch (e) {
61 | return false
62 | }
63 | }
64 |
65 |
66 | /**
67 | * setup all the necessities for the plugin to work
68 | */
69 | exports.register = function (server, opts, next) {
70 | if (opts && opts.compiler) {
71 |
72 | //general options beeing presented to the whole plugin
73 | options = opts;
74 |
75 | devIndex = opts.devIndex || ".";
76 | //the webpack compiler object to work with
77 | compiler = opts.compiler;
78 |
79 | pluginUtil = PluginUtil(opts);
80 | server.ext({
81 | type: "onRequest",
82 | method: runPreHandlerInterceptor
83 | });
84 |
85 | //if the pack halts, stop the compilers watching task
86 | server.on("stop", function () {
87 | //close down the watch task of the middleware
88 | pluginUtil.close();
89 | });
90 |
91 | var invalidPlugin = function () {
92 | if (io) io.sockets.emit("invalid");
93 | }.bind(this);
94 | compiler.plugin("compile", invalidPlugin);
95 | compiler.plugin("invalid", invalidPlugin);
96 | compiler.plugin("done", function (stats) {
97 | if (!io) return;
98 | sendStats(io.sockets, stats.toJson());
99 | _stats = stats;
100 | }.bind(this));
101 |
102 | //configure some standard routes which are involved in the startup process
103 | //the js file to build the hosting iframe
104 | server.route({
105 | method: "GET",
106 | path: "/__webpack_dev_server__/live.bundle.js",
107 | handler: function (req, reply) {
108 | reply(fs.createReadStream(path.join(__dirname, "..", "client", "live.bundle.js"))).header("Content-Type", "application/javascript");;
109 | },
110 | config: {
111 | auth: false
112 | }
113 | });
114 |
115 | server.route({
116 | method: "GET",
117 | path: "/webpack-dev-server.js",
118 | handler: function (req, reply) {
119 | reply(fs.createReadStream(path.join(__dirname, "..", "client", "index.bundle.js"))).header("Content-Type", "application/javascript");
120 | },
121 | config: {
122 | auth: false
123 | }
124 | });
125 |
126 | //the html requesting the live.bundle.js
127 | server.route({
128 | method: "GET",
129 | path: "/webpack-dev-server/{anything*}",
130 | handler: function (req, reply) {
131 | reply(fs.createReadStream(path.join(__dirname, "..", "client", "live.html"))).header("Content-Type", "text/html");
132 | },
133 | config: {
134 | auth: false
135 | }
136 | });
137 |
138 | //the html requesting the live.bundle.js
139 | server.route({
140 | method: "GET",
141 | path: "/index.html",
142 | handler: function (req, reply) {
143 | if (options.devView && options.devView.name) {
144 | var data = typeof (options.devView.data) == "function" ? options.devView.data(req) : options.devView.data || {};
145 | reply.view(options.devView.name, data);
146 | } else {
147 | reply.file(path.join(devIndex, "index.html"));
148 | }
149 | },
150 | config: {
151 | auth: {
152 | mode: 'try'
153 | }
154 | }
155 | });
156 |
157 | //a listing of whats contained in the dev server
158 | server.route({
159 | method: "GET",
160 | path: "/webpack-dev-server",
161 | handler: function (req, reply) {
162 | reply.redirect("webpack-dev-server/index.html");
163 | },
164 | config: {
165 | auth: false
166 | }
167 | });
168 |
169 | //register socketio to listen to any server in the pack
170 | var socketIO = require('socket.io');
171 | server.connections.forEach(function (srv) {
172 | io = socketIO.listen(srv.listener, {
173 | serveClient: false,
174 | path: '/webpackdevserversocket',
175 | log: false
176 | });
177 | io.set('transports', ['websocket']);
178 | io.sockets.on("connection", function (socket) {
179 | if (!_stats) {
180 | return;
181 | }
182 | sendStats(socket, _stats.toJson(), true);
183 | }.bind(this));
184 | });
185 |
186 | next();
187 | } else {
188 | next("unsufficient options parameter for hapiWebpackDev Plugin. A Webpack Compiler object is needed!");
189 | }
190 | };
191 |
192 | exports.register.attributes = {
193 | pkg: require('./../package.json')
194 | };
195 |
--------------------------------------------------------------------------------