Complément Front-end / Back-end
https://slides.com/benjichaz/but2
24 heures
De fin février à mi-avril
Évaluation par un projet + DS
iut@chazelle.pro
06 51 23 51 39
Rappels JavaScript
Frontend, Vue.js
Backend, Node.js/Express
API REST, Auth, Database
Ingénieur en développement
spécialisé front-end
Chez Ubitransport
Éditeur de logiciel pour le monde du transport de voyageur
Langage pour le web (et +), interprété, orienté objet,
dynamique, typage faible,
riche en API
Standard et écosystème en évolution
JavaScript != Java
1995 - Créer par Brenden Eich chez Netscape/Mozilla
1997 - Standardiser comme ECMAScript (ES1)
2015 - Version majeur ES6 (ES2015)
Depuis 2015, une nouvelle version chaque année
ES6
OLD
Apparu avec ECMAScript 6
Usage déprécié
// index.html
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
</body>
</html>
// script.js
window.addEventListener("load", () => {
let body = document.body;
body.textContent = "Hello world";
});
// Commentaire monoligne
/*
Commentaire
multi
ligne
*/
let x = 42; // 42
x = 1337;
// 1337
let y; // undefined
const x = 42; // 42
x = 1337;
// Uncaught TypeError: invalid assignment to const 'x'
ES6
if(true) {
let x = 42;
x; // 42
}
x; // undefined
ES6
OLD
function f() {
if(true) {
var x = 42;
x; // 42
}
x; // 42
}
x; // undefined
Un nom de variable correct peut contenir :
- lettres minuscules (a-z)
- lettres majuscules (A-Z)
- chiffres (0-9)
- caractères dollar ($)
- caractères underscore (_)
Un nom de variable ne commence jamais par un chiffre.
good
GOOD
$Good
_good_
gôöd
b-a-d
-Bad-
84D
Types objets (Object) |
---|
Object |
Array |
Set |
Map |
RegExp |
Function |
... |
Types primitifs |
---|
string |
number |
boolean |
null |
undefined |
symbol |
BigInt |
ES6
ES11
"hello"
1337
true
null
undefined
Symbol(42)
BigInt('1337')
[42, "Hello", true]
{ foo: "bar" }
function f() {}
let x = 1337;
x; // 1337
typeof x; // "number"
x = new Date();
typeof x; // "object"
// Le type peut varier pendant l'exécution
x = new Date();
typeof x; // "object"
x instanceof Date; // true
x instanceof RegExp; // false
// Arithmétique
(2 + 13 - 7) * 2 / 4 % 5
// Affectation
let x = 2 // 2
x += 13 // 15
x -= 7 // 8
x *= 2 // 16
x /= 4 // 4
x %= 5 // 4
// In/Décrémentation
let x = 2 // 2
x++ // 2
++x // 4
x-- // 4
--x // 2
// Logique
true && false // false
true || false // true
true || false && false // <=> true || (false && false)
// true
let z = true; !z // false
// (Non) Egalité
23 == 23 // true
23 == "23" // true
23 === "23" // false
0 == false // true
0 === false // false
23 != 23 // false
23 != "23" // false
23 !== "23" // true
// Inégalité
42 < 32; // false
42 <= 42; // true
42 > 6; // true
17 >= 42; // false
let number = getNumber()
if (number > 0) {
console.log("Positive")
} else if (number < 0) {
console.log("Negative")
} else if (number === 42) {
console.log("Life answer")
} else {
console.log("Zero")
}
let color = getColor()
switch (color) {
case "RED":
code = "#f00";
break;
case "GREEN":
code = "#0f0";
break;
default:
code = "#000";
}
let i = 0
while(i < 5) {
doSomething(i)
i++
}
for(let i = 0; i < 5; i++) {
doSomething(i)
}
// condition ? siConditionVraie : siConditionFausse;
let x = age >= 18 ? "Majeur" : "Mineur";
https://interactly.glitch.me/
let a = 42;
let b = 3.;
let c = 3.14;
let d = -20;
let e = 10e5;
let f = 0x20; // 32
let g = Infinity;
let h = -Infinity;
let i = NaN; // Not a Number
let j = +0;
let h = -0;
42 / +0; // Infinity
42 / -0; // -Infinity
// Float precision IEEE 754
0.1 + 0.2; // 0.3000000000004
// De Number to String
const x = 42;
// Lisible
String(x); // "42"
// Performant
x + ""; // "42"
// De String à Number
const x = "42";
// Lisible
parseInt(x, 10); // 42
parseFloat(x); // 42
// Performant
+x; // 42
Math.round(100.7); // 101
Math.ceil(12.3); // 13
Math.floor(12.7); // 12
Math.random(); // 0.6493849611386405
Math.PI; // 3.141592653589793
Math.cos(0); // 1
Math.min(2, 6); // 2
Math.max(2, 6); // 6
const x = "I'm your \"father\"\n";
const y = 'I\'m your "father"\n';
const z = `I'm your "father"\n`;
ES6
const x = "foo";
x[0]; // "f"
x[1]; // "o"
x[2]; // "o"
const x = "foo";
x.length; // 3
const x = "Veni";
const y = "vidi";
const z = "vici";
// Performant
x + " " + y + " " + z; // "Veni vidi vici"
// Lisible
`${x} ${y} ${z}`; // "Veni vidi vici"
ES6
const x = " JavaScript\n";
x.substring(4, 9); // "vaScr";
x.toUpperCase(); // " JAVASCRIPT\n"
x.toLowerCase(); // " javascript\n"
x.replace("a", "@"); // " J@vaScript\n";
x.replace("a", /@/g); // " J@v@Script\n";
x.indexOf("Java"); // 2
x.indexOf("Vue"); // -1
x.trim(); // "JavaScript";
ES6
let str = "Hello, world!"
str.startsWith("Hello") // true
str.endsWith("world!") // false
str.includes("world") // true
str = "abc"
str.repeat(3) // "abcabcabc"
str = "123"
str.padStart(5, "0") // "00123"
str.padEnd(5, "0") // "12300"
ES6
const x = /^\d{4} \d{4} \d{4} \d{4}$/g; // Compact
const y = new RegExp("^\d{4} \d{4} \d{4} \d{4}$", "g"); // Performant
const x = "I love JavaScript, Vue.js, Vuex and Vue-router";
x.match(/[A-Z][^, ]+/g); // ["JavaScript", "Vue.js", "Vuex", "Vue-router"]
x.match(/\d/g); // null
https://github.com/benjaminchazelle/The-Quest
Projet Glitch: Digital transcriptor
https://glitch.com/edit/#!/iut-digital-transcriptor
let x = [1, 2, 3];
let y = ["Hello", true, 42, {foo: "bar"}];
let z = [[1,2,3], [4,5,6]];
let x = [1, 2, 3];
x[0]; // 1
let y = ["Hello", true, 42, {foo: "bar"}];
x[3]; // {foo: "bar"}
let z = [[1,2,3], [4,5,6]];
x[1]; // [4,5,6]
x[1][2]; // 6
let x = [1, 2, 3];
x[0] = 42;
x; // [42, 2, 3]
let x = [1, 2, 3];
x.length; // 3
let digits = [1, 2, 3];
for(let i = 0; i < digits.length; i++) {
// digits[i];
}
for(let digit of digits) {
// digit;
}
// Performance
digits.forEach(digit => {
// digit;
});
ES6
ES6
let x = [12, 3, 1, 150];
x.push(17); // Ajoute 17 en fin de tableau
x; // [12, 3, 1, 150, 17]
x.pop(); // 17 - Supprime et retourne le dernier élement
x; // [12, 3, 1, 150]
x.unshift(42); // Ajoute 17 en début de tableau
x; // [42, 12, 3, 1, 150]
x.shift(); // 42 - Supprime et retourne le premier élement
x; // [12, 3, 1, 150]
x.splice(1, 2); // [12, 150] - Supprime 2 éléments à la position 1
let x = [12, 3, 1, 150];
x.map(element => element * 2); // [24, 6, 2, 300]
x.reduce((acc, element) => acc + element) // 166
x.sort();
x; // [1, 12, 150, 3]
x.sort((a, b) => a - b);
x; // [1, 3, 12, 150]
x.reverse()
x; // [150, 12, 3, 1]
let x = [12, 3, 1, 150];
x.indexOf(3); // 1 - Trouve l'index d'un élément
let x = [12, 3, 1, 150];
x.includes(3); // true - Indique la présence d'un élément
x.find(element => element === 3); // 3 - Trouve un élément
x.find(element => element === 0); // undefined
x.findIndex(element => element === 3); // 1 - Trouve l'index d'un élément
x.findIndex(element => element === 0); // undefined
x.filter(element => element < 10); // [3, 1] - Filtre les éléments
ES6
let x = {};
let y = new Object();
let x = {
firstname: "Benjamin",
lastname: "Chazelle",
age: 24,
engineer: true,
company: "Ubitransport"
};
let x = {
firstname: "Benjamin",
"lastname": "Chazelle",
age: 24,
'engineer': true,
company: "Ubitransport"
};
let x = {
firstname: "Benjamin",
lastname: "Chazelle",
age: 24,
engineer: true,
company: "Ubitransport"
};
x.age; // 24
x['company']; // "Ubitransport"
let x = {
firstname: "Benjamin",
lastname: "Chazelle",
age: 24,
engineer: true,
company: "Ubitransport"
};
x.age = 20;
x['company'] = "IUT Lyon 1";
x['passion'] = "Piano";
x;
// {
// firstname: "Benjamin",
// lastname: "Chazelle",
// age: 20,
// engineer: true,
// company: "IUT Lyon 1",
// passion: "Piano"
// }
let x = {
firstname: "Benjamin",
company: {
name: "Ubitransport",
address: "200 Bd de la Résistance, 7100 Mâcon"
}
};
x.company.name; // "Ubitransport"
let x = {
firstname: "Benjamin",
company: {
name: "Ubitransport",
address: "200 Bd de la Résistance, 7100 Mâcon"
}
};
x.home; // undefined
x.home.address; // Uncaught TypeError: x.home is undefined
let x = {
firstname: "Benjamin",
company: {
name: "Ubitransport",
address: "200 Bd de la Résistance, 7100 Mâcon"
}
};
x.home; // undefined
x.home.address; // Uncaught TypeError: x.home is undefined
x?.home; // undefined
x?.home?.address; // undefined
ES6
let x = {
firstname: "Benjamin",
company: "Ubitransport"
};
"firstname" in x; // true
"phone" in x; // false
let x = {
firstname: "Benjamin",
company: "Ubitransport"
};
delete x.firstname;
x; // { company: "Ubitransport" }
let x = {
firstname: "Benjamin",
company: {
name: "Ubitransport",
address: "200 Bd de la Résistance, 7100 Mâcon"
}
};
Object.keys(x); // ["firstname", "company"]
Object.keys(x.company); // ["name", "address"]
let x = {
firstname: "Benjamin",
company: {
name: "Ubitransport",
address: "200 Bd de la Résistance, 7100 Mâcon"
}
};
// Historique
for(let property in x) {
// x[property];
}
// Performance
Object.keys(x).forEach(property => {
// x[property];
})
ES6
const firstname = "Benjamin";
const lastname = "Chazelle";
let x = {
firstname,
lastname
};
x;
// {
// firstname: "Benjamin",
// lastname: "Chazelle"
// }
Vue
ES6
const firstname = "Benjamin";
const contact = {
phone: "06 51 23 51 39",
email: "benjamin@chazelle.pro"
}
let x = {
firstname,
contact
};
x;
// {
// firstname: "Benjamin",
// contact: {
// phone: "06 51 23 51 39",
// email: "benjamin@chazelle.pro"
// }
// }
const firstname = "Benjamin";
const contact = {
phone: "06 51 23 51 39",
email: "benjamin@chazelle.pro"
}
let x = {
firstname,
...contact
};
x;
// {
// firstname: "Benjamin",
// phone: "06 51 23 51 39",
// email: "benjamin@chazelle.pro"
// }
Vue
ES6
const firstname = "Benjamin";
const contact = {
phone: "06 51 23 51 39",
email: "benjamin@chazelle.pro"
}
let x = {
firstname,
contact
};
x;
// {
// firstname: "Benjamin",
// contact = {
// phone: "06 51 23 51 39",
// email: "benjamin@chazelle.pro"
// }
// }
const firstname = "Benjamin";
const contact = {
phone: "06 51 23 51 39",
email: "benjamin@chazelle.pro"
}
let x = {
firstname,
contact
};
x;
// {
// firstname: "Benjamin",
// contact = {
// phone: "06 51 23 51 39",
// email: "benjamin@chazelle.pro"
// }
// }
https://interactly.glitch.me/
let foo = {};
foo.bar; // ?
let foo = {};
foo.bar.qux; // ?
let foo = {};
foo?.bar.qux; // ?
let foo = {};
foo?.bar?.qux; // ?
let width = 67
let height = 23
let measures =
{ width, height }
let color = "red"
let texture = "vinyle"
let data = {
color,
texture: "iron",
...measures
}
data // ?
{ // Réponse B
color: undefined,
texture: "iron",
width: 67,
height: 23
}
{ // Réponse D
color: "red",
texture: "iron",
measures: {
width: 67,
heigth: 23
}
}
{ // Réponse A
color: "red",
texture: "vinyle",
measures: {
width: 67,
height: 23
}
}
{ // Réponse C
color: "red",
texture: "iron",
width: 67,
height: 23
}
let width = 67
let height = 23
let weight = 19
let measures =
{width, height, weight}
let data = {
weight: 42,
texture: "iron",
...measures
}
data // ?
{ // Réponse B
texture: "iron",
width: 67,
height: 23,
weight: 19,
}
{ // Réponse D
texture: "iron",
measures: {
width: 67,
heigth: 23,
weight: 19,
},
}
{ // Réponse A
texture: "iron",
weight: 42,
measures: {
width: 67,
height: 23,
weight: 19,
}
}
{ // Réponse C
weight: 19,
texture: "iron",
width: 67,
height: 23,
}
https://github.com/benjaminchazelle/The-Quest
https://glitch.com/edit/#!/iut-search-engine
let x = new Set();
x.add(42); // { 42 }
x.add("foo"); // { 42, "foo" }
x.has("foo"); // true
x.has(1337); // false
x.delete("foo");
x.size; // 1
for (let item of x) {
// items;
}
ES6
let x = new Map();
x.set(42, "quarante-deux'");
x.set("foo", "f o o");
x.size; // 2
x.get(42); // "quarante-deux";
x.get("foo") // "f o o"
x.delete("foo");
x.forEach((value, key) => {
// value;, key;
});
ES6
let a = new Date() // Maintenant
let b = new Date(1537621200000) // Milisecondes depuis l'epoch Unix
let c = new Date('2018-09-22T15:00:00+02:00') // Format ISO 8601
let d = new Date(2018, 8, 22) // Le mois est 0-indexé
ISO 8601
let x = new Date('2018-09-22T15:00:00+02:00')
x.getTime(); // 1537621200000
x.toLocaleDateString(); // "2018-9-22"
x.toLocaleTimeString(); // "15:00:00"
x.toISOString(); // "2018-09-22T13:00:00.000Z"
ISO 8601
let x = new Date('2018-09-22T15:00:00+02:00');
x.getDate(); // 22
x.setDate(30); // Date { 2018-09-30T13:00:00.000Z }
// Je vous épargne tout les getters/setters
let now = new Date().getTime();
let now = Date.now();
ES6
JSON est une syntaxe pour sérialiser des objets, tableaux, nombres, chaînes de caractères, booléens et valeurs null.
Elle est basée sur la syntaxe de JavaScript mais en est distincte : du code JavaScript n’est pas nécessairement du JSON, et du JSON n’est pas nécessairement du JavaScript.
{
firstname: "Benjamin",
'lastname': "Chazelle",
"age": 024,
"company": {
`hiring`: true,
"nextWebTalent": undefined
},
"confinedUntil": null,
"courses": ['JavaScript', "Vue.js"],
}
{
firstname: "Benjamin",
'lastname': 'Chazelle',
"age": 024,
"company": {
`hiring`: true,
"nextWebTalent": undefined
},
"confinedUntil": null,
"courses": ['JavaScript', "Vue.js"],
}
{
"firstname": "Benjamin",
"lastname": "Chazelle",
"age": 24,
"company": {
"hiring": true
},
"confinedUntil": null,
"courses": ["JavaScript", "Vue.js"]
}
let decoded = { firstname: 'Benjamin', age: 24, company: "Ubitransport" };
JSON.stringify(decoded);
// '{"firstname":"Benjamin","age":24,"company":"Ubitransport"}'
let encoded = '{"firstname":"Benjamin","age":24,"company":"Ubitransport"}';
JSON.parse(encoded);
// {
// firstname: 'Benjamin',
// age: 24,
// company: 'Ubitransport'
// }
function maFonction () {
}
function maFonction () {
}
maFonction(); // undefined
function maFonction () {
return 42;
}
maFonction(); // 42
function addition (a, b) {
return a + b;
}
addition(10, 7); // 17
function addition (a, b, c = 0) {
return a + b + c;
}
addition(10, 7); // 17
addition(10, 7, 100); // 117
const addition = function (a, b, c = 0) {
return a + b + c;
}
addition(10, 7); // 17
addition(10, 7, 100); // 117
const addition = (a, b, c = 0) => {
return a + b + c;
}
addition(10, 7); // 17
addition(10, 7, 100); // 117
ES6
const addition = (a, b, c = 0) => a + b + c;
addition(10, 7); // 17
addition(10, 7, 100); // 117
ES6
const majuscule = str => str.toUpperCase();
majuscule("hello world"); // "HELLO WORLD"
ES6
const casting = (a, b, ...plusEncore) => {
let liste = `${a} et ${b}`;
if(plusEncore.length > 0) {
liste += ` (et ${plusEncore.join(", ")})`;
}
return liste;
};
casting("Alice", "Bob");
// "Alice et Bob"
casting("Alice", "Bob", "Charlie", "Dan");
// "Alice et Bob (et Charlie, Dan)"
ES6
function exclamer(str) {
str += " !";
return str;
}
// Type primitif
let x = "Hello world";
exclamer(x); // "Hello world !"
x; // "Hello world"
x = exclamer(x);
x; // "Hello world !"
!!!
function exclamer(obj) {
obj.y += " !";
}
// Type objet
let x = {};
x.y = "Hello world";
exclamer(x);
x.y; // "Hello world !"
Type primitif passé par copie
Type objet passé par référence
try {
vivreTranquille();
} catch (e) {
if(e.message === "covid") {
seConfiner();
}
}
function vivreTranquile() {
// ...
throw new Error("covid");
// ...
}
-
-
-
File
d'événements
Gére l'exécution asynchrone des événements
document.body.addEventListener('click', () => {
alert("Hello world");
})
console.log("Le petit oiseau va sortir dans 2 secondes !")
setTimeout(() => {
console.log("Coucou !");
}, 2000);
let compteur = 10;
setInterval(() => {
if(compteur === 0) {
console.log("BOOM !")
}
else if(compteur > 0) {
console.log(compteur);
}
compteur--;
}, 1000);
let compteur = 10;
const interval = setInterval(() => {
if(compteur === 0) {
console.log("BOOM !")
clearInterval(interval);
}
console.log(compteur--);
}, 1000);
ES6
ES6
En attente
Résolue
Rejetée
ES6
En attente
Résolue
Rejetée
Kilian se prépare à tirer
La France croise les doigts
Kilian pleure
Kilian dance
ES6
En attente
Résolue
Rejetée
Kilian se prépare à tirer
La France croise les doigts
Kilian pleure
Macron perd de la popularité
Kilian dance
La France se réjouit
ES6
promise.then(() => makeKilianDancing())
promise.then(() => rejoiceFrance())
promise.catch(() => {
makeKilianCrying()
makeMacronUnpopular()
})
const promise = new Promise((resolve, reject) => {
let kilianHasScored = undefined
while(kilianHasScored === undefined) {
kilianHasScored = doesKilianScored()
}
if(kilianHasScored) {
resolve()
} else {
reject()
}
})
ES6
promise.then((happiness) => {
if(happiness > 100) {
makeKilianDancing()
} else {
makeKilianSmiling()
}
}).catch((sadness) => {
if(sadness > 100) {
makeKilianFalling()
} else {
makeKilianCrying()
}
})
const promise = new Promise((resolve, reject) => {
let kilianHasScored = undefined
while(kilianHasScored === undefined) {
kilianHasScored = doesKilianScored()
}
if(kilianHasScored) {
resolve(getKilianHappiness())
} else {
reject(getKilianSadness())
}
})
async function () {
try {
let hapiness = await promise;
//...
} catch(sadness) {
sadness;
//...
}
}
const promise = new Promise((resolve, reject) => {
let kilianHasScored = undefined;
while(kilianHasScored === undefined) {
kilianHasScored = doesKilianScored();
}
if(kilianHasScored) {
resolve(estimateKilianHapiness());
} else {
reject(estimateKilianSadness());
}
})
Gestion asynchrone sur les fonctions basée sur l'attente de la résolution des promesses
let maVariable = 42;
console.log(maVariable);
// > 42
console.log({maVariable});
// > { maVariable: 42 }
console.info("foo");
console.warn("bar");
console.error("qux");
// Effacer la console
console.clear();
// Afficher la pile d'appel
console.trace();
// Mesurer le temps
console.time("mesurer durée traitement");
console.timeEnd("mesurer durée traitement");
debugger;
// module.js
export default {
name: "Benjamin"
};
ES6
Vue
// main.js
import Module from "./module.js";
Module; // { name: "Benjamin" }
import AutreNom from "./module.js";
AutreNom; // { name: "Benjamin" }
// module.js
export default {
name: "Benjamin"
};
export const email = "iut@chazelle.pro";
ES6
Vue
// main.js
import Module from "./module.js";
Module; // { name: "Benjamin" }
import { email } from "./module.js";
email; // "iut@chazelle.pro"
// module.js
export default {
name: "Benjamin"
};
export const email = "iut@chazelle.pro";
let website = "chazelle.pro";
export website;
ES6
Vue
// main.js
import Module from "./module.js";
Module; // { name: "Benjamin" }
import { email } from "./module.js";
email; // "iut@chazelle.pro"
import { website as url } from "./module.js";
url; // "chazelle.pro"
// module.js
export default {
name: "Benjamin"
};
const email = "iut@chazelle.pro";
let website = "chazelle.pro";
export { email, website };
ES6
Vue
// main.js
import Module from "./module.js";
Module; // { name: "Benjamin" }
import { email, website as url } from "./module.js";
email; // "iut@chazelle.pro"
url; // "chazelle.pro"
// module.js
export default {
name: "Benjamin"
};
const email = "iut@chazelle.pro";
let website = "chazelle.pro";
export { email, website };
ES6
Vue
// main.js
import Module, { email, website as url } from "./module.js";
Module; // { name: "Benjamin" }
email; // "iut@chazelle.pro"
url; // "chazelle.pro"
// module.js
export default {
name: "Benjamin"
};
const email = "iut@chazelle.pro";
function share() {};
export { email, share };
ES6
Vue
// main.js
import Module, { email, share } from "./module.js";
Module; // { name: "Benjamin" }
email; // "iut@chazelle.pro"
share; // function share() {}
https://interactly.glitch.me/
https://interactly.glitch.me/
https://interactly.glitch.me/
(réponse libre)
https://interactly.glitch.me/
https://interactly.glitch.me/
https://interactly.glitch.me/
document.body;
// <body>
document.getElementById("titre");
// <input id="titre">
document.querySelectorAll("*");
// [<html>, <head>, ...]
window.title;
// "Apprenons JS"
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<h1>Hello world</h1>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
let input = document.querySelector("#titre");
// Valeur du champ
input.value;
input.getAttribute("type"); // "text"
input.setAttribute("type", "password");
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<h1>Hello world</h1>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
let paragraph = document.querySelector("p");
paragraph.textContent;
// "Hello world"
paragraph.innerHTML;
// "Hello<br />world"
paragraph.outerHTML;
// "<p>Hello<br />world</p>"
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<p>Hello<br />world</p>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
let header = document.querySelector("h1");
header.classList.contains('head');
// true
header.classList.add('strong');
header.classList.remove('head')
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<h1 class="head">
Hello world
</h1>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
let bouton = document.querySelector("button");
bouton.addEventListener("click", () => {
alert("Clicked !");
});
// Il y a de nombreux événements écoutables
//
// mousedown, mouseup, mousemove, click, dblclick
// keydown, keyup, keypress
// scroll
// ...
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<h1>Hello<br />world</h1>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
let img = document.createElement("IMG");
document.body.appendChild(img);
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<h1 class="head">
Hello world
</h1>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
https://glitch.com/edit/#!/iut-todo-list-js
<body>
<ul></ul>
<input type="text" />
<button>Ajouter</button>
</body>
let ul = document.querySelector("ul");
let input = document.querySelector("input");
let button = document.querySelector("button");
button.onclick = () => {
let item_li = document.createElement("LI");
let item_span = document.createElement("SPAN");
item_span.textContent = input.value;
item_li.appendChild(item_span);
let item_button = document.createElement("BUTTON");
item_button.textContent = "X";
item_button.onclick = () => {
item_span.style.textDecoration = "line-through";
};
item_li.appendChild(item_button);
ul.appendChild(item_li);
input.value = "";
};
Modèle
Vue
Logique
// View
let ul = document.querySelector("ul")
let input = document.querySelector("input")
let button = document.querySelector("button")
function render() {
ul.innerHTML = ""
input.onkeyup = () => updateNewLabel(input.value)
for (let todo of todos) {
let item_li = document.createElement("LI")
let item_span = document.createElement("SPAN")
item_span.textContent = todo.label;
item_span.style.textDecoration = todo.done
? "line-through" : ""
item_li.appendChild(item_span)
let item_button = document.createElement("BUTTON")
item_button.textContent = "X"
item_button.onclick = () => removeTodo(todo);
item_li.appendChild(item_button)
ul.appendChild(item_li)
}
input.value = newLabel
button.onclick = () => addTodo()
}
// Model
let todos = []
let newLabel = ""
// Actions
function addTodo() {
todos.push({
label: newLabel,
done: false
});
newLabel = ""
render()
}
function removeTodo(todo) {
todo.done = true
render()
}
function updateNewLabel(_newLabel) {
newLabel = _newLabel
render()
}
<body>
<ul></ul>
<input type="text" />
<button>Ajouter</button>
</body>
// View
<template>
<ul>
<li v-for="todo in todos" :key="todo.key">
<span :class="{strike: todo.done}">{{ todo.label }}</span>
<button @click="toggleTodo(todo)">X</button>
</li>
</ul>
<input v-model="newLabel" type="text" />
<button @click="addTodo">Ajouter</button>
</template>
<style scoped>
.strike {
text-decoration: line-through;
}
</style>
<script setup>
// Model
import { ref } from "vue"
let todos = ref([])
let newLabel = ref("")
// Logic
function addTodo() {
todos.value.push({
id: Date.now(),
label: newLabel.value,
done: false
})
newLabel.value = ""
}
function toggleTodo(todo) {
todo.done = !todo.done
}
</script>
Utilisateur
Frontend
Backend
Database
</>
Front-end
React
201k ⭐
Angular
86k ⭐
Vue
202k ⭐
Svelte
65k ⭐
Framework JavaScript pour le développement d'application web
Simplifie le développement d'interface dynamique
Populaire : Adobe, GitLab, ...
Juil 2013 - Premier commit par Evan You (@Google Creative Lab)
Déc 2013 - Version 0.6
Oct 2015 - Version 1.0
Sep 2016 - Version 2.0
Fév 2022 - Version 3.0
Réelle plus value
Puissant
Léger
Open source
Populaire
Communauté active
Maintenu
Bien documenté
Permet de séparer la vue, le modèle de données et la logique applicative
npm init vue@latest
// Version de développement
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
// Version de production
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
// Plus usuellement
import { createApp } from "vue"
<div id="app"></div>
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount("#app")
Modèle
Vue
Logique
<script setup>
import { ref } from 'vue'
const message = ref("Hello world")
</script>
<template>
<h1>{{ message }}</h1>
</template>
<style scoped>
h1 {
text-decoration: underline;
}
</style>
Mise à jour automatique de l'UI et des données en fonction des mutations dans les données
Basé sur le design pattern Observer
Développement plus simple
et efficace
<script setup>
import { ref } from 'vue'
const message = ref("Hello world")
</script>
<template>
<h1>{{ message }}</h1>
</template>
<script setup>
import { ref } from 'vue'
const url = ref("learn.png")
setTimeout(() => {
url.value = "master.png"
}, 5000)
</script>
<template>
<img :src="url" />
</template>
<script setup>
import { ref } from "vue"
const speed = ref(10)
</script>
<template>
<p v-if="speed < 88">
Plus vite Doc !
</p>
<p v-else-if="speed === 88">
88 mph !
</p>
<p v-else>Retour vers le futur !</p>
<pre>
En console, entrer :
window.speed.value = 100
</pre>
</template>
<script setup>
import { ref } from "vue"
const verbs = ref([
{ id: 0, text: "Veni" },
{ id: 1, text: "Vidi" }
{ id: 2, text: "Vici" }
])
</script>
<template>
<ul>
<li
v-for="verb in verbs"
:key="verb.id"
>
{{ verb.text }}
</li>
</ul>
</template>
<script setup>
import { ref } from "vue"
const verbs = ref([
{ id: 0, text: "Veni" },
{ id: 1, text: "Vidi" }
{ id: 2, text: "Vici" }
])
</script>
<template>
<ul>
<li
v-for="(verb, index) in verbs"
:key="verb.id"
>
#{{ index }} {{ verb.text }}
</li>
</ul>
</template>
<script setup>
import { ref } from "vue"
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<p>{{ count }}</p>
<button @click="increment">
Incrémenter !
</button>
</template>
<script setup>
import { ref } from "vue"
const count = ref(0)
function increment(delta) {
count.value += delta
}
</script>
<template>
<p>{{ count }}</p>
<button @click="increment(3)">
Incrémenter !
</button>
</template>
<script setup>
import { ref } from "vue"
const input = ref("Hello !")
</script>
<template>
<input v-model="input" />
<p>{{ input }}</p>
<button @click="input = 'Hello !'">
Reset
</button>
</template>
<script setup>
import { ref } from "vue"
const size = ref(10)
</script>
<template>
<button @click="size = 10">Small</button>
<button @click="size = 20">Medium</button>
<button @click="size = 30">Large</button>
<h1 :style="{fontSize: size + 'px'}">
Hello world
</h1>
</template>
<script setup>
import { ref } from "vue"
const isRed = ref(true)
</script>
<template>
<h1 class="text"
:class="{highlight: isRed}"
>
Hello world
</h1>
<button @click="isRed = !isRed">
Toggle
</button>
</template>
<style scoped>
.highlight { color: red; }
.text { font-style: italic; }
</style>
// LightSaber.vue
<script setup>
</script>
<template>
<span>Yoda</span>
<span class="handle">|||||</span>
<span class="light">======</span>
</template>
<style scoped>
.handle {
display: inline-block;
background: grey;
}
.light {
display: inline-block;
background: lightgreen;
color: transparent;
}
</style>
<script setup>
import LightSaber from "./LightSaber.vue"
</script>
<template>
<LightSaber />
</template>
// LightSaber.vue
<script setup>
const props = defineProps({
owner: String,
color: String
})
</script>
<template>
<div>
<span>{{ owner }}</span>
<span class="handle">|||||</span>
<span class="light"
:style="{background: color}">
======
</span>
</div>
</template>
<style scoped>...</style>
<script setup>
import LightSaber from "./LightSaber.vue"
</script>
<template>
<LightSaber owner="Mace windu" color="magenta" />
</template>
<script setup>
import LightSaber from "./LightSaber.vue"
const jedis = ref([
{ name: 'Obi-Wan', color: 'lightblue' },
{ name: 'Darth Vader', color: 'red' },
{ name: 'Rey', color: 'orange' },
])
</script>
<template>
<LightSaber
v-for="jedi in jedis"
:owner="jedi.name"
:key="jedi.name"
:color="jedi.color"
/>
</template>
Créer une micro application permettant d'ajouter des éléments à une liste à l'aide d'un champ texte et d'un bouton.
<div>
{{ message.split('').reverse().join('') }} <!-- #1 Sémantique difficile -->
</div>
...
<div>
{{ message.split('').reverse().join('') }} <!-- #2 Performance dégradée -->
</div>
...
<div>
{{ message.split('').reverse().join('') }} <!-- #3 Répétition évitable -->
</div>
<script setup>
import { ref, computed } from "vue"
const message = ref("Hello world")
const reversed = computed(() =>
message.value
.split('')
.reverse()
.join('')
)
// Une computed est une référence calculée à partir d'une expression
// Elle se met à jour automatiquement de façon réactive
// C'est une sorte de référence en lecture seule
</script>
<template>
<p>Message original : {{ message }}</p>
<p>Message inversé : {{ reversed }}</p>
</template>
const now = computed(() =>
Date.now()
)
// Une computed n'est recalculée que sur des références réactives
<script setup>
// ...
const reverse = () =>
message.value
.split('')
.reverse()
.join('')
</script>
<template>
Message inversé : {{ reverse() }}
</template>
Différence : computed met en cache son résultat alors qu'une fonction est executé à chaque rendu de la vue
<script setup>
import { ref, computed } from "vue"
const numbers = ref([1, 2, 3, 4, 5])
const even = computed(() => {
return numbers.value.filter(
n => n%2 === 0
)
}) // [2, 4]
</script>
<template>
<li v-for="n in even">{{ n }}</li>
</template>
<script setup>
import { ref } from "vue"
let letters = ref(
["J", "X", "O", "B", "U", "I"]
)
let sort = () => letters.value.sort()
</script>
<template>
<div @click="sort">
{{ letters }}
</div>
</template>
Méthodes de mutations réactives
push(), pop(), shift(), unshift(),
splice(), sort(), reverse()
Rajouter un select et un champ pour filtrer la liste des tâches par status et par label.
https://glitch.com/edit/#!/iut-todo-base
function getHelloWorld() {
const promise = fetch('https://www.xul.fr/ajax/ajax-get.json')
promise.then(res => res.json()) // { message: "Hello World !" }
}
// On préférera utiliser la syntaxe async/await
async function getHelloWorld() {
const res = await fetch('https://www.xul.fr/ajax/ajax-get.json')
const obj = await res.json() // { message : "Hello World !" }
}
async function getSomething() {
const res = await fetch('https://dummyjson.com/products/3')
// res.text()
// res.json()
// res.blob()
// res.arrayBuffer()
// res.formData()
}
async function deleteProduct() {
await fetch('https://dummyjson.com/products/1', {
method: 'DELETE'
})
}
async function getProducts() {
const res = await fetch('https://dummyjson.com/products/', {
headers: {
Authorization: `Token MY_API_KEY`,
Accept: 'application/json',
},
})
return await res.json()
}
async function putProduct() {
const res = await fetch('https://dummyjson.com/products/', {
method: 'PUT',
headers: {
Content-Type: 'application/json',
},
body: JSON.stringify({ name: 'Foo', description: 'Lorem ipsum' })
})
return await res.json()
}
async function getNotFound() {
const res = await fetch('https://www.jack-ceparou.com/olydri')
// res.status // 404
// res.statusText // "Not Found"
}
async function getDocumentations() {
const res = await fetch('https://developer.mozilla.org')
res.headers.get("Content-type") // "text/html; charset=utf-8"
for(let [header, value] of res.headers.entries()) {
console.log(header, value)
}
}
async function getDocumentations() {
try {
const res = await fetch(`http://somewhere.com/somewhat`)
if (!response.ok) {
throw new Error('Fail during fetching somewhat');
}
return await response.json();
} catch (error) {
console.error(error);
}
}
Brancher un back-end sur le front-end
Frontend de base https://glitch.com/edit/#!/iut-todo-more
Spec : https://glitch.com/edit/#!/iut-todo-server?path=README.md
1) Récupérer toutes les tâches + label bouton "Chargement"
2) Permettre de filtrer par owner (computed)
3) Pouvoir enregistrer une nouvelle tâche dans le backend
4) Pouvoir passer à done une tâche
Application Programming Interface
Fourni un accès structuré à une application ou un service
Basé sur un protocole (ex: HTTP, TCP, ...) et un standard (ex: XML, JSON, ...) de communication
Architectures populaires : REST, SOAP
Representational State Transfer
Architecture logicielle pour système distribué largement utilisé pour les API web
Basé sur HTTP pour effectuer des opérations CRUD (Create, Read, Update, Delete) via les méthodes/verbes HTTP POST, GET, PUT ou PATCH, DELETE sur des ressources
Données au format JSON ou plus rarement XML
Framework back-end open-source pour Node.js.
Fonctionnalités : routage, gestion des requêtes et des réponses HTTP, authentification et bien plus encore.
Adapté pour la création d'API
npm install express
import express from 'express'
const app = express()
app.listen(3000, () => {
console.log('Server is listening on port 3000 !')
})
import express from 'express'
const app = express()
app.get('/', (req, res) => {
res.send('Hello world !');
})
app.listen(3000, () => {
console.log('Server is listening on port 3000 !')
})
app.get('/', (req, res) => {
res.send('GET at root');
})
app.post('/', (req, res) => {
res.send('POST at root');
})
app.put('/', (req, res) => {
res.send('PUT at root');
})
app.delete('/', (req, res) => {
res.send('DELETE at root');
})
app.get('/', (req, res) => {
res.send('GET at root');
})
app.get('/foo', (req, res) => {
res.send('GET at /foo');
})
app.get('/asHTML', (req, res) => {
res.send('GET at root'); // Content-Type : text/html
})
app.get('/asJSON', (req, res) => {
res.send({ foo: 'bar' }); // Content-Type : application/json
})
app.get('/asBinary', (req, res) => {
res.send(Buffer.from('baz')); // Content-Type : application/octet-stream
})
app.get('/fromFile', (req, res) => {
res.sendFile('/path/to/image.png');
})
app.get('/notFound', (req, res) => {
res.status(404)
res.end()
})
app.get('/headers', (req, res) => {
res.set('Access-Control-Allow-Origin', '*')
res.set({
'Content-Type': 'text/plain',
'Content-Length': '123',
'ETag': '12345'
})
res.end()
})
app.get('/redirection', (req, res) => {
res.redirect('/foo/bar')
// res.redirect('http://example.com')
// res.redirect(301, 'http://example.com')
// res.redirect('../login')
})
app.use(express.static('public'))
// http://localhost:3000/js/app.js
// http://localhost:3000/images/bg.png
// http://localhost:3000/hello.html
app.use('/static', express.static('public'))
// http://localhost:3000/static/js/app.js
// http://localhost:3000/static/images/bg.png
// http://localhost:3000/static/hello.html
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.use((req, res, next) => {
console.log(`Request received at ${new Date()}`)
next() // Call the next middleware function
})
app.use((req, res, next) => {
res.setHeader('X-Custom-Header', 'Hello World')
next() // Call the next middleware function
})
const authMiddleware = (req, res, next) => {
const token = req.headers.authorization.split(' ')[1]
if(verifyToken(token)) {
next() // Call the next middleware function
} else {
res.status(401).json({ error: "Not authorized" })
}
}
app.get('/accounts', authMiddleware, (req, res) => {
res.send('Safe Hello World!')
})
import express from 'express'
const app = express()
app.use(express.json())
app.post('/users', (req, res) => {
const { name, email } = req.body
res.send(`User created: ${name} (${email})`)
})
import express from 'express'
import bodyParser from 'body-parser' // npm install body-parser
const app = express()
app.use(bodyParser.urlencoded({ extended: true }))
app.post('/login', (req, res) => {
const { username, password } = req.body
res.send(`Logged in as ${username}`)
})
<form method="POST" action="/login">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Log In</button>
</form>
// http://localhost/search?q=Foo
app.get('/search', (req, res) => {
const { q } = req.query
res.send(`Search query is: ${q}`) // Search query is: Foo
})
// http://localhost/search?tag=Foo&tag=Bar
app.get('/search', (req, res) => {
const { tag } = req.query // Array if several given
res.send(`Tags are: ${tag}`) // Tags are: ['Foo', 'Bar']
})
app.get('/users/:userId', (req, res) => {
const { userId } = req.params
res.send(`User ID is: ${userId}`)
})
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params
res.send(`User ID is: ${userId}, Post ID is: ${postId}`)
})
npm i sequelize sqlite3
const sequelize = new Sequelize('sqlite::memory:')
import { DataTypes } from 'sequelize'
const User = sequelize.define('User', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
age: {
type: DataTypes.DECIMAL,
allowNull: false
},
})
import { Op } from "sequelize"
// Create
await User.create({ username: "john", age: 42 }) // instance
// Read
const userById = await User.findByPk("de262606-070c-42e1-a43d-75c9696e919a") // instance | null
const userByName = await User.findOne({ where: { username: "alice" }) // instance | null
const users = await User.findAll({ where: { age: { [Op.gt]: 18 } } }) // array<instance>
// Update
await userByName.update({ age: 43 }) // instance
// Delete
await User.destroy({ where: { id: "de262606-070c-42e1-a43d-75c9696e919a" } }) // number
userById.destroy() // void
try {
User.create({
email: 'user@example.com',
password: 'password'
})
} catch(e) {
// e.errors
// e.errors.map(error => error.message)
}
const User = sequelize.define('User', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [8, 20]
}
}
})
Remixer ou cloner
https://glitch.com/edit/#!/iut-todo-server-base
User.hasMany(Task, {
as: 'tasks',
foreignKey: 'userId',
onDelete: 'CASCADE'
})
Task.belongsTo(User, {
as: 'owner',
foreignKey: 'userId'
})
import { DataTypes } from 'sequelize'
const Task = sequelize.define('Task', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true
},
text: {
type: DataTypes.STRING,
allowNull: false
},
done: {
type: DataTypes.BOOLEAN,
allowNull: false
},
})
User.hasMany(Task, {
as: 'tasks',
foreignKey: 'userId',
onDelete: 'CASCADE'
})
Task.belongsTo(User, {
as: 'owner',
foreignKey: 'userId'
})
const user = await User.findOne({
attributes: ['id', 'username'],
include: [{
model: Task,
as: 'tasks',
attributes: ['label'],
where: { done: false }
}]
})
{
id: '1f2fadb1-0d7e-47a7-9d5b-5d3e',
username: 'casear',
tasks: [
{ label: 'Vini' },
{ label: 'Vidi' },
{ label: 'Vici' }
]
}
User.hasMany(Collection, {
as: 'collections',
foreignKey: 'userId',
onDelete: 'CASCADE'
})
Collection.belongsTo(User, {
as: 'owner',
foreignKey: 'userId'
})
Collection.hasMany(Task, {
as: 'tasks',
foreignKey: 'collectionId',
onDelete: 'CASCADE'
})
Task.belongsTo(Collection, {
as: 'collection',
foreignKey: 'collectionId'
})
const user = await User.findOne({
attributes: ['username'],
include: [{
model: Collection,
as: 'collections',
include: [{
model: Task,
as: 'tasks',
attributes: ['label']
}]
}]
})
{
username: 'casear',
collections: [{
tasks: [
{ label: 'Vini' },
{ label: 'Vidi' },
{ label: 'Vici' }
]
}]
}
Identification
Processus de reconnaissance d'un utilisateur ou d'une entité par le biais d'un nom d'utilisateur ou d'un identifiant unique.
Authentification
Processus de vérification de l'identité d'un utilisateur ou d'une entité.
Autorisation
Processus de détermination des droits d'accès d'un utilisateur ou d'une entité à des ressources spécifiques après qu'ils aient été identifiés et authentifiés.
Utilisateur
Authentification
Service d'authentification
Preuve (mot de passe, SMS, ...)
Token
Autorisation
Utilisateur
Service applicatif
Service d'authentification
Requête + Token
Token
Validation signature + droits
Réponse
Vérification
de non-révocation
Peut être
la même instance
Émission du jeton
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "123456789",
"name": "John Doe",
"iat": 1516239022
}
Hash(Secret,
Header + Payload)
Header
Payload
Signature
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "123456789",
"name": "John Doe",
"iat": 1516239022
}
Hash(Secret,
Header + Payload)
Header
Payload
Signature
import jwt from 'jsonwebtoken'
// Nous sommes libres sur le contenu du payload,
// mais il des pratiques normalisées
const data = {
jti: 'd6a0bf5d-bec1-4c85-852a-80cfd6ffbc74', // Numéro de série
exp: Math.floor(Date.now() / 1000) + (60 * 60), // Expire dans 1h
username: 'john.doe',
roles: ['ADMIN']
}
const token = jwt.sign(data, 'THE_SECRET')
import jwt from 'jsonwebtoken'
try {
const data = jwt.verify(token, 'THE_SECRET');
// Vérifier que data.exp n'a pas expiré
// Vérifier que data.roles est suffisant pour l'action en cours
// Vérifier que data.jti est un numéro de série non revoquée
} catch(e) {
// Erreur de vérification
}
{
"name": "mon-projet",
"version": "1.0.0",
"description": "Un exemple de projet avec NPM",
"main": "index.js",
"scripts": {
"start": "node index.js",
"clean": "rm -rf node_modules"
},
"author": "John doe",
"license": "MIT",
"dependencies": {
"express": "^4.17.1",
"lodash": "^4.17.21"
},
"devDependencies": {
"nodemon": "^2.0.12"
}
}
{
"name": "mon-projet",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"packages": {
"": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/mon-projet/-/mon-projet-1.0.0.tgz",
"integrity": "sha512-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"dependencies": {
"express": "^4.17.1"
}
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrccoy1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
"body-parser": "1.19.0",
...
}
}
}
}
# Créer un package.json originel pour votre projet
npm init
# Ajoute une dépendance au package.json et l'installe dans node_modules/
npm install lodash
# Idem pour une dépendance de développement
npm install typescript --save-dev
# Installe les node_modules à partir des dépendances du package.json
npm install
import lodash from "lodash"
// Old way
const lodash = require("lodash")
# Lance un script définit dans package.json
npm run start
# Publie une archive du package dans npmjs.com
npm publish
https://github.com/benjaminchazelle/bidbay-starter
cd frontend && npm run test
jeudi 20 avril 2023 23:59
🚧 Remplir le Google Sheet, finir le projet
📝 Remplir le Google Form (à venir)
💼 Kiffer votre stage
Équipes bienveillantes et compétentes
Cœur de métier sympa
Boîte en hyper croissance, French Tech 120
Vue.js, Symfony, Google Cloud Platform
Lyon, Mâcon, home office