Skip to content

Commit 50ab9df

Browse files
authored
Colocate node icons per node and copy them during build (#16)
Align build.js with the node-red-example template: add copyIcons() to transfer each node's src/nodes/<node>/icons into dist/nodes/<node>/icons, drop the unused plugin/theme stubs. Move the icons out of the central icons/ directory into their nodes, ship src/resources instead.
1 parent c45c910 commit 50ab9df

9 files changed

Lines changed: 129 additions & 83 deletions

File tree

build.js

Lines changed: 103 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,57 @@
1-
const { cwd } = require("process");
1+
const { cwd } = require("node:process");
22
const ts = require("typescript");
33
const {
4-
cp,
54
readdirSync,
6-
exists,
75
existsSync,
86
readFileSync,
9-
writeFile,
107
writeFileSync,
118
mkdirSync,
129
realpathSync,
10+
copyFileSync,
1311
} = require("node:fs");
14-
const sourcePath = cwd() + "/src";
15-
const targetPath = cwd() + "/dist";
12+
13+
// directory containing the sources for nodes; defaults to `src/nodes` within the current working directory
14+
const nodesSourceDir = cwd() + "/src/nodes";
15+
// directory the generated files should be saved to; defaults to `dist/nodes` within the current working directory
16+
const nodesTargetDir = cwd() + "/dist/nodes";
17+
// path to the typescript project configuration for building the nodes
1618
const buildTsConfig = JSON.parse(readFileSync("build.tsconfig.json").toString());
1719

1820
/**
19-
* @param node String
20-
* @param source String
21-
* @param target String
21+
* Creates a directory if it does not exist
22+
* @param {string} dir directory path to create
23+
*/
24+
function makeDir(dir) {
25+
// bypass everything in case the directory already exists
26+
if (!existsSync(dir)) {
27+
const parentDir = realpathSync(`${dir}../`);
28+
29+
// check whether parent directory exists and create it recursively if not
30+
if (!existsSync(parentDir)) {
31+
makeDir(parentDir);
32+
}
33+
34+
// finally create the directory
35+
mkdirSync(dir);
36+
}
37+
}
38+
39+
/**
40+
* Builds the form for editing a node using the Node-RED Editor.
41+
*
42+
* @param {string} node name of the node the form belongs to
43+
* @param {string} sourcePath path of the directory containing the node's source files
44+
* @param {string} targetPath path of the directory the node's generated files should be saved to
2245
*/
23-
function buildNodeForm(node, source, target) {
46+
function buildForm(node, sourcePath, targetPath) {
2447
/**
2548
* @type {string[]}
2649
*/
2750
const html = [];
28-
const form = readFileSync(`${source}/form.html`).toString();
29-
const docs = existsSync(`${source}/docs.html`) ? readFileSync(`${source}/docs.html`).toString() : undefined;
51+
const form = readFileSync(`${sourcePath}/form.html`).toString();
52+
const docs = existsSync(`${sourcePath}/docs.html`) ? readFileSync(`${sourcePath}/docs.html`).toString() : undefined;
3053

54+
// parse `form.html` and wrap it to the corresponding script-tag
3155
const formLines = form.split("\n");
3256
html.push(`<script type="text/html" data-template-name="${node}">`);
3357
formLines.forEach((line) => {
@@ -37,6 +61,7 @@ function buildNodeForm(node, source, target) {
3761
});
3862
html.push("</script>");
3963

64+
// parse `docs.html` and wrap it to the corresponding script-tag
4065
if (docs) {
4166
const docsLines = docs.split("\n");
4267
html.push(`<script type="text/html" data-help-name="${node}">`);
@@ -48,12 +73,14 @@ function buildNodeForm(node, source, target) {
4873
html.push("</script>");
4974
}
5075

51-
const initTS = readFileSync(`${source}/init.ts`).toString();
76+
// read and transpile the node's TypeScript
77+
const initTS = readFileSync(`${sourcePath}/init.ts`).toString();
5278
let initJS = ts
5379
.transpileModule(initTS, buildTsConfig)
5480
.outputText.replace(/export \{(?:[^\}]+)?\};/gim, "")
5581
.replace(/RED\.nodes\.registerType\("([^\"]+)"/i, `RED.nodes.registerType("${node}"`);
5682

83+
// inject the node's init logic into the node's HTML
5784
const initJSLines = initJS.split("\n");
5885
html.push('<script type="text/javascript">');
5986
// wrap in an IIFE so top-level declarations (e.g. `const def`, helper functions)
@@ -70,40 +97,47 @@ function buildNodeForm(node, source, target) {
7097

7198
html.push("");
7299

73-
mkdirSync(`${target}/`, {
100+
// create node's target directory
101+
mkdirSync(`${targetPath}/`, {
74102
recursive: true,
75103
});
76104

77-
writeFileSync(`${target}/${node}.html`, html.join("\n"));
105+
// save the node's editor formular
106+
writeFileSync(`${targetPath}/${node}.html`, html.join("\n"));
78107
}
79108

80109
/**
81-
* @param node String
82-
* @param source String
83-
* @param target String
110+
* Copys the locale files of a node.
111+
*
112+
* @param {string} node name of the node the form belongs to
113+
* @param {string} sourcePath path of the directory containing the node's source files
114+
* @param {string} targetPath path of the directory the node's generated files should be saved to
84115
*/
85-
function copyNodeLocales(node, source, target) {
86-
if (!existsSync(`${source}/locales`)) {
116+
function copyLocales(node, sourcePath, targetPath) {
117+
// skip in case directory `locales` does not exist within the nodes directory
118+
if (!existsSync(`${sourcePath}/locales`)) {
87119
return;
88120
}
89121

90-
readdirSync(`${source}/locales`, {
122+
readdirSync(`${sourcePath}/locales`, {
91123
recursive: false,
92124
})
93125
.filter((language) => {
126+
// filter for files named as valid language codes
94127
return language.match(/^[a-z]{2,2}(?:-[A-Z]{2,2})?\.json$/);
95128
})
96129
.forEach((language) => {
130+
// read the language code and the JSON
97131
const languageCode = language.match(/^([a-z]{2,2}(?:-[A-Z]{2,2})?)\.json$/i)[1];
98-
const content = readFileSync(`${source}/locales/${language}`).toString();
132+
const content = readFileSync(`${sourcePath}/locales/${language}`).toString();
99133

100134
/**
101135
* @type {string[]}
102136
*/
103-
const json = [];
137+
const json = ["{"];
104138

139+
// wrap locales content within a tag names like the node
105140
const contentLines = content.split("\n");
106-
const lastElement = json.push("{");
107141
json.push(` "${node}": ${contentLines[0]}`);
108142
contentLines.splice(1).forEach((line) => {
109143
if (line.length > 0) {
@@ -112,75 +146,62 @@ function copyNodeLocales(node, source, target) {
112146
});
113147
json.push("}");
114148

115-
mkdirSync(`${target}/locales/${languageCode}`, {
149+
// create node locales target directory
150+
mkdirSync(`${targetPath}/locales/${languageCode}`, {
116151
recursive: true,
117152
});
118153

119-
writeFileSync(`${target}/locales/${languageCode}/${node}.json`, json.join("\n"));
154+
// save the node's locales
155+
writeFileSync(`${targetPath}/locales/${languageCode}/${node}.json`, json.join("\n"));
120156
});
121157
}
122158

123-
function buildNodes() {
124-
readdirSync(`${sourcePath}/nodes`, {
125-
recursive: false,
126-
})
127-
.filter((node) => {
128-
const path = `${sourcePath}/nodes/${node}`;
129-
130-
return existsSync(`${path}/form.html`) && existsSync(`${path}/${node}.ts`);
131-
})
132-
.forEach((node) => {
133-
console.info(`Building node ${node}`);
134-
135-
const source = `${sourcePath}/nodes/${node}`;
136-
const target = `${targetPath}/nodes/${node}`;
137-
138-
buildNodeForm(node, source, target);
159+
/**
160+
* Copy icons of a node.
161+
*
162+
@param {string} node name of the node the form belongs to
163+
@param {string} sourcePath path of the directory containing the node's source files
164+
@param {string} targetPath path of the directory the node's generated files should be saved to
165+
*/
166+
function copyIcons(node, sourcePath, targetPath) {
167+
// skip in case directory `icons` does not exist within the nodes directory
168+
if (!existsSync(`${sourcePath}/icons`)) {
169+
return;
170+
}
139171

140-
copyNodeLocales(node, source, target);
141-
});
142-
}
172+
// create node icons target directory
173+
mkdirSync(`${targetPath}/icons`, {
174+
recursive: true,
175+
});
143176

144-
function buildPlugins() {
145-
readdirSync(`${sourcePath}/plugins`, {
177+
// copy the node's icons
178+
readdirSync(`${sourcePath}/icons`, {
146179
recursive: false,
147-
})
148-
.filter((plugin) => {
149-
const path = `${sourcePath}/plugins/${plugin}`;
150-
151-
return existsSync(`${path}/${plugin}.ts`);
152-
})
153-
.forEach((plugin) => {
154-
console.info(`Building plugin ${plugin}`);
155-
156-
// does nothing yet
157-
});
180+
}).forEach((icon) => {
181+
copyFileSync(`${sourcePath}/icons/${icon}`, `${targetPath}/icons/${icon}`);
182+
});
158183
}
159184

160-
function buildThemes() {
161-
readdirSync(`${sourcePath}/themes`, {
162-
recursive: false,
185+
/*
186+
* Iterate over every directory
187+
*/
188+
readdirSync(nodesSourceDir, {
189+
recursive: false,
190+
})
191+
.filter((node) => {
192+
const path = `${nodesSourceDir}/${node}`;
193+
194+
// filter for directories containing at least a `form.html` and the nodes logic script `node.ts`
195+
return existsSync(`${path}/form.html`) && existsSync(`${path}/${node}.ts`);
163196
})
164-
.filter((theme) => {
165-
const path = `${sourcePath}/themes/${theme}`;
197+
.forEach((node) => {
198+
const sourcePath = `${nodesSourceDir}/${node}`;
199+
const targetPath = `${nodesTargetDir}/${node}`;
166200

167-
return existsSync(`${path}/${theme}.ts`);
168-
})
169-
.forEach((theme) => {
170-
console.info(`Building theme ${theme}`);
171-
172-
// does nothing yet
173-
});
174-
}
175-
176-
if (existsSync(`${sourcePath}/plugins`)) {
177-
buildPlugins();
178-
}
179-
180-
if (existsSync(`${sourcePath}/nodes`)) {
181-
buildNodes();
182-
}
201+
// build the nodes' form for Node-RED Editor
202+
buildForm(node, sourcePath, targetPath);
183203

184-
if (existsSync(`${sourcePath}/themes`)) {
185-
buildThemes();
186-
}
204+
// copy/transfer optional assets
205+
copyLocales(node, sourcePath, targetPath);
206+
copyIcons(node, sourcePath, targetPath);
207+
});

icons/scene-controller.svg renamed to dist/nodes/scene-controller/icons/scene-controller.svg

File renamed without changes.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@
5353
},
5454
"main": "dist",
5555
"files": [
56+
"src",
5657
"dist",
5758
"examples",
58-
"icons",
59+
"resources",
5960
"LICENSE",
6061
"README.md"
6162
]

resources/.gitkeep

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Loading
Lines changed: 7 additions & 0 deletions
Loading
Lines changed: 10 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)