Webpack in TYPO3 integrieren 

published 10 February 2019

Integriere Webpack in TYPO3 um komplexere Webseiten bauen zu können

Es gibt viele Gründe ein eigenes Asset-Management in TYPO3 verwenden zu wollen. Von dingen wie scss bis über autoprefixer bis hin zu Babel um modernes Javascript verwende zu können ohne IE support zu verlieren.

Für diesen Guide erwarte ich das Du bereit grundlegendes Verständnis von Webpack und fortgeschrittenderes Verständnigs von TYPO3 hast.

Minimal Setup 

Zuerst müssen wir uns überlegen wie wie unsere Dateien nun organisieren. Normalerweise würde man JavaScript und Css in Resource/Public legen und dann über TypoScript referenzieren. Aber da wir TYPO3’s asset management umgehen wollen müssen wir uns etwas neues ausdenken.

In Webpack gibt es “entrypoints” welches JavaScript Dateien sind welche für das starten der frontend Anwendung verantwortlich sind. Ich habe mir also folgendes ausgedacht: Ich lege in jede Extension eine ext_index.js. Damit hab ich nun in jeder Erweiterung erneut die Möglichkeit zu entscheiden wie ich dort mit assets umgehe. So eine Datei sieht so aus:

import $ from 'jquery';

function requireAll(require) {
    require.keys().forEach(require);
}

requireAll(require.context('./Resources/Private/', true, /^[^_]+\.css$/));
$(() => requireAll(require.context('./Resources/Private/', true, /^[^_]+\.js$/)));

Und hier die dazugehörige kleine webpack configuration. Ich hab dort auch direkt das mini-css-extract-plugin eingebunden um neben einer js Datei auch eine css Datei zu erzeugen.

const glob = require('glob');
const path = require('path');

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    entry: {index: glob.sync('./web/typo3conf/ext/*/ext_index.js')},
    output: {path: path.resolve(__dirname, 'web/build')},
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: argv.mode === 'development' ? '[name].css' : '[contenthash].css',
            chunkFilename: argv.mode === 'development' ? '[id].css' : '[contenthash].css'
        })
    ]
};

Bei mir wird also die ext_index.js in jeder Extension in alphabetischer Reihenfolge geladen. Von dort aus lade ich schlicht alle css Dateien die im Resource/Private Ordner dieser Erweiterung sind. Dazu lade ich zusätzlich alle JavaScript Dateien, aber erst nach dem der dom ready ist. Damit sorge ich dafür das jegliche styles zuerst eingebunden sind und außerdem erspaare ich mit dieses konstrukt in den einzelnen Modulen.

Nun müssen wir die entstehende js Datei nurnoch einbinden. Dies geht am leichtesten mit etwas gutem alten TypoScript.

page.includeCss.index = build/index.css
page.includeJsFooter.index = build/index.js

Und damit kannst du nun los legen. Einfach die resourcen mit den webpack befehlen bauen und typo3 wird sie einbinden und durch einen timestamp auch cache bursting betreiben. Es geht aber noch ein wenig besser.

Dynamische filenamen und hot reloading 

Eines der wohl besten Features von Webpack ist hot module reloading. Im einfachsten Fall nutzen wir es nur für stylesheets aber selbst dort ist es ein segen nicht auf die ungecachten Ladezeiten von TYPO3 warten zu müssen.

Dafür müssen wir erst einmal 2 Probleme lösen:

Also den lokalen webserver von webpack zum laufen zu bringen ist erst einmal einfach. Installiere das Packet webpack-dev-server und du kannst theoretisch los legen. Allerdings kann es zu problemen wegen den public Path kommen. Das 2te Problem ist das du nun im typoscript auf eine localhost addresse verweisen musst damit dein Browser die richtigen Resourcen lädt.

Beides lässt sich mit den webpack-manifest-plugin lösen. Dieses schreibt eine kleine json Datei in welcher steht wie die Datei build/index.js vom Browser aufgerufen werden kann. So eine Datei sieht so aus:

{
  "build/index.js": "http://0.0.0.0:3002/build/index.js"
}

Und so binden wir wir den Spaß ein.

const glob = require('glob');
const path = require('path');

const ManifestPlugin = require('webpack-manifest-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (env, argv) => ({
    entry: {index: glob.sync('./web/typo3conf/ext/*/ext_index.js')},
    output: {path: path.resolve(__dirname, 'web/build')},
    devServer: {headers: {'Access-Control-Allow-Origin': '*'}},
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: [
                    argv.hot ? 'style-loader' : MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        new ManifestPlugin({
            basePath: 'build/',
            writeToFileEmit: true
        }),
        ...(argv.hot ? [] : [
            new MiniCssExtractPlugin({
                filename: argv.mode === 'development' ? '[name].css' : '[contenthash].css',
                chunkFilename: argv.mode === 'development' ? '[id].css' : '[contenthash].css'
            })
        ])
    ]
});

Dir fällt bestimmt auf das ich das MiniCssExtractPlugin nun nicht mehr einbinde wenn das argument --hot übergeben wurde. Das liegt daran das hot module replacement mit css dateien nur im zusammenhang mit dem style-loader funktioniert.

So, nun müssen wir TYPO3 nur noch dazu bringen unsere manifest.json zu lesen. Dies geht am besten mit einem hook.