Skip to content

Commit 092474a

Browse files
committed
Add Node script for generating ESM and prepare for autogenerate on install.
1 parent 8a4b883 commit 092474a

File tree

7 files changed

+261
-11
lines changed

7 files changed

+261
-11
lines changed

code-input.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export namespace plugins {
120120
}
121121
// ESM-SUPPORT-END-PLUGIN-autodetect Do not (re)move this - it's needed for ESM generation
122122

123-
// ESM-SUPPORT-START-PLUGIN-debounce-update Do not (re)move this - it's needed for ESM generation
123+
// E doesn't exist? SM-SUPPORT-START-PLUGIN-debounce-update Do not (re)move this - it's needed for ESM generation
124124
/**
125125
* Debounce the update and highlighting function
126126
* https://medium.com/@jamischarles/what-is-debouncing-2505c0648ff1
@@ -133,7 +133,7 @@ export namespace plugins {
133133
*/
134134
constructor(delayMs: number);
135135
}
136-
// ESM-SUPPORT-END-PLUGIN-debounce-update Do not (re)move this - it's needed for ESM generation
136+
// E doesn't exist? SM-SUPPORT-END-PLUGIN-debounce-update Do not (re)move this - it's needed for ESM generation
137137

138138
// ESM-SUPPORT-START-PLUGIN-find-and-replace Do not (re)move this - it's needed for ESM generation
139139
/**

esm/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
code-input.mjs
2+
code-input.d.mts
3+
templates/
4+
plugins/

esm/README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
# Autogenerated ECMAScript Modules
22
**Don't edit them! Edit the files outside this directory!**
33

4+
## Using
5+
6+
If you are using Yarn, NPM, or a similar package manager, the files should be generated on package install. Otherwise:
7+
8+
- If you have Node.js installed, run `node generate.mjs`.
9+
- If you don't have Node.js installed but are on a POSIX-like system with `bash`/`zsh`, run `sh ./generate.sh`.
10+
- If neither of the above are true, install Node.js or (slightly harder; look online) a POSIX/"Linux" compatible shell.
11+
12+
## Extra Information
13+
414
When code-input was started, it was written and tested only to be imported directly via a `<script>` tag, and it assigned an object to a global `codeInput` variable containing all its functionality. As plugins were added, they were implemented as similar but separate `<script>` tags. However, this limits where `codeInput` can be used, making it difficult to integrate with many larger JavaScript projects and frameworks, and causes code duplication when multiple plugins use the same code.
515

616
To fix these, code-input is gaining support for ECMAScript Modules (ESM), a standard way to import modules and export from them with JavaScript. ESM can be used directly in NPM/Yarn-led environments, bundled for inclusion in a `<script>` tag in a backwards-compatible way, or imported as a module into a web browser which supports it natively.
@@ -9,4 +19,4 @@ To ensure backwards compatibility, in the first stage of the transition a proces
919

1020
Later in the second stage, `code-input`'s daily-edited source code may be relocated to ESM, using these generated files, and the direct importable files would be produced by a bundler.
1121

12-
However, refactoring the core would need quite a lot of work and testing, and the first stage suffices for compatibility with all the examples. This directory will exist from the first stage until the second stage, containing auto-generated ESM files which **you should not edit**. After the second stage, it would likely be repurposed as the main source code directory, containing the same files which would become the main developed ones.
22+
However, refactoring the core would need quite a lot of work and testing, and the first stage suffices for compatibility with all the examples. This directory will exist from the first stage until the second stage, containing the tools to generate ESM files. After the second stage, it would likely be repurposed as the main source code directory, containing the same files which would become the main developed ones.

esm/generate.mjs

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
// NOTICE: This is a build script; do not try to import it.
2+
3+
// Generate ESM modules and modular TS definitions
4+
// Dependency: Node.js
5+
console.log("Generating ECMAScript modules using Node");
6+
7+
import { open, mkdir, access, constants } from 'node:fs/promises';
8+
9+
const AUTOGENERATED_NOTICE = "// NOTICE: This code is @generated from code outside the esm directory. Please do not edit it to contribute!\n\n";
10+
11+
// Main file
12+
// code-input.mjs: (../code-input.js without the templates) + ESM
13+
{
14+
const codeInputJs = await open("../code-input.js", "r");
15+
const codeInputMjs = await open("code-input.mjs", "w");
16+
await codeInputMjs.writeFile(AUTOGENERATED_NOTICE);
17+
// Imports: Nothing
18+
// Miss out templates
19+
let copyingCode = true;
20+
for await (const line of codeInputJs.readLines()) {
21+
if(line.includes("ESM-SUPPORT-START-TEMPLATES")) {
22+
copyingCode = false;
23+
}
24+
if(copyingCode) {
25+
await codeInputMjs.writeFile(line+"\n");
26+
}
27+
if(line.includes("ESM-SUPPORT-END-TEMPLATES")) {
28+
copyingCode = true; // After code to copy - this line missed out
29+
}
30+
}
31+
// Exports
32+
await codeInputMjs.writeFile("export const Plugin = codeInput.Plugin;\n");
33+
await codeInputMjs.writeFile("export const Template = codeInput.Template;\n");
34+
await codeInputMjs.writeFile("export const CodeInput = codeInput.CodeInput;\n");
35+
await codeInputMjs.writeFile("export const registerTemplate = codeInput.registerTemplate;\n");
36+
37+
codeInputJs.close();
38+
codeInputMjs.close();
39+
}
40+
41+
// code-input.d.mts: (../code-input.d.ts without the plugin/template namespaces) + ESM
42+
{
43+
const codeInputDTs = await open("../code-input.d.ts", "r");
44+
const codeInputDMts = await open("code-input.d.mts", "w");
45+
await codeInputDMts.writeFile(AUTOGENERATED_NOTICE);
46+
// Miss out no-ESM specific code at the top
47+
// Code before first namespace, after no-ESM specific code
48+
let copyingCode = false;
49+
for await (const line of codeInputDTs.readLines()) {
50+
if(line.includes("ESM-SUPPORT-START-NAMESPACE-1") || line.includes("ESM-SUPPORT-START-NAMESPACE-2")) {
51+
copyingCode = false;
52+
}
53+
if(copyingCode) {
54+
await codeInputDMts.writeFile(line+"\n");
55+
}
56+
if(line.includes("ESM-SUPPORT-END-NOESM-SPECIFIC") || line.includes("ESM-SUPPORT-END-NAMESPACE-1") || line.includes("ESM-SUPPORT-END-NAMESPACE-2")) {
57+
copyingCode = true; // After is code to copy - this line missed out
58+
}
59+
}
60+
61+
await codeInputDTs.close();
62+
await codeInputDMts.close();
63+
}
64+
65+
// Templates
66+
{
67+
try {
68+
await mkdir("templates");
69+
} catch(error) {
70+
if(error.code !== "EEXIST") {
71+
// Ignore directory already existing; throw all other errors.
72+
throw error;
73+
}
74+
}
75+
76+
let codeInputDTs = await open("../code-input.d.ts", "r");
77+
// For each template name prepared for ESM support from the d.ts file:
78+
const templateNames = [];
79+
for await (const line of codeInputDTs.readLines()) {
80+
const templatesOnThisLine = line.match(/(?<=ESM\-SUPPORT\-START\-TEMPLATE\-)[A-Za-z]+/g);
81+
if(templatesOnThisLine !== null) {
82+
for(const templateName of templatesOnThisLine) {
83+
templateNames.push(templateName);
84+
}
85+
}
86+
}
87+
await codeInputDTs.close();
88+
89+
// templates/*.mjs
90+
templateNames.forEach(async (templateName) => {
91+
const codeInputJs = await open("../code-input.js", "r");
92+
const templateMjs = await open("templates/"+templateName+".mjs", "w")
93+
await templateMjs.writeFile(AUTOGENERATED_NOTICE);
94+
// Imports
95+
await templateMjs.writeFile("import { Template } from \"../code-input.mjs\";\n")
96+
// Template syntax is to be stored in an object; do so temporarily.
97+
await templateMjs.writeFile("const templates = {\n");
98+
// Code after start and before end of this template, making use of the imported Template, not codeInput.Template
99+
let copyingCode = false;
100+
for await (const line of codeInputJs.readLines()) {
101+
if(line.includes("ESM-SUPPORT-END-TEMPLATE-"+templateName)) {
102+
break;
103+
}
104+
if(copyingCode) {
105+
await templateMjs.writeFile(line.replaceAll("codeInput.Template", "Template")+"\n");
106+
}
107+
if(line.includes("ESM-SUPPORT-START-TEMPLATE-"+templateName)) {
108+
copyingCode = true; // After is code to copy - this line missed out
109+
}
110+
}
111+
await templateMjs.writeFile("};\n");
112+
// Export, assuming the name of the function is the same as the name of the file.
113+
await templateMjs.writeFile("export default templates."+templateName+";\n");
114+
await codeInputJs.close();
115+
await templateMjs.close();
116+
});
117+
118+
// templates/*.d.mts
119+
templateNames.forEach(async (templateName) => {
120+
const codeInputDTs = await open("../code-input.d.ts", "r");
121+
const templateDMts = await open("templates/"+templateName+".d.mts", "w")
122+
await templateDMts.writeFile(AUTOGENERATED_NOTICE);
123+
// Imports
124+
await templateDMts.writeFile("import { Template, Plugin } from \"../code-input.d.mts\";\n");
125+
// Code after start and before end of this template, making use of the imported Template, not codeInput.Template
126+
let copyingCode = false;
127+
let functionSeen = false;
128+
for await (let line of codeInputDTs.readLines()) {
129+
if(line.includes("ESM-SUPPORT-END-TEMPLATE-"+templateName)) {
130+
break;
131+
}
132+
if(copyingCode) {
133+
if(/( |\t)*function.*/.test(line) && !functionSeen) {
134+
// Replace only first occurrence
135+
line = line.replace("function", "export default function");
136+
functionSeen = true;
137+
}
138+
await templateDMts.writeFile(line.replaceAll("codeInput.Template", "Template").replaceAll("codeInput.Plugin", "Plugin")+"\n");
139+
}
140+
if(line.includes("ESM-SUPPORT-START-TEMPLATE-"+templateName)) {
141+
copyingCode = true; // After is code to copy - this line missed out
142+
}
143+
}
144+
await codeInputDTs.close();
145+
await templateDMts.close();
146+
});
147+
}
148+
149+
// Plugins
150+
{
151+
try {
152+
await mkdir("plugins");
153+
} catch(error) {
154+
if(error.code !== "EEXIST") {
155+
// Ignore directory already existing; throw all other errors.
156+
throw error;
157+
}
158+
}
159+
160+
let codeInputDTs = await open("../code-input.d.ts", "r");
161+
// For each plugin name prepared for ESM support from the d.ts file:
162+
const pluginNames = [];
163+
for await (const line of codeInputDTs.readLines()) {
164+
const pluginsOnThisLine = line.match(/(?<=ESM\-SUPPORT\-START\-PLUGIN\-)[A-Za-z\-]+/g);
165+
if(pluginsOnThisLine !== null) {
166+
for(const templateName of pluginsOnThisLine) {
167+
pluginNames.push(templateName);
168+
}
169+
}
170+
}
171+
await codeInputDTs.close();
172+
173+
// plugins/*.mjs
174+
for(let i = 0; i < pluginNames.length; i++) {
175+
const pluginName = pluginNames[i];
176+
const pluginJs = await open("../plugins/"+pluginName+".js", "r");
177+
const pluginMjs = await open("plugins/"+pluginName+".mjs", "w")
178+
await pluginMjs.writeFile(AUTOGENERATED_NOTICE);
179+
// Imports
180+
await pluginMjs.writeFile("import { Plugin } from \"../code-input.mjs\";\n")
181+
// Plugin syntax is to be stored in an object; do so temporarily.
182+
await pluginMjs.writeFile("const plugins = {};\n");
183+
// Code from this plugin"s file, making use of the imported Plugin, not codeInput.Plugin, and of the created plugins object, not codeInput.plugins
184+
let pluginClassName = null;
185+
for await (const line of pluginJs.readLines()) {
186+
if(pluginClassName === null) {
187+
const pluginClassNameThisLine = line.match(/(?<=codeInput\.plugins\.)[A-Za-z]+/);
188+
if(pluginClassNameThisLine !== null && pluginClassNameThisLine.length > 0) {
189+
pluginClassName = pluginClassNameThisLine;
190+
}
191+
}
192+
193+
await pluginMjs.writeFile(line.replaceAll("codeInput.Plugin", "Plugin").replaceAll("codeInput.plugins", "plugins")+"\n");
194+
}
195+
196+
// Export, assuming the name of the function is the same as the name of the file.
197+
await pluginMjs.writeFile("export default plugins."+pluginClassName+";\n");
198+
199+
await pluginJs.close();
200+
await pluginMjs.close();
201+
}
202+
203+
// plugins/*.d.mts
204+
for(let i = 0; i < pluginNames.length; i++) {
205+
const pluginName = pluginNames[i];
206+
const codeInputDTs = await open("../code-input.d.ts", "r");
207+
const pluginDMts = await open("plugins/"+pluginName+".d.mts", "w")
208+
await pluginDMts.writeFile(AUTOGENERATED_NOTICE);
209+
// Imports
210+
await pluginDMts.writeFile("import { Plugin, CodeInput } from \"../code-input.d.mts\";\n");
211+
// Code after start and before end of this plugin, making use of the imported Template, not codeInput.Template
212+
let copyingCode = false;
213+
let functionSeen = false;
214+
for await (let line of codeInputDTs.readLines()) {
215+
if(line.includes("ESM-SUPPORT-END-PLUGIN-"+pluginName)) {
216+
break;
217+
}
218+
if(copyingCode) {
219+
if(/( |\t)*class.*/.test(line) && !functionSeen) {
220+
// Replace only first occurrence
221+
line = line.replace("class", "export default class");
222+
functionSeen = true;
223+
}
224+
await pluginDMts.writeFile(line.replaceAll("codeInput.Plugin", "Plugin").replaceAll("codeInput.CodeInput", "CodeInput")+"\n");
225+
}
226+
if(line.includes("ESM-SUPPORT-START-PLUGIN-"+pluginName)) {
227+
copyingCode = true; // After is code to copy - this line missed out
228+
}
229+
}
230+
await codeInputDTs.close();
231+
await pluginDMts.close();
232+
}
233+
}

esm/generate.sh

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Generate ESM modules and modular TS definitions
2+
# Dependency: POSIX shell, e.g. bash
3+
echo "Generating ECMAScript modules using Shell"
24

35
AUTOGENERATED_NOTICE="// NOTICE: This code is @generated from code outside the esm directory. Please do not edit it to contribute!"
46

@@ -15,12 +17,12 @@ AUTOGENERATED_NOTICE="// NOTICE: This code is @generated from code outside the e
1517
# Exports
1618
echo "export const Plugin = codeInput.Plugin;" >> code-input.mjs
1719
echo "export const Template = codeInput.Template;" >> code-input.mjs
20+
echo "export const CodeInput = codeInput.CodeInput;" >> code-input.mjs
1821
echo "export const registerTemplate = codeInput.registerTemplate;" >> code-input.mjs
1922

2023
# code-input.d.mts: (../code-input.d.ts without the plugin/template namespaces) + ESM
2124
echo $AUTOGENERATED_NOTICE > code-input.d.mts
2225
echo "" >> code-input.d.mts
23-
# Code before templates
2426
# Miss out no-ESM specific code at the top
2527
# Code before first namespace, after no-ESM specific code
2628
head -$(($(sed -n "/ESM-SUPPORT-START-NAMESPACE-1/=" ../code-input.d.ts | head -1) - 1)) ../code-input.d.ts | tail --line=+$(($(sed -n "/ESM-SUPPORT-END-NOESM-SPECIFIC/=" ../code-input.d.ts | head -1) + 1)) >> code-input.d.mts
@@ -30,7 +32,7 @@ AUTOGENERATED_NOTICE="// NOTICE: This code is @generated from code outside the e
3032
tail --line=+$(($(sed -n "/ESM-SUPPORT-END-NAMESPACE-2/=" ../code-input.d.ts | head -1) + 1)) ../code-input.d.ts >> code-input.d.mts
3133

3234
# Templates
33-
mkdir templates
35+
mkdir -p templates
3436
# templates/*.mjs
3537
# For each template name prepared for ESM support from the d.ts file:
3638
grep -Eo "ESM-SUPPORT-START-TEMPLATE-[A-Za-z]+" ../code-input.d.ts | sed "s/ESM-SUPPORT-START-TEMPLATE-//" | xargs -I % sh -c '
@@ -56,13 +58,14 @@ mkdir templates
5658
# Imports
5759
echo "import { Template, Plugin } from \"../code-input.d.mts\";" >> templates/$0.d.mts
5860
# Code after start and before end of this template, making use of the imported Template, not codeInput.Template, and the imported Plugin, not codeInput.Plugin, exporting the function as default
59-
head -$(($(sed -n "/ESM-SUPPORT-END-TEMPLATE-$0/=" ../code-input.d.ts | head -1) - 1)) ../code-input.d.ts | tail --line=+$(($(sed -n "/ESM-SUPPORT-START-TEMPLATE-$0/=" ../code-input.d.ts | head -1) + 1)) | sed "s/codeInput\.Template/Template/g" | sed "s/codeInput\.Plugin/Plugin/g" | sed "s/function /export default function /" >> templates/$0.d.mts
61+
# export default function replacement should work but won"t leave indentation as JS version does.
62+
head -$(($(sed -n "/ESM-SUPPORT-END-TEMPLATE-$0/=" ../code-input.d.ts | head -1) - 1)) ../code-input.d.ts | tail --line=+$(($(sed -n "/ESM-SUPPORT-START-TEMPLATE-$0/=" ../code-input.d.ts | head -1) + 1)) | sed "s/codeInput\.Template/Template/g" | sed "s/codeInput\.Plugin/Plugin/g" | sed -E "s/^[[:space:]]*function /export default function /" >> templates/$0.d.mts
6063
6164
# $0 is the template name, $1 is the autogenerated notice
6265
' "%" "$AUTOGENERATED_NOTICE"
6366

6467
# Plugins
65-
mkdir plugins
68+
mkdir -p plugins
6669
# plugins/*.mjs
6770
# For each plugin name prepared for ESM support from the d.ts file:
6871
grep -Eo "ESM-SUPPORT-START-PLUGIN-[A-Za-z\-]+" ../code-input.d.ts | sed "s/ESM-SUPPORT-START-PLUGIN-//" | xargs -I % sh -c '
@@ -90,7 +93,8 @@ mkdir plugins
9093
# Imports
9194
echo "import { Plugin, CodeInput } from \"../code-input.d.mts\";" >> plugins/$0.d.mts
9295
# Code after start and before end of this template, making use of the imported Plugin, not codeInput.Plugin and the imported CodeInput, not codeInput.CodeInput, exporting the class as default
93-
head -$(($(sed -n "/ESM-SUPPORT-END-PLUGIN-$0/=" ../code-input.d.ts | head -1) - 1)) ../code-input.d.ts | tail --line=+$(($(sed -n "/ESM-SUPPORT-START-PLUGIN-$0/=" ../code-input.d.ts | head -1) + 1)) | sed "s/codeInput\.Plugin/Plugin/g" | sed "s/codeInput\.CodeInput/CodeInput/g" | sed "s/class /export default class /" >> plugins/$0.d.mts
96+
# export default class replacement should work but won"t leave indentation as JS version does.
97+
head -$(($(sed -n "/ESM-SUPPORT-END-PLUGIN-$0/=" ../code-input.d.ts | head -1) - 1)) ../code-input.d.ts | tail --line=+$(($(sed -n "/ESM-SUPPORT-START-PLUGIN-$0/=" ../code-input.d.ts | head -1) + 1)) | sed "s/codeInput\.Plugin/Plugin/g" | sed "s/codeInput\.CodeInput/CodeInput/g" | sed -E "s/^[[:space:]]*class /export default class /" >> plugins/$0.d.mts
9498
9599
# $0 is the plugin name, $1 is the autogenerated notice
96100
' "%" "$AUTOGENERATED_NOTICE"

esm/template_names

Lines changed: 0 additions & 2 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"description": "Fully customisable, editable syntax-highlighted textareas.",
55
"browser": "code-input.js",
66
"scripts": {
7-
"test": "echo \"This is a front-end library, not a Node library. Please see the README for how to use.\" && exit 1"
7+
"test": "echo \"This is a front-end library, not a Node library. Please see the README for how to use.\" && exit 1",
8+
"build": "cd esm ; node generate.mjs ; cd .."
89
},
910
"repository": {
1011
"type": "git",

0 commit comments

Comments
 (0)