fuse-box
Version:
Fuse-Box a bundler that does it right
154 lines (136 loc) • 4.93 kB
text/typescript
import { File } from "../core/File";
import { WorkFlowContext } from "../core/WorkflowContext";
import { Plugin } from "../core/WorkflowContext";
import * as fs from "fs";
import * as path from "path";
export interface VuePluginOptions {
babel?: any,
}
let vueCompiler;
let vueTranspiler;
let typescriptTranspiler;
let babelCore;
let babelConfig;
export class VuePluginClass implements Plugin {
public test: RegExp = /\.vue$/;
//
constructor(public options: VuePluginOptions = {}) {}
public init(context: WorkFlowContext) {
context.allowExtension(".vue");
}
public transform(file: File) {
const context = file.context;
// caching ...
if (context.useCache) {
let cached = context.cache.getStaticCache(file);
if (cached) {
file.isLoaded = true;
if (cached.sourceMap) {
file.sourceMap = cached.sourceMap;
}
file.analysis.skip();
file.analysis.dependencies = cached.dependencies;
file.contents = cached.contents;
return;
}
}
file.loadContents();
if (!vueCompiler) {
vueCompiler = require("vue-template-compiler");
vueTranspiler = require("vue-template-es2015-compiler");
}
let result = vueCompiler.parseComponent(file.contents, this.options);
if (result.template && result.template.type === "template") {
let templateLang = (result.template.attrs) ? result.template.attrs.lang : null;
return compileTemplateContent(context, templateLang, result.template.content).then(html => {
file.contents = compileScript(this.options, context, html, result.script)
file.analysis.parseUsingAcorn();
file.analysis.analyze();
if (context.useCache) {
context.emitJavascriptHotReload(file);
context.cache.writeStaticCache(file, file.sourceMap);
}
return true
}).catch(err => {
console.error(err);
});
}
}
};
function toFunction (code) {
return vueTranspiler('function render () {' + code + '}')
}
function compileTemplateContent (context: any, engine: string, content: string) {
return new Promise((resolve, reject) => {
if (!engine) { return resolve(content); }
const cons = require('consolidate');
if (!cons[engine]) { return content; }
cons[engine].render(content, {
filename: 'base',
basedir: context.homeDir,
includeDir: context.homeDir
}, (err, html) => {
if (err) { return reject(err); }
resolve(html)
});
});
}
function compileScript(options, context, html, script) : string {
let lang = script.attrs.lang;
if (lang === 'babel') {
return compileBabel(options, context, html, script);
} else {
return compileTypeScript(options, context, html, script);
}
}
function compileTypeScript(options, context, html, script) : string {
if (!typescriptTranspiler) {
typescriptTranspiler = require("typescript");
}
try {
const jsTranspiled = typescriptTranspiler.transpileModule(script.content, context.getTypeScriptConfig());
return reduceVueToScript(jsTranspiled.outputText, html)
} catch (err) {
console.log(err)
}
return ''
}
function compileBabel(options, context, html, script) : string {
if (!babelCore) {
babelCore = require("babel-core");
if (options.babel !== undefined) {
babelConfig = options.babel.config;
} else {
let babelRcPath = path.join(context.appRoot, `.babelrc`);
if (fs.existsSync(babelRcPath)) {
let babelRcConfig = fs.readFileSync(babelRcPath).toString();
if (babelRcConfig)
babelConfig = JSON.parse(babelRcConfig);
}
}
if (babelConfig === undefined) {
babelConfig = { plugins: ['transform-es2015-modules-commonjs'] }
}
}
try {
let jsTranspiled = babelCore.transform(script.content, babelConfig);
return reduceVueToScript(jsTranspiled.code, html)
} catch (err) {
console.log(err)
}
return '';
}
function reduceVueToScript(jsContent, html) : string {
const compiled = vueCompiler.compile(html);
return `var _p = {};
var _v = function(exports){${jsContent}
};
_p.render = ` + toFunction(compiled.render) + `
_p.staticRenderFns = [ ` + compiled.staticRenderFns.map(toFunction).join(',') + ` ];
var _e = {}; _v(_e); _p = Object.assign(_e.default, _p)
module.exports =_p
`;
}
export const VuePlugin = (options?: VuePluginOptions) => {
return new VuePluginClass(options);
};