Skip to content

Commit 9cabf9b

Browse files
Kocalweaverryan
authored andcommitted
Add JSX integration for Vue.js
1 parent 6e1b371 commit 9cabf9b

File tree

17 files changed

+338
-3
lines changed

17 files changed

+338
-3
lines changed

fixtures/vuejs-jsx/App.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#app {
2+
font-family: 'Avenir', Helvetica, Arial, sans-serif;
3+
-webkit-font-smoothing: antialiased;
4+
-moz-osx-font-smoothing: grayscale;
5+
text-align: center;
6+
color: #2c3e50;
7+
margin-top: 60px;
8+
}

fixtures/vuejs-jsx/App.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import './App.css';
2+
import './App.scss';
3+
import './App.less';
4+
import Hello from './components/Hello';
5+
6+
class TestClassSyntax {
7+
8+
}
9+
10+
export default {
11+
name: 'app',
12+
render() {
13+
return (
14+
<div id="app">
15+
<img src={require('./assets/logo.png')}/>
16+
<Hello></Hello>
17+
</div>
18+
);
19+
},
20+
};

fixtures/vuejs-jsx/App.less

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#app {
2+
margin-top: 40px;
3+
}

fixtures/vuejs-jsx/App.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#app {
2+
display: flex;
3+
color: #2c3e90;
4+
}

fixtures/vuejs-jsx/assets/logo.png

6.69 KB
Loading
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.h1, .h2 {
2+
font-weight: normal;
3+
}
4+
5+
.ul {
6+
list-style-type: none;
7+
padding: 0;
8+
}
9+
10+
.li {
11+
display: inline-block;
12+
margin: 0 10px;
13+
}
14+
15+
.a {
16+
color: #42b983;
17+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import styles from './Hello.css?module';
2+
3+
export default {
4+
name: 'hello',
5+
data() {
6+
return {
7+
msg: 'Welcome to Your Vue.js App',
8+
};
9+
},
10+
render() {
11+
return (
12+
<div class="hello">
13+
<h1 class={styles.h1}>{this.msg}</h1>
14+
<h2 class={styles.h2}>Essential Links</h2>
15+
<ul class={styles.ul}>
16+
<li class={styles.li}><a class={styles.a} href="https://vuejs.org" target="_blank">Core Docs</a></li>
17+
<li class={styles.li}><a class={styles.a} href="https://forum.vuejs.org" target="_blank">Forum</a></li>
18+
<li class={styles.li}><a class={styles.a} href="https://gitter.im/vuejs/vue" target="_blank">Gitter Chat</a></li>
19+
<li class={styles.li}><a class={styles.a} href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
20+
<br/>
21+
<li class={styles.li}><a class={styles.a} href="http://vuejs-templates.github.io/webpack/" target="_blank">Docs for This Template</a></li>
22+
</ul>
23+
<h2 class={styles.h2}>Ecosystem</h2>
24+
<ul class={styles.ul}>
25+
<li class={styles.li}><a class={styles.a} href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
26+
<li class={styles.li}><a class={styles.a} href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
27+
<li class={styles.li}><a class={styles.a} href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
28+
<li class={styles.li}><a class={styles.a} href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
29+
</ul>
30+
</div>
31+
);
32+
},
33+
};

fixtures/vuejs-jsx/main.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Vue from 'vue'
2+
import App from './App'
3+
4+
new Vue({
5+
el: '#app',
6+
template: '<App/>',
7+
components: { App }
8+
})

index.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -936,11 +936,26 @@ class Encore {
936936
* options.preLoaders = { ... }
937937
* });
938938
*
939+
* // or configure Encore-specific options
940+
* Encore.enableVueLoader(() => {}, {
941+
* // set optional Encore-specific options, for instance:
942+
*
943+
* // enable JSX usage in Vue components
944+
* // https://vuejs.org/v2/guide/render-function.html#JSX
945+
* useJsx: true
946+
* })
947+
*
948+
* Supported options:
949+
* * {boolean} useJsx (default=false)
950+
* Configure Babel to use the preset "@vue/babel-preset-jsx",
951+
* in order to enable JSX usage in Vue components.
952+
*
939953
* @param {function} vueLoaderOptionsCallback
954+
* @param {object} encoreOptions
940955
* @returns {Encore}
941956
*/
942-
enableVueLoader(vueLoaderOptionsCallback = () => {}) {
943-
webpackConfig.enableVueLoader(vueLoaderOptionsCallback);
957+
enableVueLoader(vueLoaderOptionsCallback = () => {}, encoreOptions = {}) {
958+
webpackConfig.enableVueLoader(vueLoaderOptionsCallback, encoreOptions);
944959

945960
return this;
946961
}

lib/WebpackConfig.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ class WebpackConfig {
8888
useBuiltIns: false,
8989
corejs: null,
9090
};
91+
this.vueOptions = {
92+
useJsx: false,
93+
};
9194

9295
// Features/Loaders options callbacks
9396
this.postCssLoaderOptionsCallback = () => {};
@@ -602,14 +605,23 @@ class WebpackConfig {
602605
forkedTypeScriptTypesCheckOptionsCallback;
603606
}
604607

605-
enableVueLoader(vueLoaderOptionsCallback = () => {}) {
608+
enableVueLoader(vueLoaderOptionsCallback = () => {}, vueOptions = {}) {
606609
this.useVueLoader = true;
607610

608611
if (typeof vueLoaderOptionsCallback !== 'function') {
609612
throw new Error('Argument 1 to enableVueLoader() must be a callback function.');
610613
}
611614

612615
this.vueLoaderOptionsCallback = vueLoaderOptionsCallback;
616+
617+
// Check allowed keys
618+
for (const key of Object.keys(vueOptions)) {
619+
if (!(key in this.vueOptions)) {
620+
throw new Error(`"${key}" is not a valid key for enableVueLoader(). Valid keys: ${Object.keys(this.vueOptions).join(', ')}.`);
621+
}
622+
}
623+
624+
this.vueOptions = vueOptions;
613625
}
614626

615627
enableEslintLoader(eslintLoaderOptionsOrCallback = () => {}) {

lib/features.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ const features = {
8989
],
9090
description: 'load VUE files'
9191
},
92+
'vue-jsx': {
93+
method: 'enableVueLoader()',
94+
packages: [
95+
{ name: '@vue/babel-preset-jsx' },
96+
{ name: '@vue/babel-helper-vue-jsx-merge-props' }
97+
],
98+
description: 'use Vue with JSX support'
99+
},
92100
eslint: {
93101
method: 'enableEslintLoader()',
94102
// eslint is needed so the end-user can do things

lib/loaders/babel.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ module.exports = {
7272
}
7373
}
7474

75+
if (webpackConfig.useVueLoader && webpackConfig.vueOptions.useJsx) {
76+
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('vue-jsx');
77+
babelConfig.presets.push('@vue/babel-preset-jsx');
78+
}
79+
7580
babelConfig = applyOptionsCallback(webpackConfig.babelConfigurationCallback, babelConfig);
7681
}
7782

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
"devDependencies": {
6060
"@babel/plugin-transform-react-jsx": "^7.0.0",
6161
"@babel/preset-react": "^7.0.0",
62+
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0-beta.3",
63+
"@vue/babel-preset-jsx": "^1.0.0-beta.3",
6264
"autoprefixer": "^8.5.0",
6365
"babel-eslint": "^10.0.1",
6466
"chai": "^3.5.0",

test/WebpackConfig.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,27 @@ describe('WebpackConfig object', () => {
876876
expect(config.useVueLoader).to.be.true;
877877
expect(config.vueLoaderOptionsCallback).to.equal(callback);
878878
});
879+
880+
it('Should validate Encore-specific options', () => {
881+
const config = createConfig();
882+
883+
expect(() => {
884+
config.enableVueLoader(() => {}, {
885+
notExisting: false,
886+
});
887+
}).to.throw('"notExisting" is not a valid key for enableVueLoader(). Valid keys: useJsx.');
888+
});
889+
890+
it('Should set Encore-specific options', () => {
891+
const config = createConfig();
892+
config.enableVueLoader(() => {}, {
893+
useJsx: true,
894+
});
895+
896+
expect(config.vueOptions).to.deep.equal({
897+
useJsx: true,
898+
});
899+
});
879900
});
880901

881902

test/functional.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,6 +1445,81 @@ module.exports = {
14451445
}, true);
14461446
});
14471447

1448+
it('Vue.js is compiled correctly with JSX support', (done) => {
1449+
const appDir = testSetup.createTestAppDir();
1450+
1451+
fs.writeFileSync(
1452+
path.join(appDir, 'postcss.config.js'),
1453+
`
1454+
module.exports = {
1455+
plugins: [
1456+
require('autoprefixer')()
1457+
]
1458+
} `
1459+
);
1460+
1461+
const config = testSetup.createWebpackConfig(appDir, 'www/build', 'dev');
1462+
config.enableSingleRuntimeChunk();
1463+
config.setPublicPath('/build');
1464+
config.addEntry('main', './vuejs-jsx/main');
1465+
config.enableVueLoader(() => {}, {
1466+
useJsx: true,
1467+
});
1468+
config.enableSassLoader();
1469+
config.enableLessLoader();
1470+
config.configureBabel(function(config) {
1471+
expect(config.presets[0][0]).to.equal('@babel/preset-env');
1472+
config.presets[0][1].targets = {
1473+
chrome: 52
1474+
};
1475+
});
1476+
1477+
testSetup.runWebpack(config, (webpackAssert) => {
1478+
expect(config.outputPath).to.be.a.directory().with.deep.files([
1479+
'main.js',
1480+
'main.css',
1481+
'images/logo.82b9c7a5.png',
1482+
'manifest.json',
1483+
'entrypoints.json',
1484+
'runtime.js',
1485+
]);
1486+
1487+
// test that our custom babel config is used
1488+
webpackAssert.assertOutputFileContains(
1489+
'main.js',
1490+
'class TestClassSyntax'
1491+
);
1492+
1493+
// test that global styles are working correctly
1494+
webpackAssert.assertOutputFileContains(
1495+
'main.css',
1496+
'#app {'
1497+
);
1498+
1499+
// test that CSS Modules (for scoped styles) is used
1500+
webpackAssert.assertOutputFileContains(
1501+
'main.css',
1502+
'.h1_' // `.h1` is transformed to `.h1_[a-zA-Z0-9]`
1503+
);
1504+
1505+
testSetup.requestTestPage(
1506+
path.join(config.getContext(), 'www'),
1507+
[
1508+
'build/runtime.js',
1509+
'build/main.js'
1510+
],
1511+
(browser) => {
1512+
// assert that the vue.js app rendered
1513+
browser.assert.text('#app h1', 'Welcome to Your Vue.js App');
1514+
// make sure the styles are not inlined
1515+
browser.assert.elements('style', 0);
1516+
1517+
done();
1518+
}
1519+
);
1520+
});
1521+
});
1522+
14481523
it('configureUrlLoader() allows to use the URL loader for images/fonts', (done) => {
14491524
const config = createWebpackConfig('web/build', 'dev');
14501525
config.setPublicPath('/build');

test/loaders/babel.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,22 @@ describe('loaders/babel', () => {
126126
const actualLoaders = babelLoader.getLoaders(config);
127127
expect(actualLoaders[0].options).to.deep.equal({ 'foo': true });
128128
});
129+
130+
it('getLoaders() with Vue and JSX support', () => {
131+
const config = createConfig();
132+
config.enableVueLoader(() => {}, {
133+
useJsx: true,
134+
});
135+
136+
config.configureBabel(function(babelConfig) {
137+
babelConfig.presets.push('foo');
138+
});
139+
140+
const actualLoaders = babelLoader.getLoaders(config);
141+
142+
expect(actualLoaders[0].options.presets).to.deep.include.members([
143+
'@vue/babel-preset-jsx',
144+
'foo'
145+
]);
146+
});
129147
});

0 commit comments

Comments
 (0)