├── test
├── fixtures
│ ├── style-import.css
│ ├── template-import.jade
│ ├── style-import-scoped.css
│ ├── script-import.vue
│ ├── style-export.vue
│ ├── postcss.vue
│ ├── template-import.vue
│ ├── pug.vue
│ ├── media-query.vue
│ ├── style-import.vue
│ ├── script-import.js
│ ├── basic.vue
│ ├── scoped-css.vue
│ └── pre-processors.vue
└── test.js
├── .gitignore
├── circle.yml
├── .eslintrc
├── lib
├── gen-id.js
├── compilers
│ ├── index.js
│ ├── pug.js
│ ├── jade.js
│ ├── coffee.js
│ ├── less.js
│ ├── stylus.js
│ ├── sass.js
│ └── babel.js
├── normalize.js
├── insert-css.js
├── template-compiler.js
├── ensure-require.js
├── style-rewriter.js
└── compiler.js
├── plugins
└── extract-css.js
├── LICENSE
├── index.js
├── package.json
└── README.md
/test/fixtures/style-import.css:
--------------------------------------------------------------------------------
1 | h1 { color: red; }
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | test/temp
4 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 6
4 |
--------------------------------------------------------------------------------
/test/fixtures/template-import.jade:
--------------------------------------------------------------------------------
1 | div
2 | h1 hello
3 |
--------------------------------------------------------------------------------
/test/fixtures/style-import-scoped.css:
--------------------------------------------------------------------------------
1 | h1 { color: green; }
2 |
--------------------------------------------------------------------------------
/test/fixtures/script-import.vue:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/style-export.vue:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/test/fixtures/postcss.vue:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/test/fixtures/template-import.vue:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": "vue",
4 | "env": {
5 | "mocha": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/pug.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 | h1 This is the app
4 | comp-a
5 | comp-b
6 |
7 |
--------------------------------------------------------------------------------
/test/fixtures/media-query.vue:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/test/fixtures/style-import.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/fixtures/script-import.js:
--------------------------------------------------------------------------------
1 | export default {
2 | data () {
3 | return {
4 | msg: 'Hello from Component A!'
5 | }
6 | }
7 | };
--------------------------------------------------------------------------------
/lib/gen-id.js:
--------------------------------------------------------------------------------
1 | // utility for generating a uid for each component file
2 | // used in scoped CSS rewriting
3 | var hash = require('hash-sum')
4 | var cache = Object.create(null)
5 |
6 | module.exports = function genId (file) {
7 | return cache[file] || (cache[file] = hash(file))
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixtures/basic.vue:
--------------------------------------------------------------------------------
1 |
2 | {{msg}}
3 |
4 |
5 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/lib/compilers/index.js:
--------------------------------------------------------------------------------
1 | // built-in compilers
2 | module.exports = {
3 | coffee: require('./coffee'),
4 | babel: require('./babel'),
5 | less: require('./less'),
6 | sass: require('./sass'),
7 | scss: require('./sass'),
8 | stylus: require('./stylus'),
9 | jade: require('./jade'),
10 | pug: require('./pug')
11 | }
12 |
--------------------------------------------------------------------------------
/lib/compilers/pug.js:
--------------------------------------------------------------------------------
1 | var ensureRequire = require('../ensure-require.js')
2 |
3 | module.exports = function (raw, cb, compiler) {
4 | ensureRequire('pug', 'pug')
5 | var pug = require('pug')
6 | try {
7 | var html = pug.compile(raw, compiler.options.pug || {})()
8 | } catch (err) {
9 | return cb(err)
10 | }
11 | cb(null, html)
12 | }
13 |
--------------------------------------------------------------------------------
/lib/compilers/jade.js:
--------------------------------------------------------------------------------
1 | var ensureRequire = require('../ensure-require.js')
2 |
3 | module.exports = function (raw, cb, compiler) {
4 | ensureRequire('jade', 'jade')
5 | var jade = require('jade')
6 | try {
7 | var html = jade.compile(raw, compiler.options.jade || {})()
8 | } catch (err) {
9 | return cb(err)
10 | }
11 | cb(null, html)
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixtures/scoped-css.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
hi
16 |
hi
17 |
yo
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/lib/normalize.js:
--------------------------------------------------------------------------------
1 | var IS_TEST = !!process.env.VUEIFY_TEST
2 | var fs = require('fs')
3 | var path = require('path')
4 |
5 | exports.lib = function (file) {
6 | if (IS_TEST) {
7 | return path.resolve(__dirname, file)
8 | } else {
9 | return 'vueify/lib/' + file
10 | }
11 | }
12 |
13 | exports.dep = function (dep) {
14 | if (IS_TEST) {
15 | return dep
16 | } else if (fs.existsSync(path.resolve(__dirname, '../node_modules', dep))) {
17 | // npm 2 or npm linked
18 | return 'vueify/node_modules/' + dep
19 | } else {
20 | // npm 3
21 | return dep
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/fixtures/pre-processors.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
15 |
16 |
22 |
23 |
24 | div
25 | h1 This is the app
26 | comp-a
27 | comp-b
28 |
29 |
30 |
35 |
--------------------------------------------------------------------------------
/lib/insert-css.js:
--------------------------------------------------------------------------------
1 | var inserted = exports.cache = {}
2 |
3 | function noop () {}
4 |
5 | exports.insert = function (css) {
6 | if (inserted[css]) return noop
7 | inserted[css] = true
8 |
9 | var elem = document.createElement('style')
10 | elem.setAttribute('type', 'text/css')
11 |
12 | if ('textContent' in elem) {
13 | elem.textContent = css
14 | } else {
15 | elem.styleSheet.cssText = css
16 | }
17 |
18 | document.getElementsByTagName('head')[0].appendChild(elem)
19 | return function () {
20 | document.getElementsByTagName('head')[0].removeChild(elem)
21 | inserted[css] = false
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/compilers/coffee.js:
--------------------------------------------------------------------------------
1 | var ensureRequire = require('../ensure-require.js')
2 |
3 | module.exports = function (raw, cb, compiler) {
4 | ensureRequire('coffee', ['coffee-script'])
5 | var coffee = require('coffee-script')
6 | var compiled
7 | try {
8 | compiled = coffee.compile(raw, compiler.options.coffee || {
9 | bare: true,
10 | sourceMap: compiler.options.sourceMap
11 | })
12 | } catch (err) {
13 | return cb(err)
14 | }
15 | if (compiler.options.sourceMap) {
16 | compiled = {
17 | code: compiled.js,
18 | map: compiled.v3SourceMap
19 | }
20 | }
21 | cb(null, compiled)
22 | }
23 |
--------------------------------------------------------------------------------
/lib/template-compiler.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var vueCompiler = require('vue-template-compiler')
3 | var transpile = require('vue-template-es2015-compiler')
4 |
5 | module.exports = function compileTemplate (template, compiler) {
6 | var compiled = vueCompiler.compile(template)
7 | if (compiled.errors.length) {
8 | compiled.errors.forEach(function (msg) {
9 | console.error('\n' + chalk.red(msg) + '\n')
10 | })
11 | throw new Error('Vue template compilation failed')
12 | } else {
13 | return {
14 | render: toFunction(compiled.render),
15 | staticRenderFns: '[' + compiled.staticRenderFns.map(toFunction).join(',') + ']'
16 | }
17 | }
18 | }
19 |
20 | function toFunction (code) {
21 | return transpile('function render () {' + code + '}')
22 | }
23 |
--------------------------------------------------------------------------------
/plugins/extract-css.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var compiler = require('../lib/compiler')
3 |
4 | module.exports = function (b, opts) {
5 | compiler.applyConfig({
6 | extractCSS: true
7 | })
8 |
9 | var styles = Object.create(null)
10 | var outPath = opts.out || opts.o || 'bundle.css'
11 |
12 | b.on('bundle', function (bs) {
13 | bs.on('end', function () {
14 | var css = Object.keys(styles)
15 | .map(function (file) { return styles[file] })
16 | .join('\n')
17 | if (typeof outPath === 'object' && outPath.write) {
18 | outPath.write(css)
19 | outPath.end()
20 | } else if (typeof outPath === 'string') {
21 | fs.writeFile(outPath, css, function () {})
22 | }
23 | })
24 | })
25 |
26 | b.on('transform', function (tr, file) {
27 | if (tr.vueify) {
28 | tr.on('vueify-style', function (e) {
29 | styles[e.file] = e.style
30 | })
31 | }
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/lib/compilers/less.js:
--------------------------------------------------------------------------------
1 | var assign = require('object-assign')
2 | var path = require('path')
3 | var ensureRequire = require('../ensure-require.js')
4 |
5 | module.exports = function (raw, cb, compiler, filePath) {
6 | ensureRequire('less', 'less')
7 | var less = require('less')
8 |
9 | var opts = assign({
10 | filename: path.basename(filePath)
11 | }, compiler.options.less)
12 |
13 | // provide import path
14 | var dir = path.dirname(filePath)
15 | var paths = [dir, process.cwd()]
16 | opts.paths = opts.paths
17 | ? opts.paths.concat(paths)
18 | : paths
19 |
20 | less.render(raw, opts, function (err, res) {
21 | if (err) {
22 | return cb(err)
23 | }
24 | // Less 2.0 returns an object instead rendered string
25 | if (typeof res === 'object') {
26 | res.imports.forEach(function (file) {
27 | compiler.emit('dependency', file)
28 | })
29 | res = res.css
30 | }
31 | cb(null, res)
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/lib/compilers/stylus.js:
--------------------------------------------------------------------------------
1 | var assign = require('object-assign')
2 | var path = require('path')
3 | var ensureRequire = require('../ensure-require.js')
4 |
5 | module.exports = function (raw, cb, compiler, filePath) {
6 | ensureRequire('stylus', 'stylus')
7 | var stylus = require('stylus')
8 |
9 | var opts = assign({
10 | filename: path.basename(filePath)
11 | }, compiler.options.stylus || {})
12 |
13 | var dir = path.dirname(filePath)
14 | var paths = [dir, process.cwd()]
15 | opts.paths = opts.paths
16 | ? opts.paths.concat(paths)
17 | : paths
18 |
19 | // using the renderer API so that we can
20 | // check deps after compilation
21 | var renderer = stylus(raw)
22 | Object.keys(opts).forEach(function (key) {
23 | renderer.set(key, opts[key])
24 | })
25 |
26 | renderer.render(function (err, css) {
27 | if (err) return cb(err)
28 | renderer.deps().forEach(function (file) {
29 | compiler.emit('dependency', file)
30 | })
31 | cb(null, css)
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2016 Evan You
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/ensure-require.js:
--------------------------------------------------------------------------------
1 | module.exports = function (name, deps) {
2 | var i, len
3 | var missing = []
4 | if (typeof deps === 'string') {
5 | deps = [deps]
6 | }
7 | for (i = 0, len = deps.length; i < len; i++) {
8 | var mis
9 | var req = deps[i]
10 | if (typeof req === 'string') {
11 | mis = req
12 | } else {
13 | mis = req[1]
14 | req = req[0]
15 | }
16 | try {
17 | // hack for babel-runtime because it does not expose "main" field
18 | if (req === 'babel-runtime') {
19 | req = 'babel-runtime/core-js'
20 | }
21 | require.resolve(req)
22 | } catch (e) {
23 | missing.push(mis)
24 | }
25 | }
26 | if (missing.length > 0) {
27 | var message = 'You are trying to use "' + name + '". '
28 | var npmInstall = 'npm install --save-dev ' + missing.join(' ')
29 | if (missing.length > 1) {
30 | var last = missing.pop()
31 | message += missing.join(', ') + ' and ' + last + ' are '
32 | } else {
33 | message += missing[0] + ' is '
34 | }
35 | message += 'missing.\n\nTo install run:\n' + npmInstall
36 | throw new Error(message)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/compilers/sass.js:
--------------------------------------------------------------------------------
1 | var assign = require('object-assign')
2 | var path = require('path')
3 | var ensureRequire = require('../ensure-require.js')
4 |
5 | module.exports = function (raw, cb, compiler, filePath) {
6 | ensureRequire('sass', 'node-sass')
7 | var sass = require('node-sass')
8 |
9 | var sassOptions = assign({
10 | data: raw,
11 | success: function (res) {
12 | if (typeof res === 'object') {
13 | cb(null, res.css)
14 | } else {
15 | cb(null, res) // compat for node-sass < 2.0.0
16 | }
17 | },
18 | error: function (err) {
19 | cb(err)
20 | }
21 | }, compiler.options.sass || {
22 | sourceComments: true
23 | })
24 |
25 | var dir = path.dirname(filePath)
26 | var paths = [dir, process.cwd()]
27 | sassOptions.includePaths = sassOptions.includePaths
28 | ? sassOptions.includePaths.concat(paths)
29 | : paths
30 |
31 | sass.render(
32 | sassOptions,
33 | // callback for node-sass > 3.0.0
34 | function (err, res) {
35 | if (err) {
36 | cb(err)
37 | } else {
38 | res.stats.includedFiles.forEach(function (file) {
39 | compiler.emit('dependency', file)
40 | })
41 | cb(null, res.css.toString())
42 | }
43 | }
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var through = require('through')
2 | var compiler = require('./lib/compiler')
3 |
4 | module.exports = function vueify (file, options) {
5 | if (!/.vue$/.test(file)) {
6 | return through()
7 | }
8 |
9 | compiler.loadConfig()
10 | compiler.applyConfig(options)
11 | compiler.applyConfig({
12 | sourceMap: options._flags.debug
13 | })
14 |
15 | var data = ''
16 | var stream = through(write, end)
17 | stream.vueify = true
18 |
19 | function dependency (file) {
20 | stream.emit('file', file)
21 | }
22 |
23 | function emitStyle (e) {
24 | stream.emit('vueify-style', e)
25 | }
26 |
27 | function write (buf) {
28 | data += buf
29 | }
30 |
31 | function end () {
32 | stream.emit('file', file)
33 | compiler.on('dependency', dependency)
34 | compiler.on('style', emitStyle)
35 |
36 | compiler.compile(data, file, function (error, result) {
37 | compiler.removeListener('dependency', dependency)
38 | compiler.removeListener('style', emitStyle)
39 | if (error) {
40 | stream.emit('error', error)
41 | // browserify doesn't log the stack by default...
42 | console.error(error.stack.replace(/^.*?\n/, ''))
43 | }
44 | stream.queue(result)
45 | stream.queue(null)
46 | })
47 | }
48 |
49 | return stream
50 | }
51 |
52 | // expose compiler
53 | module.exports.compiler = compiler
54 |
--------------------------------------------------------------------------------
/lib/compilers/babel.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var path = require('path')
3 | var json = require('json5')
4 | var assign = require('object-assign')
5 | var ensureRequire = require('../ensure-require')
6 |
7 | var defaultBabelOptions = {
8 | presets: ['es2015'],
9 | plugins: ['transform-runtime']
10 | }
11 |
12 | var babelRcPath = path.resolve(process.cwd(), '.babelrc')
13 | var babelOptions = fs.existsSync(babelRcPath)
14 | ? getBabelRc() || defaultBabelOptions
15 | : defaultBabelOptions
16 |
17 | function getBabelRc () {
18 | var rc
19 | try {
20 | rc = json.parse(fs.readFileSync(babelRcPath, 'utf-8'))
21 | } catch (e) {
22 | throw new Error('[vueify] Your .babelrc seems to be incorrectly formatted.')
23 | }
24 | return rc
25 | }
26 |
27 | module.exports = function (raw, cb, compiler, filePath) {
28 | if ((compiler.options.babel || babelOptions) === defaultBabelOptions) {
29 | try {
30 | ensureRequire('babel', ['babel-preset-es2015', 'babel-runtime', 'babel-plugin-transform-runtime'])
31 | } catch (e) {
32 | console.error(e.message)
33 | console.error(
34 | '\n^^^ You are seeing this because you are using Vueify\'s default babel ' +
35 | 'configuration. You can override this with .babelrc or the babel option ' +
36 | 'in vue.config.js.'
37 | )
38 | }
39 | }
40 |
41 | try {
42 | var babel = require('babel-core')
43 | var options = assign({
44 | comments: false,
45 | filename: filePath,
46 | sourceMaps: compiler.options.sourceMap
47 | }, compiler.options.babel || babelOptions)
48 | var res = babel.transform(raw, options)
49 | } catch (err) {
50 | return cb(err)
51 | }
52 | cb(null, res)
53 | }
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vueify",
3 | "version": "9.4.1",
4 | "description": "Vue component transform for Browserify",
5 | "main": "index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/vuejs/vueify"
9 | },
10 | "keywords": [
11 | "vue",
12 | "browserify"
13 | ],
14 | "author": "Evan You",
15 | "license": "MIT",
16 | "bugs": {
17 | "url": "https://github.com/vuejs/vueify/issues"
18 | },
19 | "scripts": {
20 | "test": "eslint index.js lib && mocha test/test.js --slow=5000 --timeout=10000"
21 | },
22 | "homepage": "https://github.com/vuejs/vueify",
23 | "dependencies": {
24 | "chalk": "^1.1.1",
25 | "convert-source-map": "^1.2.0",
26 | "cssnano": "^3.3.2",
27 | "hash-sum": "^1.0.2",
28 | "lru-cache": "^4.0.0",
29 | "object-assign": "^4.0.1",
30 | "postcss": "^5.0.10",
31 | "postcss-selector-parser": "^2.0.0",
32 | "source-map": "^0.5.6",
33 | "through": "^2.3.6",
34 | "json5": "^0.5.1",
35 | "vue-hot-reload-api": "^2.0.1",
36 | "vue-template-compiler": "^2.0.0-alpha.8",
37 | "vue-template-es2015-compiler": "^1.2.2"
38 | },
39 | "devDependencies": {
40 | "babel-core": "^6.0.0",
41 | "babel-plugin-transform-runtime": "^6.0.0",
42 | "babel-preset-es2015": "^6.0.0",
43 | "babel-runtime": "^6.0.0",
44 | "browserify": "^13.0.1",
45 | "chai": "^3.5.0",
46 | "coffee-script": "^1.10.0",
47 | "eslint": "^2.13.0",
48 | "eslint-config-vue": "^1.0.3",
49 | "eslint-plugin-html": "^1.5.3",
50 | "jade": "^1.11.0",
51 | "jsdom": "^9.2.1",
52 | "less": "^2.5.1",
53 | "mkdirp": "^0.5.1",
54 | "mocha": "^2.3.3",
55 | "node-sass": "^3.3.3",
56 | "pug": "^2.0.0-alpha6",
57 | "rimraf": "^2.5.2",
58 | "stylus": "^0.52.4"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/lib/style-rewriter.js:
--------------------------------------------------------------------------------
1 | var postcss = require('postcss')
2 | var selectorParser = require('postcss-selector-parser')
3 | var cache = require('lru-cache')(100)
4 | var assign = require('object-assign')
5 |
6 | var currentId
7 | var addId = postcss.plugin('add-id', function () {
8 | return function (root) {
9 | root.each(function rewriteSelector (node) {
10 | if (!node.selector) {
11 | // handle media queries
12 | if (node.type === 'atrule' && node.name === 'media') {
13 | node.each(rewriteSelector)
14 | }
15 | return
16 | }
17 | node.selector = selectorParser(function (selectors) {
18 | selectors.each(function (selector) {
19 | var node = null
20 | selector.each(function (n) {
21 | if (n.type !== 'pseudo') node = n
22 | })
23 | selector.insertAfter(node, selectorParser.attribute({
24 | attribute: currentId
25 | }))
26 | })
27 | }).process(node.selector).result
28 | })
29 | }
30 | })
31 |
32 | /**
33 | * Add attribute selector to css
34 | *
35 | * @param {String} id
36 | * @param {String} css
37 | * @param {Boolean} scoped
38 | * @param {Object} options
39 | * @return {Promise}
40 | */
41 |
42 | module.exports = function (id, css, scoped, options) {
43 | var key = id + '!!' + scoped + '!!' + css
44 | var val = cache.get(key)
45 | if (val) {
46 | return Promise.resolve(val)
47 | } else {
48 | var plugins = []
49 | var opts = {}
50 |
51 | if (options.postcss instanceof Array) {
52 | plugins = options.postcss.slice()
53 | } else if (options.postcss instanceof Object) {
54 | plugins = options.postcss.plugins || []
55 | opts = options.postcss.options
56 | }
57 |
58 | // scoped css rewrite
59 | // make sure the addId plugin is only pushed once
60 | if (scoped && plugins.indexOf(addId) === -1) {
61 | plugins.push(addId)
62 | }
63 |
64 | // remove the addId plugin if the style block is not scoped
65 | if (!scoped && plugins.indexOf(addId) !== -1) {
66 | plugins.splice(plugins.indexOf(addId), 1)
67 | }
68 |
69 | // minification
70 | if (process.env.NODE_ENV === 'production') {
71 | plugins.push(require('cssnano')(assign({
72 | safe: true
73 | }, options.cssnano)))
74 | }
75 | currentId = id
76 | return postcss(plugins)
77 | .process(css, opts)
78 | .then(function (res) {
79 | cache.set(key, res.css)
80 | return res.css
81 | })
82 | }
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | process.env.VUEIFY_TEST = true
2 |
3 | const fs = require('fs')
4 | const path = require('path')
5 | const expect = require('chai').expect
6 | const rimraf = require('rimraf')
7 | const mkdirp = require('mkdirp')
8 | const browserify = require('browserify')
9 | const vueify = require('../index')
10 | const jsdom = require('jsdom')
11 | const vueCompiler = require('vue-template-compiler')
12 | const transpile = require('vue-template-es2015-compiler')
13 | const genId = require('../lib/gen-id')
14 |
15 | const tempDir = path.resolve(__dirname, './temp')
16 | const mockEntry = path.resolve(tempDir, 'entry.js')
17 | rimraf.sync(tempDir)
18 | mkdirp.sync(tempDir)
19 |
20 | function test (file, assert) {
21 | it(file, done => {
22 | fs.writeFileSync(mockEntry, 'window.vueModule = require("../fixtures/' + file + '.vue")')
23 | browserify(mockEntry)
24 | .transform(vueify)
25 | .bundle((err, buf) => {
26 | if (err) return done(err)
27 | jsdom.env({
28 | html: '
',
29 | src: [buf.toString()],
30 | done: (err, window) => {
31 | if (err) return done(err)
32 | assert(window)
33 | done()
34 | }
35 | })
36 | })
37 | })
38 | }
39 |
40 | function testCssExtract (file, assert) {
41 | it(file, done => {
42 | fs.writeFileSync(mockEntry, 'window.vueModule = require("../fixtures/' + file + '.vue")')
43 | browserify(mockEntry)
44 | .transform(vueify)
45 | .plugin('./plugins/extract-css', { out: { write: assert, end: done }})
46 | .bundle((err, buf) => {
47 | if (err) return done(err)
48 | })
49 | })
50 | }
51 |
52 | function assertRenderFn (options, template) {
53 | const compiled = vueCompiler.compile(template)
54 | expect(options.render.toString()).to.equal(transpile('function render() {' + compiled.render + '}'))
55 | }
56 |
57 | describe('vueify', () => {
58 | test('basic', window => {
59 | const module = window.vueModule
60 | assertRenderFn(module, '{{msg}} ')
61 | expect(module.data().msg).to.contain('Hello from Component A!')
62 | const style = window.document.querySelector('style').textContent
63 | expect(style).to.contain('comp-a h2 {\n color: #f00;\n}')
64 | })
65 |
66 | test('pre-processors', window => {
67 | var module = window.vueModule
68 | assertRenderFn(module,
69 | '' +
70 | '
This is the app ' +
71 | ' ' +
72 | ' ' +
73 | ''
74 | )
75 | expect(module.data().msg).to.contain('Hello from coffee!')
76 | var style = window.document.querySelector('style').textContent
77 | // stylus
78 | expect(style).to.contain('body {\n font: 100% Helvetica, sans-serif;\n color: #999;\n}')
79 | // sass
80 | expect(style).to.contain('h1 {\n color: red;')
81 | // less
82 | expect(style).to.contain('h1 {\n color: green;')
83 | })
84 |
85 | test('pug', window => {
86 | var module = window.vueModule
87 | assertRenderFn(module,
88 | '' +
89 | '
This is the app ' +
90 | ' ' +
91 | ' ' +
92 | ''
93 | )
94 | })
95 |
96 | test('scoped-css', window => {
97 | var module = window.vueModule
98 | var id = 'data-v-' + genId(require.resolve('./fixtures/scoped-css.vue'))
99 | expect(module._scopeId).to.equal(id)
100 | assertRenderFn(module,
101 | '' +
102 | '
hi \n' +
103 | '
hi
\n' +
104 | '
yo
\n' +
105 | '
' +
106 | '
'
107 | )
108 | var style = window.document.querySelector('style').textContent
109 | expect(style).to.contain('.test[' + id + '] {\n color: yellow;\n}')
110 | expect(style).to.contain('.test[' + id + ']:after {\n content: \'bye!\';\n}')
111 | expect(style).to.contain('h1[' + id + '] {\n color: green;\n}')
112 | })
113 |
114 | test('style-import', window => {
115 | var styles = window.document.querySelectorAll('style')
116 | expect(styles[0].textContent).to.contain('h1 { color: red; }')
117 | // import with scoped
118 | var id = 'data-v-' + genId(require.resolve('./fixtures/style-import.vue'))
119 | expect(styles[0].textContent).to.contain('h1[' + id + '] { color: green; }')
120 | })
121 |
122 | test('template-import', window => {
123 | var module = window.vueModule
124 | assertRenderFn(module, '
hello ')
125 | })
126 |
127 | test('script-import', window => {
128 | var module = window.vueModule
129 | expect(module.data().msg).to.contain('Hello from Component A!')
130 | })
131 |
132 | test('media-query', window => {
133 | var style = window.document.querySelector('style').textContent
134 | var id = 'data-v-' + genId(require.resolve('./fixtures/media-query.vue'))
135 | expect(style).to.contain('@media print {\n .foo[' + id + '] {\n color: #000;\n }\n}')
136 | })
137 |
138 | testCssExtract('style-export', css => {
139 | expect(css).to.equal('h2 {color: red;}')
140 | })
141 | })
142 |
--------------------------------------------------------------------------------
/lib/compiler.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var path = require('path')
3 | var chalk = require('chalk')
4 | var hash = require('hash-sum')
5 | var Emitter = require('events').EventEmitter
6 | var vueCompiler = require('vue-template-compiler')
7 | var sourceMap = require('source-map')
8 | var convert = require('convert-source-map')
9 |
10 | var genId = require('./gen-id')
11 | var normalize = require('./normalize')
12 | var compilers = require('./compilers')
13 | var rewriteStyle = require('./style-rewriter')
14 | var compileTemplate = require('./template-compiler')
15 |
16 | // determine dynamic script paths
17 | var hotReloadAPIPath = normalize.dep('vue-hot-reload-api')
18 | var insertCSSPath = normalize.lib('insert-css')
19 |
20 | var hasBabel = true
21 | try {
22 | require('babel-core')
23 | } catch (e) {
24 | hasBabel = false
25 | }
26 |
27 | var splitRE = /\r?\n/g
28 | var resolvedPartsCache = Object.create(null)
29 |
30 | // expose compiler
31 | var compiler = module.exports = new Emitter()
32 | compiler.setMaxListeners(Infinity)
33 |
34 | // options
35 | var options = compiler.options = {}
36 |
37 | // load user config
38 | compiler.loadConfig = function () {
39 | var fs = require('fs')
40 | var path = require('path')
41 | var configPath = path.resolve(process.cwd(), 'vue.config.js')
42 | if (fs.existsSync(configPath)) {
43 | compiler.applyConfig(require(configPath))
44 | }
45 | }
46 |
47 | // apply config
48 | compiler.applyConfig = function (config) {
49 | // copy user options to default options
50 | Object.keys(config).forEach(function (key) {
51 | if (key !== 'customCompilers') {
52 | options[key] = config[key]
53 | } else {
54 | // register compilers
55 | Object.keys(config[key]).forEach(function (name) {
56 | compilers[name] = config[key][name]
57 | })
58 | }
59 | })
60 | }
61 |
62 | compiler.compile = function (content, filePath, cb) {
63 | var isProduction = process.env.NODE_ENV === 'production'
64 | var isServer = process.env.VUE_ENV === 'server'
65 | var isTest = !!process.env.VUEIFY_TEST
66 |
67 | // generate css scope id
68 | var id = 'data-v-' + genId(filePath)
69 | // parse the component into parts
70 | var parts = vueCompiler.parseComponent(content, { pad: true })
71 |
72 | // check for scoped style nodes
73 | var hasScopedStyle = parts.styles.some(function (style) {
74 | return style.scoped
75 | })
76 |
77 | var resolvedParts = {
78 | template: null,
79 | script: null,
80 | styles: []
81 | }
82 |
83 | Promise.all([
84 | processTemplate(parts.template, filePath, resolvedParts),
85 | processScript(parts.script, filePath, resolvedParts)
86 | ].concat(parts.styles.map(function (style) {
87 | return processStyle(style, filePath, id, resolvedParts)
88 | })))
89 | .then(mergeParts)
90 | .catch(cb)
91 |
92 | function mergeParts () {
93 | // check whether script/template has changed
94 | var prevParts = resolvedPartsCache[id] || {}
95 | resolvedPartsCache[id] = resolvedParts
96 | var scriptChanged = resolvedParts.script !== prevParts.script
97 | var templateChanged = resolvedParts.template !== prevParts.template
98 |
99 | var output = ''
100 | var map = null
101 | // styles
102 | var style = resolvedParts.styles.join('\n')
103 | if (style && !isServer) {
104 | // emit style
105 | compiler.emit('style', {
106 | file: filePath,
107 | style: style
108 | })
109 | if (!options.extractCSS) {
110 | style = JSON.stringify(style)
111 | output +=
112 | 'var __vueify_style_dispose__ = require("' + insertCSSPath + '").insert(' + style + ')\n'
113 | }
114 | }
115 | // script
116 | var script = resolvedParts.script
117 | if (script) {
118 | if (options.sourceMap) {
119 | map = generateSourceMap(script, output)
120 | }
121 | output +=
122 | ';(function(){\n' + script + '\n})()\n' +
123 | // babel 6 compat
124 | 'if (module.exports.__esModule) module.exports = module.exports.default\n'
125 | }
126 | // in case the user exports with Vue.extend
127 | output += 'var __vue__options__ = (typeof module.exports === "function"' +
128 | '? module.exports.options' +
129 | ': module.exports)\n'
130 | // template
131 | var template = resolvedParts.template
132 | if (template) {
133 | if (!isProduction && !isServer) {
134 | output +=
135 | 'if (__vue__options__.functional) {console.error("' +
136 | '[vueify] functional components are not supported and ' +
137 | 'should be defined in plain js files using render functions.' +
138 | '")}\n'
139 | }
140 | var beforeLines
141 | if (map) {
142 | beforeLines = output.split(splitRE).length
143 | }
144 | output +=
145 | '__vue__options__.render = ' + template.render + '\n' +
146 | '__vue__options__.staticRenderFns = ' + template.staticRenderFns + '\n'
147 | if (map) {
148 | addTemplateMapping(content, parts, output, map, beforeLines)
149 | }
150 | }
151 | // scoped CSS id
152 | if (hasScopedStyle) {
153 | output += '__vue__options__._scopeId = "' + id + '"\n'
154 | }
155 | // hot reload
156 | if (!isProduction && !isTest && !isServer) {
157 | output +=
158 | 'if (module.hot) {(function () {' +
159 | ' var hotAPI = require("' + hotReloadAPIPath + '")\n' +
160 | ' hotAPI.install(require("vue"), true)\n' +
161 | ' if (!hotAPI.compatible) return\n' +
162 | ' module.hot.accept()\n' +
163 | // remove style tag on dispose
164 | (style && !options.extractCSS
165 | ? ' module.hot.dispose(__vueify_style_dispose__)\n'
166 | : '') +
167 | ' if (!module.hot.data) {\n' +
168 | // initial insert
169 | ' hotAPI.createRecord("' + id + '", __vue__options__)\n' +
170 | ' } else {\n' +
171 | // update
172 | (scriptChanged
173 | ? ' hotAPI.reload("' + id + '", __vue__options__)\n'
174 | : templateChanged
175 | ? ' hotAPI.rerender("' + id + '", __vue__options__)\n'
176 | : ''
177 | ) +
178 | ' }\n' +
179 | '})()}'
180 | }
181 | if (map) {
182 | output += '\n' + convert.fromJSON(map.toString()).toComment()
183 | }
184 | cb(null, output)
185 | }
186 |
187 | function generateSourceMap (script, output) {
188 | // hot-reload source map busting
189 | var hashedFilename = path.basename(filePath) + '?' + hash(filePath + content)
190 | var map = new sourceMap.SourceMapGenerator()
191 | map.setSourceContent(hashedFilename, content)
192 | // check input source map from babel/coffee etc
193 | var inMap = resolvedParts.map
194 | var inMapConsumer = inMap && new sourceMap.SourceMapConsumer(inMap)
195 | var generatedOffset = (output ? output.split(splitRE).length : 0) + 1
196 | script.split(splitRE).forEach(function (line, index) {
197 | var ln = index + 1
198 | var originalLine = inMapConsumer
199 | ? inMapConsumer.originalPositionFor({ line: ln, column: 0 }).line
200 | : ln
201 | if (originalLine) {
202 | map.addMapping({
203 | source: hashedFilename,
204 | generated: {
205 | line: ln + generatedOffset,
206 | column: 0
207 | },
208 | original: {
209 | line: originalLine,
210 | column: 0
211 | }
212 | })
213 | }
214 | })
215 | map._hashedFilename = hashedFilename
216 | return map
217 | }
218 | }
219 |
220 | function addTemplateMapping (content, parts, output, map, beforeLines) {
221 | var afterLines = output.split(splitRE).length
222 | var templateLine = content.slice(0, parts.template.start).split(splitRE).length
223 | for (; beforeLines < afterLines; beforeLines++) {
224 | map.addMapping({
225 | source: map._hashedFilename,
226 | generated: {
227 | line: beforeLines,
228 | column: 0
229 | },
230 | original: {
231 | line: templateLine,
232 | column: 0
233 | }
234 | })
235 | }
236 | }
237 |
238 | function processTemplate (part, filePath, parts) {
239 | if (!part) return Promise.resolve()
240 | var template = getContent(part, filePath)
241 | return compileAsPromise('template', template, part.lang, filePath)
242 | .then(function (res) {
243 | parts.template = compileTemplate(res, compiler)
244 | })
245 | }
246 |
247 | function processScript (part, filePath, parts) {
248 | if (!part) return Promise.resolve()
249 | var lang = part.lang || (hasBabel ? 'babel' : null)
250 | var script = getContent(part, filePath)
251 | return compileAsPromise('script', script, lang, filePath)
252 | .then(function (res) {
253 | if (typeof res === 'string') {
254 | parts.script = res
255 | } else {
256 | parts.script = res.code
257 | parts.map = res.map
258 | }
259 | })
260 | }
261 |
262 | function processStyle (part, filePath, id, parts) {
263 | var style = getContent(part, filePath)
264 | return compileAsPromise('style', style, part.lang, filePath)
265 | .then(function (res) {
266 | res = res.trim()
267 | return rewriteStyle(id, res, part.scoped, options).then(function (res) {
268 | parts.styles.push(res)
269 | })
270 | })
271 | }
272 |
273 | function getContent (part, filePath) {
274 | return part.src
275 | ? loadSrc(part.src, filePath)
276 | : part.content
277 | }
278 |
279 | function loadSrc (src, filePath) {
280 | var dir = path.dirname(filePath)
281 | var srcPath = path.resolve(dir, src)
282 | compiler.emit('dependency', srcPath)
283 | try {
284 | return fs.readFileSync(srcPath, 'utf-8')
285 | } catch (e) {
286 | console.error(chalk.red(
287 | 'Failed to load src: "' + src +
288 | '" from file: "' + filePath + '"'
289 | ))
290 | }
291 | }
292 |
293 | function compileAsPromise (type, source, lang, filePath) {
294 | var compile = compilers[lang]
295 | if (compile) {
296 | return new Promise(function (resolve, reject) {
297 | compile(source, function (err, res) {
298 | if (err) {
299 | // report babel error codeframe
300 | if (err.codeFrame) {
301 | process.nextTick(function () {
302 | console.error(err.codeFrame)
303 | })
304 | }
305 | return reject(err)
306 | }
307 | resolve(res)
308 | }, compiler, filePath)
309 | })
310 | } else {
311 | return Promise.resolve(source)
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # THIS REPOSITORY IS DEPRECATED
2 |
3 | > Note: We are concentrating our efforts on supporting webpack and rollup.
4 |
5 | ## vueify [](https://circleci.com/gh/vuejs/vueify) [](http://badge.fury.io/js/vueify)
6 |
7 | > [Browserify](http://browserify.org/) transform for [Vue.js](http://vuejs.org/) components, with scoped CSS and component hot-reloading.
8 |
9 | **NOTE: master branch now hosts version ^9.0, which only works with Vue ^2.0. Vueify 8.x which works with Vue 1.x is in the [8.x branch](https://github.com/vuejs/vueify/tree/8.x).**
10 |
11 | This transform allows you to write your components in this format:
12 |
13 | ``` html
14 | // app.vue
15 |
20 |
21 |
22 | {{msg}}
23 |
24 |
25 |
34 | ```
35 |
36 | You can also mix preprocessor languages in the component file:
37 |
38 | ``` vue
39 | // app.vue
40 |
44 |
45 |
46 | h1(class="red") {{msg}}
47 |
48 |
49 |
54 | ```
55 |
56 | And you can import using the `src` attribute:
57 |
58 | ``` html
59 |
60 | ```
61 |
62 | Under the hood, the transform will:
63 |
64 | - extract the styles, compile them and insert them with the `insert-css` module.
65 | - extract the template, compile it and add it to your exported options.
66 |
67 | You can `require()` other stuff in the `
97 |