diff --git a/.gitattributes b/.gitattributes index d36a461..4f9aada 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,5 +8,6 @@ LICENSE text *.md text *.js text +*.ts text *.json text *.yml text diff --git a/.gitignore b/.gitignore index 6d30f40..73ccc6a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,9 @@ !/.vscode !/src !/test +!/globals.d.ts !/package.json +!/tsconfig.json #-----------------------------------------------------------------------------# # But make sure to ignore those regardless: diff --git a/README.md b/README.md index 3761984..4b219be 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,25 @@ You can now import the classes in your application: import {Scene, WebGLRenderer} from 'three'; // Import from "three/examples/js" for addditional classes -import OrbitControls from 'three/examples/js/controls/OrbitControls'; +import {OrbitControls} from 'three/examples/js/controls/OrbitControls'; // Use the imported classes const scene = new Scene(); const renderer = new WebGLRenderer(); const controls = new OrbitControls(); ```` + + +## Typescript + +Until definitions are integrated directly in `@types/three`, add a file `globals.d.ts` +at the root of your project to specify the types of the imports, e.g.: + +````ts +declare module 'three/examples/js/controls/OrbitControls' { + export const OrbitControls: typeof THREE.OrbitControls; +} +```` + +Note that this is *not* required for compiling to JS, it improves Intellisense in your code editor. + diff --git a/globals.d.ts b/globals.d.ts new file mode 100644 index 0000000..6852dd2 --- /dev/null +++ b/globals.d.ts @@ -0,0 +1,21 @@ + +declare module 'three/examples/js/controls/OrbitControls' { + export const OrbitControls: typeof THREE.OrbitControls; +} + +declare module 'three/examples/js/loaders/OBJLoader' { + export const OBJLoader: typeof THREE.OBJLoader; +} + +declare module 'three/examples/js/postprocessing/EffectComposer' { + export const EffectComposer: typeof THREE.EffectComposer; + export const Pass: typeof THREE.Pass; +} + +declare module 'three/examples/js/postprocessing/RenderPass' { + export const RenderPass: typeof THREE.RenderPass; +} + +declare module 'three/examples/js/shaders/CopyShader' { + export const CopyShader: typeof THREE.CopyShader; +} diff --git a/package.json b/package.json index fc38663..dcb958c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@wildpeaks/three-webpack-plugin", - "version": "1.0.0", + "version": "2.0.0", "description": "Webpack plugin to use Three.js \"examples\" classes that aren't ES Modules", "author": "Cecile Muller", "license": "MIT", @@ -25,10 +25,10 @@ }, "homepage": "https://github.com/wildpeaks/package-three-webpack-plugin#readme", "dependencies": { - "exports-loader": "0.7.0", - "imports-loader": "0.8.0" + "loader-utils": "1.1.0" }, "devDependencies": { + "@types/three": "0.92.7", "@wildpeaks/eslint-config-commonjs": "4.9.0", "eslint": "4.19.1", "express": "4.16.3", @@ -37,6 +37,8 @@ "puppeteer": "1.5.0", "rimraf": "2.6.2", "three": "0.93.0", + "ts-loader": "4.4.1", + "typescript": "2.9.2", "webpack": "4.12.0" }, "peerDependencies": { diff --git a/src/Loader.js b/src/Loader.js new file mode 100644 index 0000000..43bba76 --- /dev/null +++ b/src/Loader.js @@ -0,0 +1,30 @@ +'use strict'; +const {getOptions} = require('loader-utils'); + + +function Loader(source){ + const options = getOptions(this); + let code = `'use strict';\nvar THREE = require('three');\n`; + + const optionsRequires = options.requires; + if (Array.isArray(optionsRequires)){ + code += optionsRequires.map(moduleId => `require(${JSON.stringify(moduleId)});`).join('\n'); + } + + code += `${source}\n`; + + const optionsExports = options.exports; + if ((typeof optionsExports === 'object') && (optionsExports !== null)){ + const lines = []; + for (const id in optionsExports){ + const exportId = optionsExports[id]; + lines.push(`${JSON.stringify(id)}: ${exportId}`); + } + code += 'module.exports = {' + lines.join(',') + '}'; // eslint-disable-line prefer-template + } + + return code; +} + + +module.exports = Loader; diff --git a/src/Plugin.js b/src/Plugin.js index 37e30e2..70f47d3 100644 --- a/src/Plugin.js +++ b/src/Plugin.js @@ -1,6 +1,7 @@ /* eslint-env node */ 'use strict'; const PLUGIN_ID = 'wildpeaks-three'; +const Loader = require.resolve('./Loader'); class Plugin { apply(compiler){ // eslint-disable-line class-methods-use-this @@ -9,8 +10,38 @@ class Plugin { const {loaders, rawRequest} = data; if (rawRequest.startsWith('three/examples/js/')){ const exportId = rawRequest.split('/').pop(); - loaders.push('imports-loader?THREE=three'); - loaders.push(`exports-loader?THREE.${exportId}`); + if (rawRequest === 'three/examples/js/postprocessing/EffectComposer'){ + loaders.push({ + loader: Loader, + options: { + exports: { + EffectComposer: 'THREE.EffectComposer', + Pass: 'THREE.Pass' + } + } + }); + } else if (rawRequest.startsWith('three/examples/js/postprocessing/')){ + loaders.push({ + loader: Loader, + options: { + requires: [ + 'three/examples/js/postprocessing/EffectComposer' + ], + exports: { + [exportId]: `THREE.${exportId}` + } + } + }); + } else { + loaders.push({ + loader: Loader, + options: { + exports: { + [exportId]: `THREE.${exportId}` + } + } + }); + } } return data; }); diff --git a/test/fixtures.spec.js b/test/fixtures.spec.js index eedf168..433e1bb 100644 --- a/test/fixtures.spec.js +++ b/test/fixtures.spec.js @@ -51,6 +51,21 @@ async function testFixture(entry, expectError, expectText){ path: outputFolder, filename: '[name].js' }, + module: { + rules: entry.endsWith('.ts') ? [ + { + test: /\.(ts|js)$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true + } + } + ] + } + ] : [] + }, plugins: [ new HtmlWebpackPlugin(), new Plugin() @@ -118,8 +133,28 @@ it('With examples', async() => { await testFixture('./with-examples.js', false, 'function function function'); }); +it('EffectComposer', async() => { + await testFixture('./effectcomposer.js', false, 'function function function'); +}); + +it('RenderPass', async() => { + await testFixture('./renderpass.js', false, 'function function'); +}); + +it('CopyShader', async() => { + await testFixture('./copyshader.js', false, 'object object string string'); +}); + it('Invalid path', async() => { const errors = await testFixture('./wrong-examples.js', true, ''); expect(errors.length).toBe(1, 'Has one error'); expect(errors[0] instanceof ModuleNotFoundError).toBe(true, 'The error is a ModuleNotFoundError'); }); + +it('Typescript: With examples', async() => { + await testFixture('./ts-with-examples.ts', false, 'function function function'); +}); + +it('Typescript: RenderPass', async() => { + await testFixture('./ts-renderpass.ts', false, 'function function'); +}); diff --git a/test/fixtures/copyshader.js b/test/fixtures/copyshader.js new file mode 100644 index 0000000..dc6a4f2 --- /dev/null +++ b/test/fixtures/copyshader.js @@ -0,0 +1,6 @@ +import {CopyShader} from 'three/examples/js/shaders/CopyShader'; + +const $div = document.createElement('div'); +$div.setAttribute('id', 'fixture'); +$div.innerText = `${typeof CopyShader} ${typeof CopyShader.uniforms} ${typeof CopyShader.vertexShader} ${typeof CopyShader.fragmentShader}`; +document.body.appendChild($div); diff --git a/test/fixtures/effectcomposer.js b/test/fixtures/effectcomposer.js new file mode 100644 index 0000000..d375fc4 --- /dev/null +++ b/test/fixtures/effectcomposer.js @@ -0,0 +1,7 @@ +import {Vector3} from 'three'; +import {EffectComposer, Pass} from 'three/examples/js/postprocessing/EffectComposer'; + +const $div = document.createElement('div'); +$div.setAttribute('id', 'fixture'); +$div.innerText = ` ${typeof Vector3} ${typeof EffectComposer} ${typeof Pass}`; +document.body.appendChild($div); diff --git a/test/fixtures/renderpass.js b/test/fixtures/renderpass.js new file mode 100644 index 0000000..388b444 --- /dev/null +++ b/test/fixtures/renderpass.js @@ -0,0 +1,7 @@ +import {Vector3} from 'three'; +import {RenderPass} from 'three/examples/js/postprocessing/RenderPass'; + +const $div = document.createElement('div'); +$div.setAttribute('id', 'fixture'); +$div.innerText = ` ${typeof Vector3} ${typeof RenderPass}`; +document.body.appendChild($div); diff --git a/test/fixtures/ts-renderpass.ts b/test/fixtures/ts-renderpass.ts new file mode 100644 index 0000000..858a9e7 --- /dev/null +++ b/test/fixtures/ts-renderpass.ts @@ -0,0 +1,7 @@ +import {Vector3} from 'three'; +import {RenderPass} from 'three/examples/js/postprocessing/RenderPass'; + +const $div: HTMLDivElement = document.createElement('div'); +$div.setAttribute('id', 'fixture'); +$div.innerText = ` ${typeof Vector3} ${typeof RenderPass}`; +document.body.appendChild($div); diff --git a/test/fixtures/ts-with-examples.ts b/test/fixtures/ts-with-examples.ts new file mode 100644 index 0000000..1c7b9d2 --- /dev/null +++ b/test/fixtures/ts-with-examples.ts @@ -0,0 +1,8 @@ +import {Vector3} from 'three'; +import {OrbitControls} from 'three/examples/js/controls/OrbitControls'; +import {OBJLoader} from 'three/examples/js/loaders/OBJLoader'; + +const $div: HTMLDivElement = document.createElement('div'); +$div.setAttribute('id', 'fixture'); +$div.innerText = `${typeof Vector3} ${typeof OBJLoader} ${typeof OrbitControls}`; +document.body.appendChild($div); diff --git a/test/fixtures/with-examples.js b/test/fixtures/with-examples.js index 739c86c..8bc4112 100644 --- a/test/fixtures/with-examples.js +++ b/test/fixtures/with-examples.js @@ -1,9 +1,6 @@ import {Vector3} from 'three'; -import OrbitControls from "three/examples/js/controls/OrbitControls"; -import OBJLoader from "three/examples/js/loaders/OBJLoader"; - -console.log('[OrbitControls]', OrbitControls); -console.log('[OBJLoader]', OBJLoader); +import {OrbitControls} from 'three/examples/js/controls/OrbitControls'; +import {OBJLoader} from 'three/examples/js/loaders/OBJLoader'; const $div = document.createElement('div'); $div.setAttribute('id', 'fixture'); diff --git a/test/fixtures/wrong-examples.js.js b/test/fixtures/wrong-examples.js similarity index 77% rename from test/fixtures/wrong-examples.js.js rename to test/fixtures/wrong-examples.js index 204c21f..4af272a 100644 --- a/test/fixtures/wrong-examples.js.js +++ b/test/fixtures/wrong-examples.js @@ -1,5 +1,5 @@ import {Vector3} from 'three'; -import OBJLoader from "three/examples/js/fake/OBJLoader"; +import OBJLoader from 'three/examples/js/fake/OBJLoader'; const $div = document.createElement('div'); $div.setAttribute('id', 'fixture'); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ee5c632 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "newLine": "LF", + "alwaysStrict": true, + "noEmitOnError": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strictNullChecks": true, + "preserveConstEnums": true, + "removeComments": true, + "sourceMap": true, + "module": "esnext", + "target": "es5", + "lib": ["es2017", "dom"], + "allowJs": true + } +}