Skip to content

Commit e8e2eb6

Browse files
author
Chris Joel
committed
Implement Koa middleware.
1 parent 4aeaea4 commit e8e2eb6

File tree

5 files changed

+343
-50
lines changed

5 files changed

+343
-50
lines changed

package-lock.json

+181
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"dom5": "^3.0.0",
1717
"escodegen": "^1.9.1",
1818
"express": "^4.16.3",
19+
"koa": "^2.5.0",
1920
"parse5": "^4.0.0",
2021
"through2": "^2.0.3",
2122
"vinyl-fs": "^3.0.2",
@@ -25,6 +26,7 @@
2526
"@types/acorn": "^4.0.3",
2627
"@types/escodegen": "0.0.6",
2728
"@types/express": "^4.11.1",
29+
"@types/koa": "^2.0.44",
2830
"@types/through2": "^2.0.33",
2931
"@types/vinyl": "^2.0.2",
3032
"@types/vinyl-fs": "^2.4.8",

src/file.ts

+90
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313
*/
1414

1515
import * as File from 'vinyl';
16+
import * as vfs from 'vinyl-fs';
17+
import * as nodePath from 'path';
18+
import * as fs from 'fs';
19+
import { promisify } from 'util';
20+
import { Transform } from 'stream';
21+
22+
const stat = promisify(fs.stat);
23+
1624

1725
/**
1826
* Returns the string contents of a Vinyl File object, waiting for
@@ -41,3 +49,85 @@ export async function getFileContents(file: File): Promise<string> {
4149
`It has neither a buffer nor a stream.`);
4250
};
4351

52+
53+
export class SyntheticFileMap {
54+
protected fileMap: Map<string, File> = new Map();
55+
protected watcher: fs.FSWatcher | null = null;
56+
57+
constructor(readonly basePath: string, protected transform: Transform) {
58+
this.watchFilesystem();
59+
}
60+
61+
watchFilesystem() {
62+
if (this.watcher != null) {
63+
return;
64+
}
65+
66+
this.watcher = fs.watch(this.basePath, {
67+
recursive: true,
68+
persistent: false
69+
}, (eventType: string, filename: string) =>
70+
this.onFsEvent(eventType, filename));
71+
}
72+
73+
stopWatchingFilesystem() {
74+
if (this.watcher == null) {
75+
return;
76+
}
77+
78+
this.watcher.close();
79+
}
80+
81+
async hasFile(path: string): Promise<boolean> {
82+
const filePath = this.mappedPath(path);
83+
84+
if (this.fileMap.has(filePath)) {
85+
return true;
86+
}
87+
88+
try {
89+
const stats = await stat(filePath);
90+
91+
if (!stats.isDirectory()) {
92+
return true;
93+
}
94+
} catch (e) {}
95+
96+
return false;
97+
}
98+
99+
async readFile(path: string): Promise<File> {
100+
const filePath = this.mappedPath(path);
101+
102+
if (this.fileMap.has(filePath)) {
103+
return this.fileMap.get(filePath);
104+
}
105+
106+
return await new Promise<File>((resolve, reject) => {
107+
vfs.src([filePath])
108+
.pipe(this.transform)
109+
.on('data', (file: File) => {
110+
// NOTE(cdata): A transform may emit more than one file here, as is
111+
// the case for HTML Modules => JS Modules
112+
this.fileMap.set(file.path, file);
113+
})
114+
.on('end', () => {
115+
if (this.fileMap.has(filePath)) {
116+
resolve(this.fileMap.get(filePath));
117+
} else {
118+
reject(new Error('Not found!'));
119+
}
120+
});
121+
});
122+
}
123+
124+
private mappedPath(path: string): string {
125+
return nodePath.join(this.basePath, path);
126+
}
127+
128+
private onFsEvent(_eventType: string, filePath: string): void {
129+
if (this.fileMap.has(filePath)) {
130+
this.fileMap.delete(filePath);
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)