Try copying the code from this GitHub (to be more precise: https://github.com/bestiejs/punycode.js/blob/master/punycode.js ). I did not use it myself, but many people refer to it on the Internet. The author himself collected this code by reworking the Punicode implementations and documentation available at that time (RFC 3492, RFC 5891), and how BE positions it as the most documented, productive, and covered with tests.
The source code is beautifully and competently designed, and most importantly, it is commented out through each line. I will give only the main function encode , the shell and decoding can be thought out independently, even if the GitHub fell or the author removes the project from public access:
/** * Converts a string of Unicode symbols (eg a domain name label) to a * Punycode string of ASCII-only symbols. * @memberOf punycode * @param {String} input The string of Unicode symbols. * @returns {String} The resulting Punycode string of ASCII-only symbols. */ const encode = function(input) { const output = []; // Convert the input in UCS-2 to an array of Unicode code points. input = ucs2decode(input); // Cache the length. let inputLength = input.length; // Initialize the state. let n = initialN; let delta = 0; let bias = initialBias; // Handle the basic code points. for (const currentValue of input) { if (currentValue < 0x80) { output.push(stringFromCharCode(currentValue)); } } let basicLength = output.length; let handledCPCount = basicLength; // `handledCPCount` is the number of code points that have been handled; // `basicLength` is the number of basic code points. // Finish the basic string with a delimiter unless it's empty. if (basicLength) { output.push(delimiter); } // Main encoding loop: while (handledCPCount < inputLength) { // All non-basic code points < n have been handled already. Find the next // larger one: let m = maxInt; for (const currentValue of input) { if (currentValue >= n && currentValue < m) { m = currentValue; } } // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>, // but guard against overflow. const handledCPCountPlusOne = handledCPCount + 1; if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { error('overflow'); } delta += (m - n) * handledCPCountPlusOne; n = m; for (const currentValue of input) { if (currentValue < n && ++delta > maxInt) { error('overflow'); } if (currentValue == n) { // Represent delta as a generalized variable-length integer. let q = delta; for (let k = base; /* no condition */; k += base) { const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); if (q < t) { break; } const qMinusT = q - t; const baseMinusT = base - t; output.push( stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) ); q = floor(qMinusT / baseMinusT); } output.push(stringFromCharCode(digitToBasic(q, 0))); bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); delta = 0; ++handledCPCount; } } ++delta; ++n; } return output.join(''); };
There are a couple of interesting moments if you are going to do it yourself:
First, it is better to check for the presence of non-ASCI characters in the host; if not, you can return it as it is. regexNonASCII = /[^\x20-\x7E]/; // unprintable ASCII chars + non-ASCII chars return regexNonASCII.test(string) ? 'xn--' + encode(string) : string;
Javascript stores strings in UCS-2 format, so in order to encode in unicode, you must first decode a string from UCS-2. input = ucs2decode(input);
If you google a bit, you will find an old CO question about the same thing ( https://stackoverflow.com/questions/183485/can-anyone-recommend-a-good-free-javascript-for-punycode-to-unicode-conversion ) where both the original and the above “optimized” version are indicated.