成果
three-platfromize目前已實(shí)現(xiàn) 微信小程序 和 淘寶小程序 的適配,均可加載GLB文件
點(diǎn)擊查看微信小程序DEMO
起因
估計(jì)嘗試過在小程序使用 THREE 的朋友估計(jì)都體會到微信官方推出的適配版的難用,直接讓開發(fā)體驗(yàn)回到解放前。
- 無類型提示,對于three新手不友好,對于習(xí)慣了代碼即文檔的也不友好。
- 無法順暢使用 three.js 生態(tài)的 npm 包,需要手動 scope 注入 three 的依賴。
- 無 tree shaking,在只有 2mb 容量的包大小限制下,three 是個(gè)龐然大物。
- 沒有平臺相關(guān)資源釋放接口(即VSCode里面的Disposable模式)。
- 每個(gè)小程序平臺需要單獨(dú)適配一個(gè),定制 three 的維護(hù)成本高。
所以有了 THREE 平臺化的想法。既然微信官方的適配版這么多不能,所以平臺化的目標(biāo)就是把“不”去掉。
目標(biāo)
- 支持TS類型提示,能方便查閱API文檔(d.ts)
- 可以通過構(gòu)建修改方便使用 three 生態(tài) npm 包,無需手動 scope,比如 GLTFLoader
- 支持 tree shaking 能減少多點(diǎn)就少點(diǎn),加個(gè) tfjs 就更加頭大。
- 有資源釋放 dispose 接口。
- 支持方便動態(tài)注入多個(gè)小程序平臺的平臺接口實(shí)現(xiàn)適配器,多 backends 。
參考
既然需要多平臺支持,所以需要向平臺化很好的項(xiàng)目學(xué)習(xí)比如 tfjs ,實(shí)現(xiàn)了多個(gè)backend 有 CPU,WebGL,WASM。其運(yùn)行的平臺的適配有PlatformBrowser,PlatformNode,PlatformWechat(tfjs的微信小程序插件里有)
然后在一個(gè)Environment全局單例實(shí)現(xiàn)平臺的切換邏輯。
實(shí)現(xiàn)
所以需要一個(gè)全局的單例實(shí)現(xiàn)平臺依賴的轉(zhuǎn)發(fā)切換(模仿 THREE 的源碼風(fēng)格)
實(shí)現(xiàn)平臺的全局單例
// 為了避免重新聲明的報(bào)錯(cuò)
let $URL = null;
let $atob = null;
let $Blob = null;
let $window = null;
let $document = null;
let $XMLHttpRequest = null;
let $OffscreenCanvas = null;
let $HTMLCanvasElement = null;
let $createImageBitmap = null;
let $requestAnimationFrame = null;
class Platform {
set(platform) {
this.platform && this.platform.dispose();
this.platform = platform;
const globals = platform.getGlobals();
$atob = globals.atob;
$Blob = globals.Blob;
$window = globals.window;
$document = globals.document;
$XMLHttpRequest = globals.XMLHttpRequest;
$OffscreenCanvas = globals.OffscreenCanvas;
$HTMLCanvasElement = globals.HTMLCanvasElement;
$createImageBitmap = globals.createImageBitmap;
$requestAnimationFrame = globals.requestAnimationFrame;
$URL = globals.window.URL;
}
dispose() {
this.platform && this.platform.dispose();
$URL = null;
$Blob = null;
$atob = null;
$window = null;
$document = null;
$XMLHttpRequest = null;
$OffscreenCanvas = null;
$HTMLCanvasElement = null;
$createImageBitmap = null;
$requestAnimationFrame = null;
}
}
const PLATFORM = new Platform();
export { PLATFORM, $window, $document, $XMLHttpRequest, $atob, $OffscreenCanvas, $HTMLCanvasElement, $requestAnimationFrame, $Blob, $URL, $createImageBitmap };
由于 tfjs 是提前就做了平臺化的計(jì)劃,所以從源碼上就平臺化了,但是 THREE 并沒有,所以需要從構(gòu)建入手。實(shí)現(xiàn)平臺依賴轉(zhuǎn)發(fā),比如源碼的window對象需要指向平臺的window。
實(shí)現(xiàn)平臺依賴轉(zhuǎn)發(fā)
經(jīng)過多查閱,發(fā)現(xiàn)@rollup/plugin-inject能十分輕松實(shí)現(xiàn)依賴轉(zhuǎn)發(fā),這里是把平臺有關(guān)的變量轉(zhuǎn)發(fā)到Platform的導(dǎo)出
import path from 'path';
import inject from '@rollup/plugin-inject';
export const platformVariables = [
'URL',
'atob',
'Blob',
'window',
'document',
'XMLHttpRequest',
'OffscreenCanvas',
'HTMLCanvasElement',
'createImageBitmap',
'requestAnimationFrame',
];
export function platformize(
list = platformVariables,
platformPath = path
.resolve(__dirname, '../src/Platform')
.replaceAll('\\', '\\\\'),
) {
return inject({
exclude: /src\/platforms/, // 平臺自定義代碼無需轉(zhuǎn)發(fā)
'self.URL': [platformPath, '$URL'],
...list.reduce((acc, curr) => {
acc[curr] = [platformPath, `$${curr}`];
return acc;
}, {}),
});
}
編寫平臺比如WechatPlatform
里面可以參考微信官方適配器,同理適配淘寶小程序時(shí)候,只需編寫TaobaoPlatform即可
import URL from '../libs/URL'
import Blob from '../libs/Blob'
import atob from '../libs/atob'
import EventTarget from '../libs/EventTarget'
import XMLHttpRequest from './XMLHttpRequest'
import copyProperties from '../libs/copyProperties'
function OffscreenCanvas() {
return wx.createOffscreenCanvas()
}
export class WechatPlatform {
constructor( canvas ) {
const systemInfo = wx.getSystemInfoSync()
this.canvas = canvas;
this.document = {
createElementNS( _, type ) {
if (type === 'canvas') return canvas;
if (type === 'img') return canvas.createImage();
}
};
this.window = {
innerWidth: systemInfo.windowWidth,
innerHeight: systemInfo.windowHeight,
devicePixelRatio: systemInfo.pixelRatio,
AudioContext: function() {},
URL: new URL(),
requestAnimationFrame: this.canvas.requestAnimationFrame,
};
[this.canvas, this.document, this.window].forEach(i => {
copyProperties(i.constructor.prototype, EventTarget.prototype)
});
this.patchCanvas();
}
patchCanvas() {
Object.defineProperty(this.canvas, 'style', {
get() {
return {
width: this.width + 'px',
height: this.height + 'px'
}
}
})
Object.defineProperty(this.canvas, 'clientHeight', {
get() { return this.height }
})
Object.defineProperty(this.canvas, 'clientWidth', {
get() { return this.width }
})
}
getGlobals() {
return {
atob: atob,
Blob: Blob,
window: this.window,
document: this.document,
HTMLCanvasElement: undefined,
XMLHttpRequest: XMLHttpRequest,
requestAnimationFrame: this.canvas.requestAnimationFrame,
OffscreenCanvas: OffscreenCanvas,
createImageBitmap: undefined,
}
}
dispose() {
this.document = null;
this.window = null;
this.canvas = null;
}
}
實(shí)現(xiàn)支持類型提示
Platform.d.ts
export class Platform {
set(platform: any): void;
dispose(): void;
}
export const PLATFORM: Platform;
export let $atob: any;
export let $window: any;
export let $document: any;
export let $XMLHttpRequest: any;
export let $OffscreenCanvas: any;
export let $HTMLCanvasElement: any;
export let $createImageBitmap: any;
export let $requestAnimationFrame: any;
ThreePlatformize.d.ts
export * from 'three'
export * from './Platform'
沒錯(cuò),就是如此的簡單
支持tree shaking
package.json 設(shè)置 sideEffects 為 false
{
...
"sideEffects": false,
...
}
支持THREE的生態(tài)
目前是指 three 包下面的examples/jsm/\*\*/\*.js,依然是通過構(gòu)建支持
import path from 'path';
import copy from 'rollup-plugin-copy';
import * as fastGlob from 'fast-glob';
import { platformVariables, platformize } from './platfromize';
const ThreeOrigin = path.resolve(__dirname, '../three/build/three.module.js');
export default fastGlob.sync('three/examples/jsm/**/*.js').map(input => {
return {
input,
output: {
format: 'esm',
file: input.replace('three/', ''),
},
external: () => true,
plugins: [
platformize(platformVariables, ThreeOrigin),
copy({
targets: [
{
src: input.replace('.js', '.d.ts'),
dest: path.dirname(input.replace('three/', '')),
},
],
}),
],
};
});
依賴 three 包的npm 包如果是平臺無關(guān)的話,只需要通過 alias 指向平臺化后的 three 即可。若平臺相關(guān)的,則仍需編寫插件支持,可類比上面rollup插件platformize。
成果
所以three-platfromize的項(xiàng)目誕生了。目前已實(shí)現(xiàn)微信小程序和淘寶小程序平臺的適配。
點(diǎn)擊查看微信小程序DEMO
點(diǎn)擊查看淘寶小程序DEMO
后續(xù)會適配更多小程序平臺,讓3D開發(fā)變得更加優(yōu)雅。
demo的動圖實(shí)現(xiàn)是通過three-sprite-player實(shí)現(xiàn),能避免微信小程序紋理大小限制,也歡迎大家品嘗。