Java和PHP等编程语言中的名称空间给我留下了深刻的印象。如此之多,以至于我什至以某种方式在哈布雷(Habré)上写了一篇有关它们的文章。从那时起已经过去了将近两年,但是在这段时间内,名称空间尚未出现在JavaScript中。“如果我自己在JS中创建名称空间,它们将是什么? ”-我想。切记-我的想法是,JavaScript需要什么名称空间。
介绍性
下面我所有的道理也适用于ES6模块和不接触其他格式(AMD,UMD,CommonJS的),只是因为我很感兴趣,看这里的JavaScript是怎么回事,而不是它是。另外,在我的实践中,我以某种方式非常接近GWT,此后,我对各种编译器(以及堆,缩小器和混淆器)产生了持久的拒绝。因此,香草JS,没有TS。好吧,我有这样的东西。
ES6模块
ES模块是一个单独的源文件,它明确定义了模块外部可用的元素:
export function fn() {/*...*/}
因此,首先,您需要以某种方式解决整个应用程序中的各个ES模块。
配套
现代应用程序由单独的程序包组成,它们之间的依赖关系由程序包管理器管理。程序包本身包含单独的模块。单个供应商可以在其各种应用程序中使用相同的程序包,将其代码分成多个程序包。模块的源代码通常放置在包内的单独目录中(例如./src
)。
软件包管理器将一个应用程序的所有软件包放入目录node_modules
。因此,从某个开发人员的程序包编译而来的nodejs应用程序的结构可能看起来像这样:
* node_modules
* @vendor
* package1
* src
* module1.js
* ...
* moduleN.js
* ...
* packageN
* src
* module1.mjs
* ...
* moduleN.mjs
模块寻址
在文件结构中,任何ES模块的源代码都是通过相对于应用程序根目录的文件路径来寻址的:
./node_modules/@vendor/package1/src/module1.js
...
./node_modules/@vendor/packageN/src/moduleN.mjs
作为nodejs应用程序模块加载器的一部分,一部分 ./node_modules/
消失了:
import SomeThing from '@vendor/package1/src/module1.js';
, , :
import SomeThing from './module1.js';
web- , web- node_modules
, web- ES-, , nodejs:
<script type="module">
import {fn} from './@vendor/package1/src/module1.js'
fn();
</script>
:
<script>
import('./@vendor/package1/src/module1.js').then((mod) => {
mod.fn();
});
</script>
, web' ./
. :
import {fn} from '@vendor/package1/src/module1.js'
:
Uncaught TypeError: Failed to resolve module specifier "@vendor/package1/src/module1.js". Relative references must start with either "/", "./", or "../".
, ES-:
( ):
./module1.js
(nodejs):
@vendor/package1/src/module1.js
(web):
./@vendor/package1/src/module1.js
./
nodejs-, ./
.
, JS- , , ( - ) , ( ).
" " ( , namespace'), ES- ( ), ES- , , nodejs, .
, ./
, , ( , ):
@vendor/package1/src/module1
- : ./src/
, ./lib/
, ./dist/
. - , , :
@vendor/package1/module1
, , .
Namespace mapping
, , . - web-, node_modules
web- ( - ./packages/
):
const node = {
'@vendor/package1': {path: '/.../node_modules/@vendor/package1/src', ext: 'js'},
'@vendor/packageN': {path: '/.../node_modules/@vendor/packageN/src', ext: 'mjs'},
};
const browser = {
'@vendor/package1': {path: 'https://.../packages/@vendor/package1/src', ext: 'js'},
'@vendor/packageN': {path: 'https://.../packages/@vendor/packageN/src', ext: 'mjs'},
};
Module loader
, '' ( @vendor/package1/module1
) ( - ) (node ):
@vendor/package1/module1 => /.../node_modules/@vendor/package1/src/module1.js // node
@vendor/packageN/moduleN => https://.../packages/@vendor/packageN/src/moduleN.mjs // browser
并使用它动态导入模块。当然,不需要映射包中的每个模块-您只需要映射包的根即可。输出是这样的:
const loader = new ModuleLoader();
loader.addNamespace('@vendor/package1', {path: '/.../node_modules/@vendor/package1/src', ext: 'js'});
// ...
loader.addNamespace('@vendor/packageN', {path: '/.../node_modules/@vendor/packageN/src', ext: 'js'});
const module1 = await loader.import('@vendor/package1/module1');
模块的导入必须是异步的,因为 内部将使用异步函数import()
。
概要
以这种优雅的方式,有可能从导入期间ES模块的物理寻址转移到其逻辑寻址(命名空间),并为nodejs应用程序和浏览器使用相同的模块。这里没有发明新的东西(在PHP中已经做了类似的事情,这个想法从那里被偷走了)。