| @@ -0,0 +1,28 @@ | |||
| { | |||
| "name": "@threepipe/plugin-network", | |||
| "version": "0.1.0", | |||
| "lockfileVersion": 3, | |||
| "requires": true, | |||
| "packages": { | |||
| "": { | |||
| "name": "@threepipe/plugin-network", | |||
| "version": "0.1.0", | |||
| "license": "Apache-2.0", | |||
| "dependencies": { | |||
| "aws4fetch": "^1.0.18", | |||
| "threepipe": "file:./../../src/" | |||
| }, | |||
| "devDependencies": {} | |||
| }, | |||
| "../../src": {}, | |||
| "node_modules/aws4fetch": { | |||
| "version": "1.0.18", | |||
| "resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.18.tgz", | |||
| "integrity": "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ==" | |||
| }, | |||
| "node_modules/threepipe": { | |||
| "resolved": "../../src", | |||
| "link": true | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| { | |||
| "name": "@threepipe/plugin-network", | |||
| "description": "Network/AWS/Cloud related plugins for threepipe", | |||
| "version": "0.1.0", | |||
| "devDependencies": { | |||
| }, | |||
| "dependencies": { | |||
| "threepipe": "file:./../../src/", | |||
| "aws4fetch": "^1.0.18" | |||
| }, | |||
| "clean-package": { | |||
| "remove": [ | |||
| "clean-package", | |||
| "scripts", | |||
| "devDependencies", | |||
| "//", | |||
| "markdown-to-html" | |||
| ], | |||
| "replace": { | |||
| "dependencies": {}, | |||
| "peerDependencies": { | |||
| "threepipe": "^0.0.30" | |||
| } | |||
| } | |||
| }, | |||
| "type": "module", | |||
| "main": "dist/index.js", | |||
| "module": "dist/index.mjs", | |||
| "types": "dist/index.d.ts", | |||
| "files": [ | |||
| "dist", | |||
| "src" | |||
| ], | |||
| "scripts": { | |||
| "new:pack": "npm run prepare && clean-package && npm pack && clean-package restore", | |||
| "new:publish": "npm run prepare && clean-package && npm publish --access public && clean-package restore", | |||
| "prepare": "npm run build && npm run docs", | |||
| "build": "rimraf dist && vite build", | |||
| "dev": "NODE_ENV=development vite build --watch", | |||
| "docs": "rimraf docs && npx typedoc" | |||
| }, | |||
| "author": "repalash <palash@shaders.app>", | |||
| "license": "Apache-2.0", | |||
| "keywords": [ | |||
| "three", | |||
| "three.js", | |||
| "threepipe", | |||
| "vite", | |||
| "plugin" | |||
| ], | |||
| "bugs": { | |||
| "url": "https://github.com/repalash/threepipe/issues" | |||
| }, | |||
| "homepage": "https://github.com/repalash/threepipe#readme", | |||
| "repository": { | |||
| "type": "git", | |||
| "url": "git://github.com/repalash/threepipe.git" | |||
| } | |||
| } | |||
| @@ -0,0 +1,288 @@ | |||
| import { | |||
| AViewerPluginSync, | |||
| FileTransferPlugin, | |||
| pathJoin, | |||
| serialize, | |||
| ThreeViewer, | |||
| timeout, | |||
| uiButton, | |||
| uiFolderContainer, | |||
| uiInput, | |||
| UiObjectConfig, | |||
| uiToggle, | |||
| } from 'threepipe' | |||
| import {AwsClient, AwsV4Signer} from 'aws4fetch' | |||
| /** | |||
| * AWSClientPlugin | |||
| * Provides `fetch` function that performs a fetch request with AWS v4 signing. | |||
| * This is useful for connecting to AWS services like S3 directly from the client. | |||
| * It also interfaces with the {@link FileTransferPlugin} to directly upload file when exported with the viewer or the plugin. | |||
| * Note: Make sure to use keys with limited privileges and correct CORS settings. | |||
| * All the keys will be stored in plain text if `serializeSettings` is set to true | |||
| * | |||
| * {@todo Make an example for AWSClient Plugin} | |||
| */ | |||
| @uiFolderContainer('AWS/S3 Client') | |||
| export class AWSClientPlugin extends AViewerPluginSync<'fileUpload'> { | |||
| static readonly PluginType = 'AWSClientPlugin1' | |||
| uiConfig?: UiObjectConfig | |||
| enabled = true | |||
| private _connected = false | |||
| // do not serialize in exported file. | |||
| readonly serializeWithViewer = false | |||
| private _client: AwsClient | undefined | |||
| dependencies = [FileTransferPlugin] | |||
| constructor() { | |||
| super() | |||
| } | |||
| @serialize() | |||
| @uiInput('Access Key ID', (t: AWSClientPlugin)=>({ | |||
| disabled: ()=>!t.enabled || t._connected, | |||
| })) | |||
| accessKeyId = '' | |||
| @serialize() | |||
| @uiInput('Access Key Secret', (t: AWSClientPlugin)=>({ | |||
| disabled: ()=>!t.enabled || t._connected, | |||
| })) | |||
| accessKeySecret = '' | |||
| @serialize() | |||
| @uiInput('Endpoint URL', (t: AWSClientPlugin)=>({ | |||
| disabled: ()=>!t.enabled || t._connected, | |||
| })) | |||
| endpointURL = '' | |||
| @serialize() | |||
| @uiInput('Path Prefix', (t: AWSClientPlugin)=>({ | |||
| disabled: ()=>!t.enabled, | |||
| })) | |||
| pathPrefix = 'webgi' | |||
| @serialize() | |||
| @uiToggle('Remember', (t: AWSClientPlugin)=>({ | |||
| disabled: ()=>!t.enabled || t._connected, | |||
| })) | |||
| serializeSettings = false | |||
| @uiButton(undefined, (t: AWSClientPlugin)=>({ | |||
| label: ()=>t._connected ? 'Disconnect' : 'Connect', | |||
| })) | |||
| toggleConnection = ()=>{ | |||
| if (this._connected) { | |||
| this.disconnect() | |||
| } else { | |||
| this.connect() | |||
| } | |||
| } | |||
| /** | |||
| * Set to true to use a proxy for all requests. | |||
| * This can be used to move the access credentials to the server side or set custom headers. | |||
| * This is required for some services like cloudflare R2 that do not support CORS. | |||
| * usage: `AWSClientPlugin.USE_PROXY = true`, optionally set `AWSClientPlugin.PROXY_URL` to a custom proxy. | |||
| */ | |||
| static USE_PROXY = false | |||
| static PROXY_URL = 'https://r2-s3-api.repalash.com/{path}' | |||
| connect() { | |||
| if (this._connected) this.disconnect() | |||
| this._client = new AwsClient({ | |||
| accessKeyId: this.accessKeyId, | |||
| secretAccessKey: this.accessKeySecret, | |||
| }) | |||
| this._connected = true | |||
| this.refreshUi() | |||
| } | |||
| refreshUi() { | |||
| this.uiConfig?.uiRefresh?.(true, 'postFrame') | |||
| } | |||
| disconnect() { | |||
| this._client = undefined | |||
| this._connected = false | |||
| this.refreshUi() | |||
| } | |||
| get connected(): boolean { | |||
| return this._connected | |||
| } | |||
| get client(): AwsClient | undefined { | |||
| return this._client | |||
| } | |||
| toJSON(meta?: any): any { | |||
| if (!this.serializeSettings) return {type: (this as any).constructor.PluginType} | |||
| return super.toJSON(meta) | |||
| } | |||
| private _savedExportFile?: FileTransferPlugin['defaultActions']['exportFile'] | |||
| onAdded(viewer: ThreeViewer) { | |||
| super.onAdded(viewer) | |||
| const tr = viewer.getPlugin(FileTransferPlugin)! | |||
| this._savedExportFile = tr.actions.exportFile || tr.defaultActions.exportFile | |||
| if (!this._savedExportFile) throw new Error('FileTransferPlugin must have exportFile action') | |||
| tr.actions.exportFile = this.exportFile | |||
| } | |||
| onRemove(viewer: ThreeViewer) { | |||
| const tr = viewer.getPlugin(FileTransferPlugin)! | |||
| tr.actions.exportFile = this._savedExportFile! | |||
| this._savedExportFile = undefined | |||
| super.onRemove(viewer) | |||
| } | |||
| exportFile: FileTransferPlugin['defaultActions']['exportFile'] = async(blob, name, onProgress)=>{ | |||
| const viewer = this._viewer | |||
| if (!viewer) return | |||
| const tr = viewer.getPlugin(FileTransferPlugin) | |||
| if (!tr) return | |||
| const defaultExport = this._savedExportFile ?? tr.defaultActions.exportFile | |||
| if (!this._connected) { | |||
| await defaultExport(blob, name) | |||
| return | |||
| } | |||
| const path = pathJoin([this.endpointURL, this.pathPrefix, name]) | |||
| const response = await this.fetch(path, { | |||
| method: 'PUT', | |||
| body: blob, | |||
| }, onProgress) | |||
| if (!response.ok) { | |||
| viewer.console.error('Error uploading file', response) | |||
| await defaultExport(blob, name) | |||
| return | |||
| } | |||
| this.dispatchEvent({type: 'fileUpload', name, blob, response, path}) | |||
| viewer.console.log('File uploaded', response) | |||
| } | |||
| fetchFunction = fetch | |||
| async fetch(input: RequestInfo, init: RequestInit, _onProgress?: (d: {state?: string, progress?: number})=>void) { | |||
| if (!this._client) throw new Error('Not connected') | |||
| for (let i = 0; i <= this._client.retries; i++) { | |||
| // todo: add onProgress (using futch in dom.ts?): https://github.com/github/fetch/issues/89 | |||
| const signed = await sign2(this._client, input, init) | |||
| let url = signed.url.toString() | |||
| if (AWSClientPlugin.USE_PROXY && url && !url.includes(AWSClientPlugin.PROXY_URL)) { | |||
| // const options: RequestInit = { | |||
| // headers: signed.headers, | |||
| // method: signed.method, | |||
| // body: signed.body, | |||
| // // ts-expect-error this is a valid option | |||
| // // duplex: 'half', // todo; get from request? | |||
| // } | |||
| // https://github.com/sindresorhus/ky/blob/2af72bfa7a391662a8ee6b1671979069f7f20737/source/core/Ky.ts#L176 | |||
| // https://issues.chromium.org/issues/40237822 | |||
| // if (supportsRequestStreams) { | |||
| // // @ts-expect-error - Types are outdated. | |||
| // options.duplex = 'half' | |||
| // } | |||
| url = AWSClientPlugin.PROXY_URL.replace('{path}', url) | |||
| } | |||
| // try { | |||
| // signed = new Request(url, options) | |||
| // } catch (e) { | |||
| // if (e instanceof TypeError) { | |||
| // // https://bugs.chromium.org/p/chromium/issues/detail?id=1360943 | |||
| // signed = new Request(url, Object.assign({duplex: 'half'}, options)) | |||
| // } else throw e | |||
| // } | |||
| const f = this.fetchFunction // required to first put it in a variable and then call. | |||
| const fetched = f(url, signed) | |||
| if (i === this._client.retries) { | |||
| return fetched // No need to await if we're returning anyway | |||
| } | |||
| const res = await fetched | |||
| if (res.status < 500 && res.status !== 429) { | |||
| return res | |||
| } | |||
| await timeout(Math.random() * this._client.initRetryMs * Math.pow(2, i)) | |||
| } | |||
| throw new Error('An unknown error occurred, ensure retries is not negative') | |||
| } | |||
| } | |||
| export type AwsRequestInit = RequestInit & { | |||
| aws?: { | |||
| accessKeyId?: string | undefined; | |||
| secretAccessKey?: string | undefined; | |||
| sessionToken?: string | undefined; | |||
| service?: string | undefined; | |||
| region?: string | undefined; | |||
| cache?: Map<string, ArrayBuffer> | undefined; | |||
| datetime?: string | undefined; | |||
| signQuery?: boolean | undefined; | |||
| appendSessionToken?: boolean | undefined; | |||
| allHeaders?: boolean | undefined; | |||
| singleEncode?: boolean | undefined; | |||
| } | undefined; | |||
| } | |||
| export async function sign2(client: AwsClient, input: RequestInfo, init?: AwsRequestInit) { | |||
| if (input instanceof Request) { | |||
| const {method, url, headers, body} = input | |||
| init = Object.assign({method, url, headers}, init) | |||
| if (init.body == null && headers.has('Content-Type')) { | |||
| init.body = body != null && headers.has('X-Amz-Content-Sha256') ? body : await input.clone().arrayBuffer() | |||
| } | |||
| input = url | |||
| console.warn('There could be a bug in chrome with cloning Request objects, see https://bugs.chromium.org/p/chromium/issues/detail?id=1360943') | |||
| } | |||
| const signer = new AwsV4Signer(Object.assign({url: input}, init, client, init && init.aws)) | |||
| const signed = Object.assign({}, init, await signer.sign()) | |||
| delete signed.aws | |||
| return signed | |||
| // try { | |||
| // return new Request(signed.url.toString(), signed) | |||
| // } catch (e) { | |||
| // if (e instanceof TypeError) { | |||
| // // https://bugs.chromium.org/p/chromium/issues/detail?id=1360943 | |||
| // return new Request(signed.url.toString(), Object.assign({duplex: 'half'}, signed)) | |||
| // } | |||
| // throw e | |||
| // } | |||
| } | |||
| // https://github.com/sindresorhus/ky/blob/main/source/core/constants.ts | |||
| // https://issues.chromium.org/issues/40237822 | |||
| // todo: right now we are using try catch like in aws4fetch | |||
| // export const supportsRequestStreams = (() => { | |||
| // let duplexAccessed = false | |||
| // let hasContentType = false | |||
| // const supportsReadableStream = typeof globalThis.ReadableStream === 'function' | |||
| // const supportsRequest = typeof globalThis.Request === 'function' | |||
| // | |||
| // if (supportsReadableStream && supportsRequest) { | |||
| // hasContentType = new globalThis.Request('https://empty.invalid', { | |||
| // body: new globalThis.ReadableStream(), | |||
| // method: 'POST', | |||
| // // @ts-expect-error - Types are outdated. | |||
| // get duplex() { | |||
| // duplexAccessed = true | |||
| // return 'half' | |||
| // }, | |||
| // }).headers.has('Content-Type') | |||
| // } | |||
| // | |||
| // return duplexAccessed && !hasContentType | |||
| // })() | |||
| @@ -0,0 +1,127 @@ | |||
| import {AssetExporterPlugin, AViewerPluginSync, IObject3D, uiButton, uiFolderContainer, uiInput} from 'threepipe' | |||
| /** | |||
| * Transfr Share Plugin | |||
| * A sample plugin that provides helpers to export and upload scene to a server and get a shareable link. | |||
| * It uses the options from the {@link AssetExporterPlugin} to export the scene or object, and can be configured using it's ui. | |||
| * | |||
| * Uses the free service [transfr.one](https://transfr.one/) by default which deletes the files after a certain time, | |||
| * but the url can be changed to a custom backend or a self-hosted version of transfr. | |||
| * | |||
| * Note: since the uploaded files are publicly accessible by anyone by default, it is recommended to encrypt the file using the exporter options or use a secure backend. | |||
| */ | |||
| @uiFolderContainer('Share Link') | |||
| export class TransfrSharePlugin extends AViewerPluginSync<''> { | |||
| public static readonly PluginType = 'TransfrSharePlugin' | |||
| toJSON: any = null | |||
| enabled = true | |||
| dependencies = [AssetExporterPlugin] | |||
| @uiInput('Server URL') | |||
| serverUrl = 'https://bee.transfr.one/scene.glb' | |||
| @uiInput() | |||
| queryParam = 'm' | |||
| @uiInput() | |||
| pageUrl = window.location.href | |||
| baseUrls: Record<string, string> = { | |||
| 'editor': '', | |||
| 'viewer': '', | |||
| } | |||
| async exportObject(model?: IObject3D) { | |||
| const exporter = this._viewer?.getPlugin(AssetExporterPlugin) | |||
| if (!this._viewer) throw new Error('TransfrSharePlugin: AssetExporter not found') | |||
| return model ? | |||
| this._viewer.export(model, exporter?.exportOptions ?? {format: 'glb'}) : | |||
| this._viewer.exportScene(exporter?.exportOptions ?? {}) | |||
| } | |||
| /** | |||
| * Export and get the link of the 3d model or scene | |||
| * @param model | |||
| */ | |||
| async getLink(model?: IObject3D) { | |||
| const obj = await this.exportObject(model) | |||
| if (!obj) { | |||
| throw new Error('Failed to export object or scene') | |||
| } | |||
| const path = 'transfr.one/scene.glb' | |||
| this._viewer!.assetManager.setProcessState(path, { | |||
| state: 'Uploading', | |||
| // progress: data.progress ? data.progress * 100 : undefined, | |||
| }) | |||
| const res = await fetch(this.serverUrl, { | |||
| method: 'PUT', | |||
| body: obj, | |||
| }) | |||
| if (res.status !== 200) { | |||
| throw new Error('Failed to upload file') | |||
| } | |||
| const data = (await res.text())?.trim() | |||
| this._viewer!.assetManager.setProcessState(path, undefined) | |||
| // console.log(data) | |||
| try { | |||
| new URL(data) | |||
| } catch (e) { | |||
| throw new Error('Invalid URL ' + data) | |||
| } | |||
| return data | |||
| } | |||
| private _exporting = false | |||
| /** | |||
| * Upload the scene and copy the link to clipboard along with the base url and query param if provided | |||
| * @param base | |||
| * @param param | |||
| */ | |||
| async shareLink(base?: string|URL, param?: string) { | |||
| if (this._exporting) return null | |||
| this._exporting = true | |||
| let link = await this.getLink().catch(e=>{ | |||
| this._viewer?.console.error(e) | |||
| this._viewer?.dialog.alert('Error: Failed to share scene: \n' + e.message) | |||
| return null | |||
| }) | |||
| if (link) { | |||
| if (base) { | |||
| const url = typeof base === 'string' ? | |||
| new URL(this.baseUrls[base] ?? base) : base | |||
| url.searchParams.set(param || this.queryParam || 'm', link) | |||
| link = url.href | |||
| } | |||
| let copied = false | |||
| try { | |||
| if (window && window.navigator && navigator.clipboard) { | |||
| await navigator.clipboard.writeText(link) | |||
| copied = true | |||
| } | |||
| } catch (e) { | |||
| console.error('Failed to copy link', e) | |||
| } | |||
| this._viewer?.dialog.alert('Link' + (copied ? ' Copied' : '') + ': ' + link + '\n\nNote: File will be deleted in 1 days') | |||
| } | |||
| this._exporting = false | |||
| return link | |||
| } | |||
| @uiButton('Share editor link', (t: TransfrSharePlugin)=>({hidden: ()=>!t.baseUrls.editor})) | |||
| async shareEditorLink() { | |||
| return this.shareLink(this.baseUrls.editor, this.queryParam) | |||
| } | |||
| @uiButton('Share viewer link', (t: TransfrSharePlugin)=>({hidden: ()=>!t.baseUrls.viewer})) | |||
| async shareViewerLink() { | |||
| return this.shareLink(this.baseUrls.viewer, this.queryParam) | |||
| } | |||
| @uiButton('Share page link', (t: TransfrSharePlugin)=>({hidden: ()=>!t.pageUrl})) | |||
| async sharePageLink() { | |||
| return this.shareLink(this.pageUrl, this.queryParam) | |||
| } | |||
| @uiButton('Share glb link') | |||
| async shareGlb() { | |||
| return this.shareLink() | |||
| } | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| declare module '*.txt' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.glsl' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.vert' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.frag' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.module.scss' { | |||
| const content: any | |||
| export default content | |||
| export const stylesheet: string | |||
| } | |||
| declare module '*.module.css' { | |||
| const content: any | |||
| export default content | |||
| export const stylesheet: string | |||
| } | |||
| declare module '*.css' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| declare module '*.css?inline' { // for vite | |||
| const content: string | |||
| export default content | |||
| } | |||
| // export {} | |||
| // hack for typedoc | |||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||
| // declare type OffscreenCanvas = HTMLCanvasElement | |||
| @@ -0,0 +1,2 @@ | |||
| export {AWSClientPlugin} from './AWSClientPlugin' | |||
| export {TransfrSharePlugin} from './TransfrSharePlugin' | |||
| @@ -0,0 +1,41 @@ | |||
| { | |||
| "compilerOptions": { | |||
| "baseUrl": "./src", | |||
| "rootDir": "./src", | |||
| "allowJs": true, | |||
| "checkJs": false, | |||
| "skipLibCheck": true, | |||
| "allowSyntheticDefaultImports": true, | |||
| "experimentalDecorators": true, | |||
| "isolatedModules": true, | |||
| "module": "es2020", | |||
| "noImplicitAny": true, | |||
| "declaration": true, | |||
| "declarationMap": true, | |||
| "declarationDir": "dist", | |||
| "outDir": "dist", | |||
| "noImplicitThis": true, | |||
| "noUnusedLocals": true, | |||
| "noUnusedParameters": true, | |||
| "removeComments": false, | |||
| "preserveConstEnums": true, | |||
| "moduleResolution": "node", | |||
| "emitDecoratorMetadata": false, | |||
| "sourceMap": true, | |||
| "target": "ES2021", | |||
| "strictNullChecks": true, | |||
| "lib": [ | |||
| "es2020", | |||
| "esnext", | |||
| "dom" | |||
| ] | |||
| }, | |||
| "include": [ | |||
| "src/**/*" | |||
| ], | |||
| "exclude": [ | |||
| "node_modules", | |||
| "**/*.spec.ts", | |||
| "dist" | |||
| ] | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| { | |||
| "extends": [ | |||
| "../../typedoc.json" | |||
| ], | |||
| "entryPoints": [ | |||
| "src/index.ts" | |||
| ], | |||
| "name": "Threepipe Network/Cloud Plugins", | |||
| "readme": "none" | |||
| } | |||
| @@ -0,0 +1,90 @@ | |||
| import {defineConfig} from 'vite' | |||
| import json from '@rollup/plugin-json'; | |||
| import dts from 'vite-plugin-dts' | |||
| import packageJson from './package.json'; | |||
| import license from 'rollup-plugin-license'; | |||
| import replace from '@rollup/plugin-replace'; | |||
| import glsl from 'rollup-plugin-glsl'; | |||
| import path from 'node:path'; | |||
| const isProd = process.env.NODE_ENV === 'production' | |||
| const { name, version, author } = packageJson | |||
| const {main, module, browser} = packageJson | |||
| const globals = { | |||
| 'three': 'threepipe', // just incase someone uses three | |||
| 'threepipe': 'threepipe', | |||
| } | |||
| export default defineConfig({ | |||
| optimizeDeps: { | |||
| exclude: ['uiconfig.js', 'ts-browser-helpers'], | |||
| }, | |||
| base: '', | |||
| // define: { | |||
| // 'process.env': process.env | |||
| // }, | |||
| build: { | |||
| sourcemap: true, | |||
| minify: false, | |||
| cssMinify: isProd, | |||
| cssCodeSplit: false, | |||
| watch: !isProd ? { | |||
| buildDelay: 1000, | |||
| } : null, | |||
| lib: { | |||
| entry: 'src/index.ts', | |||
| formats: isProd ? ['es', 'umd'] : ['es'], | |||
| name: name, | |||
| fileName: (format) => (format === 'umd' ? main : module).replace('dist/', ''), | |||
| }, | |||
| outDir: 'dist', | |||
| emptyOutDir: isProd, | |||
| commonjsOptions: { | |||
| exclude: [/uiconfig.js/, /ts-browser-helpers/], | |||
| }, | |||
| rollupOptions: { | |||
| output: { | |||
| // inlineDynamicImports: false, | |||
| globals, | |||
| }, | |||
| external: Object.keys(globals), | |||
| }, | |||
| }, | |||
| plugins: [ | |||
| isProd ? dts({tsconfigPath: './tsconfig.json'}) : null, | |||
| replace({ | |||
| 'from \'three\'': 'from \'threepipe\'', | |||
| delimiters: ['', ''], | |||
| }), | |||
| replace({ | |||
| 'process.env.NODE_ENV': JSON.stringify(isProd ? 'production' : 'development'), | |||
| preventAssignment: true, | |||
| }), | |||
| glsl({ // todo: minify glsl. | |||
| include: 'src/**/*.glsl', | |||
| }), | |||
| json(), | |||
| // postcss({ | |||
| // modules: false, | |||
| // autoModules: true, // todo; issues with typescript import css, because inject is false | |||
| // inject: false, | |||
| // minimize: isProduction, | |||
| // // Or with custom options for `postcss-modules` | |||
| // }), | |||
| license({ | |||
| banner: ` | |||
| @license | |||
| ${name} v${version} | |||
| Copyright 2022<%= moment().format('YYYY') > 2022 ? '-' + moment().format('YYYY') : null %> ${author} | |||
| ${packageJson.license} License | |||
| See ./dependencies.txt for any bundled third-party dependencies and licenses. | |||
| `, | |||
| thirdParty: { | |||
| output: path.join(__dirname, 'dist', 'dependencies.txt'), | |||
| includePrivate: true, // Default is false. | |||
| }, | |||
| }), | |||
| ], | |||
| }) | |||