Die Agentur
Kompetenzen

    Anfrage senden

    Senden Sie uns eine unverbindliche Anfrage.
    Wir beraten Sie gerne.

    PowerShell ist ein sehr mächtiges Werkzeug, egal was man damit anstellen möchte. Das Anwendungsgebiet ist einfach gigantisch. Vielleicht fällt es deswegen dem einen oder anderen auch so schwer wie mir einen Einstieg in die richtige Syntax zu finden, wenn es einfach darum geht Daten auszuwerten. Denn anders als für gewöhnliche Programmiersprachen bin ich noch nicht auf eine einfache Einführung gestoßen. Dieser Beitrag soll das nachholen. Ich werde versuchen die Syntax zu erklären und anhand von einfachen Beispielen zu erklären, wie man durch Probieren und Beobachten zu den gewünschten Ergebnissen gelangt.

    Dieser Beitrag soll in erster Linie diejenigen unter euch unterstützen, die via PowerShell Datenerfassen und ausgeben wollen. Die Anwendungsmöglichkeiten von PowerShell sind zu komplex um sie in einen Guide zu packen.

    1. Variablen, Objekte, Kommentare

    Variablen werden einfach mit einem $ Zeichen markiert. Der Aufruf von Variablen und das Setzen von Werten ist erstmal nichts besonderes.

    1#Das ist ein Kommentar
    2
    3<# Das
    4ist ein längerer Kommentar
    5#>
    6
    7#eine beliebige Variable
    8$anyVar
    9
    10#explizit als String deklarierte Variable
    11[String] $StringVar
    12
    13#implizit als String angelegte Variable
    14$variable = 'Das ist ein String'
    15
    16#implizit als Int32 angelegte Variable
    17$var2 = 2

    Boolsche Werte setzt man in PowerShell mit $true oder $false.

    1$boolean = $true

    Eine Konsolenausgabe erhalten wir mit Write Host

    1>> Write-Host 'Ich werde in der Konsole ausgegeben
    2<< Ich werde in der Konsole ausgegeben
    

    Spannend wird es bei Arrays und Objekten. Ein Array kann direkt als solches deklariert werden, ohne Deklaration wird es aber als Object Array angelegt. Wir können ein Array mit Werten bestücken, oder auch abkürzen:

    1#explizit deklarierters Int32 Array
    2[int[]] $integerArray = 1,2,3,4,5,6,7,8,9
    3
    4#ObjectArray
    5$objectArray = 1,2,3,4,5,6,7,8,9
    6
    7#Array mit Werten [1,2,3,4,5,6,7,8,9]
    8shortCutArray = 1..9

    Strings lassen sich mit dem ‚+‘ Operator verketten

    1$var1 = 'Hello'
    2$var2 = '!'
    3$var3 = $var1 + ' World' + $var2
    4>> $var3
    5<< Hello World!

    Eine Integer Variable wird auch einfach über ‚++‘ inkrementiert und mit ‚–‚ dekrementiert

    1$zahl = 1
    2
    3$zahl++
    4>> $zahl
    5<< 2
    6
    7$zahl--
    8>> $zahl
    9<< 1
    

    Hier eine kleine Auswahl der wichtigsten Datentypen

    DeklarationBeschreibung
    [Array]Array
    [Bool]True/False
    [Int32]/[Int]Ganzzahl
    [PsObject]Object
    [String]Zeichenkette
    [Float]Fließkommazahl

    Ein leeres Object Array könnt ihr einfach folgendermaßen erstellen. Es ist sehr nützlich um gesammelte Daten wie in einem Array zu bündeln, durchzuiterieren oder auszugeben:

    1$newEmptyObjectArray = @()

    Einem Array können andere Objekte einfach hinzugefügt werden

    1$newEmptyObjectArray += 'ich werde hinzugefuegt'

    Eine Abfrage erfolgt wie gewohnt

    1>> $newEmptyObjectArray[0]
    2<< ich werde hinzugefuegt
    

    Spannend sind auch die PSCustomObjects für Datensammler:

    1#Objekt anlegen
    2$newObject = New-Object PSCustomObject
    3
    4#Wert hinzufügen
    5$newObject | Add-Member -type NoteProperty -name 'Name' -value 'Wert'
    6
    7#Wert abfragen
    8>> $newObject.Name
    9<< Wert
    10
    11#Object abfragen
    12>> $newObject
    13<< Name
    14   -----
    15   Wert
    

    2. Schleifen und Verzweigungen

    If und Else funktionieren gewöhnlich. Dafür sind die Vergleichsoperatoren für den gemeinen Javaprogrammierer gewöhnungsbedürftig:

    1if($true){
    2    #do something
    3}else if($false){
    4    #do something else
    5}else{
    6    #do something different
    7}
    8#if(1 == 1)
    9if(1 -eq 1)
    10
    11#if(4 > 1)
    12if(4 -gt 1)
    13
    14#if(1 < 5)
    15if(1 -lt 5)
    16
    17#if(1 != 2)
    18if(1 -ne 2)
    19
    20#Ein Switch sieht so aus
    21$switchVariable = 5
    22switch ($switchVariable){
    23    1 { Write-Host 'Ich werde bei 1 ausgegeben' }
    24    5 { Write-Host 'Ich werde bei 5 ausgegeben' }
    25    Default { Write-Host 'Ich werde ausgegeben, wenn nichts passt' }
    26}

    While-, For- oder Foreach Schleifen gibt es auch

    1While($true){
    2    #do something
    3}
    4
    5$Numbers = 1..9
    6ForEach($Number in $Numbers){
    7    Write-Host $Number
    8}

    Fortsetzung folgt…

    Wir beschäftigen uns bald noch mit weiteren Themen wie:

    • Parameterübergabe
    • Weitere Schleifenarten
    • Filter
    • Nutzereingaben
    • Date
    • Try/Catch
    • CSV Export

    Mit etwas Kreativität eignet sich React für eine Vielzahl von Anwendungsmöglichkeiten. Doch dem Standard Package sind gewisse Grenzen gesetzt. Spätestens beim Thema SEO und HTTP Status Codes stoßen wir schnell an unsere Grenzen. Mit Server-Side Rendering können wir einige Klippen umschiffen. Dieser Guide soll euch eine Vorstellung geben wie ihr euer eigenes Boilerplate für React mit Server Side Rendering erstellt, ordentlich konfiguriert und möglichst komfortabel an eurer App arbeiten könnt, fast wie mit dem Standard CRA Package.

    Das Problem – Client Side Rendering

    React wird wie die meisten Javascript Frameworks Clientseitig gerendert. Eure Nutzer bekommen beim Aufruf eurer Website also erstmal eure komplette „Web-app“, aber nur eine sperrliche Index.html. Sobald alles geladen wurde übernimmt React und füllt eure Seite, aber eben Clientseitig. Das bedeutet zum einen, dass der Server nicht mehr gefragt wird, ob eine Seite existiert oder weiterleitet etc. Zum anderen sind Suchmaschinen meiner Erfahrung nach noch nicht darauf ausgelegt euren Javascript Code auszuführen. Suchmaschinen werden also immer nur im ersten Durchlauf eure leere Index.html lesen und keinen Informationsgehalt daraus ziehen. Das hat weiter zur Folge, dass die Suchmaschine den Inhalt verschiedener Unterseiten nicht unterscheiden kann und euch im Worst Case für doppelten Content abstraft. Das mag sich in Zukunft ändern, aber so lange Suchmaschinen euren Javascript Code nicht interpretieren bleibt euch keine andere Wahl als auf statische Seiten zu setzen oder serverseitig zu rendern.

    Die Lösung

    Wir werden einen Server aufsetzen, der die URL interpretiert, den passenden Inhalt auf dem Server rendert und das vollständige Ergebnis an den User oder die Suchmaschine übergibt. Für den User macht es im weiteren Verlauf keinen Unterschied. Sobald die Seite geladen wurde wird wie gewohnt React übernehmen und flüssig und schnell den Content bereitstellen. Suchmaschinen sind für die Bereitstellung des Inhalts aber äußerst dankbar. Denn auch sie bekommen den gerenderten Inhalt zu sehen. Des Weiteren habt ihr so die Möglichkeit HTTP Statuscodes zu setzen, z.B. für Seiten die nicht verfügbar sind einen 404 zu versenden. Darüber hinaus könnt ihr euch für jede Seite um SEO Inhalte bemühen wie Metatags und strukturierte Daten.

    1. Projekt Initialisieren

    Leg einen neuen Ordner für dein Projekt an, öffne ihn in einem Codeeditor deiner Wahl (ich empfehle Visual Studio Code) und initiiere dein Projekt

    1npm init

    Dieser Vorgang erstellt euer package.json. Diese sieht ähnlich aus wie folgende:

    1//package.json
    2{
    3  "name": "blog",
    4  "version": "1.0.0",
    5  "description": "test",
    6  "main": "src/server/server.js",
    7  "scripts": {
    8    "test": "echo \"Error: no test specified\" && exit 1"
    9  },
    10  "author": "Your Name",
    11  "license": "MIT"
    12}

    2. Server aufsetzen

    In eurem Projektordner erstellt ihr einen Ordner /src und darin einen Ordner /server. Darin erstellt ihr eine Datei server.js.
    Die Ordnerstruktur sieht nun so aus:

    Projectfolder
    |-package.json
    |-src
    ||-server
    |||-server.js

    Für den Anfang installieren wir express.js als Server.

    1npm i express --save

    öffnet nun server.js und setzt den Server auf:

    1const express = require('express')
    2
    3const app = express();
    4const PORT = process.env.PORT || 3000;
    5
    6app.listen(PORT,()=>{
    7    console.log(`App running ${PORT}`)
    8})

    Wenn wir später Webpack mit Babel installieren können wir die Syntax auf die gewohnte ES6 Syntax abändern. Für den Moment sollte es aber genügen. Wir können unseren Server starten und testen.
    Startet den Server mit

    1node src/server/server.js

    Öffnet einen Browser und ruft die Seite localhost:3000 auf. Dort wird die Meldung Cannot GET / erscheinen. Daran arbeiten wir als nächstes.

    3. Serverseitig rendern

    Wir bleiben in server.js und fügen nach dem PORT und vor app.listen... folgende Zeilen ein:

    1...
    2app.get('*', (req,res)=>{
    3    const html = `
    4    <!DOCTYPE html>
    5    <html lang="de">
    6    <head>
    7        <meta charset="UTF-8">
    8        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    9        <meta http-equiv="X-UA-Compatible" content="ie=edge">
    10        <title>Document</title>
    11    </head>
    12    <body>
    13        <h1>Hello Server</h1>
    14    </body>
    15    </html> 
    16    `
    17    res.send(html)
    18})
    19...

    Achtet auf die „ bei der html Konstanten.

    app.get(...) fängt alle URL Abfragen ab und verarbeitet diese. Mit res.send() senden wir die Antwort vom Server. In diesem Fall senden wir den Inhalt der Variable html. Diese wird unsere index.html ersetzen, die wir aus dem Standard CRA Package kennen.

    Ein neuer Test mach deutlich was wir gemacht haben. Startet den Server erneut wie oben beschrieben. Geht auf die Seite und siehe da…Hello Server sollte euch nun begrüßen.

    4. React einbinden

    Jetzt wird es spannend. Wir legen einen neuen Ordner /client im src-Ordner an. Darin erstellen wir die folgenden 3 Dateien: index.js, App.js und index.css. Eure Ordnerstruktur sieht nun so aus:

    1Projectfolder
    2|-package.json
    3|-src
    4||-server
    5|||-server.js
    6||-client
    7|||-App.js
    8|||-index.css
    9|||-index.js

    Nun installieren wir die notwendigen Pakete für React und React-Router:

    • react
    • react-router
    • react-router-dom
    • react-dom
    1npm i react react-router react-router-dom react-dom

    Die App.js könnt ihr fast identisch zum CRA – Standard anlegen. Wir wollen aber gleich den <Switch> und die erste <Route /> anlegen, damit wir gleich den Router integrieren:

    1import React, {Component} from 'react';
    2import{Route,Switch} from 'react-router-dom';
    3
    4class App extends Component{
    5    render(){
    6        return (
    7            <Switch>
    8                <Route exact path='/' render={props=>(
    9                    <h1>Hello React from Server</h1>
    10                )}/>
    11            </Switch>
    12        )
    13    }
    14}
    15export default App;

    Die index.js sieht ebenfalls fast wie das CRA Original aus. Wir verwenden allerdings hydrate statt render, damit React nur den Teil des DOMs updatet, der sich tatsächlich vom Original unterscheidet. An dieser Stelle wickeln wir die <App /> auch gleich in den <BrowserRouter> ein:

    1import React from 'react'
    2import ReactDOM from 'react-dom'
    3import './index.css'
    4import App from './App'
    5import {BrowserRouter} from 'react-router-dom'
    6
    7ReactDOM.hydrate(
    8    <BrowserRouter>
    9        <App />
    10    </BrowserRouter>
    11, document.getElementById('root'));

    In der index.css könnt ihr euch austoben wie ihr wollt. Wir kommen später zu einigen Besonderheiten.

    Nun sind ein paar Anpassungen an server.js notwendig, damit der Server die React App rendert und das Ergebnis an den Client sendet:

    1import express from 'express';
    2import React from 'react';
    3import ReactDOMServer from 'react-dom/server';
    4import App from '../client/App'
    5import {StaticRouter} from 'react-router';
    6
    7const app = express();
    8const PORT = process.env.PORT || 3000;
    9
    10app.get('*', (req,res)=>{
    11    const context = {}
    12    const content = ReactDOMServer.renderToString(
    13        <StaticRouter location={req.url} context={context}>
    14            <App />
    15        </StaticRouter>
    16    )
    17
    18    const html = `
    19    <!DOCTYPE html>
    20    <html lang="de">
    21    <head>
    22        <meta charset="UTF-8">
    23        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    24        <meta http-equiv="X-UA-Compatible" content="ie=edge">
    25        <title>Document</title>
    26    </head>
    27    <body>
    28        <div id='root'>${content}</div>
    29    </body>
    30    </html> 
    31    `
    32
    33    res.send(html)
    34})

    Gehen wir die Änderungen durch:

    1import express from 'express';
    2import React from 'react';
    3import ReactDOMServer from 'react-dom/server';
    4import App from '../client/App'
    5import {StaticRouter} from 'react-router';

    Wir ändern schon mal die Syntax zu ES6. So wird aus

    const express = require('express') --> import express from 'express';

    Dazu kommen die Importe für unsere React App.
    In der app.get(...) Methode ergänzen wir die Konstanten context, die für den HTTP Status Code relevant wird und erzeugen in content mit der renderToString() Methode, dem <StaticRouter>und der location unseren eigentlichen Inhalt für die Seite.
    Im html Boilerplate richten wir das bekannte <div id='root'> ein, welches Clientseitig verwendet wird um den Inhalt clientseitig zu rendern.
    Innerhalb des Tags setzen wir den ${content} als variable. Dieser wird den angefragten Inhalt, der serverseitig gerendert wurde initial an den Client übergeben. So kann auch eine Suchmaschine euren Inhalt lesen, denn sie bekommt den kompletten angefragten Content direkt vom Server. Interagiert euer User nun weiter mit der Seite, wird React mit dem <div id='root'> Container wie gewohnt arbeiten können.

    Noch können wir es nicht testen, denn euer Server kann die ES6 Syntax nicht interpretieren. Darum kümmern wir uns im nächsten Schritt.

    5. Webpack und Babel installieren und einrichten

    Erst mal brauchen wir eine ganze reihe von DevDependencies:

    • @babel/core
    • @babel/preset-env
    • @babel/preset-react
    • babel-loader
    • webpack
    • webpack-cli
    • mini-css-extract-plugin
    • css-loader
    • clean-webpack-plugin
    1npm i --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli mini-css-extract-plugin css-loader clean-webpack-plugin

    Außerdem verwenden wir nodemon um den Server automatisch zu starten:

    1npm i nodemon

    Das ist eine Menge, achtet bei der Installation auf --save-dev und auf die richtige Schreibweise. Dieser Guide bezieht sich auf die aktuellen Versionen von 2019. Es hat mich bei der Suche nach einem Tutorial wahnsinnig gemacht, dass größtenteils veraltete Versionen und damit auch veraltete Herangehensweisen verwendet wurden. Achtet also bitte auf die Versionen. Hier ist meine package.json damit ihr euch orientieren könnt. Weichen eure Versionen zu sehr ab, könnte es sein, dass es so nicht mehr funktioniert. Ich werde mich bemühen diesen Beitrag zu updaten, wenn sich grundlegend etwas ändert:

    1{
    2    "name": "blog",
    3    "version": "1.0.0",
    4    "description": "test",
    5    "main": "src/server/server.js",
    6    "scripts": {
    7        "webpack:build": "webpack --mode development --config webpack.config.js ",
    8        "server": "nodemon ./dist/main.js"
    9    },
    10    "author": "Christian Figul",
    11    "license": "MIT",
    12    "dependencies": {
    13        "express": "^4.17.1",
    14        "nodemon": "^1.19.2",
    15        "react": "^16.9.0",
    16        "react-dom": "^16.9.0",
    17        "react-router": "^5.1.0",
    18        "react-router-dom": "^5.1.0"
    19    },
    20    "devDependencies": {
    21        "@babel/core": "^7.6.2",
    22        "@babel/plugin-proposal-class-properties": "^7.5.5",
    23        "@babel/preset-env": "^7.6.2",
    24        "@babel/preset-react": "^7.0.0",
    25        "babel-loader": "^8.0.6",
    26        "clean-webpack-plugin": "^3.0.0",
    27        "css-loader": "^3.2.0",
    28        "mini-css-extract-plugin": "^0.8.0",
    29        "webpack": "^4.41.0",
    30        "webpack-cli": "^3.3.9"
    31    }
    32}

    Webpack benutzen wir zum einen um den babel-loader einzubinden, der die ES6 Syntax für den Browser interpretierbar macht. Außerdem nutzen wir Webpack um ein fertiges Build erstellen zu können, welches ihr dann auf euren Server schieben könnt, assets wie css und später auch Bilder oder Schriften sauber zu organisieren und einzubinden.

    In eurem Stammverzeichnis benötigen wir jetzt zwei neue Dateien:

    • .babelrc
    • webpack.config.js

    Euer Projektordner sieht nun also so aus:

    1Projectfolder
    2|-.babelrc
    3|-webpack.config.js
    4|-package.json
    5|-src
    6||-server
    7|||-server.js
    8||-client
    9|||-App.js
    10|||-index.css
    11|||-index.js

    In eure .babelrc schreibt ihr folgende Zeilen:

    1{
    2    "presets": ["@babel/preset-env", "@babel/preset-react"],
    3    "plugins": [
    4        ["@babel/plugin-proposal-class-properties", { "loose": false }]
    5    ]
    6}

    Eure webpack.config.js kann folgendermaßen aussehen:

    1const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    2const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    3
    4var path = require('path');
    5
    6const server = {
    7    target:'node',
    8    node:{
    9        __dirname:false
    10    },
    11    entry:{
    12        '../server':  './src/server/server.js',
    13        'client': './src/client/index.js'
    14    },
    15    module:{
    16        rules:[
    17            {
    18                test: /\.(js|jsx)$/,
    19                exclude:/node_modules/,
    20                use:{loader:"babel-loader"}
    21            },
    22            {
    23                test: /\.css$/,
    24                use:[
    25                    {loader: MiniCssExtractPlugin.loader},
    26                    {loader: 'css-loader'}
    27                ]
    28            }
    29        ]
    30    },
    31    plugins: [
    32        new CleanWebpackPlugin(),
    33        new MiniCssExtractPlugin({
    34            filename: '[name].css',
    35            chunkFilename: '[id].css',
    36            ignoreOrder: false
    37        }),
    38    ],
    39    output:{
    40        filename:'[name].js',
    41        path:path.resolve(__dirname,'dist', 'public')
    42    }
    43}
    44
    45module.exports = server

    Zu Beginn werden die Plugins geladen. Die path Variable brauchen wir für den aktuellen Verzeichnis Pfad. __dirname setzen wir auf false, weil ihr sonst Probleme mit der richtigen Verzeichnisangabe bekommt. Entry ist einmal euer Server mit server.js und euer Client mit index.js.

    In den Modulen übersetzen wir alle .js oder .jsx Dateien mit dem babel-loader. .css Dateien werden mit dem MiniCssExtractPlugin.loader verarbeitet. In den Plugin Einstellungen verwenden wir CleanWebpackPlugin, damit sich euer Build ordner immer säubert, bevor ihr ein neues Build erstellt, damit keine alten Dateien da drin rum liegen. Wir setzen ignoreOrder auf false, damit Klassennamen in der CSS Datei nicht umbenannt werden, was sonst die normale Folge wäre.

    Ihr könnt bei der Vergabe der Output Namen frei entscheiden wie die Dateien heißen sollen und wo sie liegen. Wenn ihr einfach diesem Guide weiter folgen wollt, empfehle ich diese Angaben nicht zu ändern.

    Nun richten wir uns noch in der package.json zwei Skripte ein, die uns die Arbeit etwas leichter machen:

    1"scripts": {
    2    "webpack:build": "webpack --mode development --config webpack.config.js ",
    3    "server": "nodemon ./dist/main.js"
    4}

    Vielleicht habt ihr sie oben schon bemerkt. Das webpack:build erzeugt euch ein Build, welches auf einen echten Server geladen werden kann. Mit server wird diese Version dann gestartet.

    Ereugt nun euer erstes Build und startet dann euren Server:

    1npm run webpack:build
    2npm run server
    

    Wenn alles gut gegangen ist, könnt ihr nun euer Ergebnis im Browser bewundern. Vielleicht sieht es noch nicht anders aus als vorher, aber es ist ein gewaltiger unterschied. Schaut euch den Source-Code an. Dort wird der Inhalt aus eurer App.js Datei angezeigt. Dieser wurde erst gerendert und dann an den Browser übergeben. Mit der Standard CRA, würdet ihr im Source-Code nur den leeren <div id='root'> Container sehen.

    6. React Funktionalität übertragen

    Ein wichtiger Schritt in der server.js steht noch aus, damit React wie gewohnt in eurer App funktioniert:

    1import ...
    2...
    3app.use(express.static('dist/public'))
    4app.get(...
    5    const html = `
    6    <!DOCTYPE html>
    7    <html lang="de">
    8    <head>
    9        ...
    10        <link href="client.css" rel="stylesheet">
    11    </head>
    12    <body>
    13        <div id='root'>${content}</div>
    14        <script type="text/javascript" src="client.js"></script>
    15    </body>
    16    </html> 
    17    `
    18    res.send(html)
    19})
    20...

    Vor app.get(...) ergänzt ihr app.use(...) und legt damit den öffentlichen Pfad eurer App fest, damit niemand Zugriff auf die server.js eures Buildordners bekommt, denn dort können sensible Daten abgelegt sein.
    Im html Boilerplate ergänzt ihr noch das Script zu eurer client.js um die React Funktionalität zu ergänzen.

    Das wars schon

    Ihr habt eine lauffähige, serverseitig gerenderte React App. In den nächsten Folgen bauen wir auf dieser Struktur auf und bauen unsere App weiter auf. Wir behandeln HTTP Status Codes, dynamische Metatags, Strukturierte Daten und binden weitere Assets wie Schriften und Bilder ein. Wir kümmern uns um Redux und Material-UI und ich zeige euch so, wie ihr eigentlich schon ab diesem Zeitpunkt wie gewohnt weiter entwickeln könnt und am wie man einen express Server selber hosten kann, ohne Heroku oder andere Dienstleister.

    business portrait von christian figul
    Lassen Sie uns gemeinsam durchstarten!Als Senior Full Stack Entwickler stehe ich Ihnen zur Seite bei allen Fragen zum Thema Projektplanung und Umsetzbarkeit.
    Lösungen finden, statt Probleme schaffen – Das ist mein Credo.
    Kontaktieren Sie mich und wir finden das perfekte Paket auch für Ihre Bedürfnisse.

    Ihr persönlicher Ansprechpartner: Christian Figul