Shopify Frontend

Usare Preprocessori CSS e Babel su Shopify

Lettura 7 minuti

Lavorare sul frontend di Shopify può essere frustrante se sei abituato alle nuove strabilianti funzionalità e strumenti come i preprocessori CSS, Javascript ES6 e i task runner o pack bundler che eseguono per te alcune comuni e noiose operazioni come aggiungere gli autoprefissi alle nuove specifiche CSS.

Questo si sente maggiormente quando non sei l’unica persona a lavorare ad un tema Shopify.

Ultimamente, infatti, ho avuto l’occasione di lavorare su un tema Shopify con due altre persone e voi sapete che, come sviluppo Shopify, non c’è ambiente locale e tutto il sito è testabile su un ambiente online tipo stage pertanto, se più persone lavorano sullo stesso file, questo viene costantemente sovrascritto.

Come funziona Shopify

Tutti i file liquid sono file speciali che, oltre a parsare la sintassi liquid, possono anche leggere le variabili delle impostazioni del sito. Questo funziona anche per i file css e js che, insieme alla propria estensione, hanno anche l’estensione liquid.
Ad esempio style.css.liquid e script.js.liquid quando inseriti nella cartella assets e aggiunti al tag <head> del layout template con {{'style.css' | asset_url | stylesheet_tag}} e {{'script.js' | asset_url | stylesheet_tag}} verranno parsati per sostituire tutte le variabili di liquid con il loro valore.
Se poi si include un style.scss.liquid così {{'style.scss' | asset_url | stylesheet_tag}} il sass sarà, inoltre, compilato in css.
Ma questo è tutto. Non è possibile scrivere js moderno perché non esiste una traspilazione babel-like sul server per js, come avviene invece per la compilazione di sass ad esempio, e non sono fornite sourcemap. Un po’ poco per essere nel 2019.

Quindi, dopo aver studiato come Shopify funziona e aver analizzato i nostri bisogni, questa è la soluzione proposta.

Necessità

  • Vorremmo essere in grado di lavorare su file scss separati che possano essere combinati insieme per la produzione senza ulteriori interventi manuali
  • Vorremmo usare un task con l’autoprefixer automatico durante la compilazione dei file
  • Vorremmo avere le sourcemaps nei nostri css compilati per facilitarci il debug
  • Vorremmo poter essere in grado di usare Javascript ES6 senza preoccuparci delle compatibilità tra browser

Perciò configuriamo il nostro package.json!

Creiamo un file package.json e copiamoci questo dentro:

{
    "name": "cool-shopify",
    "version": "1.0.0",
    "devDependencies": {
        "babel-core": "^6.26.3",
        "babel-preset-env": "^1.7.0",
        "babel-preset-es2015": "^6.24.1",
        "gulp": "^3.9.1",
        "gulp-autoprefixer": "^6.0.0",
        "gulp-babel": "^7.0.1",
        "gulp-concat": "^2.6.0",
        "gulp-if": "^3.0.0",
        "gulp-rename": "^1.4.0",
        "gulp-sass": "^4.0.2",
        "gulp-sourcemaps": "^2.6.5",
        "gulp-uglify": "^3.0.1",
        "node-sass": "^4.11.0"
    },
    "dependencies": {
        "babel-polyfill": "^6.26.0"
    },
    "browserslist": [
        "defaults"
    ]
}

e poi lanciamo npm install.

Useremo Gulp + Babel per comodità, ma può essere tranquillamente configurato anche con Webpack, credo.

Il Gulpfile

'use strict';

const gulp = require('gulp');
const babel = require('gulp-babel');
const autoprefixer = require('gulp-autoprefixer');
const concat = require('gulp-concat');
const gulpif = require('gulp-if');
const rename = require('gulp-rename');
const sass = require('gulp-sass');
const uglify = require('gulp-uglify');
const sourcemaps = require('gulp-sourcemaps');

// set variables
const env = process.env.NODE_ENV || 'production';
const srcDest = './assets/';
const srcPath = './src/';

/**
* SCSS task
*/
gulp.task('css', function () {
    if (env === 'production') {
        gulp.src(srcPath + 'scss/**/*.scss.liquid')
        .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError))
        .pipe(autoprefixer({ cascade : false }))
        .pipe(rename('theme.scss.liquid'))
        .pipe(gulp.dest(srcDest));
    } else {
        // splitted css when more than one person is working on the project. Example of css name: <name-of-the-person>.chunk.scss
        gulp.src(srcPath + 'scss/**/*.chunk.scss')
        .pipe(gulpif(env !== 'production', sourcemaps.init()))
        .pipe(sass({ outputStyle: 'compact' }).on('error', sass.logError))
        .pipe(autoprefixer({ cascade : false }))
        .pipe(gulpif(env !== 'production', sourcemaps.write()))
        .pipe(rename(function (path) {
            path.basename = `style-${path.basename.replace('.chunk', '')}.scss`;
            path.extname = '.liquid';
        }))
        .pipe(gulp.dest(srcDest));
    }
});

/**
* JS task
*/

gulp.task('js', function () {
    return gulp.src(srcPath + 'js/**/*.chunk.js')
    .pipe(babel({
        presets: ['es2015']
    }))
    // if on production concat all chunks together to create a unique file
    .pipe(gulpif(env === 'production', concat('theme.js')))
    .pipe(gulp.dest(srcDest))
    .pipe(gulpif(env === 'production', uglify()))
    .pipe(gulp.dest(srcDest));
});


/**
* Watch task
*/
gulp.task('watch', function () {
    gulp.watch([srcPath + 'scss/**/*.scss', srcPath + 'scss/**/*.scss.liquid'], ['css']);
    gulp.watch(srcPath + 'js/*.js', ['js']);
});

Come potete vedere, affinché questo funzioni, abbiamo bisogno di creare una cartella src nella root e all’interno altre due cartelle: scss e js.

src
|_ scss
|_ js

Per i file css, il pattern del nome è <name>.chunk.scss per ogni pezzo (chunk) di stile che vogliamo poter modificare senza rischio di sovrascrittura. Nel nostro caso, abbiamo scelto di crearne uno per persona.
All’interno del nostro chunk, importeremo gli scss parziali di cui abbiamo bisogno (per intenderci, quelli che iniziano per _, ad es. _header.scss).
Ogni pezzo di scss verrà compilato nella cartella assets col nome style-<name>.scss.liquid.
Per il foglio di stile di produzione, crearemo invece un file style.scss.liquid in cui includeremo tutti i parziali (escludendo i singoli chunk, ovviamente).

Così come per gli scss, i pezzi dei file js saranno creati dentro la cartella src/js usando questo pattern <name>.chunk.js e verrà elaborato utilizzando lo stesso nome.

Di seguito la situazione della cartella src arrivati a questo punto:

src
|_ scss
   |_header.scss
   |_...
   |_name1.chunk.scss
   |_name2.chunk.scss
   |_style.scss.liquid
|_ js
   |_name1.chunk.js
   |_name2.chunk.js

Quando si lancerà gulp watch in modalità sviluppo (dev) queste saranno le operazioni che eseguirà:

  • Tutti i chunk scss verranno compilati singolarmente, a cui verranno aggiunti l’autoprefixer, le sourcemaps e verrà salvato nella cartella di root assets
  • Tutti i chunk js verranno compilati singolarmente, non minificati e verranno salvati nella cartella di root assets

Mentre queste sono le operazioni eseguite quando gulp watch è lanciato in modalità produzione:

  • Solo i file *.scss.liquid sono compilati singolarmente, insieme ai parziali che importano, minificati, nessuna sourcemap aggiunta, e salvati nella cartella di root assets
  • Solo i file *.chunk.js files vengono compilati assieme e combinati in un unico file theme.js, transpilato con babel, minificato, e salvato nella cartella di root assets

A questo punto dobbiamo affrontare un problema con il compilatore: usare le variabili Shopify nel formato Shopify {{ variable }} non è possibile né nei scss né nei js.
E mentre per i css possiamo facilmente risolvere creando un file *.scss.liquid dentro la cartella assets scrivendo una lista di variabili css e unendoci la sintassi delle variabili Shopify o semplicemente creando un css con gli stili degli elementi del sito che necessitano di essere configurati tramite queste variabili, in js dobbiamo usare un trucchetto.
Ho, così, aggirato il problema creando un file variable.js.liquid (ricorda che i file liquid possono accedere e leggere le variabili Shopify) dentro la cartella assets e l’ho aggiunto nel nostro tag <head> prima di ogni nostro js:

<script src="{{ 'variables.js' | asset_url }}" defer="defer"></script>

All’interno di questo file scriveremo:

window.Shopify = window.Shopify || {};

Shopify.settings = {{ settings | json}};
Shopify.trans = {
  productsProductAdd_to_cart: "{{ 'products.product.add_to_cart' | t }}",
  productsProductSold_out: "{{ 'products.product.sold_out' | t }}",
};

Assegniamo a Shopify.setting l’intero json delle configurazioni e a Shopify.trans tutte le traduzioni di cui abbiamo bisogno (riportiamo singolarmente quelle necessarie dal momento che nel nostro js non avremo mai bisogno di tutte le stringhe di traduzione).

Fatto questo, possiamo allora richiamare i settings del tema dentro al nostro js:

Shopify.trans.productsProductAdd_to_cart

Installiamo ora cross-env con npm install cross-env --save-dev e modifichiamo ora il nostro package.json aggiungendo due diversi comandi per lanciare la compilazione per lo sviluppo e la produzione:

    "scripts": {
        "dev": "cross-env NODE_ENV=development gulp watch",
        "build": "cross-env NODE_ENV=production gulp watch"
    },

Infine, avremo bisogno di includere questi assets nel sito.

All’interno di layout/theme.liquid aggiungiamo queste righe:

  {% if settings.enable_production %}
    {{ 'theme.scss' | asset_url | stylesheet_tag }}
  {% else %}
    {{ 'style-name1.scss' | asset_url | stylesheet_tag }}
    {{ 'style-name2.scss' | asset_url | stylesheet_tag }}
  {% endif %}
  
  {% if settings.enable_production %}
    <script src="{{ 'theme.js' | asset_url }}" defer="defer"></script>
  {% else %}
    <script src="{{ 'name1.chunk.js' | asset_url }}" defer="defer"></script>
    <script src="{{ 'name2.chunk.js' | asset_url }}" defer="defer"></script>
  {% endif %}

Potete notare che l’inclusione di uno o dell’altro file dipende da una variabile chiamata settings.enable_production.
Questo è un settaggio a livello del tema che andremo adesso ad implementare.

Aprite il vostro config/settings_schema.json e aggiungete questo al json:

{
  "name": "Developer",
  "settings": [
    {
      "type": "checkbox",
      "id": "enable_production",
      "label": "Enable production mode",
      "default": true,
      "info": "This will load production assets file instead of development one."
    }
  ]
}

Ed eccoci!
Durante lo sviluppo manterremo la configurazione enable_production a false e lanceremo npm run dev per usare i nostri pezzi di file, mentre prima di andare in produzione lanceremo npm run build e imposteremo enable_production a true.

Articolo scritto da