Como importar módulos usando caminhos não relativos em TypeScript e como compilar o resultado para JavaScript com a correcta resolução de caminhos.
Quando usamos caminhos relativos num projecto TypeScript de um certo tamanho e complexidade, as importações começam a ficar assim:
import { SpecialCollection } from "../../special";
import { LoginComponent } from "../login";
import { TextUtils } from ".../../utils/text";
import { Router } from "../../../core/router";
Torna-se incómodo importar módulos e fica mais difícil modificar a estrutura ou o código.
A solução apresentada neste artigo resume-se ao seguinte:
- Configurar o TypeScript para usar caminhos não relativos.
- Usar TypeScript para compilar o código para ES6.
- Configurar o Babel para transformar os caminhos.
- Usar Babel para compilar o código para ES5.
- Combinar o resultado para o browser usando o Browserify.
Definir caminhos personalizados em TypeScript
O compilador de TypeScript pode ser configurado com as seguintes propriedades:
baseUrl
: o caminho base para os módulos.paths
: mapeamento de caminhos relativos abaseUrl
.
Podem ler mais sobre estas e outras opções em Module Resolution.
Vamos considerar a seguinte estrutura de um projecto:
src
│ main.ts
│
└───core
│ │ router.ts
│
└───components
│ special.ts
│
└───auth
│ │ login.ts
│
└───utils
│ text.ts
Podemos configurar o tsconfig.json
assim:
{
"files": [
"src/main.ts"
],
"compilerOptions": {
"target": "es6",
"baseUrl": "./src",
"paths": {
"@core/*": ["core/*"],
"@components/*": ["components/*"],
"@auth/*": ["components/auth/*"],
"@utils/*": ["components/utils/*"]
}
}
}
As importações podem ser agora alteradas da seguinte forma:
import { SpecialCollection } from "@components/special";
import { LoginComponent } from "@auth/login";
import { TextUtils } from "@utils/text";
import { Router } from "@core/router";
O compilador de TypeScript não muda os caminhos no resultado. Estas opções servem apenas para informar o compilador sobre onde encontrar os módulos, mas não transforma os caminhos no resultado final. Se correrem o código compilado no browser este não saberá onde encontrar os módulos.
Definir caminhos personalizados em Babel
Dissemos ao TypeScript para compilar o código para ES6. Vamos usar Babel para transformar os caminhos e compilar para ES5.
Para transformar os caminhos em Babel vamos usar babel-plugin-module-resolver.
O nosso .babelrc
poderá ser assim:
{
"presets": ["env"],
"plugins": [
["module-resolver", {
"root": ["./src"],
"alias": {
"@core": "./src/core",
"@components": "./src/components",
"@auth": "./src/components/auth",
"@utils": "./src/components/utils"
}
}]
]
}
root
e alias
são parecidos a baseUrl
e paths
do TypeScript mas notem as diferenças.
Construir o resultado final
Agora podemos criar um script gulp para fazer a compilação.
Criem o projecto:
npm init
Instalem os pacotes necessários:
npm install --save-dev babel-plugin-module-resolver babel-preset-env babelify browserify gulp tsify typescript vinyl-source-stream watchify
Exemplo de um gulpfile.js
:
var gulp = require("gulp");
var browserify = require("browserify");
var source = require("vinyl-source-stream");
var tsify = require("tsify");
var watchify = require("watchify");
var babelify = require("babelify");
var watchedBrowserify =
watchify(
browserify({
"basedir": ".",
"entries": "src/main.ts"
"debug": true,
"cache": {},
"packageCache": {}
})
.plugin(tsify)
.transform(babelify, { "extensions": [".js", ".ts"] })
);
function build() {
return watchedBrowserify
.bundle()
.on('error', function(error){
console.log(error.message);
this.emit('end');
})
.pipe(source("bundle.js"))
.pipe(gulp.dest("dist"));
}
gulp.task("build", [], build);
watchedBrowserify.on("update", build);
watchedBrowserify.on('log', function (msg) {
console.log(msg);
});
tsconfig.json
e .babelrc
serão lidos automaticamente pelos plugins.
Em resumo, os ficheiros fonte em TypeScript vão ser compilados usando tsify, depois transformados usando babelify onde os caminhos finais serão definidos e finalmente será tudo agrupado para o browser com Browserify.