Estou muito impressionado com os namespaces em linguagens de programação como Java e PHP. Tanto que até de alguma forma escrevi um artigo sobre eles no Habré. Quase dois anos se passaram desde então, mas os namespaces não apareceram em JavaScript durante esse tempo. “ E se eu fizesse namespaces em JS para mim, o que seriam? ” - pensei. Sob o corte - meus pensamentos, quais namespaces eu preciso em JavaScript.
Introdutório
Todo o meu raciocínio abaixo se aplica aos módulos ES6 e não toca em outros formatos (AMD, UMD, CommonJS) simplesmente porque estou interessado em ver para onde o JavaScript está indo, não para onde estava . Além disso, na minha prática, de alguma forma encontrei o GWT bastante próximo, após o que desenvolvi uma rejeição persistente de vários transpilers (bem como, para um heap, minificadores e ofuscadores). Portanto, JS vanilla e não TS. Bem, eu tenho esses itens.
Módulos ES6
Um módulo ES é um arquivo de origem separado que define explicitamente os elementos disponíveis fora do módulo:
export function fn() {/*...*/}
Portanto, para começar, você precisa abordar de alguma forma os módulos ES individuais em todo o aplicativo.
Pacotes
, . . (vendor) , . (, ./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
e usá-lo para importar módulos dinamicamente. Obviamente, não há necessidade de mapear todos os módulos do pacote - você só precisa mapear a raiz do pacote. A saída é mais ou menos assim:
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');
A importação de módulos deve ser assíncrona, uma vez que uma função assíncrona será usada internamente import()
.
Resumo
De forma tão elegante, seria possível alternar do endereçamento físico de módulos ES durante a importação para seu endereçamento lógico (namespaces) e usar os mesmos módulos para aplicativos nodejs e no navegador. Nada de novo foi inventado aqui ( algo semelhante já foi feito no PHP, de onde essa ideia foi roubada).