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

Mjmlconfig #1206

Merged
merged 8 commits into from
Aug 16, 2018
2 changes: 1 addition & 1 deletion doc/community-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ To use a community component, proceed as follows:
```
{
"packages": [
"./node_modules/component-name/path-to-js-file"
"component-name/path-to-js-file"
]
}
```
Expand Down
5 changes: 4 additions & 1 deletion doc/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ argument | description | default value
`mjml -w [input]` | Watches the changes made to [input] (file or folder) | NA
`mjml [input] --config.beautify` | Beautifies the output (`true` or `false`) | true
`mjml [input] --config.minify` | Minifies the output (`true` or `false`) | false
`mjml [input] --config.configPath [mjmlconfigPath]` | Uses the `.mjmlconfig` file in the specified path or directory to include custom components | *The `.mjmlconfig` file in the current working directory, if any*
`mjml [input] --config.validationLevel` | [Validation level](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml): 'strict', 'soft' or 'skip' | 'soft'


## Inside Node.js

```javascript
Expand Down Expand Up @@ -97,7 +99,8 @@ keepComments | boolean | Option to keep comments in the HTML output | true
beautify | boolean | Option to beautify the HTML output | false
minify | boolean | Option to minify the HTML output | false
validationLevel | string | Available values for the [validator](https://github.com/mjmlio/mjml/tree/master/packages/mjml-validator#validating-mjml): 'strict', 'soft', 'skip' | 'soft'
filePath | string | Full path of the specified file | '.'
filePath | string | Full path of the specified file to use when resolving paths from [`mj-include` components](#mj-include) | '.'
configPath | string | The path or directory of the [`.mjmlconfig` file](#community-components) | `process.cwd()`

## API

Expand Down
79 changes: 79 additions & 0 deletions packages/mjml-core/src/helpers/mjmlconfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import path from 'path'
import fs from 'fs'

import { registerComponent } from '../components'

export function readMjmlConfig(configPathOrDir = process.cwd()) {
let componentRootPath = process.cwd()
let mjmlConfigPath = configPathOrDir
try {
mjmlConfigPath = path.basename(configPathOrDir) === '.mjmlconfig'
? path.resolve(configPathOrDir)
: path.resolve(configPathOrDir, '.mjmlconfig')
componentRootPath = path.dirname(mjmlConfigPath)
const mjmlConfig = JSON.parse(fs.readFileSync(path.resolve(mjmlConfigPath), 'utf8'))
return { mjmlConfig, componentRootPath }
} catch (e) {
if (e.code !== 'ENOENT') {
console.log('Error reading mjmlconfig : ', e) // eslint-disable-line no-console
}
return { mjmlConfig: { packages: [] }, mjmlConfigPath, componentRootPath }
}
}


export function resolveComponentPath(compPath, componentRootPath) {
if (!compPath) {
return null
}
if (!compPath.startsWith('.') && !path.isAbsolute(compPath)) {
try {
return require.resolve(compPath)
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
console.log('Error resolving custom component path : ', e) // eslint-disable-line no-console
return null
}
// we got a 'MODULE_NOT_FOUND' error
try {
// try again as relative path to node_modules: (this may be necessary if mjml is installed globally or by npm link)
return resolveComponentPath(`./node_modules/${compPath}`, componentRootPath)
} catch (e) {
// try again as a plain local path:
return resolveComponentPath(`./${compPath}`, componentRootPath)
}
}
}
return require.resolve(path.resolve(componentRootPath, compPath))
}

export function registerCustomComponent(comp, registerCompFn = registerComponent) {
if (comp instanceof Function) {
registerCompFn(comp)
} else {
const compNames = Object.keys(comp) // this approach handles both an array and an object (like the mjml-accordion default export)
compNames.forEach(compName => {
registerCustomComponent(comp[compName], registerCompFn)
})
}
}

export default function registerCustomComponents(configPathOrDir = process.cwd(), registerCompFn = registerComponent) {
const { mjmlConfig: { packages }, componentRootPath } = readMjmlConfig(configPathOrDir)
packages.forEach(compPath => {
let resolvedPath = compPath
try {
resolvedPath = resolveComponentPath(compPath, componentRootPath)
if (resolvedPath) {
const requiredComp = require(resolvedPath) // eslint-disable-line global-require, import/no-dynamic-require
registerCustomComponent(requiredComp.default || requiredComp, registerCompFn)
}
} catch (e) {
if (e.code === 'ENOENT' || e.code !== 'MODULE_NOT_FOUND') {
console.log('Missing or unreadable custom component : ', resolvedPath) // eslint-disable-line no-console
} else {
console.log('Error when registering custom component : ', resolvedPath, e) // eslint-disable-line no-console
}
}
})
}
21 changes: 5 additions & 16 deletions packages/mjml-core/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { find, get, identity, map, omit, reduce, isObject } from 'lodash'
import path from 'path'
import fs from 'fs'
import juice from 'juice'
import { html as htmlBeautify } from 'js-beautify'
import { minify as htmlMinify } from 'html-minifier'
Expand All @@ -16,6 +15,8 @@ import mergeOutlookConditionnals from './helpers/mergeOutlookConditionnals'
import minifyOutlookConditionnals from './helpers/minifyOutlookConditionnals'
import defaultSkeleton from './helpers/skeleton'

import registerCustomComponents from './helpers/mjmlconfig'

class ValidationError extends Error {
constructor(message, errors) {
super(message)
Expand Down Expand Up @@ -54,8 +55,11 @@ export default function mjml2html(mjml, options = {}) {
skeleton = defaultSkeleton,
validationLevel = 'soft',
filePath = '.',
configPath = process.cwd(),
} = options

registerCustomComponents(configPath, registerComponent)

if (typeof mjml === 'string') {
mjml = MJMLParser(mjml, {
keepComments,
Expand Down Expand Up @@ -287,21 +291,6 @@ export default function mjml2html(mjml, options = {}) {
}
}

// register components from mjmlconfig
try {
const mjmlConfig = fs.readFileSync(path.join(process.cwd(), '.mjmlconfig'))
const customComps = JSON.parse(mjmlConfig).packages

customComps.forEach(compPath => {
const requiredComp = require(path.join(process.cwd(), compPath)) // eslint-disable-line global-require, import/no-dynamic-require
registerComponent(requiredComp.default || requiredComp)
})
} catch (e) {
if (e.code !== 'ENOENT') {
console.log('Error when registering custom components : ', e) // eslint-disable-line no-console
}
}

export { components, initComponent, registerComponent, suffixCssClasses }

export { BodyComponent, HeadComponent } from './createComponent'