Skip to content

Commit d9d2c55

Browse files
authored
Merge pull request #157 from WebCoder49/esm-support
Add ECMAScript Module support
2 parents 56eec90 + 7b1c88d commit d9d2c55

19 files changed

+597
-93
lines changed

.npmignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.github/
2+
tests/
3+
CODE_OF_CONDUCT.md
4+
CONTRIBUTING.md

code-input.d.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export as namespace codeInput;
2+
// ESM-SUPPORT-END-NOESM-SPECIFIC Do not (re)move this - it's needed for ESM generation
23

34
/**
45
* Plugins are imported from the plugins folder. They will then
@@ -47,6 +48,7 @@ export abstract class Plugin {
4748
observedAttributes: Array<string>
4849
}
4950

51+
// ESM-SUPPORT-START-NAMESPACE-1 Do not (re)move this - it's needed for ESM generation
5052
/**
5153
* Before using any plugin in this namespace, please ensure you import its JavaScript
5254
* files (in the plugins folder), or continue to get a more detailed error in the developer
@@ -62,6 +64,7 @@ export abstract class Plugin {
6264
* @type {Object}
6365
*/
6466
export namespace plugins {
67+
// ESM-SUPPORT-START-PLUGIN-test Do not (re)move this - it's needed for ESM generation
6568
/**
6669
* JavaScript example of a plugin, which brings extra,
6770
* non-central optional functionality to code-input.
@@ -75,7 +78,9 @@ export namespace plugins {
7578
class Test extends Plugin {
7679
constructor();
7780
}
81+
// ESM-SUPPORT-END-PLUGIN-test Do not (re)move this - it's needed for ESM generation
7882

83+
// ESM-SUPPORT-START-PLUGIN-auto-close-brackets Do not (re)move this - it's needed for ESM generation
7984
/**
8085
* Automatically closes pairs of brackets/quotes/other syntaxes in code, but also lets you choose the brackets this
8186
* is activated for.
@@ -88,7 +93,9 @@ export namespace plugins {
8893
*/
8994
constructor(bracketPairs: Object);
9095
}
96+
// ESM-SUPPORT-END-PLUGIN-auto-close-brackets Do not (re)move this - it's needed for ESM generation
9197

98+
// ESM-SUPPORT-START-PLUGIN-autocomplete Do not (re)move this - it's needed for ESM generation
9299
/**
93100
* Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
94101
* Files: autocomplete.js / autocomplete.css
@@ -100,7 +107,9 @@ export namespace plugins {
100107
*/
101108
constructor(updatePopupCallback: (popupElem: HTMLElement, textarea: HTMLTextAreaElement, selectionEnd: number, selectionStart?: number) => void);
102109
}
110+
// ESM-SUPPORT-END-PLUGIN-autocomplete Do not (re)move this - it's needed for ESM generation
103111

112+
// ESM-SUPPORT-START-PLUGIN-autodetect Do not (re)move this - it's needed for ESM generation
104113
/**
105114
* Autodetect the language live and change the `lang` attribute using the syntax highlighter's
106115
* autodetect capabilities. Works with highlight.js only.
@@ -109,7 +118,9 @@ export namespace plugins {
109118
class Autodetect extends Plugin {
110119
constructor();
111120
}
121+
// ESM-SUPPORT-END-PLUGIN-autodetect Do not (re)move this - it's needed for ESM generation
112122

123+
// E doesn't exist? SM-SUPPORT-START-PLUGIN-debounce-update Do not (re)move this - it's needed for ESM generation
113124
/**
114125
* Debounce the update and highlighting function
115126
* https://medium.com/@jamischarles/what-is-debouncing-2505c0648ff1
@@ -122,7 +133,9 @@ export namespace plugins {
122133
*/
123134
constructor(delayMs: number);
124135
}
136+
// E doesn't exist? SM-SUPPORT-END-PLUGIN-debounce-update Do not (re)move this - it's needed for ESM generation
125137

138+
// ESM-SUPPORT-START-PLUGIN-find-and-replace Do not (re)move this - it's needed for ESM generation
126139
/**
127140
* Add Find-and-Replace (Ctrl+F for find, Ctrl+H for replace by default) functionality to the code editor.
128141
* Files: find-and-replace.js / find-and-replace.css
@@ -163,7 +176,9 @@ export namespace plugins {
163176
*/
164177
showPrompt(codeInputElement: CodeInput, replacePartExpanded: boolean): void;
165178
}
179+
// ESM-SUPPORT-END-PLUGIN-find-and-replace Do not (re)move this - it's needed for ESM generation
166180

181+
// ESM-SUPPORT-START-PLUGIN-go-to-line Do not (re)move this - it's needed for ESM generation
167182
/**
168183
* Add basic Go-To-Line (ctrl-G by default) functionality to the code editor.
169184
* Files: go-to-line.js / go-to-line.css
@@ -185,7 +200,9 @@ export namespace plugins {
185200
*/
186201
showPrompt(codeInput: CodeInput): void;
187202
}
203+
// ESM-SUPPORT-END-PLUGIN-go-to-line Do not (re)move this - it's needed for ESM generation
188204

205+
// ESM-SUPPORT-START-PLUGIN-indent Do not (re)move this - it's needed for ESM generation
189206
/**
190207
* Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it
191208
* possible to indent/unindent multiple lines using Tab/Shift+Tab
@@ -205,7 +222,9 @@ export namespace plugins {
205222
tabForNavigation?: string;
206223
});
207224
}
225+
// ESM-SUPPORT-END-PLUGIN-indent Do not (re)move this - it's needed for ESM generation
208226

227+
// ESM-SUPPORT-START-PLUGIN-select-token-callbacks Do not (re)move this - it's needed for ESM generation
209228
/**
210229
* Make tokens in the <pre><code> element that are included within the selected text of the <code-input>
211230
* gain a CSS class while selected, or trigger JavaScript callbacks.
@@ -254,7 +273,9 @@ export namespace plugins {
254273
static createClassSynchronisation(selectedClass: string): codeInput.plugins.SelectTokenCallbacks.TokenSelectorCallbacks;
255274
}
256275
}
276+
// ESM-SUPPORT-END-PLUGIN-select-token-callbacks Do not (re)move this - it's needed for ESM generation
257277

278+
// ESM-SUPPORT-START-PLUGIN-special-chars Do not (re)move this - it's needed for ESM generation
258279
/**
259280
* Render special characters and control characters as a symbol with their hex code.
260281
* Files: special-chars.js, special-chars.css
@@ -269,14 +290,9 @@ export namespace plugins {
269290
*/
270291
constructor(colorInSpecialChars?: boolean, inheritTextColor?: boolean, specialCharRegExp?: RegExp);
271292
}
293+
// ESM-SUPPORT-END-PLUGIN-special-chars Do not (re)move this - it's needed for ESM generation
272294
}
273-
274-
/**
275-
* Register a plugin class under `codeInput.plugins`.
276-
* @param {string} pluginName The identifier of the plugin: if it is `"foo"`, `new codeInput.plugins.foo(`...`)` will instantiate it, etc.
277-
* @param {Object} pluginClass The class of the plugin, created with `class extends codeInput.plugin {`...`}`
278-
*/
279-
export function registerPluginClass(pluginName: string, pluginClass: Object): void;
295+
// ESM-SUPPORT-END-NAMESPACE-1 Do not (re)move this - it's needed for ESM generation
280296

281297
/**
282298
* Please see `codeInput.templates.prism` or `codeInput.templates.hljs`.
@@ -330,6 +346,7 @@ export class Template {
330346
constructor(highlight?: (code: HTMLElement, codeInput: CodeInput) => void, preElementStyled?: boolean, isCode?: boolean, includeCodeInputInHighlightFunc?: true, plugins?: Plugin[])
331347
}
332348

349+
// ESM-SUPPORT-START-NAMESPACE-2 Do not (re)move this - it's needed for ESM generation
333350
/**
334351
* Shortcut functions for creating templates.
335352
* Each code-input element has a template attribute that
@@ -344,6 +361,7 @@ export class Template {
344361
* For adding small pieces of functionality, please see `codeInput.plugins`.
345362
*/
346363
export namespace templates {
364+
// ESM-SUPPORT-START-TEMPLATE-prism Do not (re)move this - it's needed for ESM generation
347365
/**
348366
* A template that uses Prism.js syntax highlighting (https://prismjs.com/).
349367
*/
@@ -356,10 +374,12 @@ export namespace templates {
356374
*/
357375
constructor(prism: Object, plugins?: Plugin[])
358376
}
377+
// ESM-SUPPORT-END-TEMPLATE-prism Do not (re)move this - it's needed for ESM generation
359378
/**
360379
* @deprecated Please use `new codeInput.templates.Prism(...)`
361380
*/
362381
function prism(prism: Object, plugins?: Plugin[]): Template
382+
// ESM-SUPPORT-START-TEMPLATE-hljs Do not (re)move this - it's needed for ESM generation
363383
/**
364384
* A template that uses highlight.js syntax highlighting (https://highlightjs.org/).
365385
*/
@@ -372,6 +392,7 @@ export namespace templates {
372392
*/
373393
constructor(hljs: Object, plugins?: Plugin[])
374394
}
395+
// ESM-SUPPORT-END-TEMPLATE-hljs Do not (re)move this - it's needed for ESM generation
375396
/**
376397
* @deprecated Please use `new codeInput.templates.Hljs(...)`
377398
*/
@@ -391,6 +412,7 @@ export namespace templates {
391412
*/
392413
function rainbowText(rainbowColors?: string[], delimiter?: string, plugins?: Plugin[]): Template
393414
}
415+
// ESM-SUPPORT-END-NAMESPACE-2 Do not (re)move this - it's needed for ESM generation
394416

395417
/**
396418
* A `<code-input>` element, an instance of an `HTMLElement`, and the result

code-input.js

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
/**
22
* **code-input** is a library which lets you create custom HTML `<code-input>`
33
* elements that act like `<textarea>` elements but support syntax-highlighted
4-
* code, implemented using any typical syntax highlighting library. [MIT-Licensed]
5-
*
4+
* code, implemented using any typical syntax highlighting library.
5+
*
6+
* License of whole library for bundlers:
7+
*
8+
* Copyright 2021-2025 Oliver Geer and contributors
9+
* @license MIT
10+
*
611
* **<https://github.com/WebCoder49/code-input>**
712
*/
13+
"use strict";
814

915

1016
var codeInput = {
@@ -121,30 +127,27 @@ var codeInput = {
121127
// Add waiting code-input elements wanting this template from queue
122128
if (templateName in codeInput.templateNotYetRegisteredQueue) {
123129
for (let i in codeInput.templateNotYetRegisteredQueue[templateName]) {
124-
elem = codeInput.templateNotYetRegisteredQueue[templateName][i];
130+
const elem = codeInput.templateNotYetRegisteredQueue[templateName][i];
125131
elem.template = template;
126-
codeInput.runOnceWindowLoaded((function(elem) { elem.connectedCallback(); }).bind(null, elem), elem);
132+
elem.connectedCallback();
127133
// Bind sets elem as first parameter of function
128134
// So innerHTML can be read
129135
}
130-
console.log(`code-input: template: Added existing elements with template ${templateName}`);
131136
}
132137

133138
if (codeInput.defaultTemplate == undefined) {
134139
codeInput.defaultTemplate = templateName;
135140
// Add elements with default template from queue
136141
if (undefined in codeInput.templateNotYetRegisteredQueue) {
137142
for (let i in codeInput.templateNotYetRegisteredQueue[undefined]) {
138-
elem = codeInput.templateNotYetRegisteredQueue[undefined][i];
143+
const elem = codeInput.templateNotYetRegisteredQueue[undefined][i];
139144
elem.template = template;
140-
codeInput.runOnceWindowLoaded((function(elem) { elem.connectedCallback(); }).bind(null, elem), elem);
145+
elem.connectedCallback();
141146
// Bind sets elem as first parameter of function
142147
// So innerHTML can be read
143148
}
144149
}
145-
console.log(`code-input: template: Set template ${templateName} as default`);
146150
}
147-
console.log(`code-input: template: Created template ${templateName}`);
148151
},
149152

150153
/**
@@ -209,6 +212,7 @@ var codeInput = {
209212
plugins = [];
210213
},
211214

215+
// ESM-SUPPORT-START-TEMPLATES-BLOCK-1 Do not (re)move this - it's needed for ESM generation!
212216
/**
213217
* For creating a custom template from scratch, please
214218
* use `new codeInput.Template(...)`
@@ -311,6 +315,7 @@ var codeInput = {
311315
};
312316
},
313317
},
318+
// ESM-SUPPORT-END-TEMPLATES-BLOCK-1 Do not (re)move this - it's needed for ESM generation!
314319

315320
/* ------------------------------------
316321
* ------------Plugins-----------------
@@ -351,7 +356,6 @@ var codeInput = {
351356
* modifications to the `codeInput.Plugin.attributeChanged` method.
352357
*/
353358
constructor(observedAttributes) {
354-
console.log("code-input: plugin: Created plugin");
355359

356360
observedAttributes.forEach((attribute) => {
357361
codeInput.observedAttributes.push(attribute);
@@ -720,10 +724,8 @@ var codeInput = {
720724
this.template = this.getTemplate();
721725
if (this.template != undefined) {
722726
this.classList.add("code-input_registered");
723-
codeInput.runOnceWindowLoaded(() => {
724-
this.setup();
725-
this.classList.add("code-input_loaded");
726-
}, this);
727+
this.setup();
728+
this.classList.add("code-input_loaded");
727729
}
728730
this.mutationObserver = new MutationObserver(this.mutationObserverCallback.bind(this));
729731
this.mutationObserver.observe(this, {
@@ -1013,20 +1015,65 @@ var codeInput = {
10131015
formResetCallback() {
10141016
this.value = this.initialValue;
10151017
};
1016-
},
1018+
}
1019+
}
10171020

1018-
/**
1019-
* To ensure the DOM is ready, run this callback after the window
1020-
* has loaded (or now if it has already loaded)
1021+
// ESM-SUPPORT-START-TEMPLATES-BLOCK-2 Do not (re)move this - it's needed for ESM generation!
1022+
{
1023+
// Templates are defined here after the codeInput variable is set, because they reference it by extending codeInput.Template.
1024+
1025+
// ESM-SUPPORT-START-TEMPLATE-prism Do not (re)move this - it's needed for ESM generation!
1026+
/**
1027+
* A template that uses Prism.js syntax highlighting (https://prismjs.com/).
1028+
*/
1029+
class Prism extends codeInput.Template { // Dependency: Prism.js (https://prismjs.com/)
1030+
/**
1031+
* Constructor to create a template that uses Prism.js syntax highlighting (https://prismjs.com/)
1032+
* @param {Object} prism Import Prism.js, then after that import pass the `Prism` object as this parameter.
1033+
* @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins`
1034+
* @returns {codeInput.Template} template object
1035+
*/
1036+
constructor(prism, plugins = []) {
1037+
super(
1038+
prism.highlightElement, // highlight
1039+
true, // preElementStyled
1040+
true, // isCode
1041+
false, // includeCodeInputInHighlightFunc
1042+
plugins
1043+
);
1044+
}
1045+
};
1046+
// ESM-SUPPORT-END-TEMPLATE-prism Do not (re)move this - it's needed for ESM generation!
1047+
codeInput.templates.Prism = Prism;
1048+
1049+
// ESM-SUPPORT-START-TEMPLATE-hljs Do not (re)move this - it's needed for ESM generation!
1050+
/**
1051+
* A template that uses highlight.js syntax highlighting (https://highlightjs.org/).
10211052
*/
1022-
runOnceWindowLoaded(callback, codeInputElem) {
1023-
if(document.readyState == "complete") {
1024-
callback(); // Fully loaded
1025-
} else {
1026-
window.addEventListener("load", callback);
1053+
class Hljs extends codeInput.Template { // Dependency: Highlight.js (https://highlightjs.org/)
1054+
/**
1055+
* Constructor to create a template that uses highlight.js syntax highlighting (https://highlightjs.org/)
1056+
* @param {Object} hljs Import highlight.js, then after that import pass the `hljs` object as this parameter.
1057+
* @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins`
1058+
* @returns {codeInput.Template} template object
1059+
*/
1060+
constructor(hljs, plugins = []) {
1061+
super(
1062+
function(codeElement) {
1063+
codeElement.removeAttribute("data-highlighted");
1064+
hljs.highlightElement(codeElement);
1065+
}, // highlight
1066+
false, // preElementStyled
1067+
true, // isCode
1068+
false, // includeCodeInputInHighlightFunc
1069+
plugins
1070+
);
10271071
}
1028-
}
1072+
};
1073+
// ESM-SUPPORT-END-TEMPLATE-hljs Do not (re)move this - it's needed for ESM generation!
1074+
codeInput.templates.Hljs = Hljs;
10291075
}
1076+
// ESM-SUPPORT-END-TEMPLATES-BLOCK-2 Do not (re)move this - it's needed for ESM generation!
10301077

10311078
{
10321079
// Templates are defined here after the codeInput variable is set, because they reference it by extending codeInput.Template.

esm/.gitignore

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

esm/.npmignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# In NPM, ignore generator files and keep ES modules
2+
.gitignore
3+
generate.mjs
4+
generate.sh

esm/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Autogenerated ECMAScript Modules
2+
3+
## Using
4+
5+
If you are using Yarn, NPM, or a similar package manager, the files should have been generated before being uploaded to the package repository, or on `pack` if the package manager is fetching from Git.
6+
7+
Otherwise, after changing directory to the one containing this file:
8+
9+
- If you have Node.js installed, run `node generate.mjs`.
10+
- If you don't have Node.js installed but are on a POSIX-like system with `bash`/`zsh`, run `sh ./generate.sh`.
11+
- If neither of the above are true, install Node.js or (slightly harder; look online) a POSIX/"Linux" compatible shell.
12+
13+
## Extra Information
14+
15+
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.
16+
17+
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.
18+
19+
To ensure backwards compatibility, in the first stage of the transition a process to auto-generate ESM-compatible files from the existing JavaScript files will be created, so existing `<script>` tag users are unaffected.
20+
21+
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.
22+
23+
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.

0 commit comments

Comments
 (0)