Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Commit

Permalink
feat: Allow typescript decorators for vue components (#870)
Browse files Browse the repository at this point in the history
* feat: Allow typescript decorators for vue components

* refactor: Updates from PR feedback

* fix: Fixed a failing unit test
  • Loading branch information
Shepless authored and nchanged committed Oct 16, 2017
1 parent 2735765 commit 18f783e
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 18 deletions.
12 changes: 11 additions & 1 deletion docs/plugins/transpilers/VueComponentPlugin.md
Expand Up @@ -96,9 +96,19 @@ const fsbx = FuseBox.init({
]
})
```

note: Overriding a plugin chain for a .vue block will make the VueComponentPlugin ignore any lang attributes.

### Using Typescript Decorators
If you are writing Vue components with Typescript and are making use of [vue-class-component](https://github.com/vuejs/vue-class-component) then you will need to install [tslib](https://github.com/Microsoft/tslib) and update your `tsconfig.json`:

```js
{
"compilerOptions": {
"importHelpers": true
}
}
```

### External Files
The `VueComponentPlugin` fully understands the `src` attribute and will handle external files just the same as inline content.

Expand Down
3 changes: 2 additions & 1 deletion gulpfile.js
Expand Up @@ -246,7 +246,7 @@ gulp.task("make-test-runner", (done) => {


gulp.task("copy-to-dev", () => {
const devFolder = "random/fuse-box-quantum-test";
const devFolder = "vue-seed";

gulp.src("modules/fuse-box-css/**/**.**")
.pipe(gulp.dest(`../${devFolder}/node_modules/fuse-box/modules/fuse-box-css`));
Expand Down Expand Up @@ -324,6 +324,7 @@ gulp.task("installDevDeps", function(done) {
"vue",
"vue-server-renderer",
"vue-hot-reload-api",
"vue-class-component",
"rollup",
"buble",
"consolidate",
Expand Down
36 changes: 21 additions & 15 deletions src/plugins/vue/VuePlugin.ts
Expand Up @@ -9,11 +9,6 @@ import { VueScriptFile } from './VueScriptFile';
import * as path from "path";
import * as fs from "fs";
import { each } from "realm-utils";
const DEFAULT_OPTIONS: IVueComponentPluginOptions = {
script: [],
template: [],
style: []
};

export interface IVueComponentPluginOptions {
script?: Plugin[],
Expand All @@ -28,7 +23,12 @@ export class VueComponentClass implements Plugin {
public hasProcessedVueFile = false;

constructor(options: IVueComponentPluginOptions) {
this.options = Object.assign({}, DEFAULT_OPTIONS, options);
this.options = Object.assign({}, {
script: [],
template: [],
style: []
}, options);

this.options.script = Array.isArray(this.options.script) ? this.options.script : [this.options.script];
this.options.template = Array.isArray(this.options.template) ? this.options.template : [this.options.template];
this.options.style = Array.isArray(this.options.style) ? this.options.style : [this.options.style];
Expand Down Expand Up @@ -115,7 +115,7 @@ export class VueComponentClass implements Plugin {
if (!isComponentStyling) {
var component = FuseBox.import(fusePath).default;
api.reload(component._scopeId, component);
api.reload(component._vueModuleId||component.options._vueModuleId, component);
}
return true;
Expand Down Expand Up @@ -152,7 +152,6 @@ export class VueComponentClass implements Plugin {

const concat = new Concat(true, "", "\n");

concat.add(null, "var _options = {}");
file.loadContents();

const cache = {
Expand All @@ -161,8 +160,15 @@ export class VueComponentClass implements Plugin {
styles: {}
};
const component = vueCompiler.parseComponent(fs.readFileSync(file.info.absPath).toString());
const hasScopedStyles = component.styles && component.styles.find((style) => style.scoped);
const scopeId = hasScopedStyles ? `data-v-${hashString(file.info.absPath)}` : null;
const hasScopedStyles = component.styles && !!component.styles.find((style) => style.scoped);
const moduleId = `data-v-${hashString(file.info.absPath)}`;
const scopeId = hasScopedStyles ? moduleId : null;

concat.add(null, `var _options = { _vueModuleId: '${moduleId}'}`);

if (hasScopedStyles) {
concat.add(null, `Object.assign(_options, {_scopeId: '${scopeId}'})`);
}

if (component.template) {
const templateFile = this.createVirtualFile(file, component.template, scopeId, this.options.template);
Expand Down Expand Up @@ -192,12 +198,12 @@ export class VueComponentClass implements Plugin {
await scriptFile.process();
this.addToCacheObject(cache.script, scriptFile.info.fuseBoxPath, scriptFile.contents, scriptFile.sourceMap);
concat.add(null, scriptFile.contents, scriptFile.sourceMap);
concat.add(null, "Object.assign(exports.default.options||exports.default, _options)");
concat.add(null, `Object.assign(exports.default.options||exports.default, _options)`);
}
} else {
if (!cacheValid) {
concat.add(null, "exports.default = {}");
concat.add(null, "Object.assign(exports.default, _options)");
concat.add(null, `Object.assign(exports.default.options||exports.default, _options)`);
}
}

Expand Down Expand Up @@ -253,9 +259,9 @@ export class VueComponentClass implements Plugin {
process.env.vueHMR = process.env.vueHMR || {};
if (!process.env.vueHMR['${scopeId}']) {
process.env.vueHMR['${scopeId}'] = true;
api.createRecord('${scopeId}', module.exports.default);
if (!process.env.vueHMR['${moduleId}']) {
process.env.vueHMR['${moduleId}'] = true;
api.createRecord('${moduleId}', module.exports.default);
}
}
`);
Expand Down
39 changes: 38 additions & 1 deletion src/tests/plugins/VueComponentPlugin.test.ts
Expand Up @@ -22,6 +22,16 @@ const getScriptBlock = (langAttribute: string = '') => `
}
</script>`;

const getDecoratorScriptBlock = () => `
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
@Component({})
export default class VueClassComponent extends Vue {
}
</script>`;

const getStyleBlock = (langAttribute: string = '', isScoped: boolean = false) => `
<style ${langAttribute} ${isScoped ? 'scoped' : ''}>
.msg {
Expand Down Expand Up @@ -59,7 +69,7 @@ export class VuePluginTest {
return createEnv({
project: {
files: {
"app.vue": `${getTemplateBlock('lang="html"', 'LangAttributes')}${getScriptBlock('lang="coffee"')}${getStyleBlock('lang="scss"')}`
"app.vue": `${getTemplateBlock('lang="html"', 'LangAttributes')}${getScriptBlock('lang="ts"')}${getStyleBlock('lang="scss"')}`
},
plugins: [VueComponentPlugin()],
instructions: "app.vue",
Expand Down Expand Up @@ -162,4 +172,31 @@ export class VuePluginTest {
})
});
}

"Should be compatible with vue-class-component decorators"() {
return createEnv({
project: {
polyfillNonStandardDefaultUsage: true,
files: {
"app.vue": `${getTemplateBlock('', 'Decorators')}${getDecoratorScriptBlock()}${getStyleBlock('')}`
},
plugins: [
VueComponentPlugin()
],
instructions: "app.vue",
},
}).then((result) => {
const Vue = require('vue')
const renderer = require('vue-server-renderer').createRenderer()
const component = result.project.FuseBox.import('./app.vue').default.options;
const app = new Vue(component);

should(component.render).notEqual(undefined);
should(component.staticRenderFns).notEqual(undefined);

renderer.renderToString(app, (err, html) => {
should(html).findString('Decorators');
})
});
}
}

0 comments on commit 18f783e

Please sign in to comment.