chocola 1.1.20 → 1.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/LICENSE +21 -0
- package/README.md +428 -0
- package/compiler/component-processor.js +95 -0
- package/compiler/config.js +66 -0
- package/compiler/dom-processor.js +96 -0
- package/compiler/fs.js +23 -12
- package/compiler/index.js +153 -174
- package/compiler/pipeline.js +142 -109
- package/compiler/runtime-generator.js +19 -0
- package/compiler/utils.js +78 -43
- package/dev/index.js +26 -1
- package/package.json +4 -3
package/compiler/fs.js
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
2
|
import { throwError } from "./utils.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Reads a file and returns its contents as a UTF-8 string
|
|
6
|
+
* @param {import("fs").PathLike} filePath - Path to the file
|
|
7
|
+
* @returns {Promise<string>} - File contents
|
|
8
|
+
* @throws {Error} - If file cannot be read
|
|
9
|
+
*/
|
|
4
10
|
export async function readMyFile(filePath) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
try {
|
|
12
|
+
const data = await fs.readFile(filePath, "utf-8");
|
|
13
|
+
return data;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
throwError(`Got an error trying to read the file: ${error.message}`);
|
|
16
|
+
}
|
|
11
17
|
}
|
|
12
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Checks if a file exists
|
|
21
|
+
* @param {import("fs").PathLike} filePath - Path to check
|
|
22
|
+
* @returns {Promise<boolean>} - True if file exists, false otherwise
|
|
23
|
+
*/
|
|
13
24
|
export async function checkFile(filePath) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
try {
|
|
26
|
+
await fs.access(filePath);
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
20
31
|
}
|
package/compiler/index.js
CHANGED
|
@@ -1,27 +1,48 @@
|
|
|
1
|
-
import { throwError, genRandomId, incrementAlfabet, isWebLink } from "./utils.js";
|
|
2
|
-
import { JSDOM } from "jsdom";
|
|
3
1
|
import path from "path";
|
|
4
2
|
import { promises as fs } from "fs";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
|
|
5
|
+
// Configuration
|
|
6
|
+
import { loadConfig, resolvePaths } from "./config.js";
|
|
7
|
+
|
|
8
|
+
// DOM Processing
|
|
9
|
+
import {
|
|
10
|
+
createDOM,
|
|
11
|
+
validateAppContainer,
|
|
12
|
+
getAppElements,
|
|
13
|
+
getAssetLinks,
|
|
14
|
+
appendRuntimeScript,
|
|
15
|
+
serializeDOM,
|
|
16
|
+
writeHTMLOutput,
|
|
17
|
+
} from "./dom-processor.js";
|
|
18
|
+
|
|
19
|
+
// Component & Runtime Processing
|
|
20
|
+
import { processAllComponents } from "./component-processor.js";
|
|
21
|
+
import { generateRuntimeScript } from "./runtime-generator.js";
|
|
22
|
+
|
|
23
|
+
// Utilities & Pipeline
|
|
24
|
+
import { throwError } from "./utils.js";
|
|
25
|
+
import {
|
|
26
|
+
copyResources,
|
|
27
|
+
getComponents,
|
|
28
|
+
getSrcIndex,
|
|
29
|
+
processIcons,
|
|
30
|
+
processStylesheet,
|
|
31
|
+
} from "./pipeline.js";
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
// ===== Logging & Banners =====
|
|
10
35
|
|
|
11
36
|
const logSeparation = chalk.yellow(`
|
|
12
37
|
________________________________________________________________________
|
|
13
38
|
========================================================================
|
|
14
39
|
`);
|
|
15
40
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export default async function runtime(__rootdir) {
|
|
22
|
-
console.log(chalk.bold.hex("#945e33")(`\n RUNNING CHOCOLA BUNDLER`))
|
|
23
|
-
console.log(logSeparation);
|
|
24
|
-
console.log(chalk.hex("#945e33")(`
|
|
41
|
+
function logBanner() {
|
|
42
|
+
console.log(chalk.bold.hex("#945e33")(`\n RUNNING CHOCOLA BUNDLER`));
|
|
43
|
+
console.log(logSeparation);
|
|
44
|
+
console.log(
|
|
45
|
+
chalk.hex("#945e33")(`
|
|
25
46
|
|
|
26
47
|
|
|
27
48
|
▄████▄ ██░ ██ ▒█████ ▄████▄ ▒█████ ██▓ ▄▄▄
|
|
@@ -30,171 +51,129 @@ export default async function runtime(__rootdir) {
|
|
|
30
51
|
▒▓▓▄ ▄██▒░▓█ ░██ ▒██ ██░▒▓▓▄ ▄██▒▒██ ██░▒██░ ░██▄▄▄▄██
|
|
31
52
|
▒ ▓███▀ ░░▓█▒░██▓░ ████▓▒░▒ ▓███▀ ░░ ████▓▒░░██████▒▓█ ▓██▒
|
|
32
53
|
░ ░▒ ▒ ░ ▒ ░░▒░▒░ ▒░▒░▒░ ░ ░▒ ▒ ░░ ▒░▒░▒░ ░ ▒░▓ ░▒▒ ▓▒█░
|
|
33
|
-
░ ▒ ▒ ░▒░ ░ ░ ▒ ▒░ ░ ▒ ░
|
|
34
|
-
░ ░ ░░ ░░ ░ ░ ▒ ░ ░ ░ ░ ▒ ░ ░ ░ ▒
|
|
54
|
+
░ ▒ ▒ ░▒░ ░ ░ ░ ▒ ▒░ ░ ▒ ░ ░ ▒░ ░ ░ ▒ ░ ░ ▒▒ ░
|
|
55
|
+
░ ░ ░░ ░░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ▒
|
|
35
56
|
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
|
|
36
57
|
░ ░
|
|
37
58
|
|
|
38
59
|
|
|
39
|
-
`)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const config = await getChocolaConfig(__rootdir);
|
|
46
|
-
const bundleConfig = config.bundle;
|
|
47
|
-
|
|
48
|
-
if (bundleConfig.srcDir) { __srcdir = bundleConfig.srcDir }
|
|
49
|
-
else { console.warn(chalk.bold.yellow("WARNING!"), 'srcDir not defined in chocola.config.json file: using default "src" directory.') }
|
|
50
|
-
|
|
51
|
-
if (bundleConfig.outDir) { __outDir = bundleConfig.outDir }
|
|
52
|
-
else { console.warn(chalk.bold.yellow("WARNING!"), 'outDir not defined in chocola.config.json file: using default "dist" directory.') }
|
|
53
|
-
|
|
54
|
-
if (bundleConfig.libDir) { __libDir = bundleConfig.libDir }
|
|
55
|
-
else { console.warn(chalk.bold.yellow("WARNING!"), 'libDir not defined in chocola.config.json file: using default "lib" directory.') }
|
|
56
|
-
|
|
57
|
-
if (bundleConfig.emptyOutDir) {
|
|
58
|
-
__emptyOutDir = bundleConfig.emptyOutDir;
|
|
59
|
-
console.log(`> using emptyOutDir = ${__emptyOutDir}`);
|
|
60
|
-
} else {
|
|
61
|
-
console.log(`> using default emptyOutDir = ${__emptyOutDir}`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
console.log(logSeparation);
|
|
65
|
-
|
|
66
|
-
const outDirPath = path.join(__rootdir, __outDir);
|
|
67
|
-
const srcPath = path.join(__rootdir, __srcdir);
|
|
68
|
-
const srcComponents = path.join(srcPath, __libDir);
|
|
69
|
-
|
|
70
|
-
if (__emptyOutDir) {
|
|
71
|
-
await fs.rm(path.join(outDirPath), { recursive: true, force: true });
|
|
72
|
-
await fs.mkdir(path.join(outDirPath));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
let indexFiles = await getSrcIndex(srcPath);
|
|
76
|
-
let srcHtmlFile = indexFiles.srcHtmlFile;
|
|
77
|
-
let srcChocoFile = indexFiles.srcChocoFile;
|
|
78
|
-
|
|
79
|
-
console.log(` LOADING COMPONENTS`)
|
|
80
|
-
|
|
81
|
-
const foundComponents = await getComponents(srcComponents);
|
|
82
|
-
|
|
83
|
-
let loadedComponents = foundComponents.loadedComponents;
|
|
84
|
-
let notDefComps = foundComponents.notDefComps;
|
|
85
|
-
|
|
86
|
-
const srcDirData = {
|
|
87
|
-
index: srcHtmlFile || srcChocoFile,
|
|
88
|
-
components: foundComponents.componentsLib,
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponents) + ":");
|
|
92
|
-
console.log(" ", srcDirData.components, "\n");
|
|
93
|
-
|
|
94
|
-
if (notDefComps.length > 0) {
|
|
95
|
-
console.warn(chalk.bold.yellow("WARNING!"), "The following components don't include a default export:");
|
|
96
|
-
console.log(" ", notDefComps);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
console.log(logSeparation);
|
|
100
|
-
console.log(` BUNDLING STATIC BUILD`);
|
|
101
|
-
console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(outDirPath) + "\n");
|
|
102
|
-
console.log(logSeparation);
|
|
103
|
-
|
|
104
|
-
const dom = new JSDOM(srcDirData.index);
|
|
105
|
-
const doc = dom.window.document;
|
|
106
|
-
const appContainer = doc.querySelector("app");
|
|
107
|
-
|
|
108
|
-
if (!appContainer) {
|
|
109
|
-
throwError("Index page must have an " + chalk.blue("<app>") + " element");
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const appElements = Array.from(appContainer.querySelectorAll('*'));
|
|
113
|
-
|
|
114
|
-
let runtimeChunks = [];
|
|
115
|
-
let runtimeScript = "";
|
|
116
|
-
let compIdColl = [];
|
|
117
|
-
let letter;
|
|
118
|
-
|
|
119
|
-
appElements.forEach(el => {
|
|
120
|
-
const tagName = el.tagName.toLowerCase();
|
|
121
|
-
const compName = tagName + ".js";
|
|
122
|
-
|
|
123
|
-
const ctx = {};
|
|
124
|
-
for (const attr of el.attributes) {
|
|
125
|
-
if (attr.name.startsWith("ctx.")) {
|
|
126
|
-
const key = attr.name.slice(4);
|
|
127
|
-
ctx[key] = attr.value;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const instance = loadedComponents.get(compName);
|
|
132
|
-
if (!instance || instance === undefined) return;
|
|
133
|
-
if (instance && instance.body) {
|
|
134
|
-
let body = instance.body;
|
|
135
|
-
body = body.replace(/\$\{ctx\.(\w+)\}/g, (_, key) => ctx[key] || "");
|
|
136
|
-
const fragment = JSDOM.fragment(body);
|
|
137
|
-
const firstChild = fragment.firstChild;
|
|
138
|
-
if (firstChild && firstChild.nodeType === 1) {
|
|
139
|
-
if (instance.script || instance.effects) {
|
|
140
|
-
let script;
|
|
141
|
-
let effects;
|
|
142
|
-
const compId = "chid-" + genRandomId(compIdColl);
|
|
143
|
-
firstChild.setAttribute("chid", compId);
|
|
144
|
-
instance.script && (script = instance.script.toString())
|
|
145
|
-
|
|
146
|
-
if (!letter) {
|
|
147
|
-
letter = "a";
|
|
148
|
-
} else {
|
|
149
|
-
letter = incrementAlfabet(letter);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
script = script.replace(/RUNTIME/g, `${letter}RUNTIME`);
|
|
153
|
-
|
|
154
|
-
runtimeChunks.push(`
|
|
155
|
-
const ${letter} = document.querySelector('[chid="${compId}"]');
|
|
156
|
-
${script}
|
|
157
|
-
${letter}RUNTIME(${letter}, ${JSON.stringify(ctx)});`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
el.replaceWith(fragment);
|
|
161
|
-
} else {
|
|
162
|
-
console.warn(chalk.yellow(`${compName} component could not be loaded`));
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
runtimeScript = runtimeChunks.join("\n");
|
|
167
|
-
|
|
168
|
-
let fileIds = [];
|
|
169
|
-
|
|
170
|
-
const docLinks = Array.from(doc.querySelectorAll("link"));
|
|
171
|
-
|
|
172
|
-
for (const link of docLinks) {
|
|
173
|
-
const rel = link.rel;
|
|
174
|
-
if (rel === "stylesheet") await processStylesheet(link, __rootdir, __srcdir, outDirPath, fileIds);
|
|
175
|
-
if (rel === "icon") await processIcons(link, __rootdir, __srcdir, outDirPath, fileIds)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const runtimeFilename = "run-" + genRandomId(fileIds, 6) + ".js";
|
|
180
|
-
const runtimeFileContents = `document.addEventListener("DOMContentLoaded", () => {${runtimeScript}})`;
|
|
181
|
-
await fs.writeFile(path.join(outDirPath, runtimeFilename), runtimeFileContents);
|
|
182
|
-
const runtimeScriptEl = doc.createElement("script");
|
|
183
|
-
runtimeScriptEl.type = "module";
|
|
184
|
-
runtimeScriptEl.src = "./" + runtimeFilename;
|
|
185
|
-
doc.body.appendChild(runtimeScriptEl);
|
|
186
|
-
const finalHtml = dom.serialize();
|
|
187
|
-
const prettyHtml = beautify.html(finalHtml, { indent_size: 2 });
|
|
188
|
-
|
|
189
|
-
await fs.writeFile(path.join(outDirPath, "index.html"), prettyHtml);
|
|
190
|
-
|
|
191
|
-
await copyResources(__rootdir, __srcdir, outDirPath);
|
|
192
|
-
|
|
193
|
-
console.log(`
|
|
60
|
+
`)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function logSuccess(outDirPath) {
|
|
65
|
+
console.log(`
|
|
194
66
|
▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ██
|
|
195
67
|
██ ██▀██ ██▄██ ██▀██ ██▀██ ███▄██ ██▄▄ ██
|
|
196
68
|
▄▄█▀ ▀███▀ ██▄█▀ ████▀ ▀███▀ ██ ▀██ ██▄▄▄ ▄▄
|
|
197
69
|
|
|
198
70
|
`);
|
|
199
|
-
|
|
71
|
+
console.log(
|
|
72
|
+
chalk.bold.green(">"),
|
|
73
|
+
"Project bundled succesfully at",
|
|
74
|
+
chalk.green.underline(outDirPath) + "\n\n"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ===== Directory Setup =====
|
|
79
|
+
|
|
80
|
+
async function setupOutputDirectory(outDirPath, emptyOutDir) {
|
|
81
|
+
if (emptyOutDir) {
|
|
82
|
+
await fs.rm(outDirPath, { recursive: true, force: true });
|
|
83
|
+
await fs.mkdir(outDirPath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ===== Component Loading =====
|
|
88
|
+
|
|
89
|
+
async function loadAndDisplayComponents(srcComponentsPath) {
|
|
90
|
+
const foundComponents = await getComponents(srcComponentsPath);
|
|
91
|
+
const { loadedComponents, notDefComps, componentsLib } = foundComponents;
|
|
92
|
+
|
|
93
|
+
console.log(` LOADING COMPONENTS`);
|
|
94
|
+
console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
|
|
95
|
+
console.log(" ", componentsLib, "\n");
|
|
96
|
+
|
|
97
|
+
if (notDefComps.length > 0) {
|
|
98
|
+
console.warn(chalk.bold.yellow("WARNING!"), "The following components don't include a default export:");
|
|
99
|
+
console.log(" ", notDefComps);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return loadedComponents;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ===== Asset Processing =====
|
|
106
|
+
|
|
107
|
+
async function processAssets(doc, rootDir, srcDir, outDirPath) {
|
|
108
|
+
const { stylesheets, icons } = getAssetLinks(doc);
|
|
109
|
+
const fileIds = [];
|
|
110
|
+
|
|
111
|
+
for (const link of stylesheets) {
|
|
112
|
+
await processStylesheet(link, rootDir, srcDir, outDirPath, fileIds);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const link of icons) {
|
|
116
|
+
await processIcons(link, rootDir, srcDir, outDirPath, fileIds);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ===== Main Compilation Function =====
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Compiles a static build of your Chocola project from the directory provided.
|
|
124
|
+
* @param {import("fs").PathLike} rootDir
|
|
125
|
+
* @param {Object<{
|
|
126
|
+
* isHotReload?: boolean
|
|
127
|
+
* }
|
|
128
|
+
* >} config
|
|
129
|
+
*/
|
|
130
|
+
export default async function runtime(rootDir, config) {
|
|
131
|
+
!config.isHotReload && logBanner();
|
|
132
|
+
|
|
133
|
+
// Load Configuration
|
|
134
|
+
const config = await loadConfig(rootDir);
|
|
135
|
+
const paths = resolvePaths(rootDir, config);
|
|
136
|
+
!config.isHotReload && console.log(logSeparation);
|
|
137
|
+
|
|
138
|
+
// Setup Output Directory
|
|
139
|
+
await setupOutputDirectory(paths.outDir, config.emptyOutDir);
|
|
140
|
+
|
|
141
|
+
// Load Index File
|
|
142
|
+
const indexFiles = await getSrcIndex(paths.src);
|
|
143
|
+
const srcIndexContent = indexFiles.srcHtmlFile || indexFiles.srcChocoFile;
|
|
144
|
+
|
|
145
|
+
// Load Components
|
|
146
|
+
const loadedComponents = await loadAndDisplayComponents(paths.components);
|
|
147
|
+
!config.isHotReload && console.log(logSeparation);
|
|
148
|
+
|
|
149
|
+
// Create and Validate DOM
|
|
150
|
+
!config.isHotReload && console.log(` BUNDLING STATIC BUILD`);
|
|
151
|
+
!config.isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir) + "\n");
|
|
152
|
+
!config.isHotReload && console.log(logSeparation);
|
|
153
|
+
|
|
154
|
+
const dom = createDOM(srcIndexContent);
|
|
155
|
+
const doc = dom.window.document;
|
|
156
|
+
const appContainer = validateAppContainer(doc);
|
|
157
|
+
const appElements = getAppElements(appContainer);
|
|
158
|
+
|
|
159
|
+
// Process Components
|
|
160
|
+
const { runtimeScript } = processAllComponents(appElements, loadedComponents);
|
|
161
|
+
|
|
162
|
+
// Generate Runtime File
|
|
163
|
+
const runtimeFilename = await generateRuntimeScript(runtimeScript, paths.outDir);
|
|
164
|
+
|
|
165
|
+
// Process Assets (stylesheets, icons)
|
|
166
|
+
await processAssets(doc, rootDir, config.srcDir, paths.outDir);
|
|
167
|
+
|
|
168
|
+
// Finalize HTML
|
|
169
|
+
appendRuntimeScript(doc, runtimeFilename);
|
|
170
|
+
const html = await serializeDOM(dom);
|
|
171
|
+
await writeHTMLOutput(html, paths.outDir);
|
|
172
|
+
|
|
173
|
+
// Copy Resources
|
|
174
|
+
await copyResources(rootDir, config.srcDir, paths.outDir);
|
|
175
|
+
|
|
176
|
+
// Success Message
|
|
177
|
+
!config.isHotReload && logSuccess(paths.outDir);
|
|
178
|
+
config.isHotReload && console.log("Dev server updated");
|
|
200
179
|
}
|