Skip to content

Commit a375bdc

Browse files
committed
spa routing support
1 parent 83fcd48 commit a375bdc

File tree

4 files changed

+64
-5
lines changed

4 files changed

+64
-5
lines changed

lib/middleware.ts

+29-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ interface MiddlewareOptions {
2727
liveReloadPath?: string;
2828
}
2929

30+
function findClosestIndexFileForPath(outputPath: string, prefix: string): string | undefined {
31+
const candidates = [];
32+
const parts = prefix.split('/');
33+
while (parts.length) {
34+
parts.pop();
35+
candidates.push(resolvePath(outputPath, [...parts, 'index.html'].join(path.sep)));
36+
}
37+
return candidates.find(file => fs.existsSync(file));
38+
}
39+
3040
// You must call watcher.start() before you call `getMiddleware`
3141
//
3242
// This middleware is for development use only. It hasn't been reviewed
@@ -45,7 +55,7 @@ function handleRequest(
4555
// eslint-disable-next-line node/no-deprecated-api
4656
const urlObj = url.parse(request.url);
4757
const pathname = urlObj.pathname || '';
48-
let filename: string, stat;
58+
let filename: string, stat!: fs.Stats;
4959

5060
try {
5161
filename = decodeURIComponent(pathname);
@@ -66,9 +76,24 @@ function handleRequest(
6676
try {
6777
stat = fs.statSync(filename);
6878
} catch (e) {
69-
// not found
70-
next();
71-
return;
79+
const nameStats = path.parse(filename);
80+
const maybeIndex = findClosestIndexFileForPath(outputPath, filename.substr(1));
81+
82+
// if it's looks like an SPA path
83+
if (nameStats.ext === '' && maybeIndex) {
84+
filename = maybeIndex.replace(path.sep + 'index.html', '');
85+
try {
86+
stat = fs.statSync(filename);
87+
} catch (e) {
88+
// not found
89+
next();
90+
return;
91+
}
92+
} else {
93+
// not found
94+
next();
95+
return;
96+
}
7297
}
7398

7499
if (stat.isDirectory()) {

test/builder_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1040,7 +1040,7 @@ describe('Builder', function() {
10401040
// the actual results of process.hrtime() are not
10411041
// reliable
10421042
if (process.env.CI !== 'true') {
1043-
expect(a).to.be.within(b, b + 10e6);
1043+
expect(a).to.be.within(b, b + 15e6);
10441044
}
10451045
};
10461046

test/fixtures/spa/index.html

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<html>
2+
<body>Hello from SPA</body>
3+
</html>

test/server_test.js

+31
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,37 @@ describe('server', function() {
113113
expect(statusCode).to.eql(200);
114114
}).timeout(5000);
115115

116+
it('support SPA routing to index.html from child paths', async function() {
117+
const mockUI = new MockUI();
118+
const builder = new Builder(new broccoliSource.WatchedDir('test/fixtures/spa'));
119+
const watcher = new Watcher(builder, []);
120+
server = new Server.Server(watcher, '127.0.0.1', PORT, undefined, mockUI);
121+
server.start();
122+
await new Promise(resolve => {
123+
server.instance.on('listening', resolve);
124+
});
125+
const { statusCode, body } = await got(`http://127.0.0.1:${PORT}/foo/bar/baz`); // basic serving
126+
expect(statusCode).to.eql(200);
127+
expect(body).to.contain('Hello from SPA');
128+
}).timeout(5000);
129+
130+
it("skip SPA routing to index.html from child path if it's ends with extension", async function() {
131+
const mockUI = new MockUI();
132+
const builder = new Builder(new broccoliSource.WatchedDir('test/fixtures/spa'));
133+
const watcher = new Watcher(builder, []);
134+
server = new Server.Server(watcher, '127.0.0.1', PORT, undefined, mockUI);
135+
server.start();
136+
await new Promise(resolve => {
137+
server.instance.on('listening', resolve);
138+
});
139+
try {
140+
await got(`http://127.0.0.1:${PORT}/foo/bar/baz.png`);
141+
expect.fail('expected rejection');
142+
} catch (e) {
143+
expect(e.body).to.include(`Cannot GET /foo/bar/baz.png`);
144+
}
145+
}).timeout(5000);
146+
116147
it('buildSuccess is handled', async function() {
117148
const mockUI = new MockUI();
118149
const builder = new Builder(new broccoliSource.WatchedDir('test/fixtures/basic'));

0 commit comments

Comments
 (0)