Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support async functions for loading the utils script #1838

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,24 @@ I'm very open to contributions, big and small! For general instructions on submi

## Changes to the plugin

In order to build the project, you will first need to install [npm](https://www.npmjs.org), and then run `npm install` to install the project's dependencies. At this point, the included `demo.html` should be working, if you open it in your browser. Then you should make your changes in the `src` directory, and be sure to run the relevant build script before committing your changes - if you've modified the JS, you'll need to run `npm run build:js`, or if you've modified the CSS, it's `npm run build:css`.
In order to build the project, you will first need to install [npm](https://www.npmjs.org), and then run `npm install` to install the project's dependencies.

If you want to try out a demo playground for the component:
1. Start the server by running `npm run server`.
2. Open the demo page in your browser at the address printed to your console.

**Tests** are broken up into two parts. We are currently porting the existing test suite from Grunt & Jasmine to Jest.
- To run all tests, run `npm test`.
- To only run the new tests, run `npm run jest`.
- To only run the old tests, run `npx grunt jasmine:test`.
- To run & debug the old tests interactively in your browser, run: `npx grunt jasmine:interactive` and load the URL it prints out in your browser.

**Any time you make changes, you’ll need to re-build the plugin.** Most tests run against the builds, so after making changes, you’ll need to do a build before running tests.
- To do a complete build, run `npm run build`
- To build just the JS:
- `npm run build:js` runs various checks (linting, etc.) and then builds.
- `npm run build:jsfast` *just* builds the JS. This is useful when iterating and testing small changes. Make sure you eventually do a full build with all the checks, though!
- To build just the CSS, run `npm run build:css`.

## Updating to a new version of libphonenumber

Expand Down
3 changes: 3 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Jest uses the `vm` module, which does not have production ready module support
# yet. This option lets us use dynamic imports.
node-options='--experimental-vm-modules'
15 changes: 14 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,21 @@ module.exports = function(grunt) {
// just vue
grunt.registerTask('vue', ['replace:vueWithUtils', 'shell:buildVue', 'replace:removeImport']);

// Run tests with a server so that async imports/fetches work.
grunt.registerTask('jasmine:test', ['connect:test', 'jasmine']);
grunt.registerTask('jasmine:interactive', () => {
grunt.event.once('connect.test.listening', (host, port) => {
const origin = `http://${host === '::' ? 'localhost' : host}:${port}`;
console.log(`
To view and debug tests in your browser, go to ${origin}/spec.html
To play with a demo of the package, go to ${origin}/demo.html
`);
});
grunt.task.run('connect:test:keepalive');
});

// Travis CI
grunt.registerTask('travis', ['jasmine']);
grunt.registerTask('travis', ['jasmine:test']);
// bump version number in 3 files, rebuild js to update headers, then commit, tag and push
grunt.registerTask('version', ['shell:test', 'bump-only', 'js', 'bump-commit']);
grunt.registerTask('version:minor', ['shell:test', 'bump-only:minor', 'js', 'bump-commit']);
Expand Down
59 changes: 49 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ _Note: We have now dropped support for all versions of Internet Explorer because
<script>
const input = document.querySelector("#phone");
window.intlTelInput(input, {
utilsScript: "https://cdn.jsdelivr.net/npm/[email protected]/build/js/utils.js",
loadUtilsOnInit: "https://cdn.jsdelivr.net/npm/[email protected]/build/js/utils.js",
});
</script>
```
Expand All @@ -109,10 +109,21 @@ _Note: We have now dropped support for all versions of Internet Explorer because

const input = document.querySelector("#phone");
intlTelInput(input, {
utilsScript: "path/to/utils.js"
loadUtilsOnInit: () => import("intl-tel-input/utils")
});
```

Most bundlers (such as Webpack, Vite, or Parcel) will see this and place the [utilities script](#utilities-script) in a separate bundle and load it asynchronously, only when needed. If this doesn’t work with your bundler or you want to load the utils module from some other location (such as a CDN) you can set the `loadUtilsOnInit` option to the URL to load from as a string. For example:

```js
import intlTelInput from 'intl-tel-input';

const input = document.querySelector("#phone");
intlTelInput(input, {
loadUtilsOnInit: `https://cdn.jsdelivr.net/npm/intl-tel-input@${intlTelInput.version}/build/js/utils.js`;
});
```

## Getting Started (Not using a bundler)
1. Download the [latest release](https://github.com/jackocnr/intl-tel-input/releases/latest), or better yet install it with [npm](https://www.npmjs.com/package/intl-tel-input)

Expand All @@ -137,7 +148,7 @@ _Note: We have now dropped support for all versions of Internet Explorer because
<script>
const input = document.querySelector("#phone");
window.intlTelInput(input, {
utilsScript: "path/to/utils.js"
loadUtilsOnInit: "path/to/utils.js"
});
</script>
```
Expand Down Expand Up @@ -304,6 +315,15 @@ intlTelInput(input, {
Type: `String` Default: `""`
Set the initial country selection by specifying its country code e.g. `"us"` for the United States. (Be careful not to do this unless you are sure of the user's country, as it can lead to tricky issues if set incorrectly and the user auto-fills their national number and submits the form without checking - in certain cases, this can pass validation and you can end up storing a number with the wrong dial code). You can also set `initialCountry` to `"auto"`, which will look up the user's country based on their IP address (requires the `geoIpLookup` option - [see example](https://intl-tel-input.com/examples/lookup-country.html)). Note that however you use `initialCountry`, it will not update the country selection if the input already contains a number with an international dial code.

**loadUtilsOnInit**
Type: `String` or `() => Promise<module>` Default: `""` Example: `"/build/js/utils.js"`

This is one way to (lazy) load the included utils.js (to enable formatting/validation etc) - see [Loading The Utilities Script](#loading-the-utilities-script) for more options. You will need to host the [utils.js](https://github.com/jackocnr/intl-tel-input/blob/master/build/js/utils.js) file, and then set the `loadUtilsOnInit` option to that URL, or alternatively just point it to a CDN hosted version e.g. `"https://cdn.jsdelivr.net/npm/[email protected]/build/js/utils.js"`. The script is loaded via a [dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) statement, which means the URL cannot be relative - it must be absolute.

Alternatively, this can be a function that returns a promise for the utils module. When using a bundler like Webpack, this can be used to tell the bundler that the utils script should be kept in a separate file from the rest of your code. For example: `{ loadUtilsOnInit: () => import("intl-tel-input/utils") }`.

The script is only fetched when you initialise the plugin, and additionally, only when the page has finished loading (on the window load event) to prevent blocking (the script is ~260KB). When instantiating the plugin, a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) object is returned under the `promise` instance property, so you can do something like `iti.promise.then(callback)` to know when initialisation requests like this have finished. See [Utilities Script](#utilities-script) for more information.

**nationalMode**
Type: `Boolean` Default: `true`
Format numbers in the national format, rather than the international format. This applies to placeholder numbers, and when displaying users' existing numbers. Note that it's fine for users to type their numbers in national format - as long as they have selected the right country, you can use `getNumber` to extract a full international number - [see example](https://intl-tel-input.com/examples/national-mode.html). It is recommended to leave this option enabled, to encourage users to enter their numbers in national format as this is usually more familiar to them and so it creates a better user experience.
Expand Down Expand Up @@ -334,9 +354,10 @@ As the user types in the input, ignore any irrelevant characters. The user can o
Type: `Boolean` Default: `true on mobile devices, false otherwise`
Control when the country list appears as a fullscreen popup vs an inline dropdown. By default, it will appear as a fullscreen popup on mobile devices (based on user-agent and screen width), to make better use of the limited space (similar to how a native `<select>` works), and as an inline dropdown on larger devices/screens. Play with this option on [Storybook](https://intl-tel-input.com/storybook/?path=/docs/intltelinput--usefullscreenpopup) (using the React component).

**utilsScript**
Type: `String` Default: `""` Example: `"/build/js/utils.js"`
This is one way to (lazy) load the included utils.js (to enable formatting/validation etc) - see [Loading The Utilities Script](#loading-the-utilities-script) for more options. You will need to host the [utils.js](https://github.com/jackocnr/intl-tel-input/blob/master/build/js/utils.js) file, and then set the `utilsScript` option to that URL, or alternatively just point it to a CDN hosted version e.g. `"https://cdn.jsdelivr.net/npm/[email protected]/build/js/utils.js"`. The script is loaded via a [dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) statement, which means the URL cannot be relative - it must be absolute. The script is only fetched when you initialise the plugin, and additionally, only when the page has finished loading (on the window load event) to prevent blocking (the script is ~260KB). When instantiating the plugin, a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) object is returned under the `promise` instance property, so you can do something like `iti.promise.then(callback)` to know when initialisation requests like this have finished. See [Utilities Script](#utilities-script) for more information.
**utilsScript** ⚠️ DEPRECATED
Type: `String` or `() => Promise<module>` Default: `""` Example: `"/build/js/utils.js"`

This option is deprecated and has been renamed to `loadUtilsOnInit`. Please see the deatails for that option and use it instead.

**validationNumberType**
Type: `String` Default: `"MOBILE"`
Expand Down Expand Up @@ -472,9 +493,17 @@ iti.isValidNumber(); // etc
```

**loadUtils**
An alternative to the `utilsScript` option, this method lets you manually load the utils.js script on demand, to enable formatting/validation etc. See [Loading The Utilities Script](#loading-the-utilities-script) for more information. This method should only be called once per page. A [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) object is returned so you can use `loadUtils().then(callback)` to know when it's finished.
An alternative to the `loadUtilsOnInit` option, this method lets you manually load the utils.js script on demand, to enable formatting/validation etc. See [Loading The Utilities Script](#loading-the-utilities-script) for more information. This method should only be called once per page. A [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) object is returned so you can use `loadUtils().then(callback)` to know when it's finished.
```js
// Load from a URL:
intlTelInput.loadUtils("/build/js/utils.js");

// Or manage load via some other method with a function:
intlTelInput.loadUtils(async () => {
// Your own loading logic here. Return a JavaScript "module" object with
// the utils as the default export.
return { default: { /* a copy of the utils module */ } }
});
```

## Events
Expand Down Expand Up @@ -551,10 +580,20 @@ To recompile the utils script yourself (e.g. to update the version of libphonenu
The utils script provides lots of great functionality (see above section), but comes at the cost of increased filesize (~260KB). There are two main ways to load the utils script, depending on whether you're concerned about filesize or not.

**Option 1: intlTelInputWithUtils**
If you're not concerned about filesize (e.g. you're lazy loading this script), the easiest thing to do is to just use the full bundle /build/js/intlTelInputWithUtils.js, which comes with the utils script included. This script can be used exactly like the main intlTelInput.js - so it can either be loaded directly onto the page (which defines `window.intlTelInput` like usual), or it can be imported like so: `import intlTelInput from "intl-tel-input/intlTelInputWithUtils"`.
If you're not concerned about filesize (e.g. you're lazy loading this script), the easiest thing to do is to just use the full bundle (`/build/js/intlTelInputWithUtils.js`), which comes with the utils script included. This script can be used exactly like the main intlTelInput.js - so it can either be loaded directly onto the page (which defines `window.intlTelInput` like usual), or it can be imported like so: `import intlTelInput from "intl-tel-input/intlTelInputWithUtils"`.

**Option 2: loadUtilsOnInit**
If you *are* concerned about filesize, you can lazy load the utils script when the plugin initialises, using the `loadUtilsOnInit` initialisation option. You will need to host the [utils.js](https://github.com/jackocnr/intl-tel-input/blob/master/build/js/utils.js) file, and then set the `loadUtilsOnInit` option to that URL, or just point it to a CDN hosted version e.g. `"https://cdn.jsdelivr.net/npm/[email protected]/build/js/utils.js"`.

Alternatively, you can set the `loadUtilsOnInit` option to a function that returns a promise for the utils script as a JS module object. If you use a bundler like Webpack, Vite, or Parcel to build your app, you can use it like this automatically separate the utils into a different bundle:

```js
intlTelInput(htmlInputElement, {
loadUtilsOnInit: () => import("intl-tel-input/utils)
});
```

**Option 2: utilsScript**
If you *are* concerned about filesize, you can lazy load the utils script when the plugin initialises, using the `utilsScript` initialisation option. You will need to host the [utils.js](https://github.com/jackocnr/intl-tel-input/blob/master/build/js/utils.js) file, and then set the `utilsScript` option to that URL, or just point it to a CDN hosted version e.g. `"https://cdn.jsdelivr.net/npm/[email protected]/build/js/utils.js"`. If you want more control over when this file is lazy loaded, you can manually invoke the `loadUtils` static method, instead of using `utilsScript`.
If you want more control over when this file is lazy loaded, you can manually invoke the `loadUtils` static method, instead of using `loadUtilsOnInit`.

## Troubleshooting

Expand Down
14 changes: 10 additions & 4 deletions build/js/intlTelInput.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,9 @@ declare module "intl-tel-input/i18n/en" {
declare module "intl-tel-input" {
import { Country } from "intl-tel-input/data";
import { I18n } from "intl-tel-input/i18n/types";
type UtilsLoader = () => Promise<{
default: ItiUtils;
}>;
interface IntlTelInputInterface {
(input: HTMLInputElement, options?: SomeOptions): Iti;
autoCountry?: string;
Expand All @@ -296,9 +299,9 @@ declare module "intl-tel-input" {
instances: {
[key: string]: Iti;
};
loadUtils: (path: string) => Promise<unknown> | null;
startedLoadingAutoCountry?: boolean;
startedLoadingUtilsScript?: boolean;
loadUtils: (source: string | UtilsLoader) => Promise<unknown> | null;
startedLoadingAutoCountry: boolean;
startedLoadingUtilsScript: boolean;
version: string | undefined;
utils?: ItiUtils;
}
Expand Down Expand Up @@ -345,14 +348,16 @@ declare module "intl-tel-input" {
}) | null;
i18n: I18n;
initialCountry: string;
loadUtilsOnInit: string | UtilsLoader;
nationalMode: boolean;
onlyCountries: string[];
placeholderNumberType: NumberType;
showFlags: boolean;
separateDialCode: boolean;
strictMode: boolean;
useFullscreenPopup: boolean;
utilsScript: string;
/** @deprecated Please use the `loadUtilsOnInit` option. */
utilsScript: string | UtilsLoader;
validationNumberType: NumberType | null;
}
export type SomeOptions = Partial<AllOptions>;
Expand Down Expand Up @@ -400,6 +405,7 @@ declare module "intl-tel-input" {
private _handleClickOffToClose;
private _handleKeydownOnDropdown;
private _handleSearchChange;
private _handlePageLoad;
private resolveAutoCountryPromise;
private rejectAutoCountryPromise;
private resolveUtilsScriptPromise;
Expand Down
Loading