UNPKG

6.28 kBPlain TextView Raw
1import * as fs from 'fs-extra';
2import {
3 Clients,
4 CompiledFiles,
5 JavaScript,
6 TypeScript,
7 Transformer,
8 CustomHTTPHeaders,
9 HTTPHeaders,
10 FileContentsResult
11} from '../index.d.ts';
12import * as chokidar from 'chokidar';
13import * as WebSocket from 'ws';
14import * as tsc from 'typescript';
15import * as babel from '@babel/core';
16import { resolveBareSpecifiers } from './babel-plugins/babel-plugin-transform-resolve-bare-specifiers.js';
17import { resolveImportPathExtensions } from './babel-plugins/babel-plugin-transform-resolve-import-path-extensions.js';
18
19export async function getFileContents(params: {
20 url: string;
21 compiledFiles: CompiledFiles;
22 disableSpa: boolean;
23 watchFiles: boolean;
24 clients: Clients;
25 transformer: Transformer | 'NOT_SET';
26}): Promise<Readonly<FileContentsResult>> {
27
28 const cachedFileContents: Readonly<Buffer> | null | undefined = await returnFileContentsFromCache({
29 url: params.url,
30 compiledFiles: params.compiledFiles
31 });
32
33 if (
34 cachedFileContents !== null &&
35 cachedFileContents !== undefined
36 ) {
37 return {
38 fileContents: cachedFileContents
39 };
40 }
41 else {
42
43 if (await (fs.exists as any)(params.url)) {
44 const fileContents: Readonly<Buffer> = await fs.readFile(params.url);
45
46 const transformedFileContents: Readonly<Buffer> = params.transformer === 'NOT_SET' ? fileContents : Buffer.from(params.transformer(fileContents.toString()));
47
48 params.compiledFiles[params.url] = transformedFileContents;
49
50 watchFile({
51 filePath: params.url,
52 watchFiles: params.watchFiles,
53 clients: params.clients,
54 compiledFiles: params.compiledFiles
55 });
56
57 return {
58 fileContents: transformedFileContents
59 };
60 }
61
62 if (!params.disableSpa) {
63 const indexFileContents: Readonly<Buffer> = await fs.readFile(`./index.html`);
64
65 return {
66 fileContents: indexFileContents
67 };
68 }
69 else {
70 return 'FILE_NOT_FOUND';
71 }
72
73 }
74}
75
76async function returnFileContentsFromCache(params: {
77 url: string;
78 compiledFiles: CompiledFiles;
79}): Promise<Readonly<Buffer> | null | undefined> {
80
81 const cachedFileContents: Readonly<Buffer> | null | undefined = params.compiledFiles[params.url];
82
83 return cachedFileContents;
84}
85
86export function watchFile(params: {
87 filePath: string;
88 watchFiles: boolean;
89 clients: Clients;
90 compiledFiles: CompiledFiles;
91}) {
92 if (params.watchFiles) {
93 chokidar.watch(params.filePath).on('change', () => {
94
95 params.compiledFiles[params.filePath] = null;
96
97 Object.values(params.clients).forEach((client: Readonly<WebSocket>) => {
98 try {
99 client.send('RELOAD_MESSAGE');
100 }
101 catch(error) {
102 //TODO something should be done about this. What's happening I believe is that if two files are changed in a very short period of time, one file will start the browser reloading, and the other file will try to send a message to the browser while it is reloading, and thus the websocket connection will not be established with the browser. This is a temporary solution
103 console.log(error);
104 }
105 });
106 });
107 }
108}
109
110export function addGlobals(params: {
111 source: JavaScript;
112 wsPort: number;
113}): JavaScript {
114 return `
115 var process = self.process;
116 if (!self.ZWITTERION_SOCKET && self.location.host.includes('localhost:')) {
117 self.ZWITTERION_SOCKET = new WebSocket('ws://127.0.0.1:${params.wsPort}');
118 self.ZWITTERION_SOCKET.addEventListener('message', (message) => {
119 self.location.reload();
120 });
121 }
122 ${params.source}
123 `;
124}
125
126export function compileToJs(params: {
127 source: JavaScript | TypeScript;
128 jsTarget: string;
129 filePath: string;
130}): JavaScript {
131 const typeScriptTranspileOutput: Readonly<tsc.TranspileOutput> = tsc.transpileModule(params.source, {
132 compilerOptions: {
133 module: 'ES2015' as unknown as tsc.ModuleKind,
134 target: params.jsTarget as any
135 }
136 });
137
138 const babelFileResult: Readonly<babel.BabelFileResult> | null = babel.transform(typeScriptTranspileOutput.outputText, {
139 'plugins': [
140 require('@babel/plugin-syntax-dynamic-import'),
141 resolveBareSpecifiers(params.filePath, false),
142 resolveImportPathExtensions(params.filePath)
143 ]
144 });
145
146 if (
147 babelFileResult === null ||
148 babelFileResult.code === null ||
149 babelFileResult.code === undefined
150 ) {
151 throw new Error(`Compilation error`);
152 }
153
154 return babelFileResult.code;
155}
156
157export async function getCustomHTTPHeadersFromFile(headersFilePath: string) {
158 const headersFile: Readonly<Buffer> = await fs.readFile(headersFilePath);
159 return JSON.parse(headersFile.toString());
160}
161
162export function getCustomHTTPHeadersForURL(params: {
163 customHTTPHeaders: Readonly<CustomHTTPHeaders>;
164 url: string;
165 defaultHTTPHeaders: Readonly<HTTPHeaders>;
166}): Readonly<HTTPHeaders> {
167 return Object.keys(params.customHTTPHeaders).reduce((result: Readonly<HTTPHeaders>, customHTTPHeaderRegex: string) => {
168
169 if (params.url.match(customHTTPHeaderRegex)) {
170 return {
171 ...result,
172 ...params.customHTTPHeaders[customHTTPHeaderRegex]
173 };
174 }
175
176 return result;
177 }, params.defaultHTTPHeaders);
178}
179
180export function wrapWasmInJS(binary: Readonly<Uint8Array>): JavaScript {
181 return `
182 //TODO perhaps there is a better way to get the ArrayBuffer that wasm needs...but for now this works
183 const base64EncodedByteCode = Uint8Array.from('${binary}'.split(','));
184
185 export default WebAssembly.instantiate(base64EncodedByteCode, {
186 env: {
187 abort: () => console.log('aborting')
188 }
189 }).then((result) => {
190 return result.instance.exports;
191 });
192 `;
193}
\No newline at end of file