Skip to content

Commit

Permalink
spa routing support
Browse files Browse the repository at this point in the history
  • Loading branch information
lifeart committed Sep 26, 2021
1 parent 83fcd48 commit a375bdc
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 5 deletions.
33 changes: 29 additions & 4 deletions lib/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ interface MiddlewareOptions {
liveReloadPath?: string;
}

function findClosestIndexFileForPath(outputPath: string, prefix: string): string | undefined {
const candidates = [];
const parts = prefix.split('/');
while (parts.length) {
parts.pop();
candidates.push(resolvePath(outputPath, [...parts, 'index.html'].join(path.sep)));
}
return candidates.find(file => fs.existsSync(file));
}

// You must call watcher.start() before you call `getMiddleware`
//
// This middleware is for development use only. It hasn't been reviewed
Expand All @@ -45,7 +55,7 @@ function handleRequest(
// eslint-disable-next-line node/no-deprecated-api
const urlObj = url.parse(request.url);
const pathname = urlObj.pathname || '';
let filename: string, stat;
let filename: string, stat!: fs.Stats;

try {
filename = decodeURIComponent(pathname);
Expand All @@ -66,9 +76,24 @@ function handleRequest(
try {
stat = fs.statSync(filename);
} catch (e) {
// not found
next();
return;
const nameStats = path.parse(filename);
const maybeIndex = findClosestIndexFileForPath(outputPath, filename.substr(1));

// if it's looks like an SPA path
if (nameStats.ext === '' && maybeIndex) {
filename = maybeIndex.replace(path.sep + 'index.html', '');
try {
stat = fs.statSync(filename);
} catch (e) {
// not found
next();
return;
}
} else {
// not found
next();
return;
}
}

if (stat.isDirectory()) {
Expand Down
2 changes: 1 addition & 1 deletion test/builder_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,7 @@ describe('Builder', function() {
// the actual results of process.hrtime() are not
// reliable
if (process.env.CI !== 'true') {
expect(a).to.be.within(b, b + 10e6);
expect(a).to.be.within(b, b + 15e6);
}
};

Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/spa/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<html>
<body>Hello from SPA</body>
</html>
31 changes: 31 additions & 0 deletions test/server_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,37 @@ describe('server', function() {
expect(statusCode).to.eql(200);
}).timeout(5000);

it('support SPA routing to index.html from child paths', async function() {
const mockUI = new MockUI();
const builder = new Builder(new broccoliSource.WatchedDir('test/fixtures/spa'));
const watcher = new Watcher(builder, []);
server = new Server.Server(watcher, '127.0.0.1', PORT, undefined, mockUI);
server.start();
await new Promise(resolve => {
server.instance.on('listening', resolve);
});
const { statusCode, body } = await got(`http://127.0.0.1:${PORT}/foo/bar/baz`); // basic serving
expect(statusCode).to.eql(200);
expect(body).to.contain('Hello from SPA');
}).timeout(5000);

it("skip SPA routing to index.html from child path if it's ends with extension", async function() {
const mockUI = new MockUI();
const builder = new Builder(new broccoliSource.WatchedDir('test/fixtures/spa'));
const watcher = new Watcher(builder, []);
server = new Server.Server(watcher, '127.0.0.1', PORT, undefined, mockUI);
server.start();
await new Promise(resolve => {
server.instance.on('listening', resolve);
});
try {
await got(`http://127.0.0.1:${PORT}/foo/bar/baz.png`);
expect.fail('expected rejection');
} catch (e) {
expect(e.body).to.include(`Cannot GET /foo/bar/baz.png`);
}
}).timeout(5000);

it('buildSuccess is handled', async function() {
const mockUI = new MockUI();
const builder = new Builder(new broccoliSource.WatchedDir('test/fixtures/basic'));
Expand Down

0 comments on commit a375bdc

Please sign in to comment.