feat(assets): remplace l'environnement gulp par webpack

* les fichiers css et js compilés sont compressés sans suffixe `.min`
* `style-sherpa` est pour l'instant supprimé car non-maintenu depuis
  trop longtemps, avec risque d'incompatibilité.
pull/27/head
Jérôme Lebleu 2021-02-24 16:14:02 +01:00
Parent 8ee2ba0db6
révision 75923fd5d9
19 fichiers modifiés avec 248 ajouts et 490 suppressions

Voir le fichier

@ -2,12 +2,11 @@ import os
import shutil
ASSETS_FILES = (
'.babelrc',
'.browserslistrc',
'.eslintrc.json',
'.stylelintrc',
'gulpfile.js',
'.stylelintrc.json',
'package.json',
'webpack.config.js',
)
ASSETS_DIRS = (
'assets',

Voir le fichier

@ -1,3 +0,0 @@
{
"presets": [ "@babel/preset-env" ]
}

Voir le fichier

@ -2,8 +2,6 @@
"root": true,
"parser": "babel-eslint",
"extends": [
"plugin:import/errors",
"plugin:import/warnings",
"xo/esnext",
"xo/browser"
],
@ -29,6 +27,10 @@
"error",
"always"
],
"prefer-destructuring": "off"
"prefer-destructuring": "off",
"quote-props": [
"error",
"consistent-as-needed"
]
}
}

Voir le fichier

@ -30,8 +30,6 @@ htmlcov
docs/build/
{%- if cookiecutter.use_assets == 'y' %}
styleguide/index.html
# NPM
node_modules/
{%- endif %}

Voir le fichier

@ -152,21 +152,14 @@ There is some additional rules when developing, which are mainly wrappers for
### Assets
The assets - e.g. CSS, JavaScript, images, fonts - are generated using a
[Gulp](https://gulpjs.com/)-powered build system with these features:
[Webpack](https://webpack.js.org/)-powered build system with these features:
- SCSS compilation and prefixing
- JavaScript module bundling with webpack
- Styleguide and components preview
- Built-in BrowserSync server
- JavaScript module bundling and code splitting
- Built-in development server
- Compression for production builds
The source files live in `assets/`, and the styleguide in `styleguide/`.
#### Requirements
All you need is [npm](https://www.npmjs.com/). It is included in Debian since
Buster and also in the `stretch-backports` repository.
#### Usage
You will need to install [npm](https://www.npmjs.com/). It is included in Debian
since Buster - and also in the `stretch-backports` repository.
Start by installing the application dependencies - which are defined in
`package.json` - by running: `npm install`.
@ -174,9 +167,7 @@ Start by installing the application dependencies - which are defined in
The following tasks are then available:
- `npm run build`: build all the assets for development and production use,
and put them in the static folder - e.g `{{ cookiecutter.project_slug }}/static`.
- `npm run styleguide`: run a server with the styleguide and watch for file
changes.
- `npm run serve`: run a proxy server to the app - which must already be served on
- `npm run dev`: run a proxy server to the app - which must already be served on
`localhost:8000` - with the styleguide on `/styleguide` and watch for file
changes.
- `npm run lint`: lint the JavaScript and the SCSS code.

Voir le fichier

Voir le fichier

Voir le fichier

@ -1,3 +1,6 @@
// eslint-disable-next-line no-undef,camelcase
__webpack_public_path__ = window.STATIC_URL || '/';
import $ from 'jquery';
import './vendor/bootstrap';

Voir le fichier

@ -5,6 +5,7 @@
@import "abstracts/variables-bootstrap";
// Vendors
@import "vendor/fork-awesome";
@import "vendor/bootstrap";
// Base styles

Voir le fichier

@ -1,12 +0,0 @@
@charset "utf-8";
// Configuration and helpers
@import "abstracts/variables";
// Fork Awesome
// ------------
// @link https://forkawesome.github.io/
$fa-font-path: "#{$font-path}/fork-awesome";
@import "fork-awesome/scss/fork-awesome";

Voir le fichier

@ -0,0 +1,28 @@
// ----------------------------------------------------------------------------
// Fork Awesome
// ----------------------------------------------------------------------------
// see: https://forkaweso.me/
$fa-font-path: "~fork-awesome/fonts";
@import "~fork-awesome/scss/variables";
@import "~fork-awesome/scss/mixins";
@import "~fork-awesome/scss/functions";
@import "~fork-awesome/scss/core";
@import "~fork-awesome/scss/larger";
@import "~fork-awesome/scss/fixed-width";
@import "~fork-awesome/scss/list";
@import "~fork-awesome/scss/bordered-pulled";
@import "~fork-awesome/scss/animated";
@import "~fork-awesome/scss/rotated-flipped";
@import "~fork-awesome/scss/stacked";
@import "~fork-awesome/scss/icons";
@import "~fork-awesome/scss/screen-reader";
// Overwrite @font-face definition to use only woff and woff2 formats.
@font-face {
font-family: "#{$fa-font-family}";
src:
url("#{$fa-font-path}/forkawesome-webfont.woff?v=#{$fa-version}") format("woff"),
url("#{$fa-font-path}/forkawesome-webfont.woff2?v=#{$fa-version}") format("woff2");
}

Voir le fichier

@ -1,287 +0,0 @@
/* eslint-env node */
const autoprefixer = require('autoprefixer');
const merge = require('merge-stream');
const rimraf = require('rimraf');
const sherpa = require('style-sherpa');
const named = require('vinyl-named');
const webpack = require('webpack');
const webpackStream = require('webpack-stream');
const gulp = require('gulp');
const plugins = require('gulp-load-plugins');
const browser = require('browser-sync').create();
// Load all Gulp plugins into one variable
const $ = plugins({
rename: {
'gulp-touch-fd': 'touch'
}
});
/// Configuration -------------------------------------------------------------
const CONFIG = {
// Proxy target of the BrowserSync'server
SERVER_PROXY: 'http://127.0.0.1:8000',
// Port on which the BrowserSync'server will listen
SERVER_PORT: 8090,
// Paths to other assets which will be copied
ASSETS_FILES: [
{
src: [
'assets/**/*',
'!assets/{img,js,scss}',
'!assets/{img,js,scss}/**/*'
],
dest: ''
},
{
// ForkAwesome
src: 'node_modules/fork-awesome/fonts/*',
dest: 'fonts/fork-awesome'
}
],
// Paths to images which will be compressed and copied
IMAGES_FILES: [
'assets/img/**/*'
],
// Paths to JavaScript entries which will be bundled
JS_ENTRIES: [
'assets/js/app.js'
],
// Paths to Sass files which will be compiled
SASS_ENTRIES: [
'assets/scss/app.scss',
'assets/scss/fork-awesome.scss'
],
// Paths to Sass libraries, which can then be loaded with @import
SASS_INCLUDE_PATHS: [
'node_modules'
],
// Path to the build output, which can safely be be cleaned
BUILD_PATH: '{{ cookiecutter.project_slug }}/static'
};
/// CSS -----------------------------------------------------------------------
// Compile Sass into CSS.
function cssTranspile() {
return gulp.src(CONFIG.SASS_ENTRIES)
.pipe($.sourcemaps.init())
.pipe($.sass({
includePaths: CONFIG.SASS_INCLUDE_PATHS
}).on('error', $.sass.logError))
.pipe($.postcss([
autoprefixer()
]))
.pipe($.sourcemaps.write('.'))
.pipe(gulp.dest(`${CONFIG.BUILD_PATH}/css`))
.pipe($.touch())
.pipe(browser.reload({ stream: true }));
}
// Lint Sass files.
function cssLint() {
return gulp.src('assets/scss/**/*.scss')
.pipe($.stylelint({
failAfterError: true,
reporters: [
{ formatter: 'verbose', console: true }
]
}));
}
// Compress CSS files.
function cssMinify() {
return gulp.src([
`${CONFIG.BUILD_PATH}/css/*.css`,
`!${CONFIG.BUILD_PATH}/css/*.min.css`
])
.pipe($.cleanCss())
.pipe($.rename({ suffix: '.min' }))
.pipe(gulp.dest(`${CONFIG.BUILD_PATH}/css`));
}
exports.css = gulp.series(cssTranspile, cssMinify);
/// JavaScript ----------------------------------------------------------------
const webpackConfig = {
devtool: 'source-map',
mode: 'development',
module: {
rules: [
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
compact: false
}
}
}
]
},
stats: {
chunks: false,
entrypoints: false
}
};
// Bundle JavaScript module.
function jsBundle() {
return gulp.src(CONFIG.JS_ENTRIES)
.pipe(named())
.pipe(webpackStream(webpackConfig, webpack))
.pipe(gulp.dest(`${CONFIG.BUILD_PATH}/js`));
}
// Lint JavaScript source files.
function jsLint() {
return gulp.src('assets/js/**/*.js')
.pipe($.eslint())
.pipe($.eslint.format())
.pipe($.eslint.failAfterError());
}
// Compress JavaScript files.
function jsMinify() {
return gulp.src([
`${CONFIG.BUILD_PATH}/js/*.js`,
`!${CONFIG.BUILD_PATH}/js/*.min.js`
])
.pipe($.terser())
.pipe($.rename({ suffix: '.min' }))
.pipe(gulp.dest(`${CONFIG.BUILD_PATH}/js`));
}
exports.js = gulp.series(jsBundle, jsMinify);
/// Other assets --------------------------------------------------------------
// Compress and copy images.
function images() {
return gulp.src(CONFIG.IMAGES_FILES)
.pipe($.imagemin({ progressive: true }))
.pipe(gulp.dest(`${CONFIG.BUILD_PATH}/img`));
}
// Copy other assets files.
function assets() {
return merge(CONFIG.ASSETS_FILES.map(
item => gulp.src(item.src)
.pipe(gulp.dest(`${CONFIG.BUILD_PATH}/${item.dest}`))
));
}
/// HTML files ----------------------------------------------------------------
// Generate a style guide from the Markdown content.
function styleguide(done) {
sherpa('styleguide/index.md', {
output: 'styleguide/index.html',
template: 'styleguide/template.html'
}, done);
}
/// BrowserSync servers -------------------------------------------------------
// Start a server with BrowserSync and proxify the application in.
function proxyServer(done) {
browser.init({
proxy: CONFIG.SERVER_PROXY,
port: CONFIG.SERVER_PORT,
serveStatic: [
{
route: '/static',
dir: CONFIG.BUILD_PATH
},
{
route: '/styleguide',
dir: './styleguide'
}
],
ghostMode: false,
notify: false
});
done();
}
// Start a server with BrowserSync with the styleguide.
function styleguideServer(done) {
browser.init({
server: {
baseDir: './styleguide',
routes: {
'/static': CONFIG.BUILD_PATH
}
},
port: CONFIG.SERVER_PORT,
ghostMode: false,
notify: false
});
done();
}
/// Utils ---------------------------------------------------------------------
function clean(done) {
rimraf(CONFIG.BUILD_PATH, done);
}
// Reload the BrowserSync server.
function reload(done) {
browser.reload();
done();
}
// Watch for changes to static assets, Sass and JavaScript.
function watch() {
gulp.watch('assets/scss/**/*.scss',
gulp.series(exports.css));
gulp.watch('assets/js/**/*.js',
gulp.series(exports.js, reload));
gulp.watch(CONFIG.IMAGES_FILES,
gulp.series(images, reload));
gulp.watch([].concat(...CONFIG.ASSETS_FILES.map(a => a.src)),
gulp.series(assets, reload));
gulp.watch(['styleguide/*', '!styleguide/index.html'],
gulp.series(styleguide, reload));
}
/// General tasks -------------------------------------------------------------
// Build and compress CSS, JavaScript and other assets.
exports.build = gulp.series(
clean,
gulp.parallel(cssTranspile, jsBundle),
gulp.parallel(cssMinify, jsMinify),
gulp.parallel(images, assets),
styleguide
);
// Run a development server and watch for file changes.
exports.serve = gulp.series(proxyServer, watch);
// Run a preview server and watch for file changes.
exports.styleguide = gulp.series(styleguideServer, watch);
// Lint Sass and JavaScript sources.
exports.lint = gulp.parallel(cssLint, jsLint);
// An alias to the 'build' task.
exports.default = exports.build;

Voir le fichier

@ -5,10 +5,11 @@
"author": "{{ cookiecutter.author_name }} <{{ cookiecutter.email }}>",
"license": "AGPL-3.0+",
"scripts": {
"build": "gulp build",
"lint": "gulp lint",
"serve": "gulp serve",
"styleguide": "gulp styleguide"
"build": "webpack --mode production --progress",
"dev": " webpack serve --mode development --progress --open",
"lint:css": "stylelint \"assets/scss/**/*.scss\"",
"lint:js": "eslint --report-unused-disable-directives assets/js/",
"lint": "npm-run-all --parallel lint:css lint:js"
},
"dependencies": {
"bootstrap": "~4.3.1",
@ -17,37 +18,29 @@
"popper.js": "~1.16.1"
},
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/preset-env": "^7.8.4",
"autoprefixer": "^9.7.4",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.0.6",
"browser-sync": "^2.26.7",
"eslint": "^6.7.2",
"eslint-config-xo": "^0.27.2",
"eslint-plugin-import": "^2.20.0",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-clean-css": "^4.2.0",
"gulp-cli": "^2.2.0",
"gulp-eslint": "^6.0.0",
"gulp-imagemin": "^7.1.0",
"gulp-load-plugins": "^2.0.1",
"gulp-postcss": "^8.0.0",
"gulp-rename": "^2.0.0",
"gulp-sass": "^4.0.2",
"gulp-sourcemaps": "^2.6.5",
"gulp-stylelint": "^13.0.0",
"gulp-terser": "^1.2.0",
"gulp-touch-fd": "^2.0.0",
"merge-stream": "^2.0.0",
"rimraf": "^3.0.1",
"style-sherpa": "^1.0.0",
"stylelint": "^13.0.0",
"stylelint-config-twbs-bootstrap": "^2.0.0",
"vinyl-named": "^1.1.0",
"webpack": "^4.41.2",
"webpack-stream": "^5.2.1"
"@babel/core": "^7.13.1",
"@babel/preset-env": "^7.13.5",
"autoprefixer": "^10.2.4",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^5.0.2",
"css-minimizer-webpack-plugin": "^1.2.0",
"eslint": "^7.20.0",
"eslint-config-xo": "^0.35.0",
"mini-css-extract-plugin": "^1.3.8",
"node-sass": "^5.0.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.2.6",
"postcss-loader": "^5.0.0",
"sass-loader": "^11.0.1",
"stylelint": "^13.11.0",
"stylelint-config-twbs-bootstrap": "^2.1.0",
"terser-webpack-plugin": "^5.1.1",
"webpack": "^5.24.1",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2"
},
"private": true
}

Voir le fichier

@ -0,0 +1 @@
preview.html

Voir le fichier

@ -1,31 +0,0 @@
# Typographie
<p class="lead">Cette application utilise la police d'écriture <a href="#"><b>?</b></a>, sous licence ?.</p>
---
## Titres
<h1>Titre de niveau 1</h1>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic quibusdam ratione sunt dolorum, qui illo maxime doloremque accusantium cum libero eum, a optio odio placeat debitis ullam aut non distinctio.
<h2>Titre de niveau 2</h2>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic quibusdam ratione sunt dolorum, qui illo maxime doloremque accusantium cum libero eum, a optio odio placeat debitis ullam aut non distinctio.
<h3>Titre de niveau 3</h3>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic quibusdam ratione sunt dolorum, qui illo maxime doloremque accusantium cum libero eum, a optio odio placeat debitis ullam aut non distinctio.
<h4>Titre de niveau 4</h4>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic quibusdam ratione sunt dolorum, qui illo maxime doloremque accusantium cum libero eum, a optio odio placeat debitis ullam aut non distinctio.
<h5>Titre de niveau 5</h5>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic quibusdam ratione sunt dolorum, qui illo maxime doloremque accusantium cum libero eum, a optio odio placeat debitis ullam aut non distinctio.
<h6>Titre de niveau 6</h6>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic quibusdam ratione sunt dolorum, qui illo maxime doloremque accusantium cum libero eum, a optio odio placeat debitis ullam aut non distinctio.

Voir le fichier

@ -1,95 +0,0 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ cookiecutter.project_name }} - Guide des styles</title>
<!-- Load Highlight.js CSS only. -->
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.9.1/styles/github.min.css">
<link rel="stylesheet" href="/static/css/app.css">
<link rel="stylesheet" href="/static/css/fork-awesome.css">
<!-- Style guide-specific CSS. -->
<style>
/* Side menu with sections */
.sg-side-menu {
position: sticky;
top: 0;
margin-left: 1rem;
}
/* Individual sections of the style guide */
.ss-section:not(:last-child) {
padding-bottom: 4rem;
border-bottom: 2px solid #f8f9fa;
margin-bottom: 4rem;
}
/* Code blocks used for examples */
.ss-code pre {
margin-bottom: 0;
}
.ss-code code {
display: block;
padding: 1.5rem;
overflow-x: auto;
background-color: #f8f9fa;
}
/* Output of code blocks used for examples */
.ss-code-live {
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 3px solid #f8f9fa;
}
</style>
</head>
<body>
<header class="jumbotron sg-header">
<div class="container">
<h1>{{ cookiecutter.project_name }}<small class="text-muted"> : Guide des styles</small></h1>
<p class="lead">Ce guide des styles ne documente que les composants ou autres styles propres à l'application. Pour plus de détails sur l'utilisation du framework Bootstrap et de ses composants, consultez la documentation. Une page donne également un aperçu des composants principaux de Bootstrap mis en forme pour l'application.</p>
<hr class="my-4">
<a class="btn btn-primary btn-lg" href="https://getbootstrap.com/docs/" role="button">
<i class="fa fa-life-ring mr-2"></i>
Documentation de Bootstrap
</a>
<a class="btn btn-primary btn-lg" href="preview.html" role="button">
<i class="fa fa-eye mr-2"></i>
Aperçu des composants
</a>
</div>
</header>
<div class="container">
<div class="row">{% raw %}
<div class="col col-md-4 col-lg-3">
<ul class="nav flex-column sg-side-menu">
{{#each pages}}
<li class="nav-item">
<a class="nav-link" href="#{{ anchor }}">{{ title }}</a>
</li>
{{/each}}
</ul>
</div>
<div class="col col-md-8 col-lg-9">
{{#each pages}}
<section class="ss-section" id="{{ anchor }}">
{{ body }}
</section>
{{/each}}
</div>
{% endraw %}</div>
</div>
<script src="/static/js/app.js"></script>
</body>
</html>

Voir le fichier

@ -0,0 +1,167 @@
/* eslint-env node */
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const CONFIG = {
// Proxy target of the BrowserSync'server
SERVER_PROXY: 'http://127.0.0.1:8000',
// Port on which the BrowserSync'server will listen
SERVER_PORT: 8090,
// Paths to entries which will be bundled
ENTRIES: {
app: ['./assets/js/app.js', './assets/scss/app.scss']
},
// Path to the directory of fonts wich will be aliased
FONTS_PATH: 'assets/fonts',
// Path to the directory of images which will be compressed and copied
IMAGES_PATH: 'assets/img',
// Path to the directory of SCSS entries which will be aliased
STYLES_PATH: 'assets/scss',
// Path to other assets wich will be copied with copy-webpack-plugin
// See: https://www.npmjs.com/package/copy-webpack-plugin
COPY_PATTERNS: [
// { from: 'assets/favicon', to: 'favicon' }
],
// Path to the build output, which can safely be cleaned
BUILD_PATH: '{{ cookiecutter.project_slug }}/static'
};
// Return Webpack configuration depending on the mode.
module.exports = function (_, argv) {
const isDev = argv.mode !== 'production';
const config = {
entry: CONFIG.ENTRIES,
output: {
filename: '[name].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, CONFIG.BUILD_PATH)
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: { esmodules: true } }]
],
compact: false
}
},
resolve: {
alias: {
bootstrap: 'bootstrap/js/src'
}
}
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: ''
}
},
{ loader: 'css-loader' },
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['autoprefixer']
}
}
},
{ loader: 'sass-loader' }
]
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: 'asset/resource',
generator: {
filename: 'img/[name].[ext]'
}
},
{
test: /\.(woff2?|ttf|otf|eot)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[ext]'
}
}
]
},
resolve: {
alias: {
fonts: path.resolve(__dirname, CONFIG.FONTS_PATH),
styles: path.resolve(__dirname, CONFIG.STYLES_PATH)
}
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].[contenthash].css'
}),
new CopyPlugin({
patterns: [
{
from: CONFIG.IMAGES_PATH,
to: 'img/'
},
...CONFIG.COPY_PATTERNS
]
})
],
stats: true
};
if (isDev) {
config.mode = 'development';
config.devtool = 'cheap-module-source-map';
config.watchOptions = {
aggregateTimeout: 1000
};
config.devServer = {
index: '',
publicPath: '/static/',
port: CONFIG.SERVER_PORT,
proxy: {
context: path => !path.startsWith('/styleguide'),
target: CONFIG.SERVER_PROXY
},
writeToDisk: true,
// serve styleguide files
contentBase: path.join(__dirname, 'styleguide'),
contentBasePublicPath: '/styleguide',
watchContentBase: true
};
} else {
config.mode = 'production';
config.devtool = 'source-map';
config.optimization = {
minimizer: [
new TerserJSPlugin({ extractComments: false }),
new CssMinimizerPlugin()
]
};
}
return config;
};

Voir le fichier

@ -1,4 +1,4 @@
{% raw %}{% load minified %}<!DOCTYPE html>
{% raw %}{% load static %}<!DOCTYPE html>
<html class="no-js" lang="fr">
<head>
<meta charset="utf-8">
@ -14,10 +14,13 @@
<meta name="keywords" content="">
{% block css %}
<link rel="stylesheet" href="{% minified "css/fork-awesome.css" %}">
<link rel="stylesheet" href="{% minified "css/app.css" %}">
<link rel="stylesheet" href="{% static "app.css" %}">
{% endblock %}
<script>
window.STATIC_URL = '{% get_static_prefix %}';
</script>
{% block extra_head %}{% endblock %}
</head>
<body>
@ -38,7 +41,7 @@
{% block modal %}{% endblock %}
{% block javascript %}
<script src="{% minified "js/app.js" %}"></script>
<script src="{% static "app.js" %}"></script>
{% endblock %}
</body>
</html>{% endraw %}