@m00rl0ck/simple-builder 1.0.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/bundler.js ADDED
@@ -0,0 +1,151 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ let ID = 0;
5
+
6
+ // ======================
7
+ // 🔍 FIND IMPORTS
8
+ // ======================
9
+ function getImports(code) {
10
+ const regex = /import\s+{([^}]+)}\s+from\s+['"](.+?)['"]/g;
11
+ const imports = [];
12
+ let match;
13
+
14
+ while ((match = regex.exec(code))) {
15
+ imports.push({
16
+ names: match[1].split(',').map(s => s.trim()),
17
+ path: match[2]
18
+ });
19
+ }
20
+
21
+ return imports;
22
+ }
23
+
24
+ // ======================
25
+ // 🔄 TRANSFORM CODE
26
+ // ======================
27
+ function transform(code) {
28
+ const exports = [];
29
+
30
+ // 1. ЗНАХОДИМО export-и
31
+ const exportRegex = /export\s+(?:const|function)\s+(\w+)/g;
32
+ let m;
33
+
34
+ while ((m = exportRegex.exec(code))) {
35
+ exports.push(m[1]);
36
+ }
37
+
38
+ // 2. ПРИБИРАЄМО export
39
+ code = code
40
+ .replace(/export const (\w+)\s*=/g, (m, name) => {
41
+ return `const ${name} =`;
42
+ })
43
+ .replace(/export function (\w+)/g, (m, name) => {
44
+ return `function ${name}`;
45
+ });
46
+
47
+ // 3. import → require
48
+ code = code.replace(
49
+ /import\s+{([^}]+)}\s+from\s+['"](.+?)['"]/g,
50
+ (match, names, path) => {
51
+ return `const { ${names} } = require('${path}')`;
52
+ }
53
+ );
54
+
55
+ // 4. додаємо exports
56
+ if (exports.length) {
57
+ code += `\nmodule.exports = { ${exports.join(', ')} };`;
58
+ }
59
+
60
+ return code;
61
+ }
62
+
63
+ // ======================
64
+ // 🧱 CREATE MODULE
65
+ // ======================
66
+ function createModule(filePath, modulesCache) {
67
+ const absPath = path.resolve(filePath);
68
+
69
+ if (modulesCache[absPath]) {
70
+ return modulesCache[absPath];
71
+ }
72
+
73
+ const code = fs.readFileSync(absPath, 'utf-8');
74
+ const imports = getImports(code);
75
+
76
+ const id = ID++;
77
+
78
+ const mapping = {};
79
+
80
+ const dirname = path.dirname(absPath);
81
+
82
+ const module = {
83
+ id,
84
+ filePath: absPath,
85
+ code: '',
86
+ mapping
87
+ };
88
+
89
+ modulesCache[absPath] = module;
90
+
91
+ imports.forEach(imp => {
92
+ const childPath = path.join(dirname, imp.path);
93
+ const child = createModule(childPath, modulesCache);
94
+ mapping[imp.path] = child.id;
95
+ });
96
+
97
+ module.code = transform(code, mapping);
98
+
99
+ return module;
100
+ }
101
+
102
+ // ======================
103
+ // 📦 BUNDLE
104
+ // ======================
105
+ export function bundle(entry) {
106
+ ID = 0;
107
+
108
+ const modulesCache = {};
109
+
110
+ const entryModule = createModule(entry, modulesCache);
111
+
112
+ const modules = Object.values(modulesCache);
113
+
114
+ const modulesCode = modules.map(mod => {
115
+ return `
116
+ ${mod.id}: [
117
+ function(require, module, exports) {
118
+ ${mod.code}
119
+ },
120
+ ${JSON.stringify(mod.mapping)}
121
+ ]
122
+ `;
123
+ }).join(',');
124
+
125
+ return `
126
+ (function(modules){
127
+ const cache = {};
128
+
129
+ function require(id){
130
+ if (cache[id]) {
131
+ return cache[id].exports;
132
+ }
133
+
134
+ const [fn, mapping] = modules[id];
135
+
136
+ function localRequire(name){
137
+ return require(mapping[name]);
138
+ }
139
+
140
+ const module = { exports: {} };
141
+ cache[id] = module;
142
+
143
+ fn(localRequire, module, module.exports);
144
+
145
+ return module.exports;
146
+ }
147
+
148
+ require(${entryModule.id});
149
+ })({${modulesCode}});
150
+ `;
151
+ }
package/dev-server.js ADDED
@@ -0,0 +1,134 @@
1
+ import http from 'http';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { build } from './index.js';
5
+
6
+ const PORT = 3000;
7
+ const ROOT = process.cwd();
8
+ const DIST_DIR = path.join(ROOT, 'dist');
9
+
10
+ let clients = [];
11
+
12
+ // ======================
13
+ // 📡 MIME TYPES
14
+ // ======================
15
+
16
+ const MIME = {
17
+ '.html': 'text/html',
18
+ '.js': 'text/javascript',
19
+ '.css': 'text/css',
20
+ '.json': 'application/json',
21
+ '.png': 'image/png',
22
+ '.jpg': 'image/jpeg',
23
+ '.svg': 'image/svg+xml'
24
+ };
25
+
26
+ // ======================
27
+ // 🔄 LIVE RELOAD SCRIPT
28
+ // ======================
29
+
30
+ function injectReload(html) {
31
+ return html.replace(
32
+ '</body>',
33
+ `
34
+ <script>
35
+ const es = new EventSource('/__reload');
36
+ es.onmessage = () => location.reload();
37
+ </script>
38
+ </body>`
39
+ );
40
+ }
41
+
42
+ // ======================
43
+ // 📂 STATIC SERVER
44
+ // ======================
45
+
46
+ function serveFile(req, res) {
47
+ let filePath = path.join(DIST_DIR, req.url === '/' ? 'index.html' : req.url);
48
+
49
+ const exists = fs.existsSync(filePath);
50
+ const ext = path.extname(filePath);
51
+
52
+ // 👉 якщо файл не існує
53
+ if (!exists) {
54
+ // SPA fallback тільки для HTML запитів
55
+ if (req.headers.accept && req.headers.accept.includes('text/html')) {
56
+ filePath = path.join(DIST_DIR, 'index.html');
57
+ } else {
58
+ res.writeHead(404);
59
+ return res.end('Not found');
60
+ }
61
+ }
62
+
63
+ const contentType = MIME[ext] || 'text/plain';
64
+
65
+ let content = fs.readFileSync(filePath);
66
+
67
+ if (ext === '.html') {
68
+ content = injectReload(content.toString());
69
+ }
70
+
71
+ res.writeHead(200, { 'Content-Type': contentType });
72
+ res.end(content);
73
+ }
74
+ // ======================
75
+ // 🔁 RELOAD CHANNEL (SSE)
76
+ // ======================
77
+
78
+ function handleReload(req, res) {
79
+ res.writeHead(200, {
80
+ 'Content-Type': 'text/event-stream',
81
+ 'Cache-Control': 'no-cache',
82
+ 'Connection': 'keep-alive'
83
+ });
84
+
85
+ clients.push(res);
86
+
87
+ req.on('close', () => {
88
+ clients = clients.filter(c => c !== res);
89
+ });
90
+ }
91
+
92
+ function triggerReload() {
93
+ clients.forEach(res => {
94
+ res.write('data: reload\n\n');
95
+ });
96
+ }
97
+
98
+ // ======================
99
+ // 👀 WATCH
100
+ // ======================
101
+
102
+ function watch() {
103
+ const SRC_DIR = path.join(ROOT, 'src');
104
+
105
+ build();
106
+
107
+ fs.watch(SRC_DIR, { recursive: true }, () => {
108
+ console.log('🔄 Rebuilding...');
109
+ build();
110
+ triggerReload();
111
+ });
112
+ }
113
+
114
+ // ======================
115
+ // 🚀 SERVER
116
+ // ======================
117
+
118
+ const server = http.createServer((req, res) => {
119
+ if (req.url === '/__reload') {
120
+ return handleReload(req, res);
121
+ }
122
+
123
+ serveFile(req, res);
124
+ });
125
+
126
+ // ======================
127
+ // ▶️ START
128
+ // ======================
129
+
130
+ watch();
131
+
132
+ server.listen(PORT, () => {
133
+ console.log(`\n🚀 Dev server running: http://localhost:${PORT}\n`);
134
+ });
package/index.js ADDED
@@ -0,0 +1,135 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { IN_POINT, NODE_ENV } from '../../env.js';
4
+ import { minifyJS } from './minify.js';
5
+ import { minifyCSS } from './minify-css.js';
6
+ import { bundle } from './bundler.js';
7
+
8
+ // ======================
9
+ // ⚙️ CONFIG
10
+ // ======================
11
+
12
+ const ROOT = process.cwd();
13
+ const SRC_DIR = path.join(ROOT, 'src');
14
+ const DIST_DIR = path.join(ROOT, 'dist');
15
+
16
+ const ENTRY = path.join(SRC_DIR, `${IN_POINT}.js`);
17
+ const HTML_INPUT = path.join(SRC_DIR, 'index.html');
18
+ const HTML_OUTPUT = path.join(DIST_DIR, 'index.html');
19
+
20
+ const isProd = NODE_ENV === 'production';
21
+ const isWatch = process.argv.includes('--watch');
22
+
23
+ // ======================
24
+ // 🧹 UTILS
25
+ // ======================
26
+
27
+ function ensureDir(dir) {
28
+ if (!fs.existsSync(dir)) {
29
+ fs.mkdirSync(dir, { recursive: true });
30
+ }
31
+ }
32
+
33
+ function cleanDist() {
34
+ if (fs.existsSync(DIST_DIR)) {
35
+ fs.rmSync(DIST_DIR, { recursive: true, force: true });
36
+ }
37
+ }
38
+
39
+ // ======================
40
+ // 📦 BUILD JS
41
+ // ======================
42
+ function buildJS() {
43
+ const files = fs.readdirSync(SRC_DIR).filter(f => f.endsWith('.js'));
44
+
45
+ const bundle_result = bundle('./src/app.js');
46
+ const result = isProd ? minifyJS(bundle_result) : bundle_result;
47
+
48
+ fs.writeFileSync('./dist/bundle.min.js', result);
49
+ }
50
+
51
+ function buildCSS() {
52
+ const files = fs.readdirSync(SRC_DIR).filter(f => f.endsWith('.css'));
53
+
54
+ files.forEach(file => {
55
+ const inputPath = path.join(SRC_DIR, file);
56
+ const code = fs.readFileSync(inputPath, 'utf-8');
57
+
58
+ const outputName = isProd
59
+ ? file.replace('.css', '.min.css')
60
+ : file;
61
+
62
+ const outputPath = path.join(DIST_DIR, outputName);
63
+
64
+ const result = isProd ? minifyCSS(code) : code;
65
+
66
+ fs.writeFileSync(outputPath, result);
67
+
68
+ console.log(`🎨 CSS built: ${outputName}`);
69
+ });
70
+ }
71
+
72
+ // ======================
73
+ // 🌐 BUILD HTML
74
+ // ======================
75
+
76
+ function buildHTML() {
77
+ let html = fs.readFileSync(HTML_INPUT, 'utf-8');
78
+
79
+ if (isProd) {
80
+ // JS
81
+ html = html.replace(/src="(.+?)\.js"/g, (match, p1) => {
82
+ if (p1.endsWith('.min')) return match;
83
+ return `src="bundle.min.js"`;
84
+ });
85
+
86
+ // CSS
87
+ html = html.replace(/href="(.+?)\.css"/g, (match, p1) => {
88
+ if (p1.endsWith('.min')) return match;
89
+ return `href="${p1}.min.css"`;
90
+ });
91
+ }
92
+
93
+ fs.writeFileSync(HTML_OUTPUT, html);
94
+
95
+ console.log(`✅ HTML built`);
96
+ }
97
+
98
+ // ======================
99
+ // 👀 WATCH MODE
100
+ // ======================
101
+
102
+ function watch() {
103
+ console.log('👀 Watch mode ON...\n');
104
+
105
+ fs.watch(SRC_DIR, { recursive: true }, (eventType, filename) => {
106
+ console.log(`🔄 File changed: ${filename}`);
107
+ build();
108
+ });
109
+
110
+ fs.watch(HTML_INPUT, () => {
111
+ console.log(`🔄 HTML changed`);
112
+ build();
113
+ });
114
+ }
115
+
116
+ // ======================
117
+ // 🚀 BUILD
118
+ // ======================
119
+
120
+ export function build() {
121
+ console.log(`\n🚀 Build started (${isProd ? 'production' : 'development'})`);
122
+
123
+ cleanDist();
124
+ ensureDir(DIST_DIR);
125
+
126
+ buildJS();
127
+ buildCSS(); // 👈 ДОДАЛИ
128
+ buildHTML();
129
+
130
+ console.log(`🎉 Build finished\n`);
131
+ }
132
+
133
+ if (isWatch) {
134
+ watch();
135
+ }
package/minify-css.js ADDED
@@ -0,0 +1,16 @@
1
+ export function minifyCSS(code) {
2
+ return code
3
+ // remove comments
4
+ .replace(/\/\*[\s\S]*?\*\//g, '')
5
+
6
+ // remove whitespace
7
+ .replace(/\s+/g, ' ')
8
+
9
+ // remove space around symbols
10
+ .replace(/\s*([{}:;,>])\s*/g, '$1')
11
+
12
+ // remove last semicolon
13
+ .replace(/;}/g, '}')
14
+
15
+ .trim();
16
+ }
package/minify.js ADDED
@@ -0,0 +1,135 @@
1
+ export function minifyJS(code) {
2
+ let out = '';
3
+ let i = 0;
4
+
5
+ let inString = false;
6
+ let stringChar = '';
7
+ let inTemplate = false;
8
+ let inRegex = false;
9
+ let inSingleComment = false;
10
+ let inMultiComment = false;
11
+
12
+ while (i < code.length) {
13
+ const c = code[i];
14
+ const next = code[i + 1];
15
+
16
+ // ======================
17
+ // COMMENTS
18
+ // ======================
19
+
20
+ if (inSingleComment) {
21
+ if (c === '\n') {
22
+ inSingleComment = false;
23
+ }
24
+ i++;
25
+ continue;
26
+ }
27
+
28
+ if (inMultiComment) {
29
+ if (c === '*' && next === '/') {
30
+ inMultiComment = false;
31
+ i += 2;
32
+ } else {
33
+ i++;
34
+ }
35
+ continue;
36
+ }
37
+
38
+ if (!inString && !inTemplate && !inRegex) {
39
+ if (c === '/' && next === '/') {
40
+ inSingleComment = true;
41
+ i += 2;
42
+ continue;
43
+ }
44
+
45
+ if (c === '/' && next === '*') {
46
+ inMultiComment = true;
47
+ i += 2;
48
+ continue;
49
+ }
50
+ }
51
+
52
+ // ======================
53
+ // STRINGS
54
+ // ======================
55
+
56
+ if (!inTemplate && !inRegex && (c === '"' || c === "'")) {
57
+ if (inString && c === stringChar && code[i - 1] !== '\\') {
58
+ inString = false;
59
+ } else if (!inString) {
60
+ inString = true;
61
+ stringChar = c;
62
+ }
63
+ out += c;
64
+ i++;
65
+ continue;
66
+ }
67
+
68
+ // ======================
69
+ // TEMPLATE
70
+ // ======================
71
+
72
+ if (!inString && !inRegex && c === '`') {
73
+ inTemplate = !inTemplate;
74
+ out += c;
75
+ i++;
76
+ continue;
77
+ }
78
+
79
+ // ======================
80
+ // REGEX (simplified)
81
+ // ======================
82
+
83
+ if (!inString && !inTemplate && c === '/' && !inRegex) {
84
+ // naive check: після ( або = або : або , → regex
85
+ const prev = out.trim().slice(-1);
86
+ if (!prev || '({[=,:;!&|?'.includes(prev)) {
87
+ inRegex = true;
88
+ out += c;
89
+ i++;
90
+ continue;
91
+ }
92
+ }
93
+
94
+ if (inRegex) {
95
+ if (c === '/' && code[i - 1] !== '\\') {
96
+ inRegex = false;
97
+ }
98
+ out += c;
99
+ i++;
100
+ continue;
101
+ }
102
+
103
+ // ======================
104
+ // WHITESPACE
105
+ // ======================
106
+
107
+ if (!inString && !inTemplate && !inRegex) {
108
+ if (/\s/.test(c)) {
109
+ const prev = out[out.length - 1];
110
+ const nextChar = code[i + 1];
111
+
112
+ // не додаємо зайві пробіли
113
+ if (
114
+ prev &&
115
+ /[a-zA-Z0-9_$]/.test(prev) &&
116
+ /[a-zA-Z0-9_$]/.test(nextChar)
117
+ ) {
118
+ out += ' ';
119
+ }
120
+
121
+ i++;
122
+ continue;
123
+ }
124
+ }
125
+
126
+ // ======================
127
+ // DEFAULT
128
+ // ======================
129
+
130
+ out += c;
131
+ i++;
132
+ }
133
+
134
+ return out.trim();
135
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@m00rl0ck/simple-builder",
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency JS bundler with dev server and minifier",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "simple-builder": "./cli.js"
9
+ },
10
+ "scripts": {
11
+ "dev": "node dev-server.js",
12
+ "build": "node bundler.js"
13
+ },
14
+ "keywords": [
15
+ "bundler",
16
+ "javascript",
17
+ "build-tool",
18
+ "zero-deps",
19
+ "minifier"
20
+ ],
21
+ "author": "m00rlIOck",
22
+ "license": "MIT",
23
+ "files": [
24
+ "bundler.js",
25
+ "dev-server.js",
26
+ "index.js",
27
+ "minify.js",
28
+ "minify-css.js"
29
+ ]
30
+ }