Skip to content

Add support for custom blocks in .vue files #499

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/en/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ There are many cool features provided by `vue-loader`:

- ES2015 enabled by default;
- Allows using other Webpack loaders for each part of a Vue component, for example SASS for `<style>` and Jade for `<template>`;
- Allows custom sections in a .vue file that can have custom loader chains applied to them
- Treat static assets referenced in `<style>` and `<template>` as module dependencies and handle them with Webpack loaders;
- Can simulate scoped CSS for each component;
- Supports component hot-reloading during development.
Expand Down
7 changes: 5 additions & 2 deletions docs/en/configurations/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,13 @@ module.exports = {
{
test: /\.vue$/,
loader: 'vue-loader',
// vue-loader options goes here
options: {
loaders: {
// ...
// load all <script> without "lang" attribute with coffee-loader
js: 'coffee',
// load <template> directly as HTML string, without piping it
// through vue-html-loader first
html: 'raw'
}
}
}
Expand Down
73 changes: 72 additions & 1 deletion docs/en/start/spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Vue Component Spec

A `*.vue` file is a custom file format that uses HTML-like syntax to describe a Vue component. Each `*.vue` file consists of three types of top-level language blocks: `<template>`, `<script>` and `<style>`:
A `*.vue` file is a custom file format that uses HTML-like syntax to describe a Vue component. Each `*.vue` file consists of three types of top-level language blocks: `<template>`, `<script>`, and `<style>`, and optionally additional custom blocks:

``` html
<template>
Expand All @@ -22,6 +22,10 @@ export default {
color: red;
}
</style>

<custom1>
This could be e.g. documentation for the component.
</custom1>
```

`vue-loader` will parse the file, extract each language block, pipe them through other loaders if necessary, and finally assemble them back into a CommonJS module whose `module.exports` is a Vue.js component options object.
Expand Down Expand Up @@ -66,6 +70,66 @@ More details can be found in [Using Pre-Processors](../configurations/pre-proces

- By default, contents will be extracted and dynamically inserted into the document's `<head>` as an actual `<style>` tag using `style-loader`. It's also possible to [configure Webpack so that all styles in all components are extracted into a single CSS file](../configurations/extract-css.md).

### Custom Blocks

Additional custom blocks can be included in a `*.vue` file for any project specific needs. `vue-loader` will use the tag name to look up which webpack loaders should be applied to the contents of the section. The webpack loaders should be specified in the `loaders` hash of the `vue` section of the webpack configuration in the same way that languages are specified for the standard sections of the file. See [Advanced Loader Configuration](../configurations/advanced.md).

Example:

#### component.vue
``` html
<unit-test>
describe('example', function () {
it('basic', function (done) {
done();
})
})
</unit-test>

<template>
<h2 class="red">{{msg}}</h2>
</template>

<script>
export default {
data () {
return {
msg: 'Hello from Component A!'
}
}
}
</script>

<style>
comp-a h2 {
color: #f00;
}
</style>
```

#### webpack.config.js

``` js
// Webpack 2.x (^2.1.0-beta.25)
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue',
// vue-loader options go here
options: {
loaders: {
unit-test: 'buble-loader',
}
}
}
]
}
}
```


### Src Imports

If you prefer splitting up your `*.vue` components into multiple files, you can use the `src` attribute to import an external file for a language block:
Expand All @@ -83,6 +147,13 @@ Beware that `src` imports follow the same path resolution rules to CommonJS `req
<style src="todomvc-app-css/index.css">
```

`src` imports also work with custom blocks, e.g.:

``` html
<unit-test src="./unit-test.js">
</unit-test>
```

### Syntax Highlighting

Currently there is syntax highlighting support for [Sublime Text](https://github.com/vuejs/vue-syntax-highlight), [Atom](https://atom.io/packages/language-vue), [Vim](https://github.com/posva/vim-vue), [Visual Studio Code](https://marketplace.visualstudio.com/items/liuji-jim.vue), [Brackets](https://github.com/pandao/brackets-vue), and [JetBrains products](https://plugins.jetbrains.com/plugin/8057) (WebStorm, PhpStorm, etc). Contributions for other editors/IDEs are highly appreciated! If you are not using any pre-processors in Vue components, you can also get by treating `*.vue` files as HTML in your editor.
Expand Down
42 changes: 41 additions & 1 deletion lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var parse = require('./parser')
var genId = require('./gen-id')
var normalize = require('./normalize')
var loaderUtils = require('loader-utils')
var querystring = require('querystring')

// internal lib loaders
var selectorPath = normalize.lib('selector')
Expand Down Expand Up @@ -128,6 +129,13 @@ module.exports = function (content) {
})
}

function buildCustomBlockLoaderString (attrs) {
var noSrcAttrs = Object.assign({}, attrs)
delete noSrcAttrs.src
var qs = querystring.stringify(noSrcAttrs)
return qs ? '?' + qs : qs
}

// stringify an Array of loader objects
function stringifyLoaders (loaders) {
return loaders.map(function (obj) {
Expand Down Expand Up @@ -174,6 +182,12 @@ module.exports = function (content) {
return loader + '!' + rewriter + ensureBang(ensureLoader(lang))
case 'script':
return injectString + ensureBang(ensureLoader(lang))
default:
loader = loaders[type]
if (Array.isArray(loader)) {
loader = stringifyLoaders(loader)
}
return ensureBang(loader + buildCustomBlockLoaderString(part.attrs))
}
}
}
Expand All @@ -191,7 +205,7 @@ module.exports = function (content) {

function getSelectorString (type, index) {
return selectorPath +
'?type=' + type +
'?type=' + ((type === 'script' || type === 'template' || type === 'styles') ? type : 'customBlocks') +
'&index=' + index + '!'
}

Expand Down Expand Up @@ -249,6 +263,32 @@ module.exports = function (content) {
output += '\n'
}

// add requires for customBlocks
if (parts.customBlocks && parts.customBlocks.length) {
var addedPrefix = false

parts.customBlocks.forEach(function (customBlock, i) {
if (!loaders[customBlock.type]) {
loaderContext.emitWarning('Loader for custom block type "' + customBlock.type + '" not found in webpack configuration')
} else {
// require customBlock
customBlock.src = customBlock.attrs.src
var requireString = customBlock.src
? getRequireForImport(customBlock.type, customBlock)
: getRequire(customBlock.type, customBlock, i)

if (!addedPrefix) {
output += '\n/* customBlocks */\n'
addedPrefix = true
}

output += requireString + '\n'
}
})

output += '\n'
}

// we require the component normalizer function, and call it like so:
// normalizeComponent(
// scriptExports,
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,11 @@
"mocha": "^3.2.0",
"node-libs-browser": "^2.0.0",
"normalize-newline": "^3.0.0",
"null-loader": "^0.1.1",
"postcss": "^5.0.21",
"pug": "^2.0.0-beta6",
"raw-loader": "^0.5.1",
"skeleton-loader": "0.0.5",
"stylus": "^0.54.5",
"stylus-loader": "^2.0.0",
"sugarss": "^0.2.0",
Expand Down
22 changes: 22 additions & 0 deletions test/fixtures/custom-import.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<unit-test src="./unit-test.js">
</unit-test>

<template>
<h2 class="red">{{msg}}</h2>
</template>

<script>
export default {
data () {
return {
msg: 'Hello from Component A!'
}
}
}
</script>

<style>
comp-a h2 {
color: #f00;
}
</style>
31 changes: 31 additions & 0 deletions test/fixtures/custom-language.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<unit-test>
describe('example', function () {
it('basic', function (done) {
done();
})
})
</unit-test>

<documentation>
This is example documentation for a component.
</documentation>

<template>
<h2 class="red">{{msg}}</h2>
</template>

<script>
export default {
data () {
return {
msg: 'Hello from Component A!'
}
}
}
</script>

<style>
comp-a h2 {
color: #f00;
}
</style>
7 changes: 7 additions & 0 deletions test/fixtures/custom-options.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<unit-test foo="bar" opt2="value2">
describe('example', function () {
it('basic', function (done) {
done();
})
})
</unit-test>
5 changes: 5 additions & 0 deletions test/fixtures/unit-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe('example', function () {
it('basic', function (done) {
done();
})
})
Loading