Movatterモバイル変換


[0]ホーム

URL:


Discover three.js is nowopen source!
Word Count:2619, reading time: ~13minutes

JavaScript Modules

Since the release of JavaScript version ES6 in 2015 and the switch to a yearly release schedule, the JavaScript language has been reborn as a powerful, full-featured language that is both fun and easy to use. The need for backward compatibility means that there are still a few clunky areas, but overall the language is in a good place now. We have been referring to these new featuresmodern JavaScript, and we’ll continue to do that here.

Perhapsthe most important new feature added to JavaScript recently is the ability to split our code up into many small modules. Using old-school JavaScript, we either had to write everything in one huge file, sometimes thousands of lines long, use a non-standard solution such asbrowserify orrequire.js, or include lots of separate<script> elements in our HTML files.

The new “official JavaScript modules” are calledES6 Modules, and using them, we can break our app down into discrete components, and put each of these components into a separate file. Doing so leads to a huge improvement in code style and re-usability.

As with ourprevious chapter on JavaScript, we’re won’t attempt a complete description of ES6 modules here. We’ll only cover the bits you need to know to get through this book.

When writing modular JavaScript, each file is a module. So, we may refer to a module by its file name, for example,main.js, or simply as themain module.

Modules in Other Environments

Modules are an official feature of JavaScript so they will be supported everywhere… eventually. All modern browsers now support ES6 modules, however, Node.js has been slow to catch up. Fortunately, as of Node v14, ES6 modules are fully supported. However, when using older node versions, or very old browsers, you may need to do additional work to get modules working.

Modular Software Design

Modular software design opens up a new world of possibilities for structuring an application. Each module we create should have a single, well-defined responsibility. Additionally, each module should be self-contained, so far as possible, and rely on little or no knowledge of other modules.

These are tried and tested design patterns, known asthe single responsibility principle,loose coupling, andhigh cohesion. A well-designed module has a single responsibility and is both loosely coupled and highly cohesive.

In other words, each module should do one thing only, and do that well, without relying on outside help. High cohesion means that the functions inside a module logically belong together. When writing code in this way, each module deals with a tiny fraction of the overall complexity, and even though our applications may grow and become complex over time, at any moment we should be dealing with just a few simple modules.

ES6 Module Syntax:import andexport

ES6 modules introduced two new keywords to #"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import" rel="noopener noreferrer">import andexport. These allow us to write code in one file,export it, and thenimport it for use in a different file.

We’ll illustrate this here by exporting a variable calledx from a file namedexport.js.

export.js
const x = 'hello!';export { x };

Later, we can import this variable intomain.js, and then log the value to the console or use it in a calculation.

main.js
import { x } from './export.js';console.log(x); // -> hello!

If you open up the inline IDE, you’ll see that we have set up these two files for you. You can use the IDE to test out the rest of the examples on this page.

Import and export Statements can be Placed Anywhere in Module Scope

We don’t have to wait until the end of the file to perform an export. Instead, we can do it immediately when we declare the variable:

export.js: you can export from anywhere in module scope
export const x = 'hello!';

We can placeimport andexport statements anywherein module scope.

Import and export statements can be placed anywhere in module scope
export const x = 'hello!';import { someVariable } from './export2.js';console.log('Hey there!');export class Cat {...}import { anotherVariable } from './export2.js';export const y = 'goodbye';

However, we can’t import or export while in function or block scope.

Export statements must be in module scope, never function or block scope
const x = 'hello!';function thisWontWork() {export { x };//=> Uncaught SyntaxError: Unexpected token 'export'}
Likewise, import statements
function thisWontWork() {import { x } from './export.js';//=> Uncaught SyntaxError: Unexpected token '{'}

In this book, for clarity, we’ll always placeimport statements at the top of a module andexport statements at the bottom.

We will always place import statements at the top and export statements at the bottom.
import { someVariable } from './export2.js';const x = 'hello!';class Cat {...}export { x, Cat }

Relative Import URLs

So far, we’ve been using relative URLs to import and export between themain.js andexport.js files, which both reside in thesrc/ folder. We also use a relative URL in the<script> tag inindex.html. You can tell when an import is relative because it will start with./ or../.

We’ve placedexport.js in the same directory asmain.js, so we’re using./. If we had placed it in a subfolder calledexported, for example, the import statement would look like this:

main.js: importing from the exported folder
import { x } from "./exported/export.js";console.log(x); // -> hello!

Importing from Other Websites

You can also import from other websites like a CDN by specifying the full web address of the module.

We used this style inthe intro when we showed you how to import three.js from a CDN (content delivery network).

Importing modules from another website
import { Camera } from "https://cdn.skypack.dev/[email protected]";

For the rest of this chapter, to keep things simple, we’ll stick with relative paths. For more info on how URLs work on the web, refer toa quick primer on URLs and paths on MDN.

Importing from Node Modules (NPM or YARN)

If you’re using a package manager like NPM or Yarn, you can install packages into thenode_modules folder:

Installing the three package with NPM (run this on a command line after installing Node)
npm install three

Once you do this, you’ll findthree.module.js in thenode_modules/three/build folder. If you like, you can import it directly from there:

Importing directly from node_modules (possible, but not common)
import { Camera } from './node_modules/three/build/three.module.js'

However, it’s more common to use a bundling tool such as Rollup.js, Parcel, or Webpack in conjunction with a package manager. These bundlers follow a convention of allowing you use the package name as a shortcut when importing (in this case, the package name isthree). If you are using a bundler, these are equivalent:

When using a bundler, these are equivalent
import { Camera } from "./node_modules/three/build/three.module.js";import { Camera } from "three";

For now, remember that if you see an import that’s not a relative import or a website import, but instead start with a package name likethree, it means the code is designed to be used with a bundler and you will not be able to run it directly.

These import statements can be run directly in the browser
import { Camera } from "./node_modules/three/build/three.module.js";import { Camera } from "https://cdn.skypack.dev/[email protected]";import { x } from "./exported/export.js";import { x } from "../../../scripts/test.js";
These import statements require a bundler
import { Camera } from "three";import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";import { throttle, debounce } from "lodash-es";

There’s a lot more to using a bundler than this, which we won’t get into here. However, to keep our code clean, in most of the code examples in this book, including in the editor, we will useimport { ... } from 'three'.

Named Exports

The presence of{} aroundx means that this is anamed export. To importx we must refer to it by name, although once imported we can rename it if we need to.

You can have any number of named exports in a file. For example, here’s a file that exports twenty-six names, one for every letter of the alphabet (although we’ve skipped f-y for brevity):

export.js: exporting a name for every letter of the alphabet
const a = 'Abella';const b = 'Bertrand';const c = 'Courtney';const d = 'Dewi';const e = 'Eilinora';...const z = 'Zarathustra';export {a,b,c,d,e,...z,};

We can import all of these named exports at the same time. Here, we import all twenty-six names (again, skipping the lines f-y):

main.js: import all twenty-six names
import {a,b,c,d,e,...z,} from './export.js';console.log(a); // Abellaconsole.log(b); // Bertrandconsole.log(c); // Courtneyconsole.log(d); // Dewi...

Renaming Named Exports with theas Keyword

We can rename named exports using theas keyword, either when they are exported:

export.js: renaming variables on export
const a = 'Abella';const b = 'Bertrand';const c = 'Courtney';const d = 'Dewi';const e = 'Eilinora';...const z = 'Zarathustra';export {a as abella,b as bertrand,c as courtney,d as dewi,e as eilinora,...z as zarathustra,};

Or, when they are imported:

main.js: renaming exports on import
import {a as abella,b as bertrand,c as courtney,d as dewi,e as eilinora,...z as zarathustra,}; from './export.js';console.log(abella); //=> Abellaconsole.log(bertrand); //=> Bertrandconsole.log(courtney); //=> Courtneyconsole.log(dewi); //=> Dewi...

Using Namespaces with Named Imports

Importing a lot of things from a single module like this can get a bit messy. In these cases, it can be useful to import everything at once from a given module and save it to anamespace. We can do this usingimport * as <namespace>:

With a single line, we can import all twenty-six names from the previous file. We can then access them with dot notation:

main.js: importing to a namespace
import \* as NAMES from './export.js';console.log(NAMES.a); //=> Abellaconsole.log(NAMES.b); //=> Bertrandconsole.log(NAMES.z); //=> Zarathustra

Note that we can’t rename the individual exports when doing this. It’s a common convention to use all capitals for namespaces, but it’s not required.

TheTHREE Namespace

You will often see theTHREE namespace used when working with three.js. Thethree.js core contains hundreds of exports. It’s highly unlikely you’ll need to use all of them in a single file, but for quick tests, you can import them all at once and store them in a namespace.

main.js: importing the entire three.js core to the THREE namespace
import \* as THREE from 'three';

Until the switch to modules, to use three.js you would include the corebuild/three.js file in your HTML using a<script> tag, and theTHREE namespace would become globally available.

Now that we’ve switched to modules, we try to avoid using global namespaces. But theTHREE namespace has been associated with three.js for years, and as the examples around the web are gradually converted to modules, it’s faster to continue using the namespace.

In this book, we’ll avoid using namespaces likeTHREE, preferring to import components as we need them. This will train us to keep modules focused. Rather than having a huge number of unused components available, we’ll only have the ones we need in any given module.

Default Exports

Unlikenamed exports,default exports allow us to export a value without naming the export. To create a default export, omit the{} braces and add thedefault keyword:

export.js: a default export
const x = 'hello!';export default x;

Default exports don’t have names. Instead, we can name them whatever we like on import. Here, we import the variablex into the filemain.js and call ithello.

main.js: importing a default export
import hello from './export.js';

The variable was originally calledx, but that doesn’t matter on import for a default export.

You can only have one default export per file, otherwise, there’s no way for JavaScript to know what export we’re referring to. Youcan mix default and named exports in a single file, but we’ll avoid doing so. In fact, throughout this book, we’ll avoid using default exports completely.

Referencing JavaScript Modules from HTML

As we mentioned above, when using JavaScript modules, every file is a module. However, modern JavaScript, including ES6 modules, is built on top of old-school JavaScript, and all the old syntax and ways of doing things still work. In old-school JavaScript, files were not modules.

This means, when we pass themain.js module over to the browser, it can be interpreted in one of two ways:

  1. It’s an old-school “normal” JavaScript file.
  2. It’s a fancy new JavaScript module.

There’s no way to tell from a glance at the file name which interpretation is correct so we need to tell the browser.

To reference an old-school JavaScript file from HTML we usethe<script> element. For example, here we include an old-school, non-modular JavaScript file calledapp.js inindex.html:

index.html: using a script tag to include an old-school JavaScript file
<script src="./src/app.js"></script>

To tell the browser that the file is a module, we need to add thetype="module" attribute. Here, we include our fancy newmain.js module.

index.html: importing the main.js module
<script type="module" src="./src/main.js"></script>

We can also write JavaScript directly in an HTML<script> element:

index.html: an inline script element
<script>  const x = 'welcome to JavaScript!';</script>

However, that’s strictly old-school JavaScript. Noimport orexport allowed. But, if we add thetype="module" attribute, we can then writeimport statements directly in HTML. For example, we can bypassmain.js and import the variablex directly fromexport.js intoindex.html.

index.html: an inline module script element
<script type="module">  import {x} from './src/export.js'; console.log(x);</script>

Dynamic Imports

We’ll finish up this chapter with a brief look atdynamic imports. So far in this chapter, we’ve usedstatic imports, meaning they are evaluated atload time. By contrast,dynamic imports are evaluated atrun time.

Static imports use theimport statement, while dynamic imports use theimport() function.

With dynamic imports you can optionally load a module during the execution of your code. This might be useful, for example, if you want to create an app that can load any of thethirty or so 3D asset formats that three.js supports (there are more than thirty loaders there, but some are for textures and other things). Altogether, these loaders comprise around one megabyte of JavaScript, which is a lot to force upon a poor user if they only need a fraction of it. Instead, you can wait until the user sends you a model file, examine the file and say, “ayup, that there’s an FBX file, better be fetchin' thaFBXLoader":

Dynamically importing the FBXLoader at run time
import("./vendor/three/examples/jsm/loaders/FBXLoader.js").then((module) => {  // use the loader module to load the model});

Again, take to note that we’re using thedynamicimport() function,not astaticimport statement which would look like this:

Statically importing the FBXLoader at load time
import { FBXLoader } from "./vendor/three/examples/jsm/loaders/FBXLoader.js";

As we’ll see in the next chapter,.then means thatimport() returns aPromise. Even better, we can usetheawait keyword, which we’ll also cover in the next chapter:

Dynamically importing the FBXLoader at run time using async/await
const module = await import("/vendor/three/examples/jsm/loaders/FBXLoader.js");// use the loader module to load the model

That’s it for JavaScript modules. Next up, we’ll examine another important aspect of #"/book/appendix/dom-api-reference/" title="The Document Object Model and DOM API">

Import Style
Selected Texture

[8]ページ先頭

©2009-2025 Movatter.jp