Skip to content

Commit

Permalink
Adding HTTPS serve (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
rorticus authored Aug 31, 2018
1 parent 7e6b785 commit 0bc68e5
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 13 deletions.
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ A web server can be started with the `--serve` flag while running in `dev` or `d
dojo build -s -p 3000
```

By default, the files will be served via HTTP. HTTPS can be enabled by placing `server.crt` and `server.key` files in a `.cert` directory in the root of your project:

```text
|-- my-project
|-- .cert
|-- .server.crt
|-- .server.key
```

When these files are detected, `dojo build -s` will automatically serve files via HTTPS.

### Watching

Building with the `--watch` option observes the file system for changes, and recompiles to the appropriate `output/{dist|dev|test}` directory, depending on the current `--mode`. When used in the conjunction with the `--serve` option and `--mode=dev`, `--watch=memory` can be specified to enable automatic browser updates and hot module replacement (HMR).
Expand Down Expand Up @@ -148,12 +159,12 @@ Configuration for external dependencies can be provided under the `externals` pr
* A string that indicates that this path, and any children of this path, should be loaded via the external loader.
* An object that provides additional configuration for dependencies that need to be copied into the built application. This object has the following properties:

| Property | Type | optional | Description |
| -------- | ---- | -------- | ----------- |
| `from` | `string` | false | A path relative to the root of the project specifying the location of the files or folders to copy into the build application. |
| `to` | `string` | true | A path that replaces `from` as the location to copy this dependency to. By default, dependencies will be copied to `${externalsOutputPath}/${to}` or `${externalsOutputPath}/${from}` if `to` is not specified. If there are any `.` characters in the path and it is a directory, it needs to end with a forward slash. |
| `name` | `string` | true | Either the module id or the name of the global variable referenced in the application source. |
| `inject` | `string, string[], or boolean` | true | This property indicates that this dependency defines, or includes, scripts or stylesheets that should be loaded on the page. If `inject` is set to `true`, then the file at the location specified by `to` or `from` will be loaded on the page. If this dependency is a folder, then `inject` can be set to a string or array of strings to define one or more files to inject. Each path in `inject` should be relative to `${externalsOutputPath}/${to}` or `${externalsOutputPath}/${from}` depending on whether `to` was provided. |
| Property | Type | optional | Description |
| -------- | ---- | -------- | ----------- |
| `from` | `string` | false | A path relative to the root of the project specifying the location of the files or folders to copy into the build application. |
| `to` | `string` | true | A path that replaces `from` as the location to copy this dependency to. By default, dependencies will be copied to `${externalsOutputPath}/${to}` or `${externalsOutputPath}/${from}` if `to` is not specified. If there are any `.` characters in the path and it is a directory, it needs to end with a forward slash. |
| `name` | `string` | true | Either the module id or the name of the global variable referenced in the application source. |
| `inject` | `string, string[], or boolean` | true | This property indicates that this dependency defines, or includes, scripts or stylesheets that should be loaded on the page. If `inject` is set to `true`, then the file at the location specified by `to` or `from` will be loaded on the page. If this dependency is a folder, then `inject` can be set to a string or array of strings to define one or more files to inject. Each path in `inject` should be relative to `${externalsOutputPath}/${to}` or `${externalsOutputPath}/${from}` depending on whether `to` was provided. |

As an example the following configuration will inject `src/legacy/layer.js` into the application page, inject the file that defines the `MyGlobal` global variable, declare that modules `a` and `b` are external and should be delegated to the external layer, and then copy the folder `node_modules/legacy-dep`, from which several files are injected. All of these files will be copied into the `externals` folder, which could be overridden by specifying the `outputPath` property in the `externals` configuration.

Expand Down
43 changes: 36 additions & 7 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import * as ora from 'ora';
import * as path from 'path';
import * as webpack from 'webpack';
import chalk from 'chalk';
import * as fs from 'fs';
import * as https from 'https';

const pkgDir = require('pkg-dir');
import devConfigFactory from './dev.config';
Expand Down Expand Up @@ -125,13 +127,22 @@ function memoryWatch(config: webpack.Configuration, args: any, app: express.Appl
}

function serve(config: webpack.Configuration, args: any): Promise<void> {
let isHttps = false;

const app = express();

if (args.watch !== 'memory') {
const outputDir = (config.output && config.output.path) || process.cwd();
app.use(express.static(outputDir));
}

const defaultKey = path.resolve('.cert', 'server.key');
const defaultCrt = path.resolve('.cert', 'server.crt');

if (fs.existsSync(defaultKey) && fs.existsSync(defaultCrt)) {
isHttps = true;
}

return Promise.resolve()
.then(() => {
if (args.watch === 'memory' && args.mode === 'dev') {
Expand All @@ -149,13 +160,31 @@ function serve(config: webpack.Configuration, args: any): Promise<void> {
})
.then(() => {
return new Promise<void>((resolve, reject) => {
app.listen(args.port, (error: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
if (isHttps) {
https
.createServer(
{
key: fs.readFileSync(defaultKey),
cert: fs.readFileSync(defaultCrt)
},
app
)
.listen(args.port, (error: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
} else {
app.listen(args.port, (error: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
}
});
});
}
Expand Down
71 changes: 71 additions & 0 deletions tests/unit/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { join } from 'path';
import { SinonStub, stub } from 'sinon';
import chalk from 'chalk';
import MockModule from '../support/MockModule';
import * as fs from 'fs';

let mockModule: MockModule;
let mockLogger: any;
Expand Down Expand Up @@ -43,6 +44,7 @@ describe('command', () => {
'./dev.config',
'./dist.config',
'./test.config',
'https',
'express',
'log-update',
'ora',
Expand Down Expand Up @@ -437,6 +439,75 @@ describe('command', () => {
);
});
});

describe('https', () => {
it('starts an https server if key and cert are available', () => {
const main = mockModule.getModuleUnderTest().default;

const listenStub = stub().callsFake((port: string, callback: Function) => {
callback(false);
});
const createServerStub = mockModule.getMock('https').createServer;
createServerStub.returns({
listen: listenStub
});

const existsStub = stub(fs, 'existsSync');
existsStub.returns(true);
const readStub = stub(fs, 'readFileSync');
readStub.returns('data');

return main
.run(getMockConfiguration(), {
serve: true
})
.then(() => {
assert.isTrue(
createServerStub.calledWith({
cert: 'data',
key: 'data'
})
);
existsStub.restore();
readStub.restore();
})
.catch((e: any) => {
existsStub.restore();
readStub.restore();
throw e;
});
});

it('throws https server errors', () => {
const main = mockModule.getModuleUnderTest().default;

const listenStub = stub().callsFake((port: string, callback: Function) => {
callback('there is an error');
});
const createServerStub = mockModule.getMock('https').createServer;
createServerStub.returns({
listen: listenStub
});

const existsStub = stub(fs, 'existsSync');
existsStub.returns(true);
const readStub = stub(fs, 'readFileSync');
readStub.returns('data');

return main
.run(getMockConfiguration(), {
serve: true
})
.then(() => {
throw new Error('should not resolve');
})
.catch((e: any) => {
existsStub.restore();
readStub.restore();
assert.strictEqual(e, 'there is an error');
});
});
});
});

describe('eject', () => {
Expand Down

0 comments on commit 0bc68e5

Please sign in to comment.