- 前端項目中使用到了一個報表庫
Plotly.js
, 這個庫有 600多k。因爲報表只有部分模塊用到,因此想使用動態加載方式。
- 首先想到使用
webpack
的懶加載,可是打包時間太長。加這個庫以前 30秒,加以後 6 分鐘。使用 noParse
也沒有效果。
- 因此決定用到時,手動加載。
- js 經常使用的動態加載有兩種。
ajax 加載後使用 eval 執行
。或者使用 script 標籤加載
。
- 這裏介紹動態建立標籤的方法。不說了,直接上代碼:
// Attach handlers for all browsers
var loadScript = function (path, callback) {
const me = this;
const script = document.createElement('script');
script.type = 'text/javascript';
let done = false;
const head = document.head;
const error = () => {
if (!done) {
done = true;
script.onload = script.onreadystatechange = script.onerror = null;
head.removeChild(script);
callback(false);
}
}
const success = () => {
if (!done) {
done = true;
script.onload = script.onreadystatechange = script.onerror = null;
head.removeChild(script);
callback(true);
}
}
script.onreadystatechange = function () {
if (this.readyState == "loaded" || this.readyState == "complete") {
success();
}
};
script.onload = success;
script.onerror = error;
script.src = path;
head.appendChild(script);
}
- 上面是動態加載
js
的方法。可是可能多個模塊會用到這個 js
庫。當同時打開這些模塊時,有可能會致使屢次加載。因此能夠把加載後的模塊根據路徑緩存起來。
- 下面代碼是使用
TypeScript
寫的, 根據路徑記錄 js 文件加載信息,避免重複:
export default class LoadJs {
public static loadSync(path: string): Promise<void> {
const me = this;
return new Promise((resolve, reject) => {
me.load(path, (bSuc) => {
if (bSuc) {
resolve();
} else {
reject();
}
});
});
}
public static load(path: string, callback: (bSuc: boolean) => void): void {
let lib = this.pathLoaded[path];
// 沒有記錄,添加記錄
if (!lib) {
lib = {
isLoaded: false,
callback: [callback],
};
this.pathLoaded[path] = lib;
}
// 已加載直接返回
if (lib.isLoaded) {
callback(true);
} else {
// 添加回調
lib.callback.push(callback);
// 加載
const me = this;
this.loadScript(path, suc => {
if (suc) {
me.onLoad(lib, true);
} else {
me.onLoad(lib, false);
}
})
}
}
private static loadScript(path, callback) {
const me = this;
const script = document.createElement('script') as any;
script.type = 'text/javascript';
let done = false;
const head = document.head;
const error = () => {
if (!done) {
done = true;
script.onload = script.onreadystatechange = script.onerror = null;
head.removeChild(script);
callback(false);
}
}
const success = () => {
if (!done) {
done = true;
script.onload = script.onreadystatechange = script.onerror = null;
head.removeChild(script);
callback(true);
}
}
script.onreadystatechange = function () {
if (this.readyState == "loaded" || this.readyState == "complete") {
success();
}
};
script.onload = success;
script.onerror = error;
script.src = path;
head.appendChild(script);
}
private static pathLoaded: { [key: string]: PathLoading } = {};
private static onLoad(p: PathLoading, isSuc: boolean): void {
p.isLoaded = true;
for (const fun of p.callback) {
fun(isSuc);
}
p.callback = [];
}
}
interface PathLoading {
isLoaded: boolean;
callback: Array<(f: boolean) => void>;
}