Skip to content
42 changes: 42 additions & 0 deletions docs/introduction/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,48 @@ await import('aframe');
window.AFRAME.ready();
```

Since A-Frame 1.7.0, we provide an ES module bundle without three dependency.
This allows you to import from three and three/addons without having the
"Multiple instances of Three.js being imported." warning.
To use it you need to explicitly add the super-three dependency in the importmap.
If you update A-Frame later, be sure to also update the super-three version that matches the A-Frame version, simply by reading again that documentation.

Here is an example of the importmap to use:

```HTML
<head>
<script type="importmap">
{
"imports": {
"aframe": "https://aframe.io/releases/1.7.0/aframe.module.min.js",
"three": "https://cdn.jsdelivr.net/npm/super-three@0.172.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/super-three@0.172.0/examples/jsm/",
"aframe-extras/controls": "https://cdn.jsdelivr.net/gh/c-frame/aframe-extras@7.5.x/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 [teapot example](https://aframe.io/aframe/examples/boilerplate/teapot/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/teapot/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>Teapot • A-Frame</title>
<meta name="description" content="Teapot • A-Frame" />
<script type="importmap">
{
"imports": {
"aframe": "../../../dist/aframe-master.module.min.js",
"three": "https://cdn.jsdelivr.net/npm/super-three@0.172.0/build/three.module.js",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem is that will have to update this in the future since it’s locked to r172. Is it possible to have an example without a hardcoded version?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be an option to tag the super-three versions with the A-Frame version they are used in, so you'd be able to use super-three@aframe-1.7.0. For master we could consider pointing it to node_modules instead of using a CDN? That way when checking out older revisions, running npm install ensures that the right super-three version is referenced.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah. pointing to node_modules sounds fine to me. trying to avoid small things that will eventually break and hard to keep track of over time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you see my changes to the release script? There is no need to update manually, the script will update the super-three version in examples and docs similar to the aframe version.
But indeed for master that should reference the current version in node_modules. I just added an additional directory path to the webpack dev server config, and the preghpages script rewrite the url with the cdn.

"three/addons/": "https://cdn.jsdelivr.net/npm/super-three@0.172.0/examples/jsm/",
"aframe-extras/controls": "https://cdn.jsdelivr.net/gh/c-frame/aframe-extras@7.5.x/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/teapot/">Teapot (use importmap and import from three/addons)</a></li>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be better to rename the example to importmap. The way I see it, it's an example of using importmap using a Teapot, instead of an example of a Teapot using importmaps.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah. boilerplate/importmap or boilerplate/modules. not sure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I renamed to boilerplate/importmap.

</ul>

<h2>Examples from Documentation</h2>
Expand Down
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);
}
}
12 changes: 9 additions & 3 deletions webpack.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ module.exports = {
port: process.env.PORT || 9000,
hot: false,
liveReload: true,
static: {
directory: 'examples'
}
static: [
{
directory: 'examples'
},
{
directory: 'dist',
publicPath: '/dist'
}
]
}
};
Loading