Skip to content

Instantly share code, notes, and snippets.

@mistergraphx
Last active September 7, 2022 15:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mistergraphx/e128cfaa1d9b6efb45bbb91da34606de to your computer and use it in GitHub Desktop.
Save mistergraphx/e128cfaa1d9b6efb45bbb91da34606de to your computer and use it in GitHub Desktop.
gulp_webpack
/* jshint node: true */
'use strict';
/**
* Usage général :
*
* - tâche "gulp" : fichiers compilés dans "/dist" (ni minifiés ni concaténés).
* Le client peut modifier, améliorer et mettre en prod lui-même.
*
* - tâche "gulp --prod" : fichiers compilés dans "/dist" (minifiés, concaténés,
* optimisés, etc.). Le client utilise tel quel.
*/
/**
* Chargement et initialisation des composants utilisés
*/
require('events').EventEmitter.defaultMaxListeners = 15;
var gulp = require('gulp'),
$ = require('gulp-load-plugins')(),
argv = require('yargs').argv,
stripDebug = require('gulp-strip-debug'),
terser = require('gulp-terser'),
gulpif = require('gulp-if'),
compiler = require('webpack'),
webpack = require('webpack-stream'),
through = require('through2'),
sass = require('gulp-sass')(require('sass')),
critical = require("./critical"),
replace = require('gulp-replace');
var env = argv.env;
var debugEnabled = true;
var urlPrefix = 'local-';
if (env === 'local') {
var jshint = require('gulp-jshint');
var urlPrefix = 'local-';
} else if (env === 'preprod') {
urlPrefix = 'preprod-';
} else if (env === 'stage') {
urlPrefix = 'stage-';
} else if (env === 'prod') {
debugEnabled = false;
urlPrefix = '';
}
/**
* Configuration générale du projet et des composants utilisés
*/
var project = {
name: 'nouvelobs', // nom du projet, utilisé notamment pour le fichier ZIP
url: 'https://' + urlPrefix + 'www.nouvelobs.com/', // url du projet, utilisée par browserSync en mode proxy
urlArticle: 'https://' + urlPrefix + 'www.nouvelobs.com/monde/20190916.OBS18496/quand-boris-johnson-compare-le-royaume-uni-a-hulk.html', // url d'une page article type pour le critical
ua: '?ua=gulp',
commonJSFile: 'common.bundle.js', // nom du fichier JS après concaténation
expopJSFile: 'expop.js',
xitiJSFile: 'xiticlicks.js',
configuration: {
// configuration des différents composants de ce projet
cssbeautify: {
indent: ' '
},
htmlExtend: {
annotations: false,
verbose: false
},
imagemin: {
svgoPlugins: [{
removeViewBox: false
},
{
cleanupIDs: false
}
]
}
}
};
/**
* Chemins vers les ressources ciblées
*/
var paths = {
root: './', // dossier actuel
src: './web/src/', // dossier de travail
dist: './web/dist/', // dossier destiné à la livraison
inlineCss: './app/front/views/partials/assets/css/',
distSubscription: './web/subscription/', // dossier destiné à la livraison
vendors: './node_modules/', // dossier des dépendances du projet
assets: 'assets/',
styles: {
root: 'assets/css/', // fichier contenant les fichiers CSS & Sass
rootV7: 'assets/css/v7/', // fichier contenant les fichiers CSS & Sass pour la version 7 de knack
rootPlugins: 'assets/css/plugins/', // fichier contenant les feuilles de style pour les plugins
css: {
mainFile: 'assets/css/styles.css', // fichier CSS principal
mainFileV2: 'assets/css/stylesV2.css',
mainHeadlessFile: 'assets/css/headless.css', // fichier CSS principal but headless
mainHeadlessFileV2: 'assets/css/headlessV2.css',
articleFileV2: 'assets/css/articleV2.scss', // fichier Sass spécifique à la page d'article
articleHeadlessFileV2: 'assets/css/articleV2-headless.scss',
files: 'assets/css/*.css' // cible tous les fichiers CSS
},
sass: {
homeFile: 'assets/css/v7/home.scss', // fichier Sass homepage
homeHeadlessFile: 'assets/css/v7/home-headless.scss', // fichier Sass homepage but headless
mainFile: 'assets/css/styles.scss', // fichier Sass principal
mainFileV2: 'assets/css/stylesV2.scss',
mainHeadlessFile: 'assets/css/headless.scss', // fichier Sass principal but headless
mainHeadlessFileV2: 'assets/css/headlessV2.scss',
articleFileV2: 'assets/css/articleV2.scss', // fichier Sass spécifique à la page d'article
articleFileV2Full: 'assets/css/articleV2-full.scss', // Article large en theme V2
articleFileV2FullHeadless: 'assets/css/articleV2-full-headless.scss', // Article large en theme V2 but headless
articleFileV2Dossier: 'assets/css/articleV2-dossier.scss', // Theme V2 Dossier
articleHeadlessFileV2: 'assets/css/articleV2-headless.scss',
mainFileAMP: 'assets/css/styles-amp.scss', // fichier Sass AMP
oFile: 'assets/css/o.scss', // fichier Sass spécifique à Ô
//oHeadlessFile: 'assets/css/o-headless.scss', // fichier Sass spécifique à Ô
headerAndFooterFile: 'assets/css/headerAndFooter.scss',
headerAndFooterFileV2: 'assets/css/headerAndFooterV2.scss',
subscriptionFile: 'assets/css/subscription.scss',
subscriptionHeadlessFile: 'assets/css/subscription-headless.scss',
subscriptionAreaFile: 'assets/css/subscriptionArea.scss',
subscriptionAreaHeadlessFile: 'assets/css/subscriptionAreaHeadless.scss',
subscriptionStepsFile: 'assets/css/subscriptionSteps.scss',
subscriptionStepsHeadlessFile: 'assets/css/subscriptionStepsHeadless.scss',
widget2022File: 'assets/css/widget2022.scss',
miscFile: 'assets/css/misc.scss', // fichier Sass spécifique aux petites pages du site
miscHeadlessFile: 'assets/css/misc-headless.scss', // fichier Sass spécifique aux petites pages du site but headless
promosFile: 'assets/css/promos.scss', // fichier Sass spécifique aux pages promotionnelles
files: 'assets/css/**/*.scss' // fichiers Sass à surveiller
}
},
scripts: {
root: 'assets/js/', // dossier contenant les fichiers JavaScript
common: 'assets/js/common/',
cmp: 'assets/js/es6/',
analytics: 'assets/js/analytics/',
expop: 'assets/js/expop.js'
},
fonts: 'assets/fonts/', // fichiers typographiques à copier,
images: {
root: 'assets/img/',
files: 'assets/{,css/}img/**/*.{png,jpg,jpeg,gif,svg,webp}'
}, // fichiers images à compresser
cp: "assets/js/cp/", // fichiers à copier (doit être dans js pour l'URL en /bucket)
maps: '/maps' // fichiers provenant de sourcemaps
};
/**
* Ressources JavaScript utilisées par ce projet (vendors + scripts JS spécifiques)
*/
var jsCommonFiles = [
paths.src + paths.scripts.root + 'smarttag.js',
paths.src + paths.scripts.root + 'load-attag.js',
paths.src + paths.scripts.common + 'attag-dynamic.js',
paths.src + paths.scripts.common + 'cookies.js',
paths.src + paths.scripts.common + 'modal.js',
paths.src + paths.scripts.common + 'try-forecast.js',
paths.src + paths.scripts.common + 'refresh.js',
paths.src + paths.scripts.common + 'scroll-helpers.js',
paths.src + paths.scripts.common + 'gsignin.js',
paths.src + paths.scripts.common + 'swg-activation.js',
];
/**
* Tâche de gestion des erreurs à la volée
*/
var onError = {
errorHandler: function (err) {
console.log(err);
this.emit('end');
}
};
var addGoogleFontWeight = function () {
return through.obj(function (chunk, enc, cb) {
const regex = /font-family:([^;}]+)/gmi;
let contents = `${chunk.contents}`;
let newContents;
let m;
while ((m = regex.exec(contents)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
const families = m[1].trim().split(',').map(e => e.trim().split(/["']/).join(''));
if (families.some((e) =>
(
e.indexOf('SourceSerifPro') > -1 ||
e.indexOf('Source Serif Pro') > -1 ||
e.indexOf('Karla') > -1 ||
e.indexOf('Newsreader') > -1 ||
e.indexOf('Oswald') > -1 ||
e.indexOf('Antonio') > -1
) &&
e.indexOf('-') > -1 &&
e.indexOf('NewsreaderDisplay') === -1 &&
e.indexOf('NewsreaderText') === -1
)) {
newContents = contents.slice(0, m.index);
const [customFont, ...fallbacks] = families;
let [fontName, modifier] = customFont.split('-');
if (fontName === 'SourceSerifPro') fontName = 'Source Serif Pro';
let newFontFamily = `font-family:${JSON.stringify(fontName)}${fallbacks.length > 0 ? `, ${fallbacks.join(', ')}` : ''}`;
const isImportant = modifier.indexOf('!important') > -1;
modifier = modifier.split('!important').join('').trim();
if (isImportant) {
newFontFamily += ' !important';
}
switch (modifier) {
case 'extralight':
newFontFamily += '; font-weight:200';
break;
case 'light':
newFontFamily += '; font-weight:300';
break;
case 'regular':
newFontFamily += '; font-weight:400';
break;
case 'medium':
newFontFamily += '; font-weight:500';
break;
case 'semibold':
newFontFamily += '; font-weight:600';
break;
case 'bold':
newFontFamily += '; font-weight:700';
break;
case 'extrabold':
newFontFamily += '; font-weight:800';
break;
default:
console.warn('unhandled modifier', modifier, customFont, newFontFamily)
}
newContents += newFontFamily;
newContents += contents.slice(m.index + m[0].length);
contents = newContents;
}
}
chunk.contents = Buffer.from(contents);
cb(null, chunk)
});
}
exports.criticals = gulp.series(...critical.default(project, paths));
/* ------------------------------------------------
* Tâches de Check : js
* ------------------------------------------------
*/
function jsHint() {
return gulp
.src([...jsCommonFiles,
paths.src + paths.scripts.root + 'form/*.js',
paths.src + paths.scripts.root + '*.js'])
.pipe(jshint())
.pipe(jshint.reporter('jshint-stylish'));
// .pipe(jshint.reporter()); // basic reporter
};
/* ------------------------------------------------
* Tâches de Build : css, js, img, fonts
* ------------------------------------------------
*/
// Tâche CSS : Sass + Autoprefixer + CSScomb + beautify + minify (si prod)
function css() {
return gulp
.src([
paths.src + paths.styles.sass.homeFile,
paths.src + paths.styles.sass.homeHeadlessFile,
paths.src + paths.styles.sass.mainFile,
paths.src + paths.styles.sass.mainFileV2,
paths.src + paths.styles.sass.mainHeadlessFile,
paths.src + paths.styles.sass.mainHeadlessFileV2,
paths.src + paths.styles.sass.articleFileV2,
paths.src + paths.styles.sass.articleFileV2Full,
paths.src + paths.styles.sass.articleFileV2FullHeadless,
paths.src + paths.styles.sass.articleFileV2Dossier,
paths.src + paths.styles.sass.articleHeadlessFileV2,
paths.src + paths.styles.sass.oFile,
//paths.src + paths.styles.sass.oHeadlessFile,
paths.src + paths.styles.sass.headerAndFooterFile,
paths.src + paths.styles.sass.headerAndFooterFileV2,
paths.src + paths.styles.sass.subscriptionFile,
paths.src + paths.styles.sass.subscriptionHeadlessFile,
paths.src + paths.styles.sass.subscriptionAreaFile,
paths.src + paths.styles.sass.subscriptionAreaHeadlessFile,
paths.src + paths.styles.sass.subscriptionStepsFile,
paths.src + paths.styles.sass.subscriptionStepsHeadlessFile,
paths.src + paths.styles.sass.widget2022File,
paths.src + paths.styles.sass.miscFile,
paths.src + paths.styles.sass.miscHeadlessFile,
paths.src + paths.styles.sass.promosFile,
])
.pipe($.plumber(onError))
.pipe(gulpif(env !== 'prod', $.sourcemaps.init()))
.pipe(sass.sync().on('error', sass.logError))
.pipe($.autoprefixer())
.pipe(gulp.dest(paths.dist + paths.styles.root))
.pipe(addGoogleFontWeight())
.pipe($.csso())
.pipe(gulpif(env !== 'prod', $.sourcemaps.write(paths.maps)))
.pipe(gulp.dest(paths.dist + paths.styles.root));
};
// Tâche CSS pour la nouvelle version de knack : Sass + Autoprefixer + CSScomb + beautify + minify (si prod)
function cssV7() {
return gulp
.src(paths.src + paths.styles.rootV7 + '*.scss')
.pipe($.plumber(onError))
.pipe(gulpif(env !== 'prod', $.sourcemaps.init()))
.pipe(sass.sync().on('error', sass.logError))
.pipe($.autoprefixer())
.pipe(gulp.dest(paths.dist + paths.styles.rootV7))
.pipe(addGoogleFontWeight())
.pipe($.csso())
.pipe(gulpif(env !== 'prod', $.sourcemaps.write(paths.maps)))
.pipe(gulp.dest(paths.dist + paths.styles.rootV7));
};
function cssPlugins() {
return gulp
.src(paths.src + paths.styles.rootPlugins + '*.scss')
.pipe($.plumber(onError))
.pipe(gulpif(env !== 'prod', $.sourcemaps.init()))
.pipe(sass.sync().on('error', sass.logError))
.pipe($.autoprefixer())
.pipe(gulp.dest(paths.dist + paths.styles.rootPlugins))
.pipe(addGoogleFontWeight())
.pipe($.csso())
.pipe(gulpif(env !== 'prod', $.sourcemaps.write(paths.maps)))
.pipe(gulp.dest(paths.dist + paths.styles.rootPlugins));
};
// Tâche CSS pour les fichiers AMP
function css_AMP() {
return gulp
.src(paths.src + paths.styles.sass.mainFileAMP)
.pipe($.plumber(onError))
.pipe(sass.sync().on('error', sass.logError))
.pipe($.autoprefixer())
.pipe(gulp.dest(paths.dist + paths.styles.root))
.pipe($.csso())
.pipe($.rename('styles-amp.css.volt'))
.pipe(replace('@charset "UTF-8";', ''))
.pipe(gulp.dest(paths.inlineCss));
};
// Tâche UNCSS pour le header et le footer
function uncss_header_footer() {
return gulp
.src(paths.dist + paths.styles.css.mainFile)
.pipe(
$.uncss({
html: [project.url + 'header', project.url + 'footer'],
ignore: [
/(js\-is\-sticky)/i,
/.fonts-loaded/,
/.modal/,
/.obs-abo/,
/.pop-co/,
/.opened/,
/.js-abonnes-nonmq/,
/.js-abonnes-nologged/,
/.js-abonnes-deconnect/,
/.slice/,
/.js-user-allowed/,
/@font-face/,
/.alert/,
/.is-paused/
]
})
)
.pipe($.rename('headerAndFooter.css'))
.pipe(gulp.dest(paths.dist + paths.styles.root));
};
// Tâche UNCSS pour le header et le footer
function uncss_header_footer_V2() {
return gulp
.src(paths.dist + paths.styles.css.mainFileV2)
.pipe(
$.uncss({
html: [project.url + 'headerV2', project.url + 'footerV2'],
ignore: [
/(js\-is\-sticky)/mig,
/\.fonts-loaded/mg,
/\.modal/mg,
/\.obs-abo/mg,
/\.pop-co/mg,
/\.opened/mg,
/\.slice/mg,
/\.js-abonnes-nonmq/mg,
/\.js-abonnes-nologged/mg,
/\.js-abonnes-deconnect/mg,
/\.js-user-allowed/mg,
/@font-face/mg,
/\.alert/mg,
/\.is-paused/mg
]
})
)
.pipe($.rename('headerAndFooterV2.css'))
.pipe(gulp.dest(paths.dist + paths.styles.root));
};
// Tâche UNCSS pour le menu (pour N-E - Nouvelles Écritures)
function uncss_menu() {
return gulp
.src(paths.dist + paths.styles.css.mainFile)
.pipe(
$.uncss({
html: [project.url + 'services' + project.ua],
ignore: [
/\.opened/g,
/\.modal/g,
/\.js-is-sticky/g,
/\.js-user/g,
/\.js-abonnes/g,
'.icon-login.is-connect',
/\.ObsHeader-with/g,
'.ObsHeader-logo-brands',
/\.btn--base/g,
]
})
)
.pipe($.rename('menu.css'))
.pipe(gulp.dest(paths.dist + paths.styles.root));
};
// Idem que précédemment mais pour la V2
function uncss_menu_V2() {
return gulp
.src(paths.dist + paths.styles.css.mainFileV2)
.pipe(
$.uncss({
html: [project.url + 'services' + project.ua],
ignore: [
/\.opened/,
/\.modal/,
/\.js-is-sticky/,
/\.js-user/,
/\.js-abonnes/,
'.icon-login.is-connect',
/\.ObsHeader-with/,
'.ObsHeader-logo-brands',
/\.btn--base/,
]
})
)
.pipe($.rename('menuV2.css'))
.pipe(gulp.dest(paths.dist + paths.styles.root));
};
function jsStandalone() {
return gulp
.src([
paths.vendors + 'jquery/dist/jquery.js',
paths.src + paths.scripts.root + 'form/*.js',
paths.src + paths.scripts.root + '*.js'
])
.pipe(gulpif(debugEnabled === false, stripDebug()))
.pipe(terser({compress: {drop_console:env === 'prd'}}))
.pipe(gulp.dest(paths.dist + paths.scripts.root));
};
function jsWebpack() {
return gulp
.src(paths.src + paths.scripts.root)
.pipe($.plumber(onError))
.pipe(webpack(require('./webpack.config.js')))
// .pipe($.uglify())
.pipe(gulp.dest(paths.dist + paths.scripts.root));
};
function jsCommon() {
var stream = gulp
.src(jsCommonFiles)
.pipe($.plumber(onError))
.pipe(gulpif(env !== 'prod', $.sourcemaps.init()))
.pipe($.concat(project.commonJSFile))
.pipe(gulpif(debugEnabled === false, stripDebug()))
.pipe(terser({compress: {drop_console:env === 'prd'}}))
.pipe(gulpif(env !== 'prod', $.sourcemaps.write(paths.maps)))
.pipe(gulp.dest(paths.dist + paths.scripts.root));
return stream;
};
// Tâche IMG : optimisation des images
function img() {
return gulp
.src(paths.src + paths.images.files)
.pipe($.changed(paths.dist + paths.assets))
.pipe($.imagemin(project.configuration.imagemin))
.pipe(gulp.dest(paths.dist + paths.assets));
};
exports.img = img;
// Tâche IMG : optimisation des images pre-commit hook
const path = require('path');
gulp.task('optimg', function () {
var imgFile = argv.file ;
let destPath = path.dirname(imgFile);
return gulp
.src(imgFile)
.pipe($.imagemin(project.configuration.imagemin))
.pipe(gulp.dest(destPath));
});
// Tâche FONTS : copie des fichiers typographiques
function fonts() {
return gulp
.src(paths.src + paths.fonts + '*')
.pipe($.changed(paths.dist + paths.fonts))
.pipe(gulp.dest(paths.dist + paths.fonts));
};
// Tâche copy : copie des fichiers à conserver tel quel
function copy() {
return gulp
.src(paths.src + paths.cp + '**/*.*')
.pipe(gulp.dest(paths.dist + paths.cp));
};
/* ----------------------------------
* Tâches principales : récapitulatif
* ----------------------------------
*/
// Tâche UNCSS :
exports.uncss = gulp.series(uncss_menu, uncss_menu_V2, exports.criticals);
exports.js = gulp.series(jsWebpack, jsCommon, jsStandalone);
var build = gulp.series(
css,
cssV7,
cssPlugins,
css_AMP,
exports.js,
img,
fonts,
copy,
exports.uncss,
);
exports.build = build;
var watchSass = () => gulp.watch(
paths.src + paths.styles.sass.files,
gulp.series(css, cssV7)
);
var watchJSCommon = () => gulp.watch(
jsCommonFiles,
jsCommon
);
var watchJSStandalone = () => gulp.watch(
[
paths.src + paths.scripts.root + 'form/*.js',
paths.src + paths.scripts.root + '*.js',
],
jsStandalone
);
var webpackConfig = require('./webpack.config.js');
exports.watch = gulp.series(
gulp.parallel(jsCommon, jsStandalone),
gulp.parallel(css, cssV7),
gulp.parallel(
watchSass,
watchJSCommon,
watchJSStandalone,
function watchWebpack() { return webpack({...webpackConfig, watch: true},compiler); }
))
exports.default = build;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment