# Wie Docker Container die Einrichtung der SPFx Umgebung überflüssig machen

[Read in English](https://spfx-app.dev/how-docker-containers-eliminate-spfx-environment-setup).

Das SharePoint Framework (SPFx) ist ein Package, welches sehr oft aktualisiert wird. Das ist auf der einen Seite sehr gut, auf der anderen Seite ist es aber auch ein Problem. Denn, je nach Version, verwendet es unterschiedliche node, npm, TypeScript und React Versionen (und natürlich noch andere Packages).

Die erste SPFx Version (1.0.0), die vor ziemlich genau 5 Jahren veröffentlicht wurde, benötigt Node v6 LTS und npm v3.

Die SPFx Version 1.4.1 (für SP 2019 On-Premises) hat schon Node v8 LTS unterstützt und auch npm v4.

Die gestern (17. Februar 2022) veröffentlichte SPFx Version 1.14 benötigt mittlerweile mindestens Node v12 LTS. Eine Auflistung aller SPFx Versionen und welche Node, npm etc. Version diese benötigt, findest Du [hier](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/compatibility).

Ist doch kein Problem, könnte man denken. Dann wird eben immer die neuste Version installiert bzw. auf die neuste Version aktualisiert. Nun, ganz so einfach ist es dann doch nicht. Was, wenn man noch "alte" Projekte hat? Projekte, die man weiter betreuen muss und die z. B. für SP2019 oder sogar SP2016 entwickelt wurden. Denn zwei unterschiedliche Node Versionen gleichzeitig zu installieren ist nicht möglich auf einem Computer. Klar, man kann eine VM verwenden. Aber dann hat man viele VMs, die auch noch unnötig CPU/RAM und vor allem auch Festplattenspeicher verbrauchen. Und jede davon muss man einrichten.

## Docker Container einrichten

Mein nächstes Projekt (kein SharePoint) soll Docker verwenden. Ist mir natürlich ein Begriff, immerhin spricht jeder darüber. Aber Erfahrung hatte ich keine damit. Um nicht ohne Kenntnisse über Docker mit diesem Projekt zu beginnen, habe ich mir vor zwei Tagen ein [super gutes Video auf Youtube (auf Deutsch)](https://www.youtube.com/watch?v=DESdVoKhIxY), von [Golo Roden](https://github.com/goloroden), angeschaut. Der hat es wahnsinnig gut erklärt und ich habe jetzt wahrscheinlich mehr als nur Basis-Kenntnisse zu Docker. Danach habe ich mir gedacht, eigentlich könnte man doch sowas auch für SPFx erstellen. Dann müsste man die Umgebung überhaupt nicht mehr einrichten (spart Zeit) und man kann unterschiedliche Versionen gleichzeitig verwenden. Bevor ich mir mein eigenes Dockerfile erstelle, habe ich natürlich geschaut, ob schon ein Docker Image existiert für SPFx. Und siehe da, es gibt [eines](https://hub.docker.com/r/m365pnp/spfx). Das wollte ich sofort ausprobieren. Ich werde übrigens **nicht** darauf eingehen, wie Du Docker einrichtest/installierst oder was es ist, wie es funktioniert oder wie man damit umgeht. Das ist für den Artikel auch nicht relevant. Es werden auch **keine** Docker Kenntnisse benötigt.

Vor zwei Tagen war SPFx 1.14 noch nicht offiziell veröffentlicht. Deswegen habe ich mir das Image für SPFx 1.13 heruntergeladen. Die [Dokumentation zu dem Docker Image](https://hub.docker.com/r/m365pnp/spfx) ist eigentlich ganz gut, jedoch bin ich über ein Paar Hindernisse gestolpert. Schon im ersten Schritt!

`in Docker Settings > Shared Drives verify that the drive where you create your projects is shared`

Diesen Einstellung gibt es bei mir nicht! Das hat mehrere Gründe. Den "Shared Drive" gibt es seit längerem nicht mehr unter [Docker Desktop](https://www.docker.com/products/docker-desktop). Denn eigentlich müsste es unter `Docker Settings > Resources > File Sharing` zu finden sein.

![wo die Einstellung eigentlich dein sollte](https://cdn.hashnode.com/res/hashnode/image/upload/v1645195399028/XbfRLlRQS.png align="left")

Ja, ich weiß, ich muss mein Docker Desktop updaten 😜

Doch auch diese Einstellung ist bei mir nicht zu finden. Man muss dazu sagen, dass ich ein Windows 10 Betriebssystem habe und WSL 2 (Windows Subsystem for Linux) verwende.

![WSL2 im docker desktop](https://cdn.hashnode.com/res/hashnode/image/upload/v1645195487297/Q1bNka9My.png align="left")

Unter WSL 2 benötigt man ein File Share nicht, weil sozusagen alles auf dem lokalen Computer ausgeführt wird statt auf einem Hyper-V backend.

Nachdem ich das herausgefunden habe, wollte ich mit den weiteren Schritten beginnen. Also habe ich einen Ordner erstellt. Dort dann den Befehl wie in der [Dokumentation](https://hub.docker.com/r/m365pnp/spfx) beschrieben eingegeben:

```bash
docker run -it --rm --name spfx-helloworld -v ${PWD}:/usr/app/spfx -p 4321:4321 -p 35729:35729 m365pnp/spfx
```

Ich erkläre Dir kurz die Befehle:

`docker run` erstellt einen neuen Container

`-it` steht für `interactive`, d. h. man bleibt nach dem erstellen "im Container". Das ist meistens eine Terminal/Bash Fenster

`--rm` bedeutet, dass nach dem beenden des Containers, dieser auch gelöscht wird

`--name` ist der Name des Containers

`-v` ist das Volume mapping. Dabei wird der aktuelle lokale Pfad (`${PWD}`) auf den Containerpfad usr/app/spfx gemapped

`-p` steht für Port. Hiermit wird der lokale Port `4321` mit den Container Port `4321` verknüpft. Und der Port `35729` mit dem Container Port `35729`.

Und zum Schluss wird noch der Name des Images angegeben ==&gt; [m365pnp/spfx](https://hub.docker.com/r/m365pnp/spfx).

Nun wird der Container erstellt und es öffnet sich eine Shell. Diese Shell ist die Shell des Containers. Deswegen musst Du dort auch Deine ganzen `yo`, `npm`, `gulp` und sonstige Befehle angeben und **nicht** auf dem lokalen Computer.

Das bedeutet, gib nach dem `yo @microsoft/sharepoint`\-Befehl Deine ganzen `npm`, `gulp` usw. Befehle ein. Erst wenn Du zu 100% fertig bist (z. B. Package veröffentlich, Testing beendet etc.) gibst Du `exit` ein. Danach ist der Container beendet und wird aufgrund des `--rm` Befehls auch sofort gelöscht. Aber keine Angst, nicht das Projekt. Das liegt schließlich auf Deinem lokalen Computer. Wenn Du später wieder mit dem Projekt arbeiten willst, musst Du den Container wie oben beschrieben einfach erneut erstellen (im Projektverzeichnis).

Theoretisch könntest Du nun mit `gulp serve` das Projekt starten. Doch das wird beim ersten Mal nicht funktionieren. Dies ist auch [in der Dokumentation des Docker Images](https://hub.docker.com/r/m365pnp/spfx) beschrieben. Denn Du musst zuerst die `serve.json` Datei, die sich im `config` Ordner befindet, bearbeiten. Dazu muss man den `hostname` auf `0.0.0.0` setzen.

```json
{
  "$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
  "port": 4321,
  "hostname": "0.0.0.0",
  "https": true,
  "initialPage": "https://yourtenant.sharepoint.com/_layouts/workbench.aspx"
}
```

Da ich - wie zu Beginn beschrieben - die SPFx Version 1.13 verwendet habe, musste ich laut Anleitung auch noch eine JavaScript Datei in Zeile 393 bearbeiten, die hier liegen soll:

`node_modules\@microsoft\spfx-heft-plugins\lib\plugins\webpackConfigurationPlugin\WebpackConfigurationGenerator.js`

Doch auch das ist nicht ganz richtig. Denn diese Datei liegt eigentlich unter:

`node_modules\@microsoft\sp-build-web\node_modules\@microsoft\spfx-heft-plugins\lib\plugins\webpackConfigurationPlugin\WebpackConfigurationGenerator.js`

![wo man den Code anpassen muss](https://cdn.hashnode.com/res/hashnode/image/upload/v1645196121048/0m1ZkZeeN.png align="left")

Diesen Schritt muss man in der neuen SPFx Version 1.14 nicht mehr durchführen. Es wird nun von Microsoft selbst geprüft, ob man sich in einem Container befindet oder nicht ([anhand der ipAddress-Eigenschaft in der serve.json-Datei](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/release-1.14#ipaddress-property-in-servejson)). Falls Du eine andere Version verwendest, schau bitte in der [Dokumentation des Docker Images](Link) nach, ob Du noch etwas beachten musst. Übrigens wurde in der Version 1.14 auch mein [gemeldeter Bug](https://github.com/SharePoint/sp-dev-docs/issues/5787) mit dem [Fullmask Permission Check](https://spfx-app.dev/sharepoint-spfx-why-you-shouldnt-use-fullmask-check-yet) behoben 😊

Jetzt könnte man `gulp serve` ausführen und die SharePoint workbench aufrufen. `https://{tenant}.sharepoint.com/_layouts/15/workbench.aspx`

Doch Du wirst feststellen, dass Deine Komponente nicht vorhanden ist. Dieser Punkt wird in der [Dokumentation der Docker Image](https://hub.docker.com/r/m365pnp/spfx) überhaupt nicht aufgeführt. Die Ursache ist, dass das Zertifikat nicht vertrauenswürdig ist. Dabei ist es egal, ob Du auf Deiner lokalen Umgebung bereits `gulp trust-dev-cert` ausgeführt hast oder ob Du das bereits im Container gemacht hast. Damit die SPFx Komponente angezeigt wird, muss die URL `https://localhost:4321` aufgerufen werden (auf der lokalen Umgebung). Danach musst Du dem Zertifikat vertrauen.

![Zertifikat im Browser Vertrauen](https://cdn.hashnode.com/res/hashnode/image/upload/v1645196549307/JL8DiyorD.png align="left")

Jetzt sollte Deine Komponente auch in der workbench (oder wo auch immer) angezeigt werden.

## SPFx Fast Serve im Docker Container

Da ich ein sehr großer Fan von dem [spfx-fast-serve Package](https://github.com/s-KaiNet/spfx-fast-serve) bin (falls Du es nicht kennst, musst Du es unbedingt verwenden!). Deswegen habe ich auch in diesem Container versucht `spfx-fast-serve` anzuwenden. Es gab dazu zwar ein [GitHub issue](https://github.com/s-KaiNet/spfx-fast-serve/issues/47), doch war der "workaround" für mich so nicht akzeptabel. Deswegen habe ich versucht das Problem anders zu beheben. Seit gestern gibt es auch hierfür eine Lösung. [Sergei Sergeev](https://github.com/s-KaiNet), der Autor des Packages, wird [meine Lösung](https://github.com/s-KaiNet/spfx-fast-serve/issues/47#issuecomment-1042910356) auch offiziell in seiner Readme aufnehmen.

Um `spfx-fast-serve` in Deinem Container zu verwenden, musst Du nur die `fast-serve/webpack.extend.js` öffnen und die Konstante `webpackConfig` anpassen:

```javascript
const webpackConfig = {
  devServer: {
    host: '0.0.0.0',
    publicPath: 'https://0.0.0.0:4321/dist/',
    sockHost: 'localhost',
    watchOptions: {
      aggregateTimeout: 500, // delay before reloading
      poll: 1000 // enable polling since fsevents are not supported in docker
    }
  },
  output: {
    publicPath: 'https://0.0.0.0:4321/dist/'
  }
}
```

Jetzt funktioniert der `npm run serve` Befehl auch im Container.

## Eigenes Docker image. 70% Kleiner und mit spfx-fast-serve

Das [offizielle Docker image](https://hub.docker.com/r/m365pnp/spfx) ist schon sehr gut und hilfreich. Was mir jedoch nicht gefallen hat, war die Imagegröße. Die beträgt nämlich 1.22 GB und ich wollte nicht immer das `spfx-fast-serve` Package zusätzlich installieren müssen. Deswegen habe ich mir überlegt, ein eigenes Image zu erstellen. Dazu habe ich in dem [Youtube Video](https://www.youtube.com/watch?v=DESdVoKhIxY) gelernt, dass das Linux Alpine Betriebssystem um einiges kleiner ist, weil es ohne bestimmte Packages ausgeliefert wird, welche die anderen Linux Distributionen verwenden. Das heißt, das bei dem [SPFx Docker Image](https://hub.docker.com/r/m365pnp/spfx) die Linux Distribution so viel Platz einnimmt bzw. das verwendete Node Image diese Linux Version verwendet. Man kann aber Node auch in der [Alpine](https://alpinelinux.org/) Version verwenden. Ich habe also das [Original Dockerfile](https://github.com/pnp/docker-spfx/blob/master/Dockerfile) kopiert und es "Alpine"-Ready gemacht. Denn die Befehle für Alpine unterscheiden sich dabei. Ich habe es sozusagen übersetzt.

Herausgekommen ist dabei folgendes Dockerfile mit spfx-fast-serve:

```bash
FROM node:14.19.0-alpine3.15

EXPOSE 5432 4321 35729

ENV NPM_CONFIG_PREFIX=/usr/app/.npm-global \
  PATH=$PATH:/usr/app/.npm-global/bin

VOLUME /usr/app/spfx
WORKDIR /usr/app/spfx

RUN apk add sudo && \
    apk --no-cache add shadow && \
    echo '%wheel ALL=(ALL) ALL' > /etc/sudoers.d/wheel && \
    adduser --home "$(pwd)" --disabled-password --ingroup wheel --shell /bin/ash spfx && \
    usermod -aG wheel spfx && \
    chown -R spfx:wheel /usr/app

USER spfx

RUN npm i -g gulp@4 yo @microsoft/generator-sharepoint@1.13.1 spfx-fast-serve

CMD /bin/ash
```

Wenn man die Image-Größen vergleicht, ist [mein Image](https://hub.docker.com/r/seryoga/spfx) um 70% kleiner, und zwar nur noch 400MB

![docker images Dateigroesse im Vergleich](https://cdn.hashnode.com/res/hashnode/image/upload/v1645197080145/kaVplMflA.png align="left")

In diesem Container musst Du die gleichen Schritte machen, die ich oben beschrieben habe. Nach dem erstellen der Solution kannst Du einmalig `spfx-fast-serve` ausführen (installiert werden muss es nicht mehr, das wurde im Docker Image gemacht) und danach `npm i` und schon kannst Du `npm run serve` verwenden.

That's it. Happy Coding ;)

![SPFx in docker](https://cdn.hashnode.com/res/hashnode/image/upload/v1645197241212/GXv6Hiy2y.png align="left")
