marp-dev-preview 0.1.11 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__mocks__/express.js +12 -0
- package/jest.config.mjs +5 -3
- package/package.json +2 -1
- package/src/client.js +3 -2
- package/src/marp-dev-preview.mjs +15 -3
- package/src/server.mjs +43 -86
- package/test/marp-dev-preview.test.mjs +9 -11
package/jest.config.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
transform: {
|
|
3
|
-
'^.+\.m?js'
|
|
4
|
-
: 'babel-jest',
|
|
3
|
+
'^.+\.m?js': 'babel-jest',
|
|
5
4
|
},
|
|
6
5
|
testEnvironment: 'node',
|
|
7
6
|
moduleFileExtensions: ['js', 'mjs'],
|
|
8
7
|
transformIgnorePatterns: [
|
|
9
8
|
'/node_modules/(?!yargs|yargs-parser)/',
|
|
10
9
|
],
|
|
11
|
-
|
|
10
|
+
moduleNameMapper: {
|
|
11
|
+
'^express$': '<rootDir>/__mocks__/express.js',
|
|
12
|
+
},
|
|
13
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "marp-dev-preview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A CLI tool to preview Marp markdown files.",
|
|
5
5
|
"main": "src/marp-dev-preview.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@marp-team/marp-core": "^4.1.0",
|
|
29
29
|
"chokidar": "^4.0.3",
|
|
30
|
+
"express": "^5.1.0",
|
|
30
31
|
"markdown-it-container": "^4.0.0",
|
|
31
32
|
"markdown-it-footnote": "^4.0.0",
|
|
32
33
|
"markdown-it-mark": "^4.0.0",
|
package/src/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
document.addEventListener('DOMContentLoaded', () => {
|
|
2
|
-
const
|
|
3
|
-
const
|
|
2
|
+
const wsHost = window.location.host;
|
|
3
|
+
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
4
|
+
const ws = new WebSocket(`${wsProtocol}//${wsHost}`);
|
|
4
5
|
|
|
5
6
|
let slides = Array.from(document.querySelectorAll('section[id]'));
|
|
6
7
|
const commandPrompt = document.getElementById('command-prompt');
|
package/src/marp-dev-preview.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import chokidar from 'chokidar';
|
|
5
|
+
import http from 'http';
|
|
5
6
|
import { WebSocketServer } from 'ws';
|
|
6
7
|
import { fileURLToPath } from 'url';
|
|
7
8
|
|
|
@@ -39,7 +40,7 @@ if (!markdownFile) {
|
|
|
39
40
|
|
|
40
41
|
const markdownDir = path.dirname(markdownFile);
|
|
41
42
|
|
|
42
|
-
const wss = new WebSocketServer({
|
|
43
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
43
44
|
|
|
44
45
|
async function renderMarp() {
|
|
45
46
|
const md = await fs.readFile(markdownFile, 'utf8');
|
|
@@ -91,7 +92,7 @@ async function renderMarp() {
|
|
|
91
92
|
<!DOCTYPE html>
|
|
92
93
|
<html>
|
|
93
94
|
<head>
|
|
94
|
-
<meta name="ws-port" content="${port
|
|
95
|
+
<meta name="ws-port" content="${port}">
|
|
95
96
|
<style id="marp-style">${css}</style>
|
|
96
97
|
<style id="custom-style">${customCss}</style>
|
|
97
98
|
<script src="https://unpkg.com/morphdom@2.7.0/dist/morphdom-umd.min.js"></script>
|
|
@@ -166,7 +167,18 @@ if (themeSet) {
|
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
initializeMarp(themeSet).then(() => {
|
|
169
|
-
createServer(
|
|
170
|
+
const app = createServer(markdownDir, renderMarp, reload, wss, __dirname);
|
|
171
|
+
const server = http.createServer(app);
|
|
172
|
+
|
|
173
|
+
server.on('upgrade', (request, socket, head) => {
|
|
174
|
+
wss.handleUpgrade(request, socket, head, ws => {
|
|
175
|
+
wss.emit('connection', ws, request);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
server.listen(port, () => {
|
|
180
|
+
console.log(`Server listening on http://localhost:${port} for ${markdownFile}`);
|
|
181
|
+
});
|
|
170
182
|
}).catch(error => {
|
|
171
183
|
console.error("Failed to initialize Marp:", error);
|
|
172
184
|
process.exit(1);
|
package/src/server.mjs
CHANGED
|
@@ -1,99 +1,56 @@
|
|
|
1
|
-
|
|
2
|
-
import http from 'http';
|
|
3
|
-
import { promises as fs } from 'fs';
|
|
1
|
+
import express from 'express';
|
|
4
2
|
import path from 'path';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
|
|
5
|
+
export function createServer(markdownDir, renderMarp, reload, wss, __dirname) {
|
|
6
|
+
const app = express();
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
'.css': 'text/css',
|
|
10
|
-
'.json': 'application/json',
|
|
11
|
-
'.png': 'image/png',
|
|
12
|
-
'.jpg': 'image/jpeg',
|
|
13
|
-
'.gif': 'image/gif',
|
|
14
|
-
'.svg': 'image/svg+xml',
|
|
15
|
-
'.wav': 'audio/wav',
|
|
16
|
-
'.mp4': 'video/mp4',
|
|
17
|
-
'.woff': 'application/font-woff',
|
|
18
|
-
'.ttf': 'application/font-ttf',
|
|
19
|
-
'.eot': 'application/vnd.ms-fontobject',
|
|
20
|
-
'.otf': 'application/font-otf',
|
|
21
|
-
'.wasm': 'application/wasm'
|
|
22
|
-
};
|
|
8
|
+
app.use(express.static(markdownDir));
|
|
9
|
+
app.use(express.text({ type: 'text/markdown' }));
|
|
10
|
+
app.use(express.json());
|
|
23
11
|
|
|
24
|
-
|
|
25
|
-
const server = http.createServer(async (req, res) => {
|
|
12
|
+
app.get('/', async (req, res) => {
|
|
26
13
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
res.end(clientJs);
|
|
35
|
-
} else if (req.url === '/api/reload' && req.method === 'POST') {
|
|
36
|
-
let body = '';
|
|
37
|
-
req.on('data', chunk => {
|
|
38
|
-
body += chunk.toString();
|
|
39
|
-
});
|
|
40
|
-
req.on('end', async () => {
|
|
41
|
-
console.debug("Reload request received");
|
|
42
|
-
const success = await reload(body);
|
|
43
|
-
if (success) {
|
|
44
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
45
|
-
res.end(JSON.stringify({ status: 'ok' }));
|
|
46
|
-
} else {
|
|
47
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
48
|
-
res.end(JSON.stringify({ status: 'error', message: 'Failed to render markdown' }));
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
} else if (req.url === '/api/command' && req.method === 'POST') {
|
|
52
|
-
let body = '';
|
|
53
|
-
req.on('data', chunk => {
|
|
54
|
-
body += chunk.toString();
|
|
55
|
-
});
|
|
56
|
-
req.on('end', () => {
|
|
57
|
-
try {
|
|
58
|
-
const command = JSON.parse(body);
|
|
59
|
-
for (const ws of wss.clients) {
|
|
60
|
-
ws.send(JSON.stringify(command));
|
|
61
|
-
}
|
|
62
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
63
|
-
res.end(JSON.stringify({ status: 'ok', command }));
|
|
64
|
-
} catch (e) {
|
|
65
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
66
|
-
res.end(JSON.stringify({ status: 'error', message: 'Invalid JSON' }));
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
} else {
|
|
70
|
-
const assetPath = path.join(markdownDir, req.url);
|
|
71
|
-
const ext = path.extname(assetPath);
|
|
72
|
-
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
14
|
+
const html = await renderMarp();
|
|
15
|
+
res.send(html);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error(error);
|
|
18
|
+
res.status(500).send('Internal Server Error');
|
|
19
|
+
}
|
|
20
|
+
});
|
|
73
21
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} catch (error) {
|
|
79
|
-
if (error.code === 'ENOENT') {
|
|
80
|
-
res.writeHead(404);
|
|
81
|
-
res.end('Not Found');
|
|
82
|
-
} else {
|
|
83
|
-
throw error;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
22
|
+
app.get('/client.js', async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const clientJs = await fs.readFile(path.join(__dirname, 'client.js'), 'utf8');
|
|
25
|
+
res.type('js').send(clientJs);
|
|
87
26
|
} catch (error) {
|
|
88
27
|
console.error(error);
|
|
89
|
-
res.
|
|
90
|
-
|
|
28
|
+
res.status(500).send('Internal Server Error');
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
app.post('/api/reload', async (req, res) => {
|
|
33
|
+
console.debug("Reload request received");
|
|
34
|
+
console.debug(req.body)
|
|
35
|
+
const success = await reload(req.body);
|
|
36
|
+
if (success) {
|
|
37
|
+
res.json({ status: 'ok' });
|
|
38
|
+
} else {
|
|
39
|
+
res.status(500).json({ status: 'error', message: 'Failed to render markdown' });
|
|
91
40
|
}
|
|
92
41
|
});
|
|
93
42
|
|
|
94
|
-
|
|
95
|
-
|
|
43
|
+
app.post('/api/command', (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
const command = req.body;
|
|
46
|
+
for (const ws of wss.clients) {
|
|
47
|
+
ws.send(JSON.stringify(command));
|
|
48
|
+
}
|
|
49
|
+
res.json({ status: 'ok', command });
|
|
50
|
+
} catch (e) {
|
|
51
|
+
res.status(400).json({ status: 'error', message: 'Invalid JSON' });
|
|
52
|
+
}
|
|
96
53
|
});
|
|
97
54
|
|
|
98
|
-
return
|
|
55
|
+
return app;
|
|
99
56
|
}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
|
+
// This test uses a mock for the express module.
|
|
2
|
+
// The mock is defined in __mocks__/express.js and configured in jest.config.mjs.
|
|
3
|
+
// This is necessary because jest has issues with importing express, which is a CJS module,
|
|
4
|
+
// in a project that uses ES modules ("type": "module" in package.json).
|
|
1
5
|
import { createServer } from '../src/server.mjs';
|
|
2
|
-
import http from 'http';
|
|
3
|
-
|
|
4
|
-
jest.mock('http', () => ({
|
|
5
|
-
createServer: jest.fn(() => ({
|
|
6
|
-
listen: jest.fn(),
|
|
7
|
-
})),
|
|
8
|
-
}));
|
|
9
6
|
|
|
10
7
|
describe('Server', () => {
|
|
11
8
|
it('should create a server', () => {
|
|
12
|
-
const port = 8080;
|
|
13
|
-
const markdownFile = 'test.md';
|
|
14
9
|
const markdownDir = '.';
|
|
15
10
|
const renderMarp = jest.fn();
|
|
16
11
|
const reload = jest.fn();
|
|
17
12
|
const wss = { clients: [] };
|
|
18
13
|
const __dirname = '.';
|
|
19
14
|
|
|
20
|
-
createServer(
|
|
15
|
+
const app = createServer(markdownDir, renderMarp, reload, wss, __dirname);
|
|
21
16
|
|
|
22
|
-
expect(
|
|
17
|
+
expect(app).toBeDefined();
|
|
18
|
+
expect(typeof app.use).toBe('function');
|
|
19
|
+
expect(typeof app.get).toBe('function');
|
|
20
|
+
expect(typeof app.post).toBe('function');
|
|
23
21
|
});
|
|
24
22
|
});
|