IFC0219CL
Desarrollo web con Angular: aplicaciones innovadoras y escalables
TSC
Types
Funciones
Interfaces
Decoradores
Metadatos de componentes
Creación de un componente
Data binding
Anidado de componentes
Pasando datos al componente
Respondiendo a eventos
Ciclo de Vida
Estilos
Attribute Directives
Structural Directives
Procesado de recursos
Clientes RESTful
Introducción
Imports del Router
Configuración
Router Outlet
Router Links
Rutas con parámetros
Usando los parámetros del padre
Router Guards
Introducción
Formularios basados en plantillas
Crear Formulario
Crear componente del formulario
Revisar app.module.ts
Crear una plantilla HTML inicial
Añadir selector con directivas
Controlar estado y validez
Enviar formulario con ngSubmit
DatePipe
DecimalPipe
CurrencyPipe
LowerCasePipe y UpperCasePipe
JSONpipe
PercentPipe y SlicePipe
Introducción
Implementación
Obteniendo datos JSON
Por qué implementar un servicio
Verificación de tipo de respuesta
Leyendo la respuesta completa
Manejar errores
Obtener los detalles del error
RetryObservables y operadores
Petición de datos no JSON
Enviar datos al servidor
Petición POST
Petición DELETE
Petición PUT
Alternativa axios
Testing
Karma
Testing a componentes
Translate
Rotate
Scale
Animaciones
Introducción
¿Qué es Material Design?
Instalación
Componentes
Buttons
Navigation – Menú
Menú Anidado
Navigation – Sidenav
Navigation – Toolbar
Posicionar toolbar content
Controles de formulario
Layout – Grid
Layout – Card
Componente Mat-table
Conexión a base de datos
Alta, baja y modificación y consulta de datos
¿Qué es Angular?
Framework JS
SPA: Single Page Applications
TypeScript
Código fuente y código compilado
¿Angular 2? ¿8? ¿AngularJS?
Entorno de desarrollo
IDE: Visual Studio Code
Configurar:
Format on Paste y Format on Save
"editor.codeActionsOnSave": {
"source.fixAll.tslint": true
}
Node.js y npm
Extensión Augury para el navegador
Comandos básicos
Clonar un repositorio:
git clone URL
Descargar última versión del repositorio:
git pull origin master
Configuración proxy
git config --global http.proxy http://username:password@host:port
git config --global https.proxy http://username:password@host:port
npm
Instalar última versión después de instalar Node.js
(configurar proxy si es necesario): npm install -g npm
Repositorio de módulos distribuibles
Módulos globales y módulos locales
La carpeta node_modules
El archivo package.json:
Registro de dependencias
Dependencias de desarrollo y de producción
Versiones (SEMVER)
Comandos npm
Instalar un paquete globalmente:
npm install -g paquete
Instalar un paquete de producción:
npm install paquete
Instalar un paquete de desarrollo:
npm install paquete --save-dev
Instalar todas las dependencias:
npm install
Instalar las dependencias de producción:
npm install --production
Listar paquetes instalados:
npm list --depth=0 (locales)angular-cli
Instalación global:
npm install -g @angular/cli
Configuración proxy
npm config set proxy http://username:password@host:port
npm config set https-proxy http://username:password@host:port
JavaScript
Interpretado, compilado y ejecutado en el navegador
Cada navegador programa su propio motor de JS
Estandarización: ECMAScript
La versión ES6 o ES2015
Transpiladores: Babel, TypeScript
Organización del código JavaScript
Ejemplo de uso clásico de JS: utilizar un plugin de jQuery en nuestra web, o implementar alguna interacción con el usuario
Pocas líneas de código, todas en un mismo archivo
Organización del código JavaScript
(function($) {
$(document).ready(function() {
// Al hacer clic en una pestaña
$(".tab a").on("click", function(e) {
// Anulamos el link
e.preventDefault();
// Ocultamos todos los bloques de contenido
// y mostramos sólo el que se ha elegido
var content_id = $(this).attr("href");
$(".tab-content").hide();
$(content_id).show();
// Desmarcamos la pestaña que estuviera activa
// y marcamos la clicada como activa
$(".tab.active").removeClass("active");
$(this).closest(".tab").addClass("active");
})
})
})(jQuery);
<head>
<meta charset="UTF-8">
<title>Mi web</title>
<script src="vendor/jquery/jquery.min.js"></script>
<script src="js/tabs.js"></script>
</head>
24 líneas
Organización del código JavaScript
<head>
<meta charset="UTF-8">
<title>Mi web</title>
<script src="vendor/jquery/jquery.min.js"></script>
<script src="js/ui.js"></script>
</head>
(function($) {
$(document).ready(function() {
$(document).on('click', '.tab_new', offerGroupSwitchTabs);
$(document).on('click', '.navigationServices-li', jumpTo);
$('.load-more_new').on('click', loadMore).each(function() {
$(this).data('main', $(this).text());
});
})
var loadMore = function(e) {
e.preventDefault();
var $list = $(this).prev('.promos-list_new');
var button_text = $(this).data('main');
var button_alt_text = $(this).data('alt');
if ($(window).width() > 992) {
var hidden_classes = ".hidden";
var $hidden = $list.find(hidden_classes);
var n_show = 3;
} else if ($(window).width() > 768) {
var hidden_classes = ".hidden, .hidden-sm";
var $hidden = $list.find(hidden_classes);
var n_show = 2;
} else {
var hidden_classes = ".hidden, .hidden-sm, .hidden-xs";
var $hidden = $list.find(hidden_classes);
var n_show = 1;
}
if ($hidden.length == 0) {
$list.find(">li:nth-child(2)").addClass('hidden-xs');
$list.find(">li:nth-child(3)").addClass('hidden-xs hidden-sm');
$list.find(">li:nth-child(n+4)").addClass('hidden');
$(this).text(button_text);
} else {
$hidden.slice(0, n_show).each(function () {
$(this).removeClass(hidden_classes.replace(/[\.,\,]/g, ''));
});
if ($list.find(hidden_classes).length == 0) {
$(this).text(button_alt_text);
}
}
}
var offerGroupSwitchTabs = function(e) {
e.preventDefault();
var $info = $(this).closest('.info_new');
var target = $(this).attr('href');
var targetClass = target.replace('#', '.');
$info.find('.active').removeClass('active');
$info.find('[href=' + target + ']').addClass('active');
$(targetClass).addClass('active');
}
var jumpTo = function(e) {
e.preventDefault();
var target = $(this).find('a').attr('href');
var $target = $(target);
var targetTop = $target.offset().top;
$('html, body').animate({
scrollTop: targetTop - 200
}, {
duration: durationOnDistance(targetTop),
complete: function() {
window.location.hash = target;
}
});
}
var durationOnDistance = function(elementTop) {
var distance = elementTop - $(window).scrollTop();
var time = distance * 0.5;
return time;
}
})(jQuery)
75 líneas
Organización del código JavaScript
Programar toda la UI de una página
(function() {
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var jsonMode = parserConfig.json;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
return {
"if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
};
}();
var isOperatorChar = /[+\-*&%=<>!?|]/;
function chain(stream, state, f) {
state.tokenize = f;
return f(stream, state);
}
function nextUntilUnescaped(stream, end) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == end && !escaped)
return false;
escaped = !escaped && next == "\\";
}
return escaped;
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function jsTokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'")
return chain(stream, state, jsTokenString(ch));
else if (/[\[\]{}\(\),;\:\.]/.test(ch))
return ret(ch);
else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
}
else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
}
else if (ch == "/") {
if (stream.eat("*")) {
return chain(stream, state, jsTokenComment);
}
else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
}
else if (state.reAllowed) {
nextUntilUnescaped(stream, "/");
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
return ret("regexp", "string-2");
}
else {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
}
else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
}
else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function jsTokenString(quote) {
return function(stream, state) {
if (!nextUntilUnescaped(stream, quote))
state.tokenize = jsTokenBase;
return ret("string", "string");
};
}
function jsTokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = jsTokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
var state = cx.state;
if (state.context) {
cx.marked = "def";
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return;
state.localVars = {name: varname, next: state.localVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
if (!cx.state.context) cx.state.localVars = defaultVars;
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state;
state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
return function expecting(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
};
}
function statement(type) {
if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
poplex, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
if (type == "operator") return cont(expression);
if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperator(type, value) {
if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
if (type == "operator") return cont(expression);
if (type == ";") return;
if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
if (type == ".") return cont(property, maybeoperator);
if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperator, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type) {
if (type == "variable") cx.marked = "property";
if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") return cont(what, proceed);
if (type == end) return cont();
return cont(expect(end));
}
return function commaSeparated(type) {
if (type == end) return cont();
else return pass(what, proceed);
};
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function vardef1(type, value) {
if (type == "variable"){register(value); return cont(vardef2);}
return cont();
}
function vardef2(type, value) {
if (value == "=") return cont(expression, vardef2);
if (type == ",") return cont(vardef1);
}
function forspec1(type) {
if (type == "var") return cont(vardef1, forspec2);
if (type == ";") return pass(forspec2);
if (type == "variable") return cont(formaybein);
return pass(forspec2);
}
function formaybein(type, value) {
if (value == "in") return cont(expression);
return cont(maybeoperator, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in") return cont(expression);
return cont(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type, value) {
if (type == "variable") {register(value); return cont();}
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: jsTokenBase,
reAllowed: true,
kwAllowed: true,
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: null,
context: null,
indented: 0
};
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
state.kwAllowed = type != '.';
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize != jsTokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + 4;
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
else if (lexical.info == "switch" && !closing)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricChars: ":{}"
};
});
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var jsonMode = parserConfig.json;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
return {
"if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
};
}();
var isOperatorChar = /[+\-*&%=<>!?|]/;
function chain(stream, state, f) {
state.tokenize = f;
return f(stream, state);
}
function nextUntilUnescaped(stream, end) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == end && !escaped)
return false;
escaped = !escaped && next == "\\";
}
return escaped;
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function jsTokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'")
return chain(stream, state, jsTokenString(ch));
else if (/[\[\]{}\(\),;\:\.]/.test(ch))
return ret(ch);
else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
}
else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
}
else if (ch == "/") {
if (stream.eat("*")) {
return chain(stream, state, jsTokenComment);
}
else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
}
else if (state.reAllowed) {
nextUntilUnescaped(stream, "/");
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
return ret("regexp", "string-2");
}
else {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
}
else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
}
else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function jsTokenString(quote) {
return function(stream, state) {
if (!nextUntilUnescaped(stream, quote))
state.tokenize = jsTokenBase;
return ret("string", "string");
};
}
function jsTokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = jsTokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
var state = cx.state;
if (state.context) {
cx.marked = "def";
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return;
state.localVars = {name: varname, next: state.localVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
if (!cx.state.context) cx.state.localVars = defaultVars;
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state;
state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
return function expecting(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
};
}
function statement(type) {
if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
poplex, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
if (type == "operator") return cont(expression);
if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperator(type, value) {
if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
if (type == "operator") return cont(expression);
if (type == ";") return;
if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
if (type == ".") return cont(property, maybeoperator);
if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperator, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type) {
if (type == "variable") cx.marked = "property";
if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") return cont(what, proceed);
if (type == end) return cont();
return cont(expect(end));
}
return function commaSeparated(type) {
if (type == end) return cont();
else return pass(what, proceed);
};
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function vardef1(type, value) {
if (type == "variable"){register(value); return cont(vardef2);}
return cont();
}
function vardef2(type, value) {
if (value == "=") return cont(expression, vardef2);
if (type == ",") return cont(vardef1);
}
function forspec1(type) {
if (type == "var") return cont(vardef1, forspec2);
if (type == ";") return pass(forspec2);
if (type == "variable") return cont(formaybein);
return pass(forspec2);
}
function formaybein(type, value) {
if (value == "in") return cont(expression);
return cont(maybeoperator, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in") return cont(expression);
return cont(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type, value) {
if (type == "variable") {register(value); return cont();}
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: jsTokenBase,
reAllowed: true,
kwAllowed: true,
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: null,
context: null,
indented: 0
};
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
state.kwAllowed = type != '.';
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize != jsTokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + 4;
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
else if (lexical.info == "switch" && !closing)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricChars: ":{}"
};
});
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var jsonMode = parserConfig.json;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
return {
"if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
};
}();
var isOperatorChar = /[+\-*&%=<>!?|]/;
function chain(stream, state, f) {
state.tokenize = f;
return f(stream, state);
}
function nextUntilUnescaped(stream, end) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == end && !escaped)
return false;
escaped = !escaped && next == "\\";
}
return escaped;
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function jsTokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'")
return chain(stream, state, jsTokenString(ch));
else if (/[\[\]{}\(\),;\:\.]/.test(ch))
return ret(ch);
else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
}
else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
}
else if (ch == "/") {
if (stream.eat("*")) {
return chain(stream, state, jsTokenComment);
}
else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
}
else if (state.reAllowed) {
nextUntilUnescaped(stream, "/");
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
return ret("regexp", "string-2");
}
else {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
}
else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
}
else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function jsTokenString(quote) {
return function(stream, state) {
if (!nextUntilUnescaped(stream, quote))
state.tokenize = jsTokenBase;
return ret("string", "string");
};
}
function jsTokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = jsTokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
var state = cx.state;
if (state.context) {
cx.marked = "def";
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return;
state.localVars = {name: varname, next: state.localVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
if (!cx.state.context) cx.state.localVars = defaultVars;
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state;
state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
return function expecting(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
};
}
function statement(type) {
if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
poplex, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
if (type == "operator") return cont(expression);
if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperator(type, value) {
if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
if (type == "operator") return cont(expression);
if (type == ";") return;
if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
if (type == ".") return cont(property, maybeoperator);
if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperator, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type) {
if (type == "variable") cx.marked = "property";
if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") return cont(what, proceed);
if (type == end) return cont();
return cont(expect(end));
}
return function commaSeparated(type) {
if (type == end) return cont();
else return pass(what, proceed);
};
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function vardef1(type, value) {
if (type == "variable"){register(value); return cont(vardef2);}
return cont();
}
function vardef2(type, value) {
if (value == "=") return cont(expression, vardef2);
if (type == ",") return cont(vardef1);
}
function forspec1(type) {
if (type == "var") return cont(vardef1, forspec2);
if (type == ";") return pass(forspec2);
if (type == "variable") return cont(formaybein);
return pass(forspec2);
}
function formaybein(type, value) {
if (value == "in") return cont(expression);
return cont(maybeoperator, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in") return cont(expression);
return cont(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type, value) {
if (type == "variable") {register(value); return cont();}
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: jsTokenBase,
reAllowed: true,
kwAllowed: true,
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: null,
context: null,
indented: 0
};
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
state.kwAllowed = type != '.';
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize != jsTokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + 4;
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
else if (lexical.info == "switch" && !closing)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricChars: ":{}"
};
});
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var jsonMode = parserConfig.json;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
return {
"if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
};
}();
var isOperatorChar = /[+\-*&%=<>!?|]/;
function chain(stream, state, f) {
state.tokenize = f;
return f(stream, state);
}
function nextUntilUnescaped(stream, end) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == end && !escaped)
return false;
escaped = !escaped && next == "\\";
}
return escaped;
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function jsTokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'")
return chain(stream, state, jsTokenString(ch));
else if (/[\[\]{}\(\),;\:\.]/.test(ch))
return ret(ch);
else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
}
else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
}
else if (ch == "/") {
if (stream.eat("*")) {
return chain(stream, state, jsTokenComment);
}
else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
}
else if (state.reAllowed) {
nextUntilUnescaped(stream, "/");
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
return ret("regexp", "string-2");
}
else {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
}
else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
}
else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function jsTokenString(quote) {
return function(stream, state) {
if (!nextUntilUnescaped(stream, quote))
state.tokenize = jsTokenBase;
return ret("string", "string");
};
}
function jsTokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = jsTokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
var state = cx.state;
if (state.context) {
cx.marked = "def";
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return;
state.localVars = {name: varname, next: state.localVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
if (!cx.state.context) cx.state.localVars = defaultVars;
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state;
state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
return function expecting(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
};
}
function statement(type) {
if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
poplex, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
if (type == "operator") return cont(expression);
if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperator(type, value) {
if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
if (type == "operator") return cont(expression);
if (type == ";") return;
if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
if (type == ".") return cont(property, maybeoperator);
if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperator, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type) {
if (type == "variable") cx.marked = "property";
if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") return cont(what, proceed);
if (type == end) return cont();
return cont(expect(end));
}
return function commaSeparated(type) {
if (type == end) return cont();
else return pass(what, proceed);
};
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function vardef1(type, value) {
if (type == "variable"){register(value); return cont(vardef2);}
return cont();
}
function vardef2(type, value) {
if (value == "=") return cont(expression, vardef2);
if (type == ",") return cont(vardef1);
}
function forspec1(type) {
if (type == "var") return cont(vardef1, forspec2);
if (type == ";") return pass(forspec2);
if (type == "variable") return cont(formaybein);
return pass(forspec2);
}
function formaybein(type, value) {
if (value == "in") return cont(expression);
return cont(maybeoperator, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in") return cont(expression);
return cont(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type, value) {
if (type == "variable") {register(value); return cont();}
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: jsTokenBase,
reAllowed: true,
kwAllowed: true,
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: null,
context: null,
indented: 0
};
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
state.kwAllowed = type != '.';
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize != jsTokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + 4;
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
else if (lexical.info == "switch" && !closing)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricChars: ":{}"
};
});
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
})();
1445
líneas
Organización del código JavaScript
¿2000 líneas en un solo archivo?
Ventajas
Inconvenientes
Organización del código JavaScript
Optimización: dividir el código en varios archivos/módulos
<head>
<meta charset="UTF-8">
<title>Mi web</title>
<script src="vendor/jquery/jquery.min.js"></script>
<script src="js/modules/tabs.js"></script>
<script src="js/modules/banners.js"></script>
<script src="js/modules/lightbox.js"></script>
<script src="js/modules/scroll.js"></script>
<script src="js/modules/carousel.js"></script>
<script src="js/modules/slideshow.js"></script>
<script src="js/modules/gallery.js"></script>
<script src="js/modules/navigation.js"></script>
</head>
Organización del código JavaScript
Ventajas
Inconvenientes
<head>
<meta charset="UTF-8">
<title>Mi web</title>
<script src="vendor/jquery/jquery.min.js"></script>
<script src="js/modules/tabs.js"></script>
<script src="js/modules/banners.js"></script>
<script src="js/modules/lightbox.js"></script>
<script src="js/modules/scroll.js"></script>
<script src="js/modules/carousel.js"></script>
<script src="js/modules/slideshow.js"></script>
<script src="js/modules/gallery.js"></script>
<script src="js/modules/navigation.js"></script>
</head>
Organización del código JavaScript
Dependencias: es difícil asegurar el orden, y no es posible tener dependencias circulares
<head>
<meta charset="UTF-8">
<title>Mi web</title>
<script src="vendor/jquery/jquery.min.js"></script>
<script src="js/modules/tabs.js"></script>
<script src="js/modules/banners.js"></script>
<script src="js/modules/lightbox.js"></script>
<script src="js/modules/scroll.js"></script>
<script src="js/modules/carousel.js"></script>
<script src="js/modules/slideshow.js"></script>
<script src="js/modules/gallery.js"></script>
<script src="js/modules/navigation.js"></script>
</head>
Organización del código JavaScript: módulos
Module loaders: ellos gestionan las dependencias y cargan los módulos (RequireJS, SystemJS)
Ventajas
Inconvenientes
Organización del código JavaScript: módulos
Module bundlers: además de lo anterior, generan un solo código encadenado y minificado (Browserify, webpack, Parcel)
Ventajas
Organización del código JavaScript: módulos
¿Puedo escribir mis módulos como yo quiera? ¿hay un estándar?
AMD: Asynchronous Module Definition
CommonJS
UMD: Universal Module Definition
ES6 Modules
define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
return {
hello: function() {
console.log('hello');
},
goodbye: function() {
console.log('goodbye');
}
};
});
var myModuleA = require('myModuleA');
function myModuleB() {
this.hello = function() {
return 'hello!';
}
this.goodbye = function() {
return 'goodbye!';
}
}
module.exports = myModuleB;
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['myModule', 'myOtherModule'], factory);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('myModule'), require('myOtherModule'));
} else {
// Browser globals (Note: root is window)
root.returnExports = factory(root.myModule, root.myOtherModule);
}
}(this, function (myModule, myOtherModule) {
// Methods
function notHelloOrGoodbye(){}; // A private method
function hello(){}; // A public method because it's returned (see below)
function goodbye(){}; // A public method because it's returned (see below)
// Exposed public methods
return {
hello: hello,
goodbye: goodbye
}
}));
import { method1 } from './moduleA.js';
method1("hello");
export let method2 = function() {
console.log("Method 2");
}
Organización del código JavaScript: módulos
¿AMD, CommonJS, UMD, ES6?
Compatibilidad de los módulos ES6 en navegadores
¡Webpack!
TypeScript usa la sintaxis ES6
TS -> ES5 -> webpack -> bundle -> browser =
Angular CLI
ES6
let y const
let a = 3;
let a = 10; // Error
var a = 12; // Error
const b = 10;
b = 3; // Error
const obj = {
x: 10,
y: 12
}
obj.x = 15; // OK
obj = { // Error
x: 15,
y: 12
}
ES6
let y const
Template literals
let nombre = "Antonio";
let cuadrado = function(x) {
return x * x;
}
let n = Math.floor(Math.random() * 10);
let saludo1 = "Hola, " + nombre + ". El cuadrado de " + n + " es " + cuadrado(n) + ".";
let saludo2 = `Hola, ${nombre}. El cuadrado de ${n} es ${cuadrado(n)}.`;
ES6
let y const
Template literals
for ... of
let nombres = ["Patricia", "Zacarías", "Miguel", "Maite"];
for (let i in nombres) {
console.log(nombres[i]);
}
for (let nombre of nombres) {
console.log(nombre);
}
let obj = {
x: 3,
y: 4
}
for (let i in obj) {
console.log(obj[i]);
}
let nombre = "Antonio Jesús";
for (let c of nombre) {
console.log(c);
}
ES6
let y const
Template literals
for ... of
Funciones
Parámetros por defecto
function potencia(x, y = 2) {
return Math.pow(x, y);
}
console.log(`10 elevado a 8 es ${potencia(10, 8)}`);
console.log(`El cuadrado de 5 es ${potencia(5)}`);
ES6
let y const
Template literals
for ... of
Funciones
Parámetros por defecto
Función arrow:
(parámetros) => expresión_devuelta;const potencia = function (x, y = 2) {
return Math.pow(x, y);
}
const potencia = (x, y = 2) => Math.pow(x, y);
setTimeout(() => console.log("pausa"), 2000);
ES6
Operador spread
Parámetros en funciones
Enviar varios parámetros a partir de un array
push y unshift
Intercalar un array dentro de otro
Copiar un array en otro
Copiar un objeto en otro
// function(a, b, c)
let nums = [1, 3, 6];
function sumar(a, b, c) {
console.log(a + b + c);
}
sumar(...nums);
// function(n parámetros)
let a = 3;
let b = 7;
let c = 8;
function sumar(...nums) {
let suma = 0;
for (n of nums) {
suma += n;
}
console.log("La suma es " + suma);
}
sumar(a, b, c);
// push y unshift
let nums1 = [1, 3, 6];
let nums2 = [0, 52, 15, 9];
nums1.push(...nums2);
console.log(nums1);
nums1.unshift(...nums2);
console.log(nums1);
// meter un array en medio de otra
let nums1 = [1, 3, 6];
let nums2 = [0, 52, 15, 9];
nums1.splice(1, 0, ...nums2);
console.log(nums1);
// copiar un array
let nums1 = [1, 3, 6];
let nums2 = [...nums1];
// mergear un objeto con defaults (ES2018)
let defaults = {
ancho: 100,
alto: 200,
color: "rojo"
}
let o1 = {
ancho: 200,
grosor: 10000
}
o1 = { ...defaults, ...o1 };
// convertir un NodeList a un array
let lis = [...document.getElementsByTagName("li")];
ES6
Destructuring
Asignar desde un array
Asignar desde un objeto
Renombrar variables
Valores por defecto
Nested destructuring
Intercambiar variables
Argumentos en las funciones
function medidasMueble() {
// ...
return [100, 70, 20];
}
let [ancho, alto, profundo] = medidasMueble();
console.log(ancho, alto, profundo);
// 100, 70, 20
function getRGB(colorHex) {
// ...
return {
alias: 'deeppink',
red: 255,
green: 20,
blue: 147,
alpha: 0.8
}
}
let { red, green, blue } = getRGB("#ff1493");
console.log(red, green, blue);
// 255, 20, 147
let notas = {
mat: 8,
fis: 6,
dib: 5,
tec: 6
}
let { mat: matematicas, fis: fisica, dib: dibujo, tec: tecnologia } = notas;
console.log(matematicas, fisica, dibujo, tecnologia);
// 8, 6, 5, 6
let persona = {
nombre: "Luis",
edad: 23
}
let { nombre, edad, estado = "soltero" } = persona;
console.log(nombre, edad, estado);
// Luis, 23, soltero
let personas = [{
nombre: "Luis",
apellido: "Herrera",
edad: 23
},
{
nombre: "Marta",
apellido: "Nieto",
edad: 29
}];
for (let {nombre, edad} of personas) {
console.log(`Me llamo ${nombre} y tengo ${edad} años`);
}
// Me llamo Luis y tengo 23 años
// Me llamo Marta y tengo 29 años
let persona = {
nombre: "Francisco José",
apellidos: "González Primo",
edad: 40,
residencia: {
ciudad: "Cornellà de Llobregat",
provincia: "Barcelona",
direccion: {
calle: "Londres",
numero: 67,
piso: ["2º", "B"]
}
}
}
// Para extraer una propiedad
let { residencia } = persona;
// Para extraer varias propiedades
let { nombre, apellidos, residencia: { ciudad, direccion: { calle, numero, piso: [ planta, puerta ] }}} = persona;
console.log(nombre, apellidos, ciudad, calle, numero, planta, puerta);
// Francisco José González Primo Cornellà de Llobregat Londres 67 2º B
let a = 10;
let b = 20;
[a, b] = [b, a];
console.log(a, b);
// 20, 10
function area({radio = 0, base = 0, altura = 0, tipo = 'circulo'} = {}) {
console.log(radio, base, altura, tipo);
}
area({ tipo: 'rectangulo', base: 10, altura: 20 });
// 0, 10, 20, "rectangulo"
area();
// 0, 0, 0, "circulo"
ES6
Clases
Propiedades y métodos
class A {
constructor(z) {
this.x = 3;
this.y = 10;
this.z = z;
}
suma() {
return this.x + this.y + this.z;
}
}
let a = new A(20);
console.log(a.suma());
ES6
Clases
Propiedades y métodos
Getters y setters
class A {
constructor(z) {
this.x = 3;
this.y = 10;
this.z = z;
}
suma() {
return this.x + this.y + this.z;
}
set zeta(z) {
this.z = z * 2;
}
get zeta() {
return this.z / 2;
}
}
let a = new A(20);
a.zeta = 15;
console.log(a.zeta);
ES6
Clases
Propiedades y métodos
Getters y setters
Métodos estáticos
class A {
constructor(z) {
this.x = 3;
this.y = 10;
this.z = z;
}
static getPI() {
return 3.14159;
}
suma() {
return this.x + this.y + this.z;
}
set zeta(z) {
this.z = z * 2;
}
get zeta() {
return this.z / 2;
}
}
let a = new A(20);
a.zeta = 15;
console.log(a.zeta);
console.log(A.getPI());
ES6
Clases
Propiedades y métodos
Getters y setters
Métodos estáticos
Herencia con extends y super()
class A {
constructor(z) {
this.x = 3;
this.y = 10;
this.z = z;
}
static getPI() {
return 3.14159;
}
suma() {
return this.x + this.y + this.z;
}
set zeta(z) {
this.z = z * 2;
}
get zeta() {
return this.z / 2;
}
}
class B extends A {
constructor() {
super(100);
this.x = 20;
}
suma() {
return this.x + this.z;
}
resta() {
return this.x - this.z;
}
}
let b = new B();
console.log(b.suma());
console.log(b.resta());
ES6
Módulos
import
import { literal } from 'ruta_modulo';ES6
Módulos
import dinámicos
import('ruta_modulo').then(
modulo => ...
);
Programación funcional con arrays
Métodos:
map
let nombres = ["juan", "luisa", "amparo", "arturo"];
nombres = nombres.map(nombre => nombre.toUpperCase());
console.log(nombres);
Programación funcional con arrays
Métodos:
map
filter
let personas = [
{
nombre: "juan",
edad: 15
},
{
nombre: "luisa",
edad: 35
},
{
nombre: "amparo",
edad: 17
},
{
nombre: "arturo",
edad: 32
}
];
let mayoresEdad = personas.filter(persona => persona.edad >= 18);
console.log(mayoresEdad);
Programación funcional con arrays
Métodos:
map
filter
reduce
let nums = [2, 4, 10, 15, 12];
let suma = nums.reduce((x, y) => x + y);
let objs = [
{
x: 3,
y: 2
},
{
x: 8,
y: 10
},
{
x: 10,
y: 15
}
]
let sumaX = objs.reduce((x, o2) => x + o2.x, 0); // Método 1
let sumaX = objs.map(o => o.x).reduce((x, y) => x + y); // Método 2
Programación funcional con arrays
Métodos:
map
filter
reduce
find
let notas = [
{
nombre: "juan",
nota: 6
},
{
nombre: "luisa",
nota: 8
},
{
nombre: "amparo",
nota: 4
},
{
nombre: "arturo",
nota: 3
}
];
let notaArturo = notas.find(n => n.nombre === "arturo");
Programación funcional con arrays
Métodos:
map
filter
reduce
find
Encadenamiento
let notas = [
{
nombre: "juan",
nota: 6
},
{
nombre: "luisa",
nota: 8
},
{
nombre: "amparo",
nota: 4
},
{
nombre: "arturo",
nota: 3
}
];
let notasAprobados = notas.filter(n => n.nota >= 5).map(n => n.nota);
console.log(notasAprobados);
TypeScript
Superconjunto de JavaScript
Transpila a ES5 (o a otra versión)
TypeScript
Superconjunto de JavaScript
Transpila a ES5 (o a otra versión)
Tipado
Errores en tiempo de compilación
TypeScript
Superconjunto de JavaScript
Transpila a ES5 (o a otra versión)
Tipado
Errores en tiempo de compilación
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "./public/js/",
}
}
tsconfig.json
TypeScript - Tipos
Tipos básicos:
number
string
boolean
Array
any
void
let peso: number;
peso = 89.5;
let saludo: string;
saludo = 'Vais a petarlo con TypeScript';
let esVerano: boolean;
esVerano = false;
let nums: Array<number>;
nums = [10, 55, -3, 4.14];
let nombres: string[];
nombres = ['Juan', 'Paqui', 'Lorenzo', 'Alicia'];
let cosas: any[];
cosas = [10, 'Teruel', -5, true, [0, -10, 15], false];
function imprimeSaludo(s: string): void {
console.log(s);
}
imprimeSaludo('Buenas tardes');
TypeScript - Tipos
Tipos básicos:
number
string
boolean
Array
any
void
Enum
enum FormasPago {
TPV,
PayPal,
transferencia
}
let pago: FormasPago;
pago = FormasPago.PayPal;
procesarPago(pago);
function procesarPago(formaPago: FormasPago): void {
switch (formaPago) {
case FormasPago.TPV:
// ...
break;
case FormasPago.PayPal:
// ...
break;
case FormasPago.transferencia:
// ...
break;
}
}
TypeScript - Tipos
Tipos básicos:
number
string
boolean
Array
any
void
Enum
Union types
let numeros: Array<number | string>;
numeros = ['3', 6, '15.8', 0];
function procesar(a: string | number): void {
if (typeof a === 'string') {
console.log(a.toUpperCase());
} else {
console.log(a.toFixed(2));
}
}
TypeScript - Tipos
Tipos básicos:
number
string
boolean
Array
any
void
Enum
Union types
Genéricos
function verDoble<T>(elem: T): T[] {
let elemDoble: T[] = [elem, elem];
return elemDoble;
}
TypeScript - Tipos
Tipos básicos:
number
string
boolean
Array
any
void
Enum
Union types
Genéricos
Type assertion
const inputText = <HTMLInputElement>document.getElementById("nombre");
inputText.select();
TypeScript - Funciones
Sin flexibilidad en el número de parámetros
function sumar(a: number, b: number): number {
return a + b;
}
sumar(); // Error
sumar(3); // Error
sumar(10, 2); // OK
sumar(4, -3, 10, 8) // Error
TypeScript - Funciones
Sin flexibilidad en el número de parámetros
Parámetros opcionales
function sumar(a: number, b: number, c?: number): number {
if (c) {
return a + b + c;
} else {
return a + b;
}
}
sumar(10, 2);
sumar(10, 2, 15);
TypeScript - Funciones
Sin flexibilidad en el número de parámetros
Parámetros opcionales
Sobrecarga
function nChars(a: number): string;
function nChars(a: string): number;
function nChars(a: string | number): number | string {
if (typeof a === 'number') {
return '¡Es un número!';
} else if (typeof a === 'string') {
return a.length;
}
}
type RGB = [number, number, number];
function convierteColor(color: string): RGB;
function convierteColor(color: RGB): string;
function convierteColor(color: string | RGB): string | RGB {
if (typeof color === 'string') {
return [0, 128, 0];
} else {
return '#006600';
}
}
const colorRGB = convierteColor('#006600');
const colorHEX = convierteColor([0, 128, 0]);
TypeScript - Funciones
Sin flexibilidad en el número de parámetros
Parámetros opcionales
Sobrecarga
Function types
function transformaNumero(x: number, callback: (n: number) => void) {
callback(x);
}
let a = 10;
transformaNumero(a, m => console.log(m * 2));
TypeScript - Módulos
import { literal } from 'ruta_modulo';
import literal from 'ruta_modulo';TypeScript - Módulos
Módulos
import dinámicos
import('ruta_modulo').then(
modulo => ...
);
TypeScript - Módulos
TypeScript - Clases
class Factura {
numero: string;
base: number;
tipoIva: number;
constructor(numero: string, base: number, tipoIva: number = 21) {
this.numero = numero;
this.base = base;
this.tipoIva = tipoIva;
}
}
TypeScript - Clases
class Factura {
private static caracteresSerie = 2;
public num: string;
public serie: string;
public base: number;
private readonly intTipoIva: number;
constructor(base: number, tipoIva: number = 21) {
this.base = base;
this.intTipoIva = tipoIva;
}
get numero(): string {
return this.serie + this.num;
}
set numero(n: string) {
this.serie = n.slice(0, Factura.caracteresSerie - 1);
this.num = n.slice(Factura.caracteresSerie);
}
}
let f = new Factura(100);
f.numero = 'AB600';
console.log(f.numero);
TypeScript - Clases
abstract class Vehiculo {
public manual: boolean;
constructor(public ruedas: number, public motor: Motor) {
this.manual = this.motor === Motor.ninguno;
}
public abstract arrancar(): void;
}
class Bici extends Vehiculo {
public arrancar(): void {
console.log('Me pongo de pie y pedaleo');
}
}
TypeScript - Clases
interface Arrancable {
arrancar(): void;
apagar(): void;
}
abstract class Vehiculo {
public manual: boolean;
constructor(public ruedas: number, public motor: Motor) {
this.manual = this.motor === Motor.ninguno;
}
}
class Bici extends Vehiculo implements Arrancable {
public arrancar(): void {
console.log('Me pongo de pie y pedaleo');
}
public apagar(): void {
console.log('Me bajo de la bici');
}
}
interface Cliente {
id: number;
login: string;
nombre: string;
tipo: TiposCliente;
fechaAlta: Date;
}
function getClientes(): Cliente[] {
let clientes: Cliente[] = conectaBD('clientes');
return clientes;
}
TypeScript - Decoradores
@
Asignar metadatos
Muy utilizados en Angular
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-factura',
templateUrl: './factura.component.html',
styleUrls: ['./factura.component.css']
})
export class FacturaComponent {
@Input()
facturaId: number;
}
Primeros pasos
Generar la app:
ng new <nombre-app> --prefix <prefijo>
Ejecutar la app y verla en el navegador:
ng serve -o
Entornos dev y prod
Archivos de configuración
Módulos: contenedores lógicos
Componentes
Divisiones de la UI
Tienen clase y template
Creando piezas:
ng generate <tipo-pieza> <nombre-pieza> [params]
Esqueleto de una pieza en Angular
clase =>
=> clase exportada =>
=> clase decorada =>
=> dependencias
Examinando un módulo
Metadata
declarations:
- componentes, directivas y pipes del módulo
imports:
- otros módulos cuyos componentes, directivas o pipes exportados queremos usar
exports:
- componentes, directivas o pipes que exponemos para que los usen otros módulos
providers:
- objetos inyectables que están disponibles para el inyector del módulo
bootstrap:
- componente(s) inicial de la app
Examinando un componente
Metadata
selector:
Selector CSS que se corresponde con una etiqueta HTML
template / templateUrl:
String con el HTML / fichero con el HTML
styles / styleUrls:
Strings con los estilos / ficheros con los estilos
ngOnInit
Componente inicializado (con su vista renderizada y sus valores cargados), se usa para los procesos iniciales (no usar el constructor).
ngOnDestroy
Examinando un template
Custom elements
Data binding
Interpolation
Property binding
Class & style binding
Event binding
Two-way binding
Examinando un template
Directivas de atributo
ngClass
ngStyle
Directivas estructurales
ngIf
ngFor
ngSwitch
Pipes
@Pipe, PipeTransform
Directivas propias
De atributo (ElementRef.nativeElement)
Estructurales (ViewContainerRef y TemplateRef)
Servicios
Dependency Injection:
Proveedores y jerarquía de inyectores
Injectable()
Singleton: tiene como ámbito su inyector y todos sus inyectores hijos.
Formularios
[(ngModel)]: Two-way binding
ngForm, ngModel y ngSubmit
Variables de template con #
Validaciones: los diferentes estados
Resetear los estados
Template driven y Reactive forms
Conexiones con el servidor
Asincronía
Observables
Suscripciones
API REST
El módulo HttpClientModule
Módulo HttpClientModule y servicio HttpClient
Métodos del servicio HttpClient:
get(), post(), put(), patch(), delete()
Obteniendo la respuesta completa:
{ observe: 'response' }
Interceptors y autentificación
Navegación por la app
El router
El RouterOutlet
Las rutas
Navegación por la app
El router
El RouterOutlet
Las rutas
Página por defecto
404
Parámetros: los observables paramMap y data de ActivatedRoute
Guards y resolvers
Links de navegación: routerLink y routerLinkActive
router.navigate()
Lazy loading
El servicio Title
Despliegue a producción
Pruebas con ng build
ng build:
--prod: código optimizado para producción
--base-href=: cambia el directorio base
--sourcemaps: genera los source maps
Entornos propios
Links