fuse-box
Version:
Fuse-Box a bundler that does it right
180 lines (163 loc) • 6.12 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 => {
var styles = extractStyle(result.styles);
if(styles.some)
file.addStringDependency("fuse-box-css");
file.contents = compileScript(file, this.options, context, html, result.script, styles);
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 extractStyle (styleList) {
var rv = {scoped: '', global: '', some: false};
if(styleList) for(let s in styleList) {
let style = styleList[s], content = style.content;
//todo here : if(style.lang === "stylus", ...)
if(style.lang)
console.error('Not supported yet: vue single-file-component style languages other than CSS')
if('style'=== style.type)
rv[style.scoped?'scoped':'global'] += content;
}
rv.some = !!(rv.global || rv.scoped);
return rv;
}
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(file, options, context, html, script, styles) : string {
let lang = script.attrs.lang;
if (lang === 'babel') {
return compileBabel(file, options, context, html, script, styles);
} else {
return compileTypeScript(file, options, context, html, script, styles);
}
}
function compileTypeScript(file, options, context, html, script, styles) : string {
if (!typescriptTranspiler) {
typescriptTranspiler = require("typescript");
}
try {
const jsTranspiled = typescriptTranspiler.transpileModule(script.content, context.getTypeScriptConfig());
return reduceVueToScript(file, jsTranspiled.outputText, html, styles)
} catch (err) {
console.log(err)
}
return ''
}
function compileBabel(file, options, context, html, script, styles) : 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(file, jsTranspiled.code, html, styles)
} catch (err) {
console.log(err)
}
return '';
}
function reduceVueToScript(file, jsContent, html, styles) : string {
const compiled = vueCompiler.compile(html);
var cssInclude = '';
if(styles.global)
cssInclude += styles.global;
if(styles.scoped)
console.error('Functionality not yet supported: scoped style')
if(cssInclude) cssInclude =
'require("fuse-box-css")('+
JSON.stringify(file.info.fuseBoxPath)+
','+
JSON.stringify(cssInclude)+
');'
return `var _p = {};
var _v = function(exports){${jsContent}
};${cssInclude}
_p.render = ` + toFunction(compiled.render) + `
_p.staticRenderFns = [ ` + compiled.staticRenderFns.map(toFunction).join(',') + ` ];
var _e = {}; _v(_e); Object.assign(_e.default.options||_e.default, _p)
module.exports = _e
`;
}
export const VuePlugin = (options?: VuePluginOptions) => {
return new VuePluginClass(options);
};