Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Teapot example that uses the new ES module bundle with importmap #5645

Merged
merged 8 commits into from
Jan 28, 2025
39 changes: 39 additions & 0 deletions docs/introduction/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,45 @@ await import('aframe');
window.AFRAME.ready();
```

Since version 1.7.0, A-Frame ships an ES module bundle without the three dependency.
Developers can import from `three` and `three/addons` and avoid the
"Multiple instances of Three.js being imported." warning. Add the three dependency in the importmap like the example below.
Make sure the three and A-Frame versions are compatible. See browser console (or package.json) to see what THREE version A-Frame ships with by default.

```HTML
<head>
<script type="importmap">
{
"imports": {
"aframe": "https://aframe.io/releases/1.7.0/aframe.module.min.js",
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/",
"aframe-extras/controls": "https://cdn.jsdelivr.net/gh/c-frame/[email protected]/dist/aframe-extras.controls.min.js"
}
}
</script>
<script type="module">
import AFRAME from "aframe";
// AFRAME and THREE variables are available globally, the imported aframe-master.module.min.js bundle basically does:
// import * as THREE from "three"
// window.THREE = THREE
import { TeapotGeometry } from "three/addons/geometries/TeapotGeometry.js"; // This uses the same three instance.
AFRAME.registerComponent("teapot", {
...
}
</script>
</head>
```

The [importmap example](https://aframe.io/aframe/examples/boilerplate/importmap/index.html) uses the above code.

## "Multiple instances of Three.js being imported." warning

See `Can I load A-Frame as an ES module?` above.

As a library author of aframe components, be sure to configure your bundler configuration to produce a build with the three dependency declared as external if you're using any `import ... from three` in your code or any addons you import like `import ... from three/addons/...js`.
You can look at the webpack configuration in the [aframe-extras repository](https://github.com/c-frame/aframe-extras) as an example.

## What order does A-Frame render objects in?

[sortTransparentObjects]: ../components/renderer.md#sorttransparentobjects
Expand Down
83 changes: 83 additions & 0 deletions examples/boilerplate/importmap/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Importmap • A-Frame</title>
<meta name="description" content="Importmap • A-Frame" />
<script type="importmap">
{
"imports": {
"aframe": "../../../dist/aframe-master.module.min.js",
"three": "../../../super-three-package/build/three.module.js",
"three/addons/": "../../../super-three-package/examples/jsm/",
"aframe-extras/controls": "https://cdn.jsdelivr.net/gh/c-frame/[email protected]/dist/aframe-extras.controls.min.js"
}
}
</script>
<script type="module">
// We use an importmap to load three as a module to avoid the warning "Multiple instances of Three.js being imported."
import AFRAME from "aframe";
// AFRAME and THREE variables are available globally, the imported aframe-master.module.min.js bundle basically does:
// import * as THREE from "three"
// window.THREE = THREE
import "aframe-extras/controls"; // This uses the THREE global variable in the bundle.
import { Mesh, MeshStandardMaterial } from "three"; // This uses the same three instance.
import { TeapotGeometry } from "three/addons/geometries/TeapotGeometry.js"; // This uses the same three instance.

AFRAME.registerComponent("teapot", {
init() {
this.geometry = new TeapotGeometry();
const mesh = new Mesh();
mesh.geometry = this.geometry;
// Default material if not defined on the entity.
if (!this.el.getAttribute("material")) {
mesh.material = new MeshStandardMaterial({
color: Math.random() * 0xffffff,
metalness: 0,
roughness: 0.5,
side: THREE.DoubleSide,
});
}
this.el.setObject3D("mesh", mesh);
},
remove() {
this.geometry.dispose();
},
});
</script>
</head>
<body>
<a-scene background="color: #ECECEC">
<a-cylinder
position="0 0.25 -2"
radius="0.5"
height="0.5"
color="#FFC65D"
>
<a-entity
teapot
position="0 0.48 0"
scale="0.005 0.005 0.005"
material="color: #713f12; roughness: 0.5; side: double"
></a-entity>
</a-cylinder>

<a-plane
position="0 0 -2"
rotation="-90 0 0"
width="4"
height="4"
color="#7BC8A4"
></a-plane>

<a-entity
position="0 0 1"
id="rig"
movement-controls="controls: gamepad,keyboard,nipple"
nipple-controls="mode: static"
>
<a-entity camera position="0 1.6 0" look-controls></a-entity>
</a-entity>
</a-scene>
</body>
</html>
1 change: 1 addition & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ <h2>Examples</h2>
<li><a href="boilerplate/3d-model/">3D Model (glTF)</a></li>
<li><a href="mixed-reality/anchor/">Anchor (Mixed Reality)</a></li>
<li><a href="mixed-reality/real-world-meshing/">Real World Meshing (Mixed Reality)</a></li>
<li><a href="boilerplate/importmap/">Importmap (import teapot geometry from three/addons)</a></li>
</ul>

<h2>Examples from Documentation</h2>
Expand Down
5 changes: 4 additions & 1 deletion scripts/preghpages.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const path = require('path');
const shell = require('shelljs');
const replaceInFileSync = require('replace-in-file').replaceInFileSync;

const pkg = require('../package.json');
const threeVersion = pkg.dependencies.three.split('@')[1];

const rootDir = path.join(__dirname, '..');

shell.cd(rootDir);
Expand All @@ -15,7 +18,6 @@ shell.cp('-r', [
'.nojekyll',
'dist',
'examples',
'*.html',
'*.md'
], 'gh-pages');

Expand All @@ -28,3 +30,4 @@ function htmlReplace (before, after) {
}

htmlReplace('dist/aframe-master.js', 'dist/aframe-master.min.js');
htmlReplace(/\.\.\/\.\.\/\.\.\/super-three-package/g, `https://cdn.jsdelivr.net/npm/super-three@${threeVersion}`);
20 changes: 20 additions & 0 deletions scripts/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,20 @@ if (!prevVersion || !nextVersion) {
process.exit(1);
}

let distModule;
let distMin;
let distMax;
if (process.env.FOR_RELEASE) {
distModule = `${pkg.scripts['dist:module']} --output-filename aframe.module.min.js`;
distMin = `${pkg.scripts['dist:min']} --output-filename aframe.min.js`;
distMax = `${pkg.scripts['dist:max']} --output-filename aframe.js`;
} else {
distModule = `${pkg.scripts['dist:module']} --output-filename aframe-v${nextVersion}.module.min.js`;
distMin = `${pkg.scripts['dist:min']} --output-filename aframe-v${nextVersion}.min.js`;
distMax = `${pkg.scripts['dist:max']} --output-filename aframe-v${nextVersion}.js`;
}

execSync(distModule, {stdio: 'inherit'});
execSync(distMin, {stdio: 'inherit'});
execSync(distMax, {stdio: 'inherit'});

Expand All @@ -32,10 +36,26 @@ glob.sync(`dist/aframe*v${prevVersion}*`).forEach(fs.unlinkSync);
var versionRegex = new RegExp(`${prevVersion.replace(/\./g, '\\.')}`, 'g');
glob.sync('docs/**/*.md').forEach(updateDoc);
glob.sync('README.md').forEach(updateDoc);

// Replace super-three version in examples, docs and README
var threeVersion = pkg.dependencies.three.split('@')[1];
var threeVersionRegex = new RegExp('super-three@.*?/', 'g');
glob.sync('examples/**/*.html').forEach(updateThreeVersion);
glob.sync('docs/**/*.md').forEach(updateThreeVersion);
glob.sync('README.md').forEach(updateThreeVersion);

function updateDoc (docFilename) {
var contents = fs.readFileSync(docFilename, 'utf-8');
if (versionRegex.exec(contents)) {
contents = contents.replace(versionRegex, nextVersion);
fs.writeFileSync(docFilename, contents);
}
}

function updateThreeVersion (docFilename) {
var contents = fs.readFileSync(docFilename, 'utf-8');
if (threeVersionRegex.exec(contents)) {
contents = contents.replaceAll(threeVersionRegex, `super-three@${threeVersion}/`);
fs.writeFileSync(docFilename, contents);
}
}
16 changes: 13 additions & 3 deletions webpack.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,18 @@ module.exports = {
port: process.env.PORT || 9000,
hot: false,
liveReload: true,
static: {
directory: 'examples'
}
static: [
{
directory: 'examples'
},
{
directory: 'dist',
publicPath: '/dist'
},
{
directory: 'node_modules/three/',
publicPath: '/super-three-package'
}
]
}
};