├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json ├── parser.js └── selector.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Q42 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This is now the official Vue loader, the latest version can be found at https://github.com/vuejs/vue-loader 2 | 3 | # vue-multi-loader 4 | 5 | > Vue.js component loader for [Webpack](http://webpack.github.io), using Webpack loaders for the parts. 6 | 7 | It allows you to write your components in this format: 8 | 9 | ``` html 10 | // app.vue 11 | 16 | 17 | 20 | 21 | 30 | ``` 31 | 32 | You can also mix preprocessor languages in the component file: 33 | 34 | ``` html 35 | // app.vue 36 | 40 | 41 | 44 | 45 | 50 | ``` 51 | 52 | And you can import using the `src` attribute (note that there's no need for a `lang` attribute here, as Webpack will 53 | be used to determine which loader applies): 54 | 55 | ``` html 56 | 57 | ``` 58 | 59 | ## Usage 60 | 61 | Config Webpack: 62 | 63 | ``` js 64 | // webpack.config.js 65 | module.exports = { 66 | entry: "./main.js", 67 | output: { 68 | filename: "build.js" 69 | }, 70 | module: { 71 | loaders: [ 72 | { test: /\.vue$/, loader: "vue-multi-loader" }, 73 | ] 74 | } 75 | } 76 | ``` 77 | 78 | And this is all you need to do in your main entry file: 79 | 80 | ``` js 81 | // main.js 82 | var Vue = require('vue') 83 | var appOptions = require('./app.vue') 84 | var app = new Vue(appOptions).$mount('#app') 85 | ``` 86 | 87 | ## Loader configuration 88 | 89 | By default, `vue-multi-loader` will try to use the loader with the same name as 90 | the `lang` attribute, but you can configure which loader should be used. 91 | 92 | For example, to extract out the generated css into a separate file, 93 | use this configuration: 94 | 95 | ``` js 96 | // webpack.config.js 97 | var ExtractTextPlugin = require("extract-text-webpack-plugin"); 98 | var vue = require("vue-multi-loader"); 99 | 100 | module.exports = { 101 | entry: "./main.js", 102 | output: { 103 | filename: "build.js" 104 | }, 105 | module: { 106 | loaders: [ 107 | { 108 | test: /\.vue$/, loader: vue.withLoaders({ 109 | css: ExtractTextPlugin.extract("css"), 110 | stylus: ExtractTextPlugin.extract("css!stylus") 111 | }) 112 | }, 113 | ] 114 | }, 115 | plugins: [ 116 | new ExtractTextPlugin("[name].css") 117 | ] 118 | } 119 | ``` 120 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var loaderUtils = require("loader-utils"); 2 | 3 | module.exports = function (content) { 4 | this.cacheable(); 5 | var cb = this.async(); 6 | var languages = {}; 7 | var output = ''; 8 | var vueUrl = loaderUtils.getRemainingRequest(this); 9 | var loaders = loaderUtils.parseQuery(this.query); 10 | loaders.html = loaders.html || 'html'; 11 | loaders.css = loaders.css || 'style!css'; 12 | loaders.js = loaders.js || ''; 13 | var loaderPrefix = { 14 | template: 'html!template-html-loader?raw&engine=', 15 | style: 'style!css!', 16 | script: '' 17 | }; 18 | var defaultLang = { 19 | template: 'html', 20 | style: 'css', 21 | script: 'js' 22 | }; 23 | 24 | function loader(part, lang) { 25 | lang = lang || defaultLang[part]; 26 | var loader = loaders[lang] !== undefined ? loaders[lang] : loaderPrefix[part] + lang; 27 | return loader ? loader + '!' : ''; 28 | } 29 | 30 | var me = this; 31 | function getRequire(part, lang) { 32 | return 'require(' + loaderUtils.stringifyRequest(me, '-!' + loader(part, lang) + require.resolve('./selector.js') + '?' + part + '/' + lang + '!' + vueUrl) + ')'; 33 | } 34 | 35 | var me = this; 36 | var url = "!!" + require.resolve("./parser.js") + "!" + vueUrl; 37 | this.loadModule(url, function(err, source, map, module) { 38 | if (err) return cb(err); 39 | 40 | var parts = me.exec(source, url); 41 | 42 | for (var i = 0; i < parts.includes.length; i++) 43 | output += 'require(' + loaderUtils.stringifyRequest(this, loaderUtils.urlToRequest(parts.includes[i])) + ')\n'; 44 | 45 | for (var lang in parts.style) 46 | output += getRequire('style', lang) + '\n'; 47 | 48 | for (var lang in parts.script) 49 | output += 'module.exports = ' + getRequire('script', lang) + '\n'; 50 | 51 | var hasTemplate = false; 52 | for (var lang in parts.template) { 53 | if (hasTemplate) 54 | return cb(new Error('Only one template element allowed per vue component!')); 55 | output += 'module.exports.template = ' + getRequire('template', lang); 56 | hasTemplate = true; 57 | } 58 | 59 | cb(null, output); 60 | }) 61 | } 62 | 63 | module.exports.withLoaders = function (opts) { 64 | return 'vue-multi-loader?' + JSON.stringify(opts).replace(/!/g, '\\u0021') 65 | } 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-multi-loader", 3 | "version": "0.0.5", 4 | "description": "Vue.js component loader for Webpack, using Webpack loaders for the parts", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Q42/vue-multi-loader.git" 9 | }, 10 | "keywords": [ 11 | "vue", 12 | "webpack", 13 | "loader" 14 | ], 15 | "author": "Sjoerd Visscher", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/Q42/vue-multi-loader/issues" 19 | }, 20 | "homepage": "https://github.com/Q42/vue-multi-loader", 21 | "dependencies": { 22 | "loader-utils": "^0.2.7", 23 | "parse5": "^1.1.4", 24 | "source-map": "^0.4.2" 25 | }, 26 | "peerDependencies": { 27 | "css-loader": "^0.14.4", 28 | "html-loader": "^0.3.0", 29 | "style-loader": "^0.12.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /parser.js: -------------------------------------------------------------------------------- 1 | var parse5 = require('parse5'); 2 | var parser = new parse5.Parser(parse5.TreeAdapters.htmlparser2, { locationInfo: true }); 3 | var serializer = new parse5.TreeSerializer(); 4 | var SourceNode = require("source-map").SourceNode; 5 | var loaderUtils = require("loader-utils"); 6 | 7 | module.exports = function (content) { 8 | this.cacheable(); 9 | var cb = this.async(); 10 | var vueRequest = loaderUtils.getRemainingRequest(this); 11 | var request = loaderUtils.getCurrentRequest(this); 12 | 13 | var languages = {}; 14 | var output = { 15 | template: {}, 16 | style: {}, 17 | script: {}, 18 | includes: [] 19 | }; 20 | 21 | function pos(offset) { 22 | return { 23 | line: content.substr(0, offset).split('\n').length, 24 | col: offset - content.lastIndexOf('\n', offset - 1) 25 | } 26 | } 27 | 28 | var fragment = parser.parseFragment(content); 29 | fragment.children.forEach(function (node) { 30 | if (node.attribs && node.attribs.src) { 31 | output.includes.push(node.attribs.src) 32 | return; 33 | } 34 | 35 | if (!node.children || !node.children.length) 36 | return; 37 | 38 | var lang = (node.attribs && node.attribs.lang) || ''; 39 | var type = node.name; 40 | if (!output[type]) 41 | return; 42 | 43 | // Work around changes in parse5 >= 1.2.0 44 | if (node.children[0].type === 'root') 45 | node = node.children[0]; 46 | 47 | var start = node.children[0].__location.start; 48 | var end = node.children[node.children.length - 1].__location.end; 49 | var lines = content.substring(start, end).split('\n'); 50 | var startPos = pos(start); 51 | var sourceNodes = lines.map(function (line, i) { 52 | return new SourceNode(startPos.line + i, i ? 0 : startPos.col, vueRequest, line + '\n'); 53 | }); 54 | output[type][lang] = (output[type][lang] || []).concat(sourceNodes) 55 | }); 56 | 57 | for (var type in output) { 58 | for (var lang in output[type]) { 59 | var sourceNodes = output[type][lang]; 60 | output[type][lang] = new SourceNode(1, 1, vueRequest, sourceNodes).toStringWithSourceMap({ 61 | file: request 62 | }) 63 | } 64 | } 65 | 66 | cb(null, 'module.exports = ' + JSON.stringify(output)); 67 | } 68 | -------------------------------------------------------------------------------- /selector.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | this.cacheable(); 3 | var cb = this.async(); 4 | var path = this.query.substr(1).split('/'); 5 | 6 | var me = this; 7 | var url = "!!" + require.resolve("./parser.js") + "!" + this.resource; 8 | this.loadModule(url, function(err, source) { 9 | if (err) return cb(err); 10 | var parts = me.exec(source, url); 11 | var part = parts[path[0]][path[1]||'']; 12 | cb(null, part.code, part.map); 13 | }) 14 | } 15 | --------------------------------------------------------------------------------