npm vs. yarn – Package Managers für JavaScript

Package Managers dienen dazu JavaScript Packages zu verwalten. Die 2 bekanntesten und weit verbreitetsten sind npm und yarn.

Was ist ein Package?

Packages sind JavaScript-Module, die wiederverwendbaren Code darstellen sollen.

Alle aktuell vorhandenen Packages sind hier zu finden: https://www.npmjs.com/

Jedes Package kann aber von weiteren Packages abhängig sein. Dies wird in der der package.json im Bereich „dependencies“ bzw. „devDependencies“ definiert.

Was ist npm?

npm („Node package manager“, 2010 erstmals veröffentlicht) wird standardmäßig bei jeder Installation von Node.js mitgeliefert und dient dazu Packages herunterzuladen bzw. zu verwalten.

Was ist yarn?

yarn ist ein von Facebook entwickelter Package-Manager (gleich wie npm), wird jedoch nicht standardmäßig beim installieren von Node.js dem User zur Verfügung gestellt.

Yarn wurde 2016 aus dem Problem entwickelt, dass in der Vergangenheit npm z.b. nicht sehr performant war und Features wie z.b. ein Lock-File nicht vorhanden waren.

Befehlsübersicht

Hier eine Übersicht der Befehle, die bei NPM und Yarn anders benannt wurden:

Commandnpmyarn
Install dependenciesnpm installyarn
Install packagenpm install [package]yarn add [package]
Install dev packagenpm install --save-dev [package]yarn add --dev [package]
Uninstall packagenpm uninstall [package]yarn remove [package]
Uninstall dev packagenpm uninstall --save-dev [package]yarn remove [package]
Updatenpm updateyarn upgrade
Update packagenpm update [package]yarn upgrade [package]
Global install packagenpm install --global [package]yarn global add [package]
Global uninstall packagenpm uninstall --global [package]yarn global remove [package]
https://alligator.io/nodejs/npm-yarn-cheatsheet/

Jedoch gibt es auch einige Befehle, die den gleichen Namen haben:

npmyarn
npm inityarn init
npm runyarn run
npm testyarn test
npm login (and logout)yarn login (and logout)
npm linkyarn link
npm publishyarn publish
npm cache cleanyarn cache clean
https://alligator.io/nodejs/npm-yarn-cheatsheet/

package-lock.json vs. yarn.lock

In der package.json werden die gewünschten Packages eingetragen inkl. der dafür gewünschten Versionen.

Jedoch können bei der Angabe der Version auch spezielle Zeichen angegeben werden wie z.B. *, ^ oder ~ (siehe https://devhints.io/semver für sehr gute Beispiele)

D.h. die package.json alleine ist nicht eindeutig welche Package-Version installiert werden soll.

Deshalb generiert NPM seit Version 5.x (Mai 2017) automatisch bei jedem „npm install“ automatisch eine package-lock.json.
Yarn hatte dieses Feature schon in der ersten Version inkludiert, generiert aber eine yarn.lock Datei.

Welcher ist besser?

Prinzipiell bietet yarn die gleichen Funktionalitäten an die auch bei npm zur Verfügung stehen und npm hat sehr viele Features, die nur in yarn zur Verfügung standen, selbst übernommen.

Daher ist es aktuell mehr eine persönliche Entscheidung mit welchem man gewohnt ist zu arbeiten.

Weitere Package Manager

Wie oben zu sehen gibt es für JavaScript Package Managers nicht nur eine Lösung sondern mehrere. Weitere Alternativen wären aktuell z.b.

Gulp-Beispiel für SCSS, JS und Browsersync

Folgendes Gulp Setup wurde auf Basis des Setups von „tristanisfeld“ auf Github erstellt. Siehe https://gist.github.com/tristanisfeld/9deea503260324f5e9b0

ZIP herunterladen: DOWNLOAD

An dem folgenden Beispiel ist ein Gulp Setup zu sehen mit dem

  • SCSS Dateien in CSS Dateien umgewandelt werden (inkl. Autoprefixer)
  • JS Dateien in eine zusammengefasste JS Datei umgewandelt werden
  • eine beliebige Live-Seite mit Browsersync gestyled werden kann

Ebenso kann über eine boolean Variable der „production“-Mode aktiviert bzw. deaktiviert werden um das „minifien“, „uglyfien“ bzw. generieren von Sourcemaps zu steuern.

package.json

{
  "name": "my_custom_gulp_setup",
  "author": "Kevin Pfeifer",
  "license": "WTFPL",
  "version": "2.0.0",
  "description": "Generate minified CSS and JS from source files",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "devDependencies": {
    "browser-sync": "^2.26.7",
    "gulp-autoprefixer": "^7.0.1",
    "gulp-concat": "^2.6.1",
    "gulp-if": "^3.0.0",
    "gulp-load-plugins": "^2.0.1",
    "gulp-minify-css": "^1.2.4",
    "gulp-rename": "^1.4.0",
    "gulp-sass": "^4.0.2",
    "gulp-sourcemaps": "^2.6.5",
    "gulp-uglify-es": "^2.0.0",
    "gulp-watch": "^5.0.1",
    "node-sass": "^4.13.0"
  },
  "dependencies": {
    "gulp": "^4.0.2"
  }
}

Hier befinden sich alle aktuell benötigten Node.js Modules (inkl. Version), die wir für das aktuelle Setup benötigen.

gulpfile.js

const gulp = require('gulp'),
      gulpLoadPlugins = require('gulp-load-plugins'),
      plugins = gulpLoadPlugins(),
      config = require('./gulp/config');

// --------------------function to get tasks from gulp/tasks
function getTask(task) {
  return require('./gulp/tasks/' + task)(gulp, plugins, config);
}

// ---------------------------------------------- Gulp Tasks
gulp.task('sass',             getTask('sass'));
gulp.task('scripts',          getTask('scripts'));
gulp.task('styles',           getTask('styles'));
gulp.task('watch',            getTask('watch'));
gulp.task('browsersync',      getTask('browsersync'));

// --------------------------------------- Default Gulp Task
gulp.task('default', gulp.series('watch'));

Der Start jedes Gulp Setups beginnt in der „gulpfile.js„.

  • gulp = require(‚gulp‘)
    • Ladet die gulp standard Funktionalitäten in die Variable gulp
  • gulpLoadPlugins = require(‚gulp-load-plugins‘)
    • Ladet das Zusatz-Modul „gulp-load-plugins“
  • plugins = gulpLoadPlugins()
  • config = require(‚./gulp/config‘)
    • Ladet die in der Datei „./gulp/config“ definierte Konfiguration in die Variable config
function getTask(task) {
  return require('./gulp/tasks/' + task)(gulp, plugins, config);
}

Als nächstes wird eine generische Funktion definiert um die unterschiedlichen Tasks aus dem „./gulp/tasks/“ zu laden. Diese ist essentiell, damit wir pro Task 1 Datei erstellen können.

gulp.task('sass',             getTask('sass'));
gulp.task('scripts',          getTask('scripts'));
gulp.task('styles',           getTask('styles'));
gulp.task('watch',            getTask('watch'));
gulp.task('browsersync',      getTask('browsersync'));

Nun werden alle Tasks geladen, die im Gulp zur Verfügung stehen sollen. D.h. wir haben für jeden Task eine Datei im Ordner „./gulp/tasks/„.

Gulp Plugin Loader

Nachdem wir nicht in jedem separaten Task-File die darin verwendeten Node.js-Modules requiren wollen gibt es die Möglichkeit über das Node.js-Module „gulp-load-plugin“ alle in der package.json vorhandenen Node.js-Modules in eine Variable zu laden.

Prinzipiell macht dieses Modul folgendes (ohne das wir es machen müssen):

plugins.sourcemaps = require('gulp-sourcemaps');
plugins.concat = require('gulp-concat');
plugins.uglifyEs = require('gulp-uglify-es');

Dies passiert ganz am Anfang in unserer gulpfile.js in den folgenden 2 Zeilen:

gulpLoadPlugins = require('gulp-load-plugins'),
plugins = gulpLoadPlugins()

Wichtig ist anzumerken, dass dieses Modul den Namen des eingebundenen Moduls abändert, damit man es per Funktionsaufruf durchführen kann.

Node Module NameFunktionsname
gulp-sourcemapssourcemaps
gulp-concatconcat
gulp-uglify-esuglifyEs

Und damit haben wir über die Variable „plugins“ auf alle in der package.json definierten Module Zugriff und können es somit über unsere generische Funktion „getTask“ an jedes Task-File übergeben.

Aufbau eines Tasks

// =========================================================
// Gulp Task: sass
// Description: transpiles sass, adds sourcemaps, prefixes
// npm install --save-dev node-sass gulp-sass gulp-sourcemaps gulp-autoprefixer gulp-load-plugins
// =========================================================

module.exports = function(gulp, plugins, config) {
  return function() {
    return gulp.src(config.sass.src)
      .pipe(plugins.if(!config.production, plugins.sourcemaps.init()))
      .pipe(plugins.sass(config.sass.opts).on('error', plugins.sass.logError))
      .pipe(plugins.autoprefixer())
      .pipe(plugins.if(!config.production, plugins.sourcemaps.write('.')))
      .pipe(gulp.dest(config.sass.dest));
  }
};

Gehen wir Zeile pro Zeile durch.

module.exports = function(gulp, plugins, config) {

Mit dem „module.exports“ wird definiert, dass diese Funktion von anderen JS Files inkludiert werden kann. Die in der function() definierten Parameter gulp, plugins und config sind die gleichen, die wir oben in der generischen Funktion mit übergeben.

return function() {

Die wirkliche Funktionalität des Tasks muss innerhalb einer function() gewrappt sein damit Gulp im Hintergrund die „Callbacks“ richtig ausführen kann.

return gulp.src(config.sass.src)
   ...

Danach folgt der „übliche“ Gulp Code, mit dem die diversen Funktionalitäten durchgeführt werden. Wichtig ist nur, dass der „Gulp-Stream“ returned wird.

Unterschiedliche Konfiguration für dev und production build

In dem oben beschriebenen Tasks is schon ein Beispiel zu sehen.

.pipe(plugins.if(!config.production, plugins.sourcemaps.init()))

Als Basis wird das Node-Module „gulp-if“ verwendet. Siehe https://www.npmjs.com/package/gulp-if

D.h. wenn in unserem Config-JSON „production“ false ist werden noch zusätzlich die Sourcemaps für das SASS generiert.

In einem anderen Task „scripts“ haben wir ebenso folgendes Beispiel:

.pipe(plugins.if(config.production, plugins.uglifyEs.default(), plugins.sourcemaps.init()))

D.h. wenn in unserem Config-JSON „production“ true ist wird der JS-Code „uglified“, andernfalls werden die Sourcemaps dafür generiert.

Autoprefixer Einstellungen

Mit der aktuellsten Version vom Autoprefixer wird es nicht mehr empfohlen die Einstellungen im JS-File durchzuführen sondern eine extra Datei Namens „.browserslistrc“ zu erstellen.

In dieser befindet sich aber die gleiche Config. Siehe https://github.com/postcss/autoprefixer#options

# Browsers that we support

last 2 version
> 1%
maintained node versions
not dead

config.js

Folgendes JSON beschreibt die diversen Einstellungen, die für die jeweiligen Tasks durchgeführt werden können.

Beschreibung der abgekürzten Variablen:

  • opts => Optionen für den jeweiligen Gulp-Task
  • src => Pfad zu Ordner mit den Source-Dateien
  • dest => Pfad zu Ordner für die generierten Dateien

Andernfalls ist Inline-Dokumentation vorhanden.

module.exports = {
  production: false,
  rename: {
    min: { suffix: '.min' }
  },
  // --------------------------------------------- browsersync
  browsersync: {
    opts: {
      proxy: "http://localhost", // The URL of the website you want to browsersync
      port: 4000, // The port, from which the browsersync tab will be opened
      serveStatic: [{
        route: '/', // The route, from which the "live" website serves CSS and JS files
        dir: './dist' // Your local path coming from the gulpfile.js where the newly local generated files are laying
      }],
    },
    watch: [
      './dist/assets/css',
      './dist/assets/js'
    ]
  },
  // ---------------------------------------------------- sass
  sass: {
    src: [
      "./source/sass/**/*.scss",
    ],
    opts: { }, // add sass options here
    dest: "./dist/assets/css"
  },
  // ------------------------------------------------- scripts
  scripts: {
    src: [
      'source/js/main.js'
    ],
    base: 'source/js', // common base folder of all concatenated JS files so sourcempas are working correctly
    filename: 'main.js', // filename of outputted, concatenated JS file
    dest: "./dist/assets/js" // folder where the JS files should be populated
  },
  // -------------------------------------------------- styles
  styles: {
    src: [
      "./source/css/**/*.css",
    ],
    dest: './dist/assets/css'
  },
};

Gulp – Automatisierung von Tasks

Was ist Gulp?

Gulp ist ein auf Node.js basierendes Tool, welches Programmieren erlaubt wiederkehrende Aufgaben zu automatisieren.

Beispiele hierfür sind z.B.

  • Bei Änderung eines SASS Files das CSS neu generieren
  • Bei Änderung eines JS Files das JS neu generieren
  • Bei Änderung eines beliebigen Files einen bestimmten Order auf einen Server hochladen
  • Abhängig von einer Variable das generierte CSS zu minifien
  • Abhängig von einer Variable Sourcemaps für JS und CSS generieren

Gulp 3 vs Gulp 4

Aktuell (November 2019) ist die neueste Version von Gulp 4.0.0
Diese ist ein „breaking change“ im Vergleich zu den vorher verwendeten Versionen 3.X

Was hat sich im Vergleich zu Gulp 3 geändert?

Gulp 4 hat das darunterliegende „Task“-System geändert, welches es nun erlaubt Tasks parallel auszuführen.

D.h. mit dem folgenden Task „default“ führt Gulp die Tasks „sass“ und „scripts“ parallel aus:

gulp.task('default', gulp.parallel('sass', 'scripts'));

Neben „gulp.parallel“ gibt es ebenso „gulp.series

gulp.task('default', gulp.series('lint', gulp.parallel('sass', 'scripts'), 'deploy'));

Wie hier zu sehen wird zuerst der Task „lint“ ausgeführt. Wenn dieser erfolgreich durchgeführt wurde werden die Tasks „sass“ und „scripts“ parallel ausgeführt und final der Task „deploy

Um Tasks innerhalb eines anderen Tasks auszuführen kann dies wie folgt durchgeführt werden:

(gulp.parallel('sass', 'scripts'))();

Wie diese Funktionalität zu einem recht mächtigen Setup führt wird im Beitrag Gulp Beispiel genauer erklärt.

React Library Beispiel mit mehreren Components

Im vorherigen Beispiel wurde die Grundstruktur von React erklärt. Hier nun ein Beispiel, in dem mehrere Components gerendert werden.

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>App Layout Demo</title>
</head>

<body>
  <div id="root"></div>
  <script src="https://unpkg.com/react/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>

  <script src="https://unpkg.com/babel-standalone"></script>

  <script src="Hello.js" type="text/jsx"></script>
  <script src="NumPicker.js" type="text/jsx"></script>
  <script src="index.js" type="text/jsx"></script>

</body>

</html>

Hello.js

class Hello extends React.Component {
  render() {
    return <h1>Hello There!!!</h1>
  }
}

NumPicker.js

function getNum() {
  return Math.floor(Math.random() * 10) + 1;
}
class NumPicker extends React.Component {
  render() {
    const num = getNum();
    let msg;
    if (num === 7) {
      msg = <h2>CONGRATS YOU WIN!</h2>
    } else {
      msg = <p>Sorry, you lose!</p>
    }
    return (
      <div>
        <h1>Your number is: {num} </h1>
        {msg}
      </div>
    );
  }
}

index.js

class App extends React.Component {
  render() {
    return (
      <div>
        <Hello />
        <NumPicker />
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

Gestartet wird alles zusammen über die letzte Zeile in der index.js

Hier wird die Component <App /> in das Element mit der ID „root“ gerendert.

Die Component <App /> beinhaltet aber sowohl die <Hello /> als auch die <NumPicker /> Component. Diese müssen vorher definiert sein bevor das rendern der Components durchgeführt werden kann. Daher die Reihenfolge der JS Dateien in der index.html

Damit erhalten wir folgenden Output:

Bzw. bei der Zahl 7

Simples React Library Beispiel

React kann über ein paar JS Dateien von CDNs relativ einfach lokal ohne eine lokal laufende Node.js Instanz laufen.

Dateien

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>First Component</title>
</head>
<body>
  <div id="root"></div>

  <script src="https://unpkg.com/react/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>

  <script src="https://unpkg.com/babel-standalone"></script>
  <script src="index.js" type="text/jsx"></script>
</body>
</html>

Wie hier zu sehen beinhaltet die index.html nicht sehr viele aufregende Elemente:

  • Der <head> beinhaltet nur standard HTML5 Meta-Tags und den Title der Seite, nichts React spezifisches.
  • Direkt nach dem <body> sehen wir unser „root“ Element über den wir unsere React-App aufbauen werden.
  • Vor dem </body> sehen wir einige JS Dateien die extern und eine Datei die lokal eingebunden wird:
    • react.development.js: Basis React-Library
    • react-dom.development.js: React-DOM Erweiterung um mit dem DOM interagieren zu können
    • babel-standalone: Babel ist für die JSX und damit die Javascript-Umwandlung verantwortlich
    • index.js: unsere React-App

index.js

class Hello extends React.Component {
	render() {
		return (
			<div>
				<h1>Hello there!</h1>
				<p>I am a React Component!</p>
			</div>
		);
	}
}

ReactDOM.render(<Hello />, document.getElementById('root'));

Hier definieren wir eine Component mit dem Namen „Hello„.
D.h. sie kann über „<Hello />“ gerenderd werden.
In dieser Component beinhaltet sich nur eine render() Funktion, die definiert, was beim rendern dieser Component an DOM ausgegeben wird.

Am Ende wird lediglich definiert, dass die Componente „<Hello />“ in das Element mit der ID „root“ gerendered wird.

Dadurch erhalten wir beim Aufruf der index.html folgenden Output

Wichtig hierbei ist, dass die index.html wirklich über einen Web-Server aufgerufen wird da wegen CORS Einschränkungen das lokale einbinden von weiteren JS-Dateien nicht erlaubt ist.

Alternativ kann natürlich der JS React Code direkt in die index.html geschrieben werden, jedoch wird dies bei größeren und komplexeren Projekten mit mehreren Components nicht wartbar. Tipp hier von mir wäre z.B. der „Live Server“ vom Visual Studio Code Editor.

Virtuelles DOM und JSX

Virtuelles DOM

Ein Hauptgrund für JS Frontend Libraries und Frameworks ist das „virtuelle DOM“. Jedoch was ist das virtuelle DOM und wieso ist es performanter als das „reguläre DOM“?

Jede Änderung im „reguläre DOM“ triggered im Browser ein neu zeichnen des gesamten DOMs was sehr aufwändig und ressourcenintensiv ist.

Anstelle aber bei jeder Änderung direkt mit dem „reguläre DOM“ zu arbeiten werden Änderungen im „virtuellen DOM“ rein im JS durchgeführt und nur die Unterschiede zwischen dem alten „virtuellen DOM“ und dem neuen „virtuellen DOM“ berechnet und im „regulären DOM“ durchgeführt.

Ebenso wird nicht jede Änderung separat vom Browser ausgeführt sondern alle Änderungen werden gesammelt auf einmal ausgeführt was ebenso einen Performance-Gewinn bringt.

vdom
Source: https://reactjs.de/artikel/vdom-react/

JSX

JSX ist die Abkürzung für Javascript XML oder Javascript Syntax Extension und ist eine Erweiterung der üblichen Javascript-Grammatik für React.

Diese erlaubt es HTML in JavaScript zu schreiben:

function render() {
  return <p>Hi everyone!</p>;
}

Normalerweise würde natives JavaScript hier Fehler produzieren.

Uncaught SyntaxError: Unexpected token <

JSX erlaubt es uns aber genau dies zu tun.

Am Beispiel von „React“ kann über den Online Babel Compiler direkt „React“ HTML-Code in JavaScript Code umgewandelt werden.

D.h. es wird HTML nicht in .html Dateien geschrieben sondern HTML in .js Dateien geschrieben.

Siehe JSX in depth für mehr Details.

JS Frontend Libraries & Frameworks

Library vs Framework

Prinzipiell sollte einmal klar gestellt werden was der Unterschied zwischen einer „Library“ und einem „Framework“ ist.

Libraries sind meist „kleinere“ Code-Bündel, die es einem Entwickler erlauben gewisse Funktionalitäten leichter in einer schon vorhandenen Applikation durchzuführen. Beispiele hierfür sind Moment.js, jQuery oder Data-Driven Documents.

Frameworks sind meist „größer“ und bieten eine vorgegebene Struktur wie Daten gespeichert werden bzw. mit Daten umgegangen werden soll. Oft bieten diese wiederverwendbare Blöcke aber auch schon implementierte Sicherheitsfeatures wie XSS Protection und CSRF Tokens.

Wieso gibt es JS Frontend Libraries/Frameworks?

Gründe für JS Frontend Libraries/Frameworks gibt es unterschiedliche:

  • Performance
  • Komplette Trennung von Frontend und Backend Logik
  • Neuere Technologie / Entwickler will nicht mit alten Web-Technologien arbeiten

Was ist das Prinzip hinter JS Frontend Libraries/Frameworks?

Gehen wir einmal vom Beispiel PHP aus.

  1. Browser ruft Webseite auf
  2. Web-Server bekommt Anfrage von Browser und sucht nach dem eingestellten Document-Root
  3. Wenn es eine PHP-Datei ist wird diese Datei von dem Web-Server oder vom verbundenen PHP-FPM Prozess interpretiert
  4. Hier werden auch alle Datenbank-Queries durchgeführt, die theoretisch viel Zeit in Anspruch nehmen können
  5. Das daraus resultierende HTML und die damit verbundenen CSS und JS werden dem Browser zurückgeschickt

Wichtig hierbei ist eben der Punkt 4. bei dem Datenbank Queries beim initialen Request schon ausgeführt werden.

Im Vergleich hier der Ablauf von JS Frontend Frameworks

  1. Browser ruft Webseite auf
  2. Web-Server bekommt Anfrage von Browser und sucht nach dem eingestellten Document-Root
  3. Web-Server liefert die HTML, CSS und JS zurück ohne jegliche DB Queries zu machen
  4. Der Browser rendered das HTML und CSS und fängt an das JS zu interpretieren
  5. Im JS werden vom Client aus asynchrone AJAX Aufruf zu einer vordefinierten API durchgeführt um die aktuellen dynamischen Daten anzuzeigen

D.h. das initiale HTML ist wesentlich geringer, da das DOM typischerweise nur 1 „root“-Element beinhaltet, in welches das JS dann den dynamischen Inhalt erst einfügt.

Hauptunterschied zu PHP ist hier, dass der dynamische Inhalt erst Clientseitig eingefügt wird, nicht vorher am Server!

Dies erzeugt einen wesentlichen Performance-Gewinn beim Pagespeed was sich auch auf das Google Search Ranking auswirkt.

Ebenso wird der Backend-Code für die Verwaltung der dynamischen Daten nicht mit dem Frontend-Code zur Darstellung der dynamischen Daten vermischt.

Aktuell bekannteste JS Libraries/Frameworks (September 2019)

  • React
  • VueJS
  • Angular

Prinzipiell gibt es kein „ultimatives“ Framework welches für alle Zwecke und Projekt-Größen perfekt ist, jedoch vereinfacht gesagt kann von folgenden Aussagen ausgegangen werden:

React ist eine „leichte“ Library/Framework, welche nur ein paar Grundprinzipien (wie z.B. „Components“) zur Verfügung stellt auf dem das Projekt aufgebaut wird. Der Einstieg ist relativ einfach da es sehr viele Beschreibungen und Videos online zur Verfügung stehen die diese Grundprinzipien schnell und einfach beschreiben.

React kann im Prinzip rein als Library verwendet werden (siehe HIER), aber am Beispiel von Next.js sehen wir ein Framework, welches auf React aufgebaut ist.

VueJS beinhaltet im Prinzip die gleichen Features wie React. Ein wichtiger Unterschied zu React ist, dass VueJS standardmäßig das virtuelle DOM nicht mit JSX aufbaut. Siehe HIER für eine Beschreibung was das virtuelles DOM und JSX ist.

Angular ist wesentlich „größer“ und bietet viel mehr Funktionalität von Haus aus an. Damit ist es prinzipiell für größere Projekte besser geeignet, da viele Funktionalitäten schon zur Verfügung stehen und nicht selber implementiert werden müssen. Jedoch ist sowohl der Einstieg als auch die Wartung des Frameworks aufwändiger.

Thread based vs. Event based

Prinzipiell gibt es 2 grundsätzliche Ansätze wie Requests von Clients von einem Web-Server verarbeitet werden können:

Thread based

Hier erstellt der Web-Server einen Thread pro Request und alle weiter Verarbeitung des Request geschieht in diesem neu erstellten Thread.

Das heist aber auch die Anzahl an maximal verfügbaren Threads auf dem Computer entschiedet die maximale Anzahl an gleichzeitigen Verbindungen.

Event based

Hier gelangt jeder Request in eine Art „Warteschlange“ welche dann von einem immer laufenden Web-Server Thread abgearbeitet wird. Damit werden alle Requests vom gleichen Thread verarbeitet.

Damit umgeht man den „Overhead“ welcher entsteht, wenn pro Request ein Thread erstellt wird.

Das bedeutet aber auch, dass bei einem sehr rechenintensiven Request alle weiteren Request „hängen“ da erst der rechenintensive Request abgearbeitet werden muss.

Da aber Javascript prinzipiell asynchron geschrieben wird und kein „blocking“ Code (einfach) geschrieben werden kann ist dies kein Problem und daher performant.

Am Beispiel von Node.js heist diese „Warteschlange“ Event Loop.

Node.js Event Loop

Source: https://stackoverflow.com/questions/21596172/what-function-gets-put-into-eventloop-in-nodejs-and-js

Was ist Node.js?

Node.js ist ein Framework welches auf der JavaScript Engine „V8“ aufgebaut ist, welche aktuell im Google Chrome verwendet wird.

Grund hinter der Entwicklung von Node.js lag darin ein ressourceneffizientes und damit performantes Framework zu bieten, welches „nonblocking I/O“ (bzw. auch Asynchrones I/O genannt) liefert.

Hier ein Beispiel von einem sehr simplen Web-Server:

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Voraussetzung zum ausführen von diesem Node.js Code ist eine installierte Version von Node.js (siehe https://nodejs.org)
Die aktuellste LTS Version is völlig ausreichend.

Wenn wir diesen Inhalt in eine index.js geben und diese mit

node index.js

ausführen erhalten wir einerseits im Terminal folgenden Output:

Server running at http://127.0.0.1:3000/

Und im Browser erhalten wir folgenden Output:

Vorteile von Node.js

  • Basiert auf Javascript und bietet damit eine sehr geringe Einstiegshürde für neue Entwickler da keine neue Syntax gelernt werden muss.
  • Node.js ist Event-basierend (siehe Vergleich HIER) und damit sehr performant
  • Starke Modularität durch den Node-Package-Manager (NPM)

Nachteile von Node.js

  • Rückwärtskompatibilität bei Node.js Version Update nicht wirklich vorhanden
  • Da die Event-basierende Loop nur von einem Node.js Thread abgearbeitet wird sind rechenintensive Aufgaben hier nicht empfohlen bzw. effizient
  • Asynchrone Code-Schreibweise nicht immer einfach zu erlernen

Source: https://nodejs.org/de/about/