src/private/Transformers.js
import {copyAsync, outputFileAsync} from 'fs-extra-promise'
import {join} from 'path'
import jstransformer from 'jstransformer'
/** Keeps track of all jstransformers and can transform any file. */
export default class Transformers {
constructor(options) {
const dependencies = options.packageConfig.dependencies
const devDependencies = options.packageConfig.devDependencies
// Object like {jade: {pretty: true}}
const transformOptions = options.opts
const unusedTransformOptions = new Set(Object.keys(transformOptions))
const transformNamesWithNoOptions = []
this._extToTransformer = new Map()
const maybeAddTransformer = packageName => {
if (packageName.startsWith('jstransformer-')) {
const pkg = require(join(options.packageDir, 'node_modules', packageName))
const {name} = pkg
let opts = null
if (name in transformOptions) {
opts = transformOptions[name]
unusedTransformOptions.delete(name)
} else
transformNamesWithNoOptions.push(name)
const transform = new RealTransform(pkg, opts)
for (const ext of transform.inExtensions())
this._extToTransformer.set(ext, transform)
}
}
for (const _ in dependencies)
maybeAddTransformer(_)
for (const _ in devDependencies)
maybeAddTransformer(_)
for (const name of unusedTransformOptions)
throw new Error(
`${JSON.stringify(name)} does not name any known transform.\n` +
`You probably meant one of: ${transformNamesWithNoOptions.join(', ')}.`)
}
/** Get file extension for output file. */
getOutExtension(inExtension) {
return this._chooseTransform(inExtension).outExtension()
}
/** Write input fileD to output file. */
async transform({inExtension, fullInPath, fullOutPath}) {
return await this._chooseTransform(inExtension).transform(fullInPath, fullOutPath)
}
_chooseTransform(inExtension) {
if (inExtension === '')
return new IdentityTransform('')
else {
if (!inExtension.startsWith('.'))
throw new Error(`Extension should start with '.'. Got: '${inExtension}'`)
inExtension = inExtension.slice(1)
const transform = this._extToTransformer.get(inExtension)
return transform === undefined ? new IdentityTransform(inExtension) : transform
}
}
}
class Transform {}
class RealTransform extends Transform {
constructor(transformer, transformOptions) {
super()
this._transformer = jstransformer(transformer)
this._options = transformOptions
}
inExtensions() {
return this._transformer.inputFormats
}
outExtension() {
return this._transformer.outputFormat
}
async transform(fullInPath, fullOutPath) {
const locals = {filename: fullInPath}
let {body, dependencies} =
await this._transformer.renderFileAsync(fullInPath, this._options, locals)
if (dependencies === undefined)
dependencies = [ ]
await outputFileAsync(fullOutPath, body)
return dependencies
}
}
/**
Transformer that changes nothing about the input.
This can't be implemented as a "real" jstransformer because those output strings.
Doing a plain file copy instead is more efficient for large files.
*/
class IdentityTransform extends Transform {
constructor(inExtension) {
super()
this._inExtension = inExtension
}
outExtension() {
return this._inExtension
}
async transform(fullInPath, fullOutPath) {
return await copyAsync(fullInPath, fullOutPath)
}
}