Comme j'ai récemment ajouté [TypeScript et WebPack encore à Grav] (/blog/adding-webpack-encore-and-typescript-to-grav), j'ai voulu trouver un moyen d'ajouter CSS et JavaScript uniquement à certaines pages.
Générer des sorties multiples pour les assets
Jusqu'à présent, j'ai généré une page assets.html
qui contenait les liens vers les fichiers JavaScript et CSS générés par
WebPack :
<script defer src="/user/themes/lingonberry-custom/build/app/app.js"></script>
<link href="/user/themes/lingonberry-custom/build/app/app.css" rel="stylesheet">
Pour générer différents fichiers, j'ai modifié le fichier webpack.config.js
. J'ai trouvé la plupart des informations dans
la [documentation Symfony] (https://symfony.com/doc/current/frontend/encore/advanced-config.html).
Cependant, comme je n'aime pas trop me répéter, j'ai créé une fonction :
function generateBaseConfig(name, buildPath, inputFile, outputFile, cleanOutput = true) {
let itemBuildPath = path.join(BUILD_PATH_BASE, name);
if (buildPath) {
itemBuildPath = path.join(BUILD_PATH_BASE, buildPath);
}
if (!inputFile) {
inputFile = `${name}.ts`
}
if (!outputFile) {
outputFile = 'assets.html'
}
let config = Encore
// directory where compiled assets will be stored
.setOutputPath(itemBuildPath)
// public path used by the web server to access the output path
.setPublicPath(path.join('/', itemBuildPath))
.addEntry(name, `./${path.join(PATH, 'js', inputFile)}`)
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
//.splitEntryChunks()
// will require an extra script tag for runtime.js
// but, you probably want this, unless you're building a single-page app
//.enableSingleRuntimeChunk()
.disableSingleRuntimeChunk()
.enableSourceMaps(!Encore.isProduction())
// enables hashed filenames (e.g. app.abc123.css)
.enableVersioning(Encore.isProduction())
.addPlugin(new CompressionPlugin())
// .addPlugin(new MiniCssExtractPlugin())
.addPlugin(new HtmlWebpackPlugin({
inject: false,
filename: outputFile,
publicPath: path.join('/', itemBuildPath),
scriptLoading: 'defer',
templateContent: ({htmlWebpackPlugin}) => `
${htmlWebpackPlugin.tags.headTags}
`
}))
// enables @babel/preset-env polyfills
.configureBabelPresetEnv((config) => {
config.useBuiltIns = 'usage';
config.corejs = 3;
})
.enableSassLoader()
.enableTypeScriptLoader();
if (cleanOutput) {
config.cleanupOutputBeforeBuild()
}
return config.getWebpackConfig()
}
Donc maintenant pour ajouter plusieurs entrées/sorties je n'ai plus qu'à ajouter quelques lignes :
const PATH = path.join('user', 'themes', 'lingonberry-custom')
const BUILD_PATH_BASE = path.join(PATH, 'build')
const baseEncoreConfiguration = generateBaseConfig('app');
baseEncoreConfiguration.name = 'baseEncoreConfiguration'
Encore.reset();
const animationsConfiguration = generateBaseConfig('animations');
animationsConfiguration.name = 'animationsConfiguration'
module.exports = [baseEncoreConfiguration, animationsConfiguration];
Si vous ajoutez seulement un nom lors de l'appel de la fonction generateBaseConfig
, l'entrée sera
de {{themeName}}/js/{{name}}.ts
et la sortie sera {{themeName}}/build/{{name}}/assets.html
. donc dans l'exemple précédent j'avais les fichiers suivants :
user/themes/lingonberry-custom
âââ js
  âââ typewriter.ts
  âââ app.ts
Ce qui a généré la sortie suivante :
user/themes/lingonberry-custom
âââ build
  âââ animations
  â  âââ animations.css
  â  âââ animations.js
  â  âââ animations.js.gz
  â  âââ assets.html
  â  âââ assets.html.gz
  â  âââ entrypoints.json
  â  âââ manifest.json
  â  âââ manifest.json.gz
  âââ app
  â  âââ app.css
  â  âââ app.css.gz
  â  âââ app.js
  â  âââ app.js.gz
  â  âââ assets.html
  â  âââ assets.html.gz
  â  âââ entrypoints.json
  â  âââ fonts
  â  âââ images
  â  âââ manifest.json
  â  âââ manifest.json.gz
  âââ entrypoints.json
  âââ manifest.json
  âââ manifest.json.gz
Chaque entrée de fichier a un répertoire de sortie séparé. Sans cela la fonction cleanupOutputBeforeBuild
d'Encore viderait toujours le répertoire principal de construction. Comme les constructions se déroulent en parallÚle, il n'en resterait toujours qu'un à la fin,
les autres étant supprimés.
Ajout des fichiers générés aux modÚles twig
Maintenant qu'ils sont gĂ©nĂ©rĂ©s, les fichiers de sortie doivent ĂȘtre inclus dans nos modĂšles twig. Pour les assets principaux, j'ai simplement ajoutĂ©
{% include 'app/assets.html' %}
dans la balise header du fichier twig de base de mes templates.
J'ai également voulu simplifier l'ajout de fichiers à partir du backend pour une page de blog spécifique. Pour ce faire, j'ai [généré un nouveau plugin grav] (https://learn.getgrav.org/17/plugins/plugin-tutorial) Header Include.
Pour ajouter des options supplémentaires à une page, j'ai ajouté un blueprint plugins/header-include/blueprints/header-include.yaml
:
form:
fields:
tabs:
fields:
options:
type: tab
fields:
header-include:
type: section
title: HEADER_INCLUDE.PLUGIN.NAME
underline: true
fields:
header.header-include.active:
type: toggle
toggleable: true
label: HEADER_INCLUDE.PLUGIN.ACTIVE
help: HEADER_INCLUDE.PLUGIN.ACTIVE_HELP
highlight: 1
# config-default@: plugins.header-include.active
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
header.include:
type: multilevel
label: HEADER_INCLUDE.PLUGIN.ITEMS
value_only: true
validate:
type: array
Il contient juste un bouton on-off, et une liste d'Ă©lĂ©ments que nous voulons inclure dans notre en-tĂȘte. Pour inclure les nouveaux fichiers d'animation dans les options de cette page, j'ai dĂ©fini la configuration suivante :
Maintenant pour s'assurer que tous les fichiers définis dans les options sont effectivement inclus, j'ai ajouté un modÚle {{themeName}}/partials/includes.html.twig
:
{% for filename in page.header.include %}
{% include filename %}
{% endof %}
et j'ai inclus ce fichier dans le fichier de base de mon modĂšle.
Maintenant, lorsque j'ajoute quelques feuilles de style en cascade (CSS) au fichier animation.scss (que j'ai volé à CSS-Tricks), un peu de script, et je peux obtenir ces jolis effets, mais uniquement dans les pages qui en ont besoin.
@use 'variables' as *;
.typewriter {
max-width: fit-content;
display: block;
text-align: left;
overflow: hidden; /* Ensures the content is not revealed until the animation */
border-right: .15em solid $primary-color;/* The typwriter cursor */
white-space: nowrap; /* Keeps the content on a single line */
margin: 0 auto; /* Gives that scrolling effect as the typing happens */
letter-spacing: .15em; /* Adjust as needed */
animation:
typing 3.5s steps(38, end),
blink-caret .75s step-end infinite;
&.slow {
animation:
typing 7s steps(52, end),
blink-caret 1.5s step-end infinite;
}
}
/* The typing effect */
@keyframes typing {
from { width: 0 }
to { width: 100% }
}
/* The typewriter cursor effect */
@keyframes blink-caret {
from, to { border-color: transparent }
50% { border-color: $primary-color}
}
.retype-btn {
border-radius: 5px;
padding: 0.6rem 1.2rem;
cursor: pointer;
background-color: $darkmode-bg-color;
color: $primary-color;
transition: color 200ms ease-in-out,
background-color 200ms ease-out;
;
&:hover {
color: $darkmode-bg-color;
background-color: $primary-color;
}
}
//components/typewriter.ts
export default class Typewriter {
retypeButtons: HTMLButtonElement[]
typewriters: HTMLElement[]
constructor() {
this.retypeButtons = <HTMLButtonElement[]><any>document.getElementsByClassName('retype-btn');
this.typewriters = <HTMLElement[]><any>document.getElementsByClassName('typewriter');
this.retypeButtons.forEach((item) => {
item.addEventListener('click', (event) => {
this.typewriters.forEach((item) => {
const clone = item.cloneNode(true)
item.parentNode?.replaceChild(clone, item)
})
})
})
}
}
//typewriter.ts
import '../css/animations.scss'
import Typewriter from './components/typewriter'
document.addEventListener("DOMContentLoaded", function () {
new Typewriter();
});
Ici, dans l'éditeur markdown de Grav admin pour ma page de blog, j'ajoute
<span class="typewriter">I'm a Typewriter !!</span>
<span class="typewriter slow">I'm a sloooow Typewriter !!</span>
<button id="retype-btn" class="retype-btn">Retype</button>
I'm a Typewriter !! I'm a sloooow Typewriter !!
Les CSS et JavaScript sont maintenant appliquĂ©s mais seulement Ă cette page :) đ đ
Dennis de Best
Commentaires