Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to latest MJML & gulp #1

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,34 +52,34 @@ __`--out`__: Replaces the default output directory (`./output`) with the one pro

A `config.js` file exists to provide configuration parameters for the build. The following attribute is supported:

- **`imageBase`**: The absolute URL where images are to be accessible publicly.
- **`assetBaseUrl`**: The absolute URL where images are to be accessible publicly.


## About images

Images in emails are expected to be hosted on a public web server (ex: S3). This tool does not embed images into emails.

As a convenience for developers when working locally (more specifically using the `development` environment), the image paths will be adjusted so that they point to the local file instead of a fully absolute URL. For that process to work, the `$IMGPATH$` replacement pattern needs to be prepended to all references to images in any of the `.mjml` templates.
As a convenience for developers when working locally (more specifically using the `development` environment), the image paths will be adjusted so that they point to the local file instead of a fully absolute URL. For that process to work, the `[$ assetBaseUrl $]` replacement pattern needs to be prepended to all references to images in any of the `.mjml` templates.

Example:

<mj-image width="100" src="$IMGBASE$/logo.gif" />
<mj-image width="100" src="[$ assetBaseUrl $]/logo.gif" />

This would refer to an image stored in the following directory: `src/assets/logo.gif`.

**Note**: Usage of the `$IMGBASE$` pattern is not required if your images are already on a public server.
**Note**: Usage of the `[$ assetBaseUrl $]` pattern is not required if your images are already on a public server.

It is not part of the scope of this tool to sync or upload images to a public hosting environment. All the tool does is repatriate every image file under the `src/assets` directory into the `output/assets` directory when building the project. Those files should then be uploaded/synced to a Web server serving static files.

When building templates for production (see _Building for production_ below), the `$IMGBASE$` variables will be replaced by the `imageBase` configuration attribute in `config.js`.
When building templates for production (see _Building for production_ below), the `[$ assetBaseUrl $]` variables will be replaced by the `assetBaseUrl` configuration attribute in `config.js`.

## About translations / i18n

The output of the tool is a different version of each email in both HTML and text version, in all languages that have a corresponding `messages.yaml` file under the `src/locales/[LANG]` directory.
The output of the tool is a different version of each email in both HTML and text version, in all languages that have a corresponding `X.yaml` files under the `src/locales/[LANG]` directory.

The `messages.yaml` files is a simple hierarchical data structure that maps keys to the actual value in a specific language.
The `FILENAME.yaml` files are a simple hierarchical data structure that maps keys to the actual value in a specific language.

To use translated strings in emails, use the following delimiter: `_(messages.[KEY])`, where `[KEY]` is the dot-delimited path that leads to the string you want in `messages.yaml`.
To use translated strings in emails, use the following delimiter: `_(FILENAME.[KEY])`, where `[KEY]` is the dot-delimited path that leads to the string you want in `FILENAME.yaml`.

For example, given a `messages.yaml` file under `src/locales/en` with the following contents:

Expand Down Expand Up @@ -108,6 +108,19 @@ Without the `{% raw %}` escape tag, the output would be

This is because Nunjucks tries to substitute the `{{ some_dynamic_variable }}` placeholder with a variable from its context. Because there is no such variable, the output is simply empty.

#### Alternative option: Change Nunjucks syntax
Nunjucks allows [configurable syntax](https://mozilla.github.io/nunjucks/api.html#customizing-syntax), e.g. `[% %]` (better than `<%` because of html parsers):
```json
tags: { // As we also want to use Blade templates at runtime, we use a different syntax for Nunjucks (blade sadly doesn't support changing the syntax)
blockStart: '[%',
blockEnd: '%]',
variableStart: '[$',
variableEnd: '$]',
commentStart: '[#',
commentEnd: '#]'
}
```

## Caveats

### RFC-2822 conformance
Expand Down Expand Up @@ -143,7 +156,7 @@ A possible solution is to remove some levels of nesting and get rid of the `mj-c
1. Encode your email contents as Base64 when sending them, which will naturally break lines but with a cost of about 20-30% increase on email size.
2. Encode your email contents with [`quoted-printable`](https://en.wikipedia.org/wiki/Quoted-printable)

### `mj-include` tag not properly working
### `mj-include` tag

The `<mj-include>` MJML tag does not currently work as there is an issue with the way it tries to find the files included.
The `<mj-include>` MJML tag needs paths relative to `src/templates/html`

6 changes: 3 additions & 3 deletions config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
// This is where your images are to be hosted publicly
imageBase: 'https://www.yourbrand.com/images', // must NOT end with a slash
};
// This is where your images are to be hosted publicly - e.g. 'https://domain.org/images'
assetBaseUrl: process.env.EMAIL_ASSET_URL,
};
69 changes: 44 additions & 25 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@ const config = require('./config');
const gulp = require('gulp');
const argv = require('yargs').argv;
const mjml = require('gulp-mjml');
const tap = require('gulp-tap');
const i18n = require('gulp-html-i18n');
const replace = require('gulp-replace');
const environments = require('gulp-environments');
const connect = require('gulp-connect');
const nunjucks = require('gulp-nunjucks');
const clean = require('gulp-clean');

const I18N_REGEX = /\_\(([\w-\.+]+)\)/g; // used to find placeholder for i18n keys in templates

const production = environments.production;

const mjmlEngine = require('mjml');

// should never end with a slash
const imageBase = production() ? config.imageBase : './assets';
let assetBaseUrl = production() ? config.assetBaseUrl : './assets';
if (!assetBaseUrl) throw new Error(`Empty assetBaseUrl in config.js`)
if (assetBaseUrl.endsWith('/')) assetBaseUrl = assetBaseUrl.replace(/\/$/, "") // remove trailing slash

const argOutput = argv.out || './output';

const dirs = {
output: argOutput ,
output: argOutput,
output_assets: argOutput + '/assets',
locales: './src/locales',
templates: './src/templates/html',
Expand All @@ -36,48 +39,64 @@ const files = {
locales: dirs.locales + '/**/*.yaml'
};

gulp.task('copy:assets', function() {
gulp.task('clean', function () {
return gulp.src('output/**', { read: false })
.pipe(clean());
});

gulp.task('copy:assets', function () {
return gulp.src(files.assets)
.pipe(gulp.dest(dirs.output_assets))
});

gulp.task('build:html', function() {
gulp.task('build:html', function () {
return gulp.src(files.templates)
.pipe(nunjucks.compile({}, {searchPath: dirs.templates}))
.pipe(replace('$IMGBASE$', imageBase)) // could be replaced with Nunjucks vars
.pipe(mjml(mjmlEngine))
.pipe(nunjucks.compile({
assetBaseUrl
}, {
searchPath: dirs.templates
}))
.pipe(mjml(mjmlEngine, {
minify: false,
filePath: dirs.templates,
// validationLevel: 'strict'
}))
.pipe(i18n({
langDir: dirs.locales,
langRegExp: I18N_REGEX,
}))
.pipe(gulp.dest(dirs.output))
.pipe(connect.reload());
.pipe(connect.reload())
});

gulp.task('build:text', function() {
return gulp.src(files.templates_text)
.pipe(nunjucks.compile({}, {searchPath: dirs.templates_text}))
.pipe(i18n({
langDir: dirs.locales,
langRegExp: I18N_REGEX,
}))
.pipe(gulp.dest(dirs.output))
.pipe(connect.reload());
gulp.task('build:text', function () {
return gulp.src(files.templates_text)
.pipe(nunjucks.compile({
assetBaseUrl
}, {
searchPath: dirs.templates_text
}))
.pipe(i18n({
langDir: dirs.locales,
langRegExp: I18N_REGEX,
}))
.pipe(gulp.dest(dirs.output))
.pipe(connect.reload());
});

gulp.task('build', ['copy:assets', 'build:text', 'build:html']);
gulp.task('build', gulp.series(['clean', 'copy:assets', 'build:text', 'build:html']));

gulp.task('watch', ['build'], function() {
gulp.watch([files.templates_all, files.assets, files.locales], ['build:html']);
gulp.watch([files.templates_text_all, files.locales], ['build:text']);
gulp.task('watch', function () {
gulp.watch([files.templates_all, files.assets, files.locales], gulp.series(['build:html']));
gulp.watch([files.templates_text_all, files.locales], gulp.series(['build:text']));
});

gulp.task('server', ['watch'], function(event) {
gulp.task('server', gulp.series(['build'], gulp.parallel(['watch'], function () {
connect.server({
root: dirs.output,
port: 1980,
livereload: true
});
});
})));

gulp.task('default', ['server']);
gulp.task('default', gulp.parallel(['server'/* , 'watch' */]));
65 changes: 34 additions & 31 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
{
"name": "mjml-starter-advanced",
"version": "1.0.0",
"description": "Advanced Email Templates based on MJML with template inheritance and i18n support",
"main": "index.js",
"scripts": {
"start": "gulp"
},
"repository": {
"type": "git",
"url": "git+https://github.com/davidmarquis/mjml-starter-advanced.git"
},
"author": "OneLastCloud",
"license": "ISC",
"bugs": {
"url": "https://github.com/davidmarquis/mjml-starter-advanced/issues"
},
"homepage": "https://github.com/davidmarquis/mjml-starter-advanced",
"devDependencies": {
"gulp": "^3.9.1",
"gulp-connect": "^5.0.0",
"gulp-environments": "^0.1.1",
"gulp-html-i18n": "^0.6.2",
"gulp-mjml": "^2.0.0",
"gulp-nunjucks": "^2.3.0",
"gulp-replace": "^0.5.4",
"mjml": "3.0.2",
"mjml-core": "3.0.2",
"mjml-serve": "0.0.7",
"yargs": "^6.2.0"
}
}
"name": "mjml-starter-advanced",
"version": "1.0.0",
"description": "Advanced Email Templates based on MJML with template inheritance and i18n support",
"main": "index.js",
"scripts": {
"start": "gulp",
"build": "gulp build --env production"
},
"repository": {
"type": "git",
"url": "git+https://github.com/davidmarquis/mjml-starter-advanced.git"
},
"author": "OneLastCloud",
"license": "ISC",
"bugs": {
"url": "https://github.com/davidmarquis/mjml-starter-advanced/issues"
},
"homepage": "https://github.com/davidmarquis/mjml-starter-advanced",
"devDependencies": {
"gulp": "^4.0.2",
"gulp-clean": "^0.4.0",
"gulp-connect": "^5.0.0",
"gulp-environments": "^1.0.1",
"gulp-html-i18n": "^0.16.0",
"gulp-mjml": "^4.0.2",
"gulp-nunjucks": "^5.1.0",
"gulp-replace": "^1.1.3",
"gulp-tap": "^2.0.0",
"mjml": "4.13.0",
"mjml-core": "4.13.0",
"mjml-serve": "0.0.7",
"yargs": "^17.6.2"
}
}
Loading