Skip to content

Commit 7273818

Browse files
committed
feature #49 Vue.js Loader (weaverryan, julienj)
This PR was merged into the master branch. Discussion ---------- Vue.js Loader Commits ------- 8c0aebd only adding alias when present 7b00ec8 Changing vue options to be a callback 2e1f9f7 Throw an error if the user tries to use (e.g.) lang="scss" in vue, but has not activated that loader 0d5cdd5 Completing vue-loader support 4094a73 Fix unit test cf2c1a7 Add vue-template-compiler dependency 5207a28 Allow to add vues loader
2 parents b063f51 + 8c0aebd commit 7273818

24 files changed

+797
-45
lines changed

fixtures/vuejs/App.vue

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<template>
2+
<div id="app">
3+
<img src="./assets/logo.png">
4+
<hello></hello>
5+
</div>
6+
</template>
7+
8+
<script>
9+
import Hello from './components/Hello'
10+
11+
class TestClassSyntax {
12+
13+
}
14+
15+
export default {
16+
name: 'app',
17+
components: {
18+
Hello
19+
}
20+
}
21+
</script>
22+
23+
<style>
24+
#app {
25+
font-family: 'Avenir', Helvetica, Arial, sans-serif;
26+
-webkit-font-smoothing: antialiased;
27+
-moz-osx-font-smoothing: grayscale;
28+
text-align: center;
29+
color: #2c3e50;
30+
margin-top: 60px;
31+
}
32+
</style>
33+
34+
<style lang="scss">
35+
#app {
36+
display: flex;
37+
color: #2c3e90;
38+
}
39+
</style>
40+
41+
<style lang="sass">
42+
#app
43+
color: #2c3e90
44+
</style>
45+
46+
<style lang="less">
47+
#app {
48+
margin-top: 40px;
49+
}
50+
</style>

fixtures/vuejs/assets/logo.png

6.69 KB
Loading

fixtures/vuejs/components/Hello.vue

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<template>
2+
<div class="hello">
3+
<h1>{{ msg }}</h1>
4+
<h2>Essential Links</h2>
5+
<ul>
6+
<li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
7+
<li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
8+
<li><a href="https://gitter.im/vuejs/vue" target="_blank">Gitter Chat</a></li>
9+
<li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
10+
<br>
11+
<li><a href="http://vuejs-templates.github.io/webpack/" target="_blank">Docs for This Template</a></li>
12+
</ul>
13+
<h2>Ecosystem</h2>
14+
<ul>
15+
<li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
16+
<li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
17+
<li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
18+
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
19+
</ul>
20+
</div>
21+
</template>
22+
23+
<script>
24+
export default {
25+
name: 'hello',
26+
data () {
27+
return {
28+
msg: 'Welcome to Your Vue.js App'
29+
}
30+
}
31+
}
32+
</script>
33+
34+
<!-- Add "scoped" attribute to limit CSS to this component only -->
35+
<style scoped>
36+
h1, h2 {
37+
font-weight: normal;
38+
}
39+
40+
ul {
41+
list-style-type: none;
42+
padding: 0;
43+
}
44+
45+
li {
46+
display: inline-block;
47+
margin: 0 10px;
48+
}
49+
50+
a {
51+
color: #42b983;
52+
}
53+
</style>

fixtures/vuejs/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: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,8 @@ module.exports = {
330330
},
331331

332332
/**
333-
* If enabled, the react preset is added to Babel:
333+
* If enabled, the react preset is added to Babel.
334+
*
334335
* https://babeljs.io/docs/plugins/preset-react/
335336
*
336337
* @returns {exports}
@@ -341,6 +342,28 @@ module.exports = {
341342
return this;
342343
},
343344

345+
/**
346+
* If enabled, the Vue.js loader is enabled.
347+
*
348+
* https://github.com/vuejs/vue-loader
349+
*
350+
* Encore.enableVueLoader();
351+
*
352+
* // or configure the vue-loader options
353+
* // https://vue-loader.vuejs.org/en/configurations/advanced.html
354+
* Encore.enableVueLoader(function(options) {
355+
* options.preLoaders = { ... }
356+
* });
357+
*
358+
* @param {function} vueLoaderOptionsCallback
359+
* @returns {exports}
360+
*/
361+
enableVueLoader(vueLoaderOptionsCallback = () => {}) {
362+
webpackConfig.enableVueLoader(vueLoaderOptionsCallback);
363+
364+
return this;
365+
},
366+
344367
/**
345368
* If enabled, the output directory is emptied between
346369
* each build (to remove old files).

lib/WebpackConfig.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class WebpackConfig {
5050
this.providedVariables = {};
5151
this.babelConfigurationCallback = function() {};
5252
this.useReact = false;
53+
this.useVueLoader = false;
54+
this.vueLoaderOptionsCallback = () => {};
5355
this.loaders = [];
5456
}
5557

@@ -222,6 +224,16 @@ class WebpackConfig {
222224
this.useReact = true;
223225
}
224226

227+
enableVueLoader(vueLoaderOptionsCallback = () => {}) {
228+
this.useVueLoader = true;
229+
230+
if (typeof vueLoaderOptionsCallback !== 'function') {
231+
throw new Error('Argument 1 to enableVueLoader() must be a callback function.');
232+
}
233+
234+
this.vueLoaderOptionsCallback = vueLoaderOptionsCallback;
235+
}
236+
225237
cleanupOutputBeforeBuild() {
226238
this.cleanupOutput = true;
227239
}

lib/config-generator.js

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
const webpack = require('webpack');
1313
const ExtractTextPlugin = require('extract-text-webpack-plugin');
14+
const extractText = require('./loaders/extract-text');
1415
const ManifestPlugin = require('./webpack/webpack-manifest-plugin');
1516
const DeleteUnusedEntriesJSPlugin = require('./webpack/delete-unused-entries-js-plugin');
1617
const AssetOutputDisplayPlugin = require('./friendly-errors/asset-output-display-plugin');
@@ -21,11 +22,14 @@ const missingLoaderTransformer = require('./friendly-errors/transformers/missing
2122
const missingLoaderFormatter = require('./friendly-errors/formatters/missing-loader');
2223
const missingPostCssConfigTransformer = require('./friendly-errors/transformers/missing-postcss-config');
2324
const missingPostCssConfigFormatter = require('./friendly-errors/formatters/missing-postcss-config');
25+
const vueUnactivatedLoaderTransformer = require('./friendly-errors/transformers/vue-unactivated-loader-error');
26+
const vueUnactivatedLoaderFormatter = require('./friendly-errors/formatters/vue-unactivated-loader-error');
2427
const pathUtil = require('./config/path-util');
2528
const cssLoaderUtil = require('./loaders/css');
2629
const sassLoaderUtil = require('./loaders/sass');
2730
const lessLoaderUtil = require('./loaders/less');
2831
const babelLoaderUtil = require('./loaders/babel');
32+
const vueLoaderUtil = require('./loaders/vue');
2933

3034
class ConfigGenerator {
3135
/**
@@ -68,9 +72,14 @@ class ConfigGenerator {
6872
config.stats = this.buildStatsConfig();
6973

7074
config.resolve = {
71-
extensions: ['.js', '.jsx']
75+
extensions: ['.js', '.jsx', '.vue'],
76+
alias: {}
7277
};
7378

79+
if (this.webpackConfig.useVueLoader) {
80+
config.resolve.alias['vue$'] = 'vue/dist/vue.esm.js';
81+
}
82+
7483
return config;
7584
}
7685

@@ -111,10 +120,7 @@ class ConfigGenerator {
111120
},
112121
{
113122
test: /\.css$/,
114-
use: ExtractTextPlugin.extract({
115-
fallback: 'style-loader' + this.getSourceMapOption(),
116-
use: cssLoaderUtil.getLoaders(this.webpackConfig)
117-
})
123+
use: extractText.extract(this.webpackConfig, cssLoaderUtil.getLoaders(this.webpackConfig, false))
118124
},
119125
{
120126
test: /\.(png|jpg|jpeg|gif|ico|svg)$/,
@@ -137,20 +143,21 @@ class ConfigGenerator {
137143
if (this.webpackConfig.useSassLoader) {
138144
rules.push({
139145
test: /\.s[ac]ss$/,
140-
use: ExtractTextPlugin.extract({
141-
fallback: 'style-loader' + this.getSourceMapOption(),
142-
use: sassLoaderUtil.getLoaders(this.webpackConfig)
143-
})
146+
use: extractText.extract(this.webpackConfig, sassLoaderUtil.getLoaders(this.webpackConfig))
144147
});
145148
}
146149

147150
if (this.webpackConfig.useLessLoader) {
148151
rules.push({
149152
test: /\.less/,
150-
use: ExtractTextPlugin.extract({
151-
fallback: 'style-loader' + this.getSourceMapOption(),
152-
use: lessLoaderUtil.getLoaders(this.webpackConfig)
153-
})
153+
use: extractText.extract(this.webpackConfig, lessLoaderUtil.getLoaders(this.webpackConfig))
154+
});
155+
}
156+
157+
if (this.webpackConfig.useVueLoader) {
158+
rules.push({
159+
test: /\.vue$/,
160+
use: vueLoaderUtil.getLoaders(this.webpackConfig, this.webpackConfig.vueLoaderOptionsCallback)
154161
});
155162
}
156163

@@ -313,11 +320,13 @@ class ConfigGenerator {
313320
clearConsole: false,
314321
additionalTransformers: [
315322
missingLoaderTransformer,
316-
missingPostCssConfigTransformer
323+
missingPostCssConfigTransformer,
324+
vueUnactivatedLoaderTransformer
317325
],
318326
additionalFormatters: [
319327
missingLoaderFormatter,
320-
missingPostCssConfigFormatter
328+
missingPostCssConfigFormatter,
329+
vueUnactivatedLoaderFormatter
321330
],
322331
compilationSuccessInfo: {
323332
messages: []
@@ -377,10 +386,6 @@ class ConfigGenerator {
377386
https: this.webpackConfig.useDevServerInHttps()
378387
};
379388
}
380-
381-
getSourceMapOption() {
382-
return this.webpackConfig.useSourceMaps ? '?sourceMap' : '';
383-
}
384389
}
385390

386391
/**
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* This file is part of the Symfony package.
3+
*
4+
* (c) Fabien Potencier <[email protected]>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
'use strict';
11+
12+
const chalk = require('chalk');
13+
14+
function formatErrors(errors) {
15+
if (errors.length === 0) {
16+
return [];
17+
}
18+
19+
let messages = [];
20+
// there will be an error for *every* file, but showing
21+
// the error over and over again is not helpful
22+
23+
messages.push(
24+
chalk.red('Vue processing failed:')
25+
);
26+
messages.push('');
27+
for (let error of errors) {
28+
messages.push(` * ${error.message}`);
29+
}
30+
31+
messages.push('');
32+
33+
return messages;
34+
}
35+
36+
function format(errors) {
37+
return formatErrors(errors.filter((e) => (
38+
e.type === 'vue-unactivated-loader-error'
39+
)));
40+
}
41+
42+
module.exports = format;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* This file is part of the Symfony package.
3+
*
4+
* (c) Fabien Potencier <[email protected]>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
'use strict';
11+
12+
const TYPE = 'vue-unactivated-loader-error';
13+
14+
function isVueUnactivatedLoaderError(e) {
15+
if (e.name !== 'ModuleBuildError') {
16+
return false;
17+
}
18+
19+
if (e.message.indexOf('Cannot process lang=') === -1) {
20+
return false;
21+
}
22+
23+
return true;
24+
}
25+
26+
function transform(error) {
27+
if (!isVueUnactivatedLoaderError(error)) {
28+
return error;
29+
}
30+
31+
error = Object.assign({}, error);
32+
33+
error.type = TYPE;
34+
error.severity = 900;
35+
36+
return error;
37+
}
38+
39+
module.exports = transform;

lib/loader-features.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ const loaderFeatures = {
3535
method: 'enableReactPreset()',
3636
packages: ['babel-preset-react'],
3737
description: 'process React JS files'
38+
},
39+
vue: {
40+
method: 'enableVueLoader()',
41+
// vue is needed so the end-user can do things
42+
// vue-template-compiler is a peer dep of vue-loader
43+
packages: ['vue', 'vue-loader', 'vue-template-compiler'],
44+
description: 'load VUE files'
3845
}
3946
};
4047

0 commit comments

Comments
 (0)