Module Specification
Module Format
This section describes how Cocos Creator determines the format of a module.
All the functionalities provided by the Cocos Creator engine are in the form of ESM modules, see engine modules.
Files in the project resources directory ending in .ts
. For example assets/scripts/foo.ts
.
For any other module formats, Cocos Creator chooses rules similar to Node.js to identify. Specifically, the following files will be considered in ESM format:
Files ending in
.mjs
.Files ending in
.js
and whose nearest parentpackage.json
file contains a top-level"type"
field with a value of"module"
.
The rest of the files will be treated as CommonJS module format, which includes
Files ending in
.cjs
.Files ending in
.js
and whose nearestpackage.json
file contains a top-level"type"
field with a value of"commonjs"
.Files ending in
.js
that do not fall under the above conditions.
Module Descriptors and Module Parsing
In an ESM module, interaction with the target module is done through standard import and export statements, e.g.
import { Foo } from '. /foo';
export { Bar } from '. /bar';
The string after the keyword from
in the import/export statement is called a module specifier. The module specifier can also appear as a parameter in the dynamic import expression import()
.
The module specifier is used to specify the target module, and the process of resolving the target module URL from the module specifier is called module resolution.
Cocos Creator supports three types of module specifiers:
relative specifier: a specifier like
'./foo'
,'../bar'
starting with'./'
and'../'
.absolute specifier: a specifier that specifies a URL. For example:
foo:/bar
.bare specifier: a specifier like
foo
orfoo/bar
that is neither a URL nor a relative specifier.
Relative Specifiers
Relative specifiers take the URL of the current module as the base URL and use the relative specifier as input to resolve the URL of the target module.
For example, for the module project path/assets/scripts/utils/foo
, './bar'
will be parsed as project path/assets/scripts/utils/bar
in the same directory; '../baz'
will be parsed as project path/assets/scripts/baz
in the upper directory.
Absolute Specifiers
The absolute specifier directly specifies the URL of the target module.
Cocos Creator currently only supports file protocol URLs, but since the file path specified in the file URL is an absolute path, it is rarely used.
Note: in Node.js, one way to access Node.js built-in modules is through
node:
protocol URLs, e.g.:node:fs
. Cocos Creator parses all requests for access to Node.js built-in modules asnode:
URL requests. For example,'fs'
inimport fs from 'fs'
will resolve tonode:fs
. However, Cocos Creator does not support Node.js built-in modules, which means that it does not support thenode:
protocol. Therefore, a loading error will occur. This error may be encountered when using modules in npm.
Bare Specifiers
Currently, Cocos Creator will apply Import Maps (experimental) and Node.js module parsing algorithm for bare specifiers.
This includes parsing of npm modules.
Conditional exports
In the Node.js module parsing algorithm, the conditional export feature of packages is used to map the subpaths in a package based on some conditions. Similar to Node.js, Cocos Creator implements built-in conditions import
and default
, but not conditions require
and node
.
Developers can specify additional conditions via the Export conditions option in the editor’s main menu Project -> Project Settings -> Scripting, which defaults to browser
. Multiple additional conditions can be specified using commas as separators, e.g. browser, bar
.
If the Export conditions option uses the default value browser
, when the package.json
of an npm package foo
contains the following configuration:
{
"exports": {
".": {
"browser": "./dist/browser-main.mjs",
"import": "./dist/main.mjs"
}
}
}
"foo"
will resolve to the module with path dist/browser-main.mjs
in the package.
The mapping configuration is done in Multiplayer Framework Colyseus for Node.js for the
browser
condition.
If the Export conditions option is empty, it means that no additional conditions are specified, and "foo"
in the above example will resolve to a module with path dist/main.mjs
in the package.
Suffixes and Directory Import
Cocos Creator’s requirements for module suffixes in module specifiers are more web-oriented — suffixes must be specified and Node.js-style directory import is not supported. However, for historical reasons and some existing restrictions, TypeScript modules do not allow suffixes and support Node.js-style directory import. Specifically:
When the target module file has the suffix .js
, .mjs
, the suffix must be specified in the module specifier: .js
, .mjs
.
import '. /foo.mjs'; // correct
import '. /foo'; // error: the specified module cannot be found
Node.js-style directory import is not supported:
import '. /foo/index.mjs'; // correct
import '. /foo'; // error: module cannot be found.
This suffix requirement applies to both relative and absolute specifiers along with the restriction on directory import. For requirements in bare specifiers please refer to the Node.js module parsing algorithm.
However, when the target module file has a suffix of .ts
, the suffix is not allowed to be specified in the module specifier:
import '. /foo'; // correct: parsed as the `foo.ts` module in the same directory
import '. /foo.ts'; // error: the specified module cannot be found
On the other hand, Node.js-style directory import is supported:
import '. /foo'; // correct: parsed as the `foo/index.ts` module
Notes:
- Cocos Creator supports the Web platform. Implementing complex module parsing algorithms like Node.js on the Web platform is expensive, and the client and server cannot try different suffixes and file paths with frequent communication between them.
- Even if such complex parsing could be done at the build stage with some post-processing tools, it would result in inconsistent algorithms for static import parsing (via
import
statements) and dynamic import parsing (viaimport()
expressions). Therefore, specify the full file path in the code for the choice of module parsing algorithm.- However, this cannot be restricted completely, since TypeScript currently doesn’t allow the suffix
.ts
to be specified in the specifier. And TypeScript does not yet support auto-completion of specific target suffixes. With these limitations, it’s hard to have it both ways, but we’re still watching to see if these conditions improve in the future.
The browser
Field is not Supported
Some npm packages have browser
fields documented in the manifest file package.json
, e.g.: JSZip. The browser
field is used to specify a module parsing method specific to the package when it is in a non-Node.js environment, which allows some Node.js-specific modules in the package to be replaced with modules that can be used in the Web. Although Cocos Creator does not support this field, if you have the ability to edit npm packages, Cocos Creator recommends using conditionalized export and subpath import instead of the browser
field.
Otherwise, the target library can be used in a non-npm way. For example, copying modules from the target library that are specifically made for non-Node.js environments into the project and importing them via relative paths.
CommonJS Module Parsing
In CommonJS modules, Cocos Creator applies the Node.js CommonJS module parsing algorithm.
Module Format Interaction
Cocos Creator allows importing CommonJS modules in ESM modules.
When importing a CommonJS module from an ESM module, the module.exports
object of the CommonJS module will be used as the default export for the ESM module:
import { log } from 'cc';
import { default as cjs } from 'cjs';
// Another way to write the above import statement:
import cjsSugar from 'cjs';
log(cjs);
log(cjs === cjsSugar);
// Print.
// <module.exports>
// true
The CommonJS module’s ECMAScript module namespace indicates that it is a namespace containing a default
export, where the default
export points to the value of module.exports
of the CommonJS module.
This module namespace foreign object can be observed by import * as m from 'cjs'
.
import * as m from 'cjs';
console.log(m);
// Print:
// [Module] { default: <module.exports> }
Cocos Creator ESM Parsing Algorithm Public Notice
The algorithm used by Cocos Creator to parse ESM module specifiers is given by the following CREATOR_ESM_RESOLVE
method. It returns the result of parsing the module specifier from the current URL.
The external algorithm is referenced in the parsing algorithm specification.
Parsing Algorithm Specification
CREATOR_ESM_RESOLVE(specifier, parentURL)
- Let
resolved
be the result ofESM_RESOLVE(specifier, parentURL)
. - If both
parentURL
andresolved
are under project assets directory, then- Let
extensionLessResolved
be the result ofTRY_EXTENSION_LESS_RESOLVE(resolved)
.- If
extensionLessResolved
is notundefined
, returnextensionLessResolved
.
- If
- Let
- Return
resolved
.
TRY_EXTENSION_LESS_RESOLVE(url)
- If the file at
url
exists, then- Return
url
.
- Return
- Let
baseName
be the portion after the last “/“ in pathname ofurl
, or whole pathname if it does not contain a “/“. - If
baseName
is empty, then- Return
undefined
.
- Return
- Let
resolved
be the result URL resolution of “./“ concatenated withbaseName
and.ts
, relative to parentURL.- If the file at
resolved
exists, then - Return
resolved
.
- If the file at
- Let
resolved
be the result URL resolution of “./“ concatenated withbaseName
and/index.ts
, relative to parentURL.- If the file at
resolved
exists, then - Return
resolved
.
- If the file at
- Return
undefined
.