-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Use import/export instead of require()/module.exports #5632
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
Conversation
| }, | ||
| "rules": { | ||
| /* To work towards no-build. */ | ||
| "import/extensions": ["warn", "always", { "js": "always" }], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Webpack doesn't need the .js suffix, but if the goal is to work towards a no-build setup it will be required. Thought it would be a good idea to start enforcing it regardless.
| { | ||
| "files": ["./src/utils/index.js"], | ||
| "parserOptions": { | ||
| "ecmaVersion": 11 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently needed due to export as usage. The utils where the hardest part to migrate as they have very inconsistent usages of default exports and re-exports. Preferably these are streamlined and simplified, along with having consumers import only the utils they use.
| import './meta-touch-controls.js'; | ||
| import './obb-collider.js'; | ||
| import './obj-model.js'; | ||
| import './oculus-go-controls.js'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Diff looks a tad strange, as I moved meta-touch-controls.js up to maintain alphabetical order.
| var utils = require('../../utils/'); | ||
| import { AFRAME_INJECTED } from '../../constants/index.js'; | ||
| import { registerComponent } from '../../core/component.js'; | ||
| import pkg from '../../../package.json'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth pointing out that importing JSON files would also not work in a no-build setup. Perhaps once JSON modules are readily available in browsers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new web standard will be:
import pkg from '../../../package.json' with { type: "json" };currently supported by node if you test it with npm run test:node.
For webpack, babel-generator gives a warning
You are using import attributes, without specifying the desired output syntax.
Please specify the "importAttributesKeyword" generator option, whose value can be one of:
- "with" : `import { a } from "b" with { type: "json" };`
- "assert" : `import { a } from "b" assert { type: "json" };`
- "with-legacy" : `import { a } from "b" with type: "json";`
Not sure where you configure it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me webpack (or rather babel) complains that the @babel/plugin-syntax-import-attributes is missing, and after installing and adding it to the plugins of babel-loader, it works fine.
But I don't think there's any upside to pre-emptively using this syntax. In fact, I'd rather see us avoiding importing package.json altogether. Currently the entirety of package.json gets embedded into the A-Frame bundle, despite only needing the version property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you, I wouldn't introduce the new syntax either. We should just inject the aframe version as part of the build like we do for INSPECTOR_VERSION really. Importing package.json was only to get the aframe version when importing from node AFAICT. I don't see a real use of importing aframe in node where there is no custom elements api.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was used for logging the THREE and WebVR polyfill versions in the past as well, but the former now uses THREE.REVISION and the latter has since been removed. Injecting the version is a lot cleaner.
|
|
||
| /** Resets the canInitializeElements flag, used for testing */ | ||
| export function reset () { | ||
| canInitializeElements = false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anything imported through import is not mutable. The readyState.test.js used to override this value to simulate various scenarios. As a workaround this method is introduced.
An alternative would be to create a holder and mutate its contents, e.g.:
export var canInitializeElements = {
value: false
}But I'd rather add this method to accommodate tests than to change all consumers because of the tests.
| import { ANode } from '../a-node.js'; | ||
| import { initPostMessageAPI } from './postMessage.js'; | ||
|
|
||
| import '../../utils/ios-orientationchange-blank-bug.js'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conditional logic is moved into this class. So while it's always "loaded", it's only applied for iOS. This is simply due to the fact that require() is synchronous, but import() isn't. Which would then either require a top-level await, or an awkwardly placed .then()
|
|
||
| // CSS. | ||
| if (utils.device.isBrowserEnvironment) { | ||
| window.logs = debug; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This used to be part of debug.js, but would be executed only when isBrowserEnvironment which is defined in device.js, which in turn depends on debug.js. This circular dependency caused issues.
| var loadPromise = Promise.allSettled([ | ||
| import('index.js'), | ||
| import('core/scene/a-scene.js') | ||
| ]).then(function (results) { | ||
| AFRAME = results[0].value.default; | ||
| AScene = results[1].value.AScene; | ||
|
|
||
| // Make sure WebGL context is not created since CI runs headless. | ||
| // Stubs below failed once in a while due to asynchronous test setup / teardown. | ||
| AScene.prototype.setupRenderer = function () {}; | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asynchronous loading of AFRAME and a-scene, as otherwise the above WebXR stubbing would happen too late.
| el.parentNode.removeChild(el); | ||
|
|
||
| utils.getUrlParameter = originalGetUrlParameter; | ||
| window.history.replaceState(null, '', originalLocationUrl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of trying to overwrite the getUrlParameter util (which is now read-only) the URL is actually rewritten using the history API.
| import { entityFactory } from '../../helpers.js'; | ||
|
|
||
| var UI_CLASSES = ['.a-orientation-modal', '.a-enter-vr-button']; | ||
| var UI_CLASSES = ['.a-orientation-modal', '.a-enter-vr']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The a-hidden class is applied to the wrapper around the button. The test was incorrect before.
| canvas = sceneEl.canvas; | ||
| sceneEl.isMobile = true; | ||
| canvas.clientWidth = 1000; | ||
| assert.isAtLeast(canvas.clientWidth, 1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again an instance where one can't set values that are read-only. From my testing the line of code wasn't actually needed, but I've added an assertion to at least make sure the desired state is there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On my Ubuntu machine, the window opens with width 886
AssertionError: expected 886 to be at least 1000
I changed it to 800 in #5641
| library: { | ||
| name: 'AFRAME', | ||
| type: 'var', | ||
| export: 'default' | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This instructs WebPack to basically ensure that when loading the UMD bundle, the default export will be available under the name AFRAME. This matches the current behaviour of the bundle AFAICT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but we should still set window.AFRAME = AFRAME; ourself for the module build used with importmap, see my patch in the last comment.
vincentfretin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow that's an insane amount of work you did here, thank you!
|
|
||
| registerComponent('oculus-touch-controls', componentConfig); | ||
| module.exports.Component = registerComponent('meta-touch-controls', componentConfig); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dmarcos kept the "oculus-touch-controls" name for backward compatibility, depending of when we merge this before or after 1.7.0, you may need to put it back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overlooked this while resolving merge conflicts during the rebase as my local branch was from before the rename. Will restore it so that the component is registered under both names.
Though, thinking about it, I'm not convinced this is a good way to achieve backwards compatibility. Something trivial like the following will already start behaving differently:
<a-entity id="leftHand" laser-controls="hand: left" oculus-touch-controls="buttonHighlightColor: red" raycaster="objects: [mixin='box']"></a-entity>The laser-controls will add the new meta-touch-controls while the oculus-touch-controls attribute will cause a second instance to be added. Pulling the trigger will result in two events being emitted...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. I think we should just remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dmarcos What is your thoughts on this? IMHO we should either remove it, or implement proper backwards compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would maintain backwards compatibility. laser-controls and oculus-touch-controls are not meant to be used together. We can perhaps add a clause somewhere to make oculus-touch-controls a no-op if meta-touch-controls defined and viceversa. That way they can't be added together.
<a-entity id="leftHand" laser-controls="hand: left" oculus-touch-controls="buttonHighlightColor: red"></a-entity>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they aren't meant to be used together, we shouldn't use them together in the docs here 😅
Either way, a quick search on GitHub reveals that they are being used together quite a lot. Often times it's just superfluous, but even those usages will "break" when upgrading to 1.7.0. Not to mention users directly referencing el.components['oculus-touch-controls'] or querying on [oculus-touch-controls].
Since this isn't really in the scope of this PR, I'll restore the double registration for now.
| if (window.debug) { | ||
| var registrationOrderWarnings = module.exports.registrationOrderWarnings = {}; | ||
| } | ||
| export var registrationOrderWarnings = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double checking this change, okay I see it's imported in tests, and it's used below in registerComponent
if (window.debug) { registrationOrderWarnings[name] = true; }it's fine 👍
tests/utils/isIOSOlderThan10.test.js
Outdated
| @@ -1,5 +1,5 @@ | |||
| /* global assert, suite, test */ | |||
| var isIOSOlderThan10 = require('utils/isIOSOlderThan10'); | |||
| var isIOSOlderThan10 = require('utils/isIOSOlderThan10.js'); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You missed this one? src/utils/isIOSOlderThan10.js is not converted either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it can be removed. It's only referenced in this test and never exposed in the bundle. This check seems to originally be used to workaround a bug in the webvr-polyfill on Safari.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talking about webvr-polyfill we removed. I think we should also remove the cardboardModeEnabled flag in xr-mode-ui component and documentation.
| warn('`utils.isOculusGo` has been deprecated, use `utils.device.isMobileVR`'); | ||
| }; | ||
| module.exports.isMobile = function () { | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So before isGearVR and isOculusGo were available on AFRAME.utils and AFRAME.utils.device I see.
Those functions are deprecated since a long time now.
Shouldn't we just remove those two functions now @dmarcos? We also removed gearvr-controls from the repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah. we can remove those functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in #5635
| // TODO: Eventually include these only if they are needed by a component. | ||
| global.THREE = THREE; | ||
| require('../../vendor/DeviceOrientationControls'); | ||
| THREE.DRACOLoader = DRACOLoader; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before we had
var THREE = { ...SUPER_THREE };that was needed for node that you can test with npm run test:node
you get TypeError: Cannot add property DRACOLoader, object is not extensible
without it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also have the error with webpack output.libraryTarget: 'module' so we need it back.
Also we should use window instead of global for a future no build context. global works only via webpack or node.
var THREE = window.THREE = {...SUPER_THREE};There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I recall having it that way before, but changing it while testing tree-shaking, as {...SUPER_THREE} prevents most tree-shaking. But indeed, it should be {...SUPER_THREE} for the time being.
Ultimately I think it would be better to have the A-Frame code import from three and its addons directly where applicable. Then construct this "SUPER_THREE + some addons" only for the final exports and populating the global scope. But that is beyond the scope of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I totally agree. For example importing LightProbeGenerator should really be done in src/components/light.js. We can do that in a separate PR.
|
Using importmap works!
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello, World! • A-Frame</title>
<meta name="description" content="Hello, World! • 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",
"three/addons/": "https://cdn.jsdelivr.net/npm/super-three@0.172.0/examples/jsm/"
}
}
</script>
<script type="module">
import AFRAME from "aframe";
// import * as THREE from "three";
import { ParametricGeometry } from "three/addons/geometries/ParametricGeometry.js";
</script>
</head>
<body>
<a-scene background="color: #ECECEC">
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" shadow></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" shadow></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>
</a-scene>
</body>
</html> diff --git a/src/index.js b/src/index.js
index 07b97618..6ba0c75b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -68,7 +68,7 @@ if (!window.AFRAME_ASYNC) {
readyState.waitForDocumentReadyState();
}
-export default {
+var AFRAME = {
AComponent: Component,
AEntity: AEntity,
ANode: ANode,
@@ -95,3 +95,5 @@ export default {
utils: utils,
version: pkg.version
};
+window.AFRAME = AFRAME;
+export default AFRAME;
diff --git a/src/lib/three.js b/src/lib/three.js
index a8e34a06..7fb4d65b 100644
--- a/src/lib/three.js
+++ b/src/lib/three.js
@@ -9,7 +9,7 @@ import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUti
import { LightProbeGenerator } from 'three/examples/jsm/lights/LightProbeGenerator.js';
import { DeviceOrientationControls } from '../../vendor/DeviceOrientationControls.js'; // THREE.DeviceOrientationControls
-var THREE = global.THREE = SUPER_THREE;
+var THREE = window.THREE = {...SUPER_THREE};
// TODO: Eventually include these only if they are needed by a component.
THREE.DRACOLoader = DRACOLoader;
var path = require('path');
var webpack = require('webpack');
var TerserPlugin = require('terser-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
libraryTarget: 'module',
path: path.resolve(__dirname, 'dist'),
publicPath: '/dist/',
filename: 'aframe-master.module.min.js'
},
experiments: {
outputModule: true
},
externalsType: 'module',
externals: {
three: 'three'
},
devtool: 'source-map',
mode: 'production',
devServer: {
port: process.env.PORT || 9000,
hot: false,
liveReload: true,
static: {
directory: 'examples'
}
},
plugins: [
new webpack.DefinePlugin({
INSPECTOR_VERSION: JSON.stringify(
process.env.INSPECTOR_VERSION
)
}),
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer']
})
],
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
passes: 2
},
format: {
comments: false
}
},
extractComments: false
})
]
}
};This produces a ES module import * as __WEBPACK_EXTERNAL_MODULE_three__ from "three";npx webpack --config webpack.prod-module.config.js
cp -rf dist/ examples/
npm starthttp://localhost:8080/boilerplate/hello-world/importmap.html |
|
For the |
I tried yesterday to fix it, that meant using the |
|
Just reviewed again after you force pushed, your changes are all okay. 👍 The webpack module build still working properly. |
|
Thanks for this. Regular script tag still works right? |
|
It needs rebase as well. |
Yes, that's the point of this PR. Switching from CommonJS to ESM internally while still outputting an UMD bundle that works like before. With this done, it becomes possible to create an ESM bundle and using it in an importmap, as @vincentfretin demonstrates, but that isn't part of this PR. |
|
Yes, that is good to be merged for me. |
|
Very nice work. @vincentfretin are you working on follow up examples / docs? |
|
@vincentfretin would be nice to have docs/examples with the new ways to import / consume aframe. Do you have anything handy we can incorporate to docs / readme / examples / maybe a glitch |
|
Yes, I said in the comment just above #5632 (comment) I'll do that. ;) |
Thanks so much. Just making sure I understood and we’re in same page. Really excited about getting best of both modules and old school builds. |
|
Example and doc in #5645 |
Description:
As discussed in #5363 replacing CommonJS (
require()/module.exports) with ES Modules (import/export) is the first step. This PR is a rebase of an earlier experiment I did, which replaces most usages.A couple of notes:
ecmaVersionto be 6 or higher, but this also allows all of the ES6 (2015) syntax and constructs. There doesn't seem to be a way to only allow import/export. Though it might be time to allow ES6 in the code base(?)import * as X from 'x.js';followed byvar y = X.ycan be simplified toimport { y } from 'x.js';. This is done in some places, but not all. Generally theutilsis being imported whole in a lot of places, where importing specific utilities would suffice. Generally preferable to only import what is needed to aid three-shaking (down the line).I'll place some comments on this PR to highlight some significant changes.
Changes proposed:
import/exportinstead ofrequire()/module.exports