pulse-js-framework 1.2.0 → 1.4.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/cli/build.js CHANGED
@@ -1,318 +1,341 @@
1
- /**
2
- * Pulse Build System
3
- *
4
- * Builds Pulse projects for production
5
- */
6
-
7
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'fs';
8
- import { join, extname, relative, dirname } from 'path';
9
- import { compile } from '../compiler/index.js';
10
-
11
- /**
12
- * Build project for production
13
- */
14
- export async function buildProject(args) {
15
- const root = process.cwd();
16
- const outDir = join(root, 'dist');
17
-
18
- // Check if vite is available
19
- try {
20
- const viteConfig = join(root, 'vite.config.js');
21
- if (existsSync(viteConfig)) {
22
- console.log('Vite config detected, using Vite build...');
23
- const { build } = await import('vite');
24
- await build({ root });
25
- return;
26
- }
27
- } catch (e) {
28
- // Vite not available, use built-in build
29
- }
30
-
31
- console.log('Building with Pulse compiler...');
32
-
33
- // Create output directory
34
- if (!existsSync(outDir)) {
35
- mkdirSync(outDir, { recursive: true });
36
- }
37
-
38
- // Copy public files
39
- const publicDir = join(root, 'public');
40
- if (existsSync(publicDir)) {
41
- copyDir(publicDir, outDir);
42
- }
43
-
44
- // Process source files
45
- const srcDir = join(root, 'src');
46
- if (existsSync(srcDir)) {
47
- processDirectory(srcDir, join(outDir, 'assets'));
48
- }
49
-
50
- // Copy and process index.html
51
- const indexHtml = join(root, 'index.html');
52
- if (existsSync(indexHtml)) {
53
- let html = readFileSync(indexHtml, 'utf-8');
54
-
55
- // Update script paths for production
56
- html = html.replace(
57
- /src="\/src\/([^"]+)"/g,
58
- 'src="/assets/$1"'
59
- );
60
-
61
- // Rewrite .pulse imports to .js
62
- html = html.replace(/\.pulse"/g, '.js"');
63
-
64
- writeFileSync(join(outDir, 'index.html'), html);
65
- }
66
-
67
- // Bundle runtime
68
- bundleRuntime(outDir);
69
-
70
- console.log(`
71
- Build complete!
72
-
73
- Output directory: ${relative(root, outDir)}
74
-
75
- To preview the build:
76
- npx serve dist
77
- `);
78
- }
79
-
80
- /**
81
- * Process a directory of source files
82
- */
83
- function processDirectory(srcDir, outDir) {
84
- if (!existsSync(outDir)) {
85
- mkdirSync(outDir, { recursive: true });
86
- }
87
-
88
- const files = readdirSync(srcDir);
89
-
90
- for (const file of files) {
91
- const srcPath = join(srcDir, file);
92
- const stat = statSync(srcPath);
93
-
94
- if (stat.isDirectory()) {
95
- processDirectory(srcPath, join(outDir, file));
96
- } else if (file.endsWith('.pulse')) {
97
- // Compile .pulse files
98
- const source = readFileSync(srcPath, 'utf-8');
99
- const result = compile(source, {
100
- runtime: './runtime.js',
101
- minify: true
102
- });
103
-
104
- if (result.success) {
105
- const outPath = join(outDir, file.replace('.pulse', '.js'));
106
- writeFileSync(outPath, result.code);
107
- console.log(` Compiled: ${file}`);
108
- } else {
109
- console.error(` Error compiling ${file}:`);
110
- for (const error of result.errors) {
111
- console.error(` ${error.message}`);
112
- }
113
- }
114
- } else if (file.endsWith('.js') || file.endsWith('.mjs')) {
115
- // Process JS files - rewrite imports
116
- let content = readFileSync(srcPath, 'utf-8');
117
-
118
- // Rewrite .pulse imports to .js
119
- content = content.replace(/from\s+['"]([^'"]+)\.pulse['"]/g, "from '$1.js'");
120
-
121
- // Rewrite runtime imports
122
- content = content.replace(
123
- /from\s+['"]pulse-framework\/runtime['"]/g,
124
- "from './runtime.js'"
125
- );
126
-
127
- // Minify
128
- content = minifyJS(content);
129
-
130
- const outPath = join(outDir, file);
131
- writeFileSync(outPath, content);
132
- console.log(` Processed & minified: ${file}`);
133
- } else {
134
- // Copy other files
135
- const outPath = join(outDir, file);
136
- copyFileSync(srcPath, outPath);
137
- }
138
- }
139
- }
140
-
141
- /**
142
- * Bundle the runtime into a single file
143
- */
144
- function bundleRuntime(outDir, shouldMinify = true) {
145
- // For simplicity, we'll create a minimal runtime bundle
146
- // In production, you'd want to use a proper bundler
147
-
148
- let runtimeCode = `
149
- // Pulse Runtime (bundled)
150
- ${readRuntimeFile('pulse.js')}
151
- ${readRuntimeFile('dom.js')}
152
- ${readRuntimeFile('router.js')}
153
- ${readRuntimeFile('store.js')}
154
- `;
155
-
156
- if (shouldMinify) {
157
- runtimeCode = minifyJS(runtimeCode);
158
- console.log(' Bundled & minified: runtime.js');
159
- } else {
160
- console.log(' Bundled: runtime.js');
161
- }
162
-
163
- writeFileSync(join(outDir, 'assets', 'runtime.js'), runtimeCode);
164
- }
165
-
166
- /**
167
- * Read a runtime file
168
- */
169
- function readRuntimeFile(filename) {
170
- const paths = [
171
- join(process.cwd(), 'node_modules', 'pulse-framework', 'runtime', filename),
172
- join(dirname(new URL(import.meta.url).pathname), '..', 'runtime', filename)
173
- ];
174
-
175
- for (const path of paths) {
176
- if (existsSync(path)) {
177
- return readFileSync(path, 'utf-8');
178
- }
179
- }
180
-
181
- console.warn(` Warning: Could not find runtime file: ${filename}`);
182
- return '';
183
- }
184
-
185
- /**
186
- * Minify JavaScript code (simple minification)
187
- */
188
- export function minifyJS(code) {
189
- return code
190
- // Remove single-line comments (but not URLs with //)
191
- .replace(/(?<!:)\/\/.*$/gm, '')
192
- // Remove multi-line comments
193
- .replace(/\/\*[\s\S]*?\*\//g, '')
194
- // Remove leading/trailing whitespace per line
195
- .split('\n')
196
- .map(line => line.trim())
197
- .filter(line => line.length > 0)
198
- .join('\n')
199
- // Collapse multiple newlines
200
- .replace(/\n{2,}/g, '\n')
201
- // Remove spaces around operators (simple)
202
- .replace(/\s*([{};,:])\s*/g, '$1')
203
- .replace(/\s*=\s*/g, '=')
204
- .replace(/\s*\(\s*/g, '(')
205
- .replace(/\s*\)\s*/g, ')')
206
- // Collapse remaining whitespace
207
- .replace(/\s+/g, ' ')
208
- .trim();
209
- }
210
-
211
- /**
212
- * Copy a directory recursively
213
- */
214
- function copyDir(src, dest) {
215
- if (!existsSync(dest)) {
216
- mkdirSync(dest, { recursive: true });
217
- }
218
-
219
- const files = readdirSync(src);
220
-
221
- for (const file of files) {
222
- const srcPath = join(src, file);
223
- const destPath = join(dest, file);
224
- const stat = statSync(srcPath);
225
-
226
- if (stat.isDirectory()) {
227
- copyDir(srcPath, destPath);
228
- } else {
229
- copyFileSync(srcPath, destPath);
230
- }
231
- }
232
- }
233
-
234
- /**
235
- * Preview production build
236
- */
237
- export async function previewBuild(args) {
238
- const port = parseInt(args[0]) || 4173;
239
- const root = process.cwd();
240
- const distDir = join(root, 'dist');
241
-
242
- if (!existsSync(distDir)) {
243
- console.error('No dist folder found. Run "pulse build" first.');
244
- process.exit(1);
245
- }
246
-
247
- // Check if vite is available for preview
248
- try {
249
- const viteConfig = join(root, 'vite.config.js');
250
- if (existsSync(viteConfig)) {
251
- console.log('Using Vite preview...');
252
- const { preview } = await import('vite');
253
- const server = await preview({
254
- root,
255
- preview: { port }
256
- });
257
- server.printUrls();
258
- return;
259
- }
260
- } catch (e) {
261
- // Vite not available, use built-in server
262
- }
263
-
264
- // Built-in static server for dist
265
- const { createServer } = await import('http');
266
-
267
- const MIME_TYPES = {
268
- '.html': 'text/html',
269
- '.js': 'application/javascript',
270
- '.css': 'text/css',
271
- '.json': 'application/json',
272
- '.png': 'image/png',
273
- '.jpg': 'image/jpeg',
274
- '.gif': 'image/gif',
275
- '.svg': 'image/svg+xml',
276
- '.ico': 'image/x-icon',
277
- '.woff': 'font/woff',
278
- '.woff2': 'font/woff2'
279
- };
280
-
281
- const server = createServer((req, res) => {
282
- let pathname = new URL(req.url, `http://localhost:${port}`).pathname;
283
-
284
- // SPA fallback - serve index.html for routes
285
- if (pathname === '/' || !pathname.includes('.')) {
286
- pathname = '/index.html';
287
- }
288
-
289
- const filePath = join(distDir, pathname);
290
-
291
- if (existsSync(filePath) && statSync(filePath).isFile()) {
292
- const ext = filePath.substring(filePath.lastIndexOf('.'));
293
- const mimeType = MIME_TYPES[ext] || 'application/octet-stream';
294
-
295
- res.writeHead(200, {
296
- 'Content-Type': mimeType,
297
- 'Cache-Control': 'public, max-age=31536000'
298
- });
299
- res.end(readFileSync(filePath));
300
- } else {
301
- res.writeHead(404, { 'Content-Type': 'text/plain' });
302
- res.end('Not Found');
303
- }
304
- });
305
-
306
- server.listen(port, () => {
307
- console.log(`
308
- Pulse Preview Server running at:
309
-
310
- Local: http://localhost:${port}/
311
-
312
- Serving production build from: dist/
313
- Press Ctrl+C to stop.
314
- `);
315
- });
316
- }
317
-
318
- export default { buildProject, previewBuild, minifyJS };
1
+ /**
2
+ * Pulse Build System
3
+ *
4
+ * Builds Pulse projects for production
5
+ */
6
+
7
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'fs';
8
+ import { join, extname, relative, dirname } from 'path';
9
+ import { compile } from '../compiler/index.js';
10
+
11
+ /**
12
+ * Build project for production
13
+ */
14
+ export async function buildProject(args) {
15
+ const root = process.cwd();
16
+ const outDir = join(root, 'dist');
17
+
18
+ // Check if vite is available
19
+ try {
20
+ const viteConfig = join(root, 'vite.config.js');
21
+ if (existsSync(viteConfig)) {
22
+ console.log('Vite config detected, using Vite build...');
23
+ const { build } = await import('vite');
24
+ await build({ root });
25
+ return;
26
+ }
27
+ } catch (e) {
28
+ // Vite not available, use built-in build
29
+ }
30
+
31
+ console.log('Building with Pulse compiler...');
32
+
33
+ // Create output directory
34
+ if (!existsSync(outDir)) {
35
+ mkdirSync(outDir, { recursive: true });
36
+ }
37
+
38
+ // Copy public files
39
+ const publicDir = join(root, 'public');
40
+ if (existsSync(publicDir)) {
41
+ copyDir(publicDir, outDir);
42
+ }
43
+
44
+ // Process source files
45
+ const srcDir = join(root, 'src');
46
+ if (existsSync(srcDir)) {
47
+ processDirectory(srcDir, join(outDir, 'assets'));
48
+ }
49
+
50
+ // Copy and process index.html
51
+ const indexHtml = join(root, 'index.html');
52
+ if (existsSync(indexHtml)) {
53
+ let html = readFileSync(indexHtml, 'utf-8');
54
+
55
+ // Update script paths for production
56
+ html = html.replace(
57
+ /src="\/src\/([^"]+)"/g,
58
+ 'src="/assets/$1"'
59
+ );
60
+
61
+ // Rewrite .pulse imports to .js
62
+ html = html.replace(/\.pulse"/g, '.js"');
63
+
64
+ writeFileSync(join(outDir, 'index.html'), html);
65
+ }
66
+
67
+ // Bundle runtime
68
+ bundleRuntime(outDir);
69
+
70
+ console.log(`
71
+ Build complete!
72
+
73
+ Output directory: ${relative(root, outDir)}
74
+
75
+ To preview the build:
76
+ npx serve dist
77
+ `);
78
+ }
79
+
80
+ /**
81
+ * Process a directory of source files
82
+ */
83
+ function processDirectory(srcDir, outDir) {
84
+ if (!existsSync(outDir)) {
85
+ mkdirSync(outDir, { recursive: true });
86
+ }
87
+
88
+ const files = readdirSync(srcDir);
89
+
90
+ for (const file of files) {
91
+ const srcPath = join(srcDir, file);
92
+ const stat = statSync(srcPath);
93
+
94
+ if (stat.isDirectory()) {
95
+ processDirectory(srcPath, join(outDir, file));
96
+ } else if (file.endsWith('.pulse')) {
97
+ // Compile .pulse files
98
+ const source = readFileSync(srcPath, 'utf-8');
99
+ const result = compile(source, {
100
+ runtime: './runtime.js',
101
+ minify: true
102
+ });
103
+
104
+ if (result.success) {
105
+ const outPath = join(outDir, file.replace('.pulse', '.js'));
106
+ writeFileSync(outPath, result.code);
107
+ console.log(` Compiled: ${file}`);
108
+ } else {
109
+ console.error(` Error compiling ${file}:`);
110
+ for (const error of result.errors) {
111
+ console.error(` ${error.message}`);
112
+ }
113
+ }
114
+ } else if (file.endsWith('.js') || file.endsWith('.mjs')) {
115
+ // Process JS files - rewrite imports
116
+ let content = readFileSync(srcPath, 'utf-8');
117
+
118
+ // Rewrite .pulse imports to .js
119
+ content = content.replace(/from\s+['"]([^'"]+)\.pulse['"]/g, "from '$1.js'");
120
+
121
+ // Rewrite runtime imports
122
+ content = content.replace(
123
+ /from\s+['"]pulse-framework\/runtime['"]/g,
124
+ "from './runtime.js'"
125
+ );
126
+
127
+ // Minify
128
+ content = minifyJS(content);
129
+
130
+ const outPath = join(outDir, file);
131
+ writeFileSync(outPath, content);
132
+ console.log(` Processed & minified: ${file}`);
133
+ } else {
134
+ // Copy other files
135
+ const outPath = join(outDir, file);
136
+ copyFileSync(srcPath, outPath);
137
+ }
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Bundle the runtime into a single file
143
+ */
144
+ function bundleRuntime(outDir, shouldMinify = true) {
145
+ // For simplicity, we'll create a minimal runtime bundle
146
+ // In production, you'd want to use a proper bundler
147
+
148
+ let runtimeCode = `
149
+ // Pulse Runtime (bundled)
150
+ ${readRuntimeFile('pulse.js')}
151
+ ${readRuntimeFile('dom.js')}
152
+ ${readRuntimeFile('router.js')}
153
+ ${readRuntimeFile('store.js')}
154
+ `;
155
+
156
+ if (shouldMinify) {
157
+ runtimeCode = minifyJS(runtimeCode);
158
+ console.log(' Bundled & minified: runtime.js');
159
+ } else {
160
+ console.log(' Bundled: runtime.js');
161
+ }
162
+
163
+ writeFileSync(join(outDir, 'assets', 'runtime.js'), runtimeCode);
164
+ }
165
+
166
+ /**
167
+ * Read a runtime file
168
+ */
169
+ function readRuntimeFile(filename) {
170
+ const paths = [
171
+ join(process.cwd(), 'node_modules', 'pulse-framework', 'runtime', filename),
172
+ join(dirname(new URL(import.meta.url).pathname), '..', 'runtime', filename)
173
+ ];
174
+
175
+ for (const path of paths) {
176
+ if (existsSync(path)) {
177
+ return readFileSync(path, 'utf-8');
178
+ }
179
+ }
180
+
181
+ console.warn(` Warning: Could not find runtime file: ${filename}`);
182
+ return '';
183
+ }
184
+
185
+ /**
186
+ * Minify JavaScript code (simple minification)
187
+ * String-aware: preserves content inside string literals
188
+ */
189
+ export function minifyJS(code) {
190
+ // Extract strings to protect them from minification
191
+ const strings = [];
192
+ const placeholder = '\x00STR';
193
+
194
+ // Replace strings with placeholders (handles ", ', and ` with escape sequences)
195
+ const withPlaceholders = code.replace(
196
+ /(["'`])(?:\\.|(?!\1)[^\\])*\1/g,
197
+ (match) => {
198
+ strings.push(match);
199
+ return placeholder + (strings.length - 1) + '\x00';
200
+ }
201
+ );
202
+
203
+ // Apply minification to non-string parts
204
+ let minified = withPlaceholders
205
+ // Remove single-line comments
206
+ .replace(/\/\/.*$/gm, '')
207
+ // Remove multi-line comments
208
+ .replace(/\/\*[\s\S]*?\*\//g, '')
209
+ // Remove leading/trailing whitespace per line
210
+ .split('\n')
211
+ .map(line => line.trim())
212
+ .filter(line => line.length > 0)
213
+ .join('\n')
214
+ // Collapse multiple newlines
215
+ .replace(/\n{2,}/g, '\n')
216
+ // Remove spaces around operators (simple)
217
+ .replace(/\s*([{};,:])\s*/g, '$1')
218
+ .replace(/\s*=\s*/g, '=')
219
+ .replace(/\s*\(\s*/g, '(')
220
+ .replace(/\s*\)\s*/g, ')')
221
+ // Collapse remaining whitespace
222
+ .replace(/\s+/g, ' ')
223
+ .trim();
224
+
225
+ // Restore strings
226
+ minified = minified.replace(
227
+ new RegExp(placeholder.replace('\x00', '\\x00') + '(\\d+)\\x00', 'g'),
228
+ (_, index) => strings[parseInt(index)]
229
+ );
230
+
231
+ return minified;
232
+ }
233
+
234
+ /**
235
+ * Copy a directory recursively
236
+ */
237
+ function copyDir(src, dest) {
238
+ if (!existsSync(dest)) {
239
+ mkdirSync(dest, { recursive: true });
240
+ }
241
+
242
+ const files = readdirSync(src);
243
+
244
+ for (const file of files) {
245
+ const srcPath = join(src, file);
246
+ const destPath = join(dest, file);
247
+ const stat = statSync(srcPath);
248
+
249
+ if (stat.isDirectory()) {
250
+ copyDir(srcPath, destPath);
251
+ } else {
252
+ copyFileSync(srcPath, destPath);
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Preview production build
259
+ */
260
+ export async function previewBuild(args) {
261
+ const port = parseInt(args[0]) || 4173;
262
+ const root = process.cwd();
263
+ const distDir = join(root, 'dist');
264
+
265
+ if (!existsSync(distDir)) {
266
+ console.error('No dist folder found. Run "pulse build" first.');
267
+ process.exit(1);
268
+ }
269
+
270
+ // Check if vite is available for preview
271
+ try {
272
+ const viteConfig = join(root, 'vite.config.js');
273
+ if (existsSync(viteConfig)) {
274
+ console.log('Using Vite preview...');
275
+ const { preview } = await import('vite');
276
+ const server = await preview({
277
+ root,
278
+ preview: { port }
279
+ });
280
+ server.printUrls();
281
+ return;
282
+ }
283
+ } catch (e) {
284
+ // Vite not available, use built-in server
285
+ }
286
+
287
+ // Built-in static server for dist
288
+ const { createServer } = await import('http');
289
+
290
+ const MIME_TYPES = {
291
+ '.html': 'text/html',
292
+ '.js': 'application/javascript',
293
+ '.css': 'text/css',
294
+ '.json': 'application/json',
295
+ '.png': 'image/png',
296
+ '.jpg': 'image/jpeg',
297
+ '.gif': 'image/gif',
298
+ '.svg': 'image/svg+xml',
299
+ '.ico': 'image/x-icon',
300
+ '.woff': 'font/woff',
301
+ '.woff2': 'font/woff2'
302
+ };
303
+
304
+ const server = createServer((req, res) => {
305
+ let pathname = new URL(req.url, `http://localhost:${port}`).pathname;
306
+
307
+ // SPA fallback - serve index.html for routes
308
+ if (pathname === '/' || !pathname.includes('.')) {
309
+ pathname = '/index.html';
310
+ }
311
+
312
+ const filePath = join(distDir, pathname);
313
+
314
+ if (existsSync(filePath) && statSync(filePath).isFile()) {
315
+ const ext = filePath.substring(filePath.lastIndexOf('.'));
316
+ const mimeType = MIME_TYPES[ext] || 'application/octet-stream';
317
+
318
+ res.writeHead(200, {
319
+ 'Content-Type': mimeType,
320
+ 'Cache-Control': 'public, max-age=31536000'
321
+ });
322
+ res.end(readFileSync(filePath));
323
+ } else {
324
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
325
+ res.end('Not Found');
326
+ }
327
+ });
328
+
329
+ server.listen(port, () => {
330
+ console.log(`
331
+ Pulse Preview Server running at:
332
+
333
+ Local: http://localhost:${port}/
334
+
335
+ Serving production build from: dist/
336
+ Press Ctrl+C to stop.
337
+ `);
338
+ });
339
+ }
340
+
341
+ export default { buildProject, previewBuild, minifyJS };