如何在Angular中动态加载外部脚本?

我有这个模块,它将外部库与额外的逻辑组件化,而不直接将<script>标记添加到index.html中:

import 'http://external.com/path/file.js'
//import '../js/file.js'


@Component({
selector: 'my-app',
template: `
<script src="http://iknow.com/this/does/not/work/either/file.js"></script>
<div>Template</div>`
})
export class MyAppComponent {...}

我注意到ES6规范中的import是静态的,在TypeScript编译期间而不是在运行时解析。

无论如何,使它可配置的文件。js将从CDN或本地文件夹加载? 如何告诉Angular 2动态加载脚本?< / p >
315661 次浏览

如果你正在使用system.js,你可以在运行时使用System.import():

export class MyAppComponent {
constructor(){
System.import('path/to/your/module').then(refToLoadedModule => {
refToLoadedModule.someFunction();
}
);
}

如果你正在使用webpack,你可以通过require.ensure充分利用它强大的代码分割支持:

export class MyAppComponent {
constructor() {
require.ensure(['path/to/your/module'], require => {
let yourModule = require('path/to/your/module');
yourModule.someFunction();
});
}
}

这可能有用。此代码在单击按钮时动态地将<script>标记追加到html文件的head

const url = 'http://iknow.com/this/does/not/work/either/file.js';


export class MyAppComponent {
loadAPI: Promise<any>;


public buttonClicked() {
this.loadAPI = new Promise((resolve) => {
console.log('resolving promise...');
this.loadScript();
});
}


public loadScript() {
console.log('preparing to load...')
let node = document.createElement('script');
node.src = url;
node.type = 'text/javascript';
node.async = true;
node.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(node);
}
}
你可以使用以下技巧来 在你的Angular项目中,动态地根据需要加载JS脚本和库

script.store.ts将包含本地或远程服务器上脚本的路径和用于动态加载脚本的的名字

 interface Scripts {
name: string;
src: string;
}
export const ScriptStore: Scripts[] = [
{name: 'filepicker', src: 'https://api.filestackapi.com/filestack.js'},
{name: 'rangeSlider', src: '../../../assets/js/ion.rangeSlider.min.js'}
];

script.service.ts是一个可注入的服务,它将处理脚本的加载,复制script.service.ts

import {Injectable} from "@angular/core";
import {ScriptStore} from "./script.store";


declare var document: any;


@Injectable()
export class ScriptService {


private scripts: any = {};


constructor() {
ScriptStore.forEach((script: any) => {
this.scripts[script.name] = {
loaded: false,
src: script.src
};
});
}


load(...scripts: string[]) {
var promises: any[] = [];
scripts.forEach((script) => promises.push(this.loadScript(script)));
return Promise.all(promises);
}


loadScript(name: string) {
return new Promise((resolve, reject) => {
//resolve if already loaded
if (this.scripts[name].loaded) {
resolve({script: name, loaded: true, status: 'Already Loaded'});
}
else {
//load script
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = this.scripts[name].src;
if (script.readyState) {  //IE
script.onreadystatechange = () => {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
this.scripts[name].loaded = true;
resolve({script: name, loaded: true, status: 'Loaded'});
}
};
} else {  //Others
script.onload = () => {
this.scripts[name].loaded = true;
resolve({script: name, loaded: true, status: 'Loaded'});
};
}
script.onerror = (error: any) => resolve({script: name, loaded: false, status: 'Loaded'});
document.getElementsByTagName('head')[0].appendChild(script);
}
});
}


}

将这个ScriptService注入到任何你需要它的地方,并像这样加载js库

this.script.load('filepicker', 'rangeSlider').then(data => {
console.log('script loaded ', data);
}).catch(error => console.log(error));

我已经修改了@rahul kumars的答案,所以它使用可观察的代替:

import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { Observer } from "rxjs/Observer";


@Injectable()
export class ScriptLoaderService {
private scripts: ScriptModel[] = [];


public load(script: ScriptModel): Observable<ScriptModel> {
return new Observable<ScriptModel>((observer: Observer<ScriptModel>) => {
var existingScript = this.scripts.find(s => s.name == script.name);


// Complete if already loaded
if (existingScript && existingScript.loaded) {
observer.next(existingScript);
observer.complete();
}
else {
// Add the script
this.scripts = [...this.scripts, script];


// Load the script
let scriptElement = document.createElement("script");
scriptElement.type = "text/javascript";
scriptElement.src = script.src;


scriptElement.onload = () => {
script.loaded = true;
observer.next(script);
observer.complete();
};


scriptElement.onerror = (error: any) => {
observer.error("Couldn't load script " + script.src);
};


document.getElementsByTagName('body')[0].appendChild(scriptElement);
}
});
}
}


export interface ScriptModel {
name: string,
src: string,
loaded: boolean
}
< p > @d123546 我也遇到了同样的问题,现在使用ngAfterContentInit(生命周期钩子)在组件中工作,就像这样
import { Component, OnInit, AfterContentInit } from '@angular/core';
import { Router } from '@angular/router';
import { ScriptService } from '../../script.service';


@Component({
selector: 'app-players-list',
templateUrl: './players-list.component.html',
styleUrls: ['./players-list.component.css'],
providers: [ ScriptService ]
})
export class PlayersListComponent implements OnInit, AfterContentInit {


constructor(private router: Router, private script: ScriptService) {
}


ngOnInit() {
}


ngAfterContentInit() {
this.script.load('filepicker', 'rangeSlider').then(data => {
console.log('script loaded ', data);
}).catch(error => console.log(error));
}

@ rahull -kumar的解决方案对我来说很好,但我想在我的typescript中调用我的javascript函数

foo.myFunctions() // works in browser console, but foo can't be used in typescript file

我通过在我的typescript中声明它来修复它:

import { Component } from '@angular/core';
import { ScriptService } from './script.service';
declare var foo;

现在,我可以调用喷火在我的类型脚本文件的任何地方

在我的例子中,我使用上述技术加载了jscss visjs文件——这非常有效。我从ngOnInit()调用新函数

我可以通过简单地添加一个<script><link>标签到html模板文件来加载它。

loadVisJsScript() {
console.log('Loading visjs js/css files...');
let script = document.createElement('script');
script.src = "../../assets/vis/vis.min.js";
script.type = 'text/javascript';
script.async = true;
script.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(script);
	

let link = document.createElement("link");
link.type = "stylesheet";
link.href = "../../assets/vis/vis.min.css";
document.getElementsByTagName('head')[0].appendChild(link);
}

你可以像这样在你的component.ts文件中加载动态多个脚本:

 loadScripts() {
const dynamicScripts = [
'https://platform.twitter.com/widgets.js',
'../../../assets/js/dummyjs.min.js'
];
for (let i = 0; i < dynamicScripts.length; i++) {
const node = document.createElement('script');
node.src = dynamicScripts[i];
node.type = 'text/javascript';
node.async = false;
node.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(node);
}
}

并在构造函数中调用这个方法,

constructor() {
this.loadScripts();
}

注意:若要动态加载更多脚本,请将它们添加到dynamicScripts数组中。

我已经用新的渲染器api完成了这个代码片段

 constructor(private renderer: Renderer2){}


addJsToElement(src: string): HTMLScriptElement {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
this.renderer.appendChild(document.body, script);
return script;
}

然后像这样叫它

this.addJsToElement('https://widgets.skyscanner.net/widget-server/js/loader.js').onload = () => {
console.log('SkyScanner Tag loaded');
}

StackBlitz

示例可以是

script-loader.service。ts文件

import {Injectable} from '@angular/core';
import * as $ from 'jquery';


declare let document: any;


interface Script {
src: string;
loaded: boolean;
}


@Injectable()
export class ScriptLoaderService {
public _scripts: Script[] = [];


/**
* @deprecated
* @param tag
* @param {string} scripts
* @returns {Promise<any[]>}
*/
load(tag, ...scripts: string[]) {
scripts.forEach((src: string) => {
if (!this._scripts[src]) {
this._scripts[src] = {src: src, loaded: false};
}
});


let promises: any[] = [];
scripts.forEach((src) => promises.push(this.loadScript(tag, src)));


return Promise.all(promises);
}


/**
* Lazy load list of scripts
* @param tag
* @param scripts
* @param loadOnce
* @returns {Promise<any[]>}
*/
loadScripts(tag, scripts, loadOnce?: boolean) {
loadOnce = loadOnce || false;


scripts.forEach((script: string) => {
if (!this._scripts[script]) {
this._scripts[script] = {src: script, loaded: false};
}
});


let promises: any[] = [];
scripts.forEach(
(script) => promises.push(this.loadScript(tag, script, loadOnce)));


return Promise.all(promises);
}


/**
* Lazy load a single script
* @param tag
* @param {string} src
* @param loadOnce
* @returns {Promise<any>}
*/
loadScript(tag, src: string, loadOnce?: boolean) {
loadOnce = loadOnce || false;


if (!this._scripts[src]) {
this._scripts[src] = {src: src, loaded: false};
}


return new Promise((resolve, reject) => {
// resolve if already loaded
if (this._scripts[src].loaded && loadOnce) {
resolve({src: src, loaded: true});
}
else {
// load script tag
let scriptTag = $('<script/>').
attr('type', 'text/javascript').
attr('src', this._scripts[src].src);


$(tag).append(scriptTag);


this._scripts[src] = {src: src, loaded: true};
resolve({src: src, loaded: true});
}
});
}
}

和使用

第一次注射

  constructor(
private _script: ScriptLoaderService) {
}

然后

ngAfterViewInit()  {
this._script.loadScripts('app-wizard-wizard-3',
['assets/demo/default/custom/crud/wizard/wizard.js']);


}

    this._script.loadScripts('body', [
'assets/vendors/base/vendors.bundle.js',
'assets/demo/default/base/scripts.bundle.js'], true).then(() => {
Helpers.setLoading(false);
this.handleFormSwitch();
this.handleSignInFormSubmit();
this.handleSignUpFormSubmit();
this.handleForgetPasswordFormSubmit();
});

我有一个好方法来动态加载脚本! 现在我在我的项目中使用ng6, echarts4 (>700Kb),ngx-echarts3。当我通过ngx-echarts的文档使用它们时,我需要在angular中导入echarts。json: “脚本”:["。/ node_modules / echarts / dist / echarts.min.js”) 因此在登录模块中,页面同时加载scripts.js,这是一个大文件!

所以,我认为angular将每个模块作为一个文件加载,我可以插入一个路由器解析器来预加载js,然后开始加载模块!

/ / PreloadScriptResolver.service.js

/**动态加载js的服务 */
@Injectable({
providedIn: 'root'
})
export class PreloadScriptResolver implements Resolve<IPreloadScriptResult[]> {
// Here import all dynamically js file
private scripts: any = {
echarts: { loaded: false, src: "assets/lib/echarts.min.js" }
};
constructor() { }
load(...scripts: string[]) {
const promises = scripts.map(script => this.loadScript(script));
return Promise.all(promises);
}
loadScript(name: string): Promise<IPreloadScriptResult> {
return new Promise((resolve, reject) => {
if (this.scripts[name].loaded) {
resolve({ script: name, loaded: true, status: 'Already Loaded' });
} else {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = this.scripts[name].src;
script.onload = () => {
this.scripts[name].loaded = true;
resolve({ script: name, loaded: true, status: 'Loaded' });
};
script.onerror = (error: any) => reject({ script: name, loaded: false, status: 'Loaded Error:' + error.toString() });
document.head.appendChild(script);
}
});
}


resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<IPreloadScriptResult[]> {
return this.load(...route.routeConfig.data.preloadScripts);
}
}

然后在子模块-routing.module中。导入PreloadScriptResolver:

const routes: Routes = [
{
path: "",
component: DashboardComponent,
canActivate: [AuthGuardService],
canActivateChild: [AuthGuardService],
resolve: {
preloadScripts: PreloadScriptResolver
},
data: {
preloadScripts: ["echarts"]  // important!
},
children: [.....]
}

这段代码工作得很好,它承诺:js文件加载后,然后模块开始加载!这个解析器可以在许多路由器中使用

然而,另一种选择是利用scriptjs

允许您按需从任何URL加载脚本资源

例子

安装包:

npm i scriptjs

scriptjs的类型定义:

npm install --save @types/scriptjs

然后导入$script.get()方法:

import { get } from 'scriptjs';

最后加载脚本资源,在我们的例子中是谷歌Maps库:

export class AppComponent implements OnInit {
ngOnInit() {
get("https://maps.googleapis.com/maps/api/js?key=", () => {
//Google Maps library has been loaded...
});
}
}

< a href = " https://codesandbox。io / s / k3587kn1yo noreferrer“rel = >演示< / >

import { Injectable } from '@angular/core';
import * as $ from 'jquery';


interface Script {
src: string;
loaded: boolean;
}


@Injectable()
export class ScriptLoaderService {
public _scripts: Script[] = [];


/**
* @deprecated
* @param tag
* @param {string} scripts
* @returns {Promise<any[]>}
*/
load(tag, ...scripts: string[]) {
scripts.forEach((src: string) => {
if (!this._scripts[src]) {
this._scripts[src] = { src: src, loaded: false };
}
});


const promises: any[] = [];
scripts.forEach(src => promises.push(this.loadScript(tag, src)));


return Promise.all(promises);
}


/**
* Lazy load list of scripts
* @param tag
* @param scripts
* @param loadOnce
* @returns {Promise<any[]>}
*/
loadScripts(tag, scripts, loadOnce?: boolean) {
debugger;
loadOnce = loadOnce || false;


scripts.forEach((script: string) => {
if (!this._scripts[script]) {
this._scripts[script] = { src: script, loaded: false };
}
});


const promises: any[] = [];
scripts.forEach(script => promises.push(this.loadScript(tag, script, loadOnce)));


return Promise.all(promises);
}


/**
* Lazy load a single script
* @param tag
* @param {string} src
* @param loadOnce
* @returns {Promise<any>}
*/
loadScript(tag, src: string, loadOnce?: boolean) {
debugger;
loadOnce = loadOnce || false;


if (!this._scripts[src]) {
this._scripts[src] = { src: src, loaded: false };
}


return new Promise((resolve, _reject) => {
// resolve if already loaded
if (this._scripts[src].loaded && loadOnce) {
resolve({ src: src, loaded: true });
} else {
// load script tag
const scriptTag = $('<script/>')
.attr('type', 'text/javascript')
.attr('src', this._scripts[src].src);


$(tag).append(scriptTag);


this._scripts[src] = { src: src, loaded: true };
resolve({ src: src, loaded: true });
}
});
}


reloadOnSessionChange() {
window.addEventListener('storage', function(data) {
if (data['key'] === 'token' && data['oldValue'] == null && data['newValue']) {
document.location.reload();
}
});
}
}

Angular的通用解决方案;在加载播放视频的脚本之前,我需要等待页面上的特定元素。

import {Inject, Injectable, PLATFORM_ID} from '@angular/core';
import {isPlatformBrowser} from "@angular/common";


@Injectable({
providedIn: 'root'
})
export class ScriptLoaderService {


constructor(
@Inject(PLATFORM_ID) private platformId: Object,
) {
}


load(scriptUrl: string) {
if (isPlatformBrowser(this.platformId)) {
let node: any = document.createElement('script');
node.src = scriptUrl;
node.type = 'text/javascript';
node.async = true;
node.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(node);
}
}
}

嗨,你可以使用Renderer2和elementRef只需几行代码:

constructor(private readonly elementRef: ElementRef,
private renderer: Renderer2) {
}
ngOnInit() {
const script = this.renderer.createElement('script');
script.src = 'http://iknow.com/this/does/not/work/either/file.js';
script.onload = () => {
console.log('script loaded');
initFile();
};
this.renderer.appendChild(this.elementRef.nativeElement, script);
}

onload函数可用于在脚本加载后调用脚本函数,如果你必须在ngOnInit()中进行调用,这是非常有用的。

您可以使用谷歌标签管理器来管理外部脚本,而无需进入代码。对于非技术用户和技术用户来说,这是一个完美的解决方案。

对于那些想动态加载样式的人来说。 (基于@Rahul库马尔 brilliant answer)

script.store.ts

interface Scripts {
name: string;
src: string;
}


export const StyleStore: Scripts[] = [
{ name: 'fancybox-css', src: 'https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css' }
];


export const ScriptStore: Scripts[] = [
{ name: 'jquery', src: 'https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js' },
{ name: 'other', src: '[other script source]'}
];

script-loader.service.ts

import { Injectable } from '@angular/core';
import { ScriptStore, StyleStore } from '../../stores/script.store';


@Injectable({
providedIn: 'root'
})
export class ScriptLoaderService {


private scripts: any = {};
private styles: any = {};


constructor() {
ScriptStore.forEach((script: any) => {
this.scripts[script.name] = {
loaded: false,
src: script.src
};
});


StyleStore.forEach((script: any) => {
this.styles[script.name] = {
loaded: false,
src: script.src
};
});
}


load(...scripts: string[]) {
var promises: any[] = [];
scripts.forEach((script) => promises.push(this.loadScript(script)));
return Promise.all(promises);
}


loadStyles(...scripts: string[]) {
var promises: any[] = [];
scripts.forEach((script) => promises.push(this.loadStyle(script)));
return Promise.all(promises);
}


loadScript(name: string) {
return new Promise((resolve, reject) => {
//resolve if already loaded
if (this.scripts[name].loaded) {
resolve({ script: name, loaded: true, status: 'Already Loaded' });
}
else {
//load script
let script = document.createElement('script') as any;
script.type = 'text/javascript';
script.src = this.scripts[name].src;
if (script.readyState) {  //IE
script.onreadystatechange = () => {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
this.scripts[name].loaded = true;
resolve({ script: name, loaded: true, status: 'Loaded' });
}
};
} else {  //Others
script.onload = () => {
this.scripts[name].loaded = true;
resolve({ script: name, loaded: true, status: 'Loaded' });
};
}
script.onerror = (error: any) => resolve({ script: name, loaded: false, status: 'Loaded' });
document.getElementsByTagName('head')[0].appendChild(script);
}
});
}


loadStyle(name: string) {
return new Promise((resolve, reject) => {
//resolve if already loaded
if (this.styles[name].loaded) {
resolve({ script: name, loaded: true, status: 'Already Loaded' });
}
else {
//load style
let style = document.createElement('link') as any;
style.type = "text/css";
style.rel = "stylesheet";
style.href = this.styles[name].src;
if (style.readyState) {  //IE
style.onreadystatechange = () => {
if (style.readyState === "loaded" || style.readyState === "complete") {
style.onreadystatechange = null;
this.styles[name].loaded = true;
resolve({ style: name, loaded: true, status: 'Loaded' });
}
};
} else {  //Others
style.onload = () => {
this.styles[name].loaded = true;
resolve({ style: name, loaded: true, status: 'Loaded' });
};
}
style.onerror = (error: any) => resolve({ style: name, loaded: false, status: 'Loaded' });
document.getElementsByTagName('head')[0].appendChild(style);
}
});
}


}

app.component.ts

constructor(private scriptLoaderService: ScriptLoaderService) {
this.scriptLoaderService.loadStyles('fancybox-css').then(x => {
this.scriptLoaderService.load('jquery', 'fancybox').then(data => {
}).catch(error => console.log(error));
});
}

对于下面的链接,我也有同样的问题。我用一种很简单的方法解决了它。

https://www.gstatic.com/charts/loader.js

我需要访问下面代码中的谷歌变量。但当我把它放到angular类中时,它就不起作用了。

google.charts.load("current", {packages:['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
["Element", "Density", { role: "style" } ],
["Copper", 8.94, "dodgerblue"],
["Silver", 10.49, "dodgerblue"],
["Gold", 19.30, "dodgerblue"],
["Platinum", 21.45, "color: dodgerblue"]
]);
var view = new google.visualization.DataView(data);
view.setColumns([0, 1,
{ calc: "stringify",
sourceColumn: 1,
type: "string",
role: "annotation" },
2]);


var options = {
title: "Density of Precious Metals, in g/cm^3",
width: 600,
height: 400,
bar: {groupWidth: "50%"},
legend: { position: "none" },
};
var chart = new google.visualization.ColumnChart(document.getElementById("columnchart_values"));
chart.draw(view, options);

我在ts类的顶部创建了一个具有相同名称的全局变量(谷歌),然后该变量自动引用所需的变量。(因为它是全局作用域)那么问题就解决了。

declare var google: any;

这个解决方案对我很有效:

1)创建一个名为URLLoader的新类

export class URLLoader {
constructor() {


}


loadScripts() {


const dynamicScripts = [
'URL 1',
'URL 2',
'URL n'
];


for (let i = 0; i < dynamicScripts.length; i++) {
const node = document.createElement('script');
node.src = dynamicScripts[i];
node.type = 'text/javascript';
node.async = false;
node.charset = 'utf-8';
document.getElementsByTagName('app-root')[0].appendChild(node);
}
}


}

2)扩展类URLLoader并从组件类中调用loadScripts方法

export class AppComponent extends URLLoader implements OnInit {


constructor(){}


ngOnInit() {
super.loadScripts();
}


}

我发现这个解决方案更干净,首先在你的模块中导入HttpClientJsonpModule,然后做这样的事情

this.apiLoaded = this.httpClient.jsonp(environment.AnyApiUrl, 'callback')
.pipe(
map(() => true),
catchError(() => of(false)),
);

在模板中:

<app-component *ngIf="apiLoaded | async"></app-component>

这个解决方案在Angular谷歌Maps的官方文档在这里中。

Angular有防止用户直接干扰html输出的逻辑。所以你必须让Angular通过在angular.json文件中指定方向来注入标签。

首先,您必须获取脚本文件。有两种方法:

  1. 下载脚本文件(例如。somelibrary.js)
  • 将它放在资产文件夹中
  • 把脚本的相对路径,放入“脚本”;angular.json文件的部分:
"scripts": [
"src/assets/somelibrary.js"
]
  1. 使用npm/yarn安装脚本:
  • 把脚本的相对路径,放入“脚本”;angular.json文件的部分:
"scripts": [
"./node_modules/somelibrary/dist/somelibrary.min.js"
]

我希望能够:

  • 当应用程序被引导时添加一个脚本
  • 不要从组件中执行,因为这感觉不像是任何组件的责任
  • 而不是通过指令来实现,原因和组件是一样的
  • 不要从服务中进行,因为除非存在某种与现有服务相关的沉重逻辑,否则这并不属于服务的IMO
  • 避免在模块中这样做。一个模块也可以,但它不如直接使用DI灵活,而且既然Angular 15的独立组件是稳定的,那为什么还要为一个模块而烦恼呢

也就是说,为了在应用程序引导之前做到这一点,这有点棘手。因为在这个阶段我们没有可用的渲染器,并且我们无法访问包含nativeElementelementRef

下面是我的看法:

export const YOUR_EXT_LIB_URL_TOKEN = new InjectionToken<string>('YOUR_EXT_LIB_URL_TOKEN');


export const YOUR_SETUP: Provider = {
provide: APP_INITIALIZER,
multi: true,
useFactory: (
doc: InjectionTokenType<typeof DOCUMENT>,
rendererFactory: RendererFactory2,
yourExternalLibToken: string,
) => {
const renderer = rendererFactory.createRenderer(null, null);


const script = renderer.createElement('script');
script.type = 'text/javascript';
script.src = yourExternalLibToken;
renderer.appendChild(doc.body, script);


return () => true;
},
deps: [DOCUMENT, RendererFactory2, YOUR_EXT_LIB_URL_TOKEN],
};

然后你所要做的就是提供YOUR_EXT_LIB_URL_TOKEN并传递YOUR_SETUP提供程序。

这样,所有东西都是通过DI注入的,非常灵活。例如,你可以在共享库中提供YOUR_SETUP令牌,并在使用共享库的不同应用程序中提供YOUR_EXT_LIB_URL_TOKEN