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.
@@ -0,0 +1,12 @@
1
+ const express = () => ({
2
+ use: jest.fn(),
3
+ get: jest.fn(),
4
+ post: jest.fn(),
5
+ listen: jest.fn(),
6
+ });
7
+
8
+ express.static = jest.fn();
9
+ express.text = jest.fn();
10
+ express.json = jest.fn();
11
+
12
+ export default express;
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.1.11",
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 wsPort = document.querySelector('meta[name="ws-port"]').content;
3
- const ws = new WebSocket(`ws://localhost:${wsPort}`);
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');
@@ -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({ port: port + 1 });
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 + 1}">
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(port, markdownFile, markdownDir, renderMarp, reload, wss, __dirname);
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
- const mimeTypes = {
7
- '.html': 'text/html',
8
- '.js': 'text/javascript',
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
- export function createServer(port, markdownFile, markdownDir, renderMarp, reload, wss, __dirname) {
25
- const server = http.createServer(async (req, res) => {
12
+ app.get('/', async (req, res) => {
26
13
  try {
27
- if (req.url === '/') {
28
- const html = await renderMarp();
29
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
30
- res.end(html);
31
- } else if (req.url === '/client.js') {
32
- const clientJs = await fs.readFile(path.join(__dirname, 'client.js'), 'utf8');
33
- res.writeHead(200, { 'Content-Type': 'text/javascript' });
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
- try {
75
- const content = await fs.readFile(assetPath);
76
- res.writeHead(200, { 'Content-Type': contentType });
77
- res.end(content);
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.writeHead(500);
90
- res.end('Internal Server Error');
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
- server.listen(port, () => {
95
- console.log(`Server listening on http://localhost:${port} for ${markdownFile}`);
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 server;
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(port, markdownFile, markdownDir, renderMarp, reload, wss, __dirname);
15
+ const app = createServer(markdownDir, renderMarp, reload, wss, __dirname);
21
16
 
22
- expect(http.createServer).toHaveBeenCalled();
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
  });