Skip to content

Commit

Permalink
feat: auto load directives (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacekkarczmarczyk authored and KaelWD committed Jan 13, 2019
1 parent 83f6ae8 commit a92e559
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 44 deletions.
99 changes: 57 additions & 42 deletions lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,70 @@ const path = require('path')
const loaderUtils = require('loader-utils')
const compiler = require('vue-template-compiler')

const vuetifyMatcher = require('./matcher')
const vuetifyMatcher = require('./matcher/tag')
const vuetifyAttrsMatcher = require('./matcher/attr')
const { camelize, capitalize, hyphenate, requirePeer } = require('./util')
const installComponentsPath = require.resolve('./runtime/installComponents')
const runtimePaths = {
installComponents: require.resolve('./runtime/installComponents'),
installDirectives: require.resolve('./runtime/installDirectives')
}

function getMatches (type, items, matches, component) {
const imports = []

items.forEach(item => {
for (const matcher of matches) {
const match = matcher(item, {
[`kebab${type}`]: hyphenate(item),
[`camel${type}`]: capitalize(camelize(item)),
path: this.resourcePath.substring(this.rootContext.length + 1),
component
})
if (match) {
imports.push(match)
break
}
}
})

imports.sort((a, b) => a[0] < b[0] ? -1 : (a[0] > b[0] ? 1 : 0))
return imports
}

function install (install, content, imports) {
if (imports.length) {
let newContent = '/* vuetify-loader */\n'
newContent += `import ${install} from ${loaderUtils.stringifyRequest(this, '!' + runtimePaths[install])}\n`
newContent += imports.map(i => i[1]).join('\n') + '\n'
newContent += `${install}(component, {${imports.map(i => i[0]).join(',')}})\n`

// Insert our modification before the HMR code
const hotReload = content.indexOf('/* hot reload */')
if (hotReload > -1) {
content = content.slice(0, hotReload) + newContent + '\n\n' + content.slice(hotReload)
} else {
content += '\n\n' + newContent
}
}

return content
}

module.exports = async function (content, sourceMap) {
this.async()
this.cacheable()

const options = {
match: [],
attrsMatch: [],
...loaderUtils.getOptions(this)
}

if (!Array.isArray(options.match)) options.match = [options.match]
if (!Array.isArray(options.attrsMatch)) options.attrsMatch = [options.attrsMatch]

options.match.push(vuetifyMatcher)
options.attrsMatch.push(vuetifyAttrsMatcher)

if (!this.resourceQuery) {
const readFile = path => new Promise((resolve, reject) => {
Expand All @@ -30,6 +78,7 @@ module.exports = async function (content, sourceMap) {
this.addDependency(this.resourcePath)

const tags = new Set()
const attrs = new Set()
const file = (await readFile(this.resourcePath)).toString('utf8')
const component = compiler.parseComponent(file)
if (component.template) {
Expand All @@ -44,50 +93,16 @@ module.exports = async function (content, sourceMap) {
}
compiler.compile(component.template.content, {
modules: [{
postTransformNode: node => { tags.add(node.tag) }
postTransformNode: node => {
Object.keys(node.attrsMap).forEach(attr => attrs.add(attr))
tags.add(node.tag)
}
}]
})
}

const imports = []
tags.forEach(tag => {
for (const matcher of options.match) {
const match = matcher(tag, {
kebabTag: hyphenate(tag),
camelTag: capitalize(camelize(tag)),
path: this.resourcePath.substring(this.rootContext.length + 1),
component
})
if (match) {
imports.push(match)
break
}
}
})

imports.sort((a, b) => a[0] < b[0] ? -1 : (a[0] > b[0] ? 1 : 0))

if (imports.length) {
let newContent = '/* vuetify-loader */\n'
newContent += `import installComponents from ${loaderUtils.stringifyRequest(this, '!' + installComponentsPath)}\n`
imports.forEach(i => {
newContent += i[1] + '\n'
})
const components = imports.map(i => i[0])
newContent += 'installComponents(component, {\n'
components.forEach(c => {
newContent += ` ${c},\n`
})
newContent += '})\n'

// Insert our modification before the HMR code
const hotReload = content.indexOf('/* hot reload */')
if (hotReload > -1) {
content = content.slice(0, hotReload) + newContent + '\n\n' + content.slice(hotReload)
} else {
content += '\n\n' + newContent
}
}
content = install('installComponents', content, getMatches.call(this, 'Tag', tags, options.match, component))
content = install('installDirectives', content, getMatches.call(this, 'Attr', attrs, options.attrsMatch, component))
}

this.callback(null, content, sourceMap)
Expand Down
8 changes: 8 additions & 0 deletions lib/matcher/attr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const directives = require('./generator').directives

module.exports = function match (_, { kebabAttr, camelAttr: attr }) {
if (!kebabAttr.startsWith('v-')) return

const directive = attr.substr(1)
if (directives.includes(directive)) return [directive, `import { ${directive} } from 'vuetify/lib/directives'`]
}
6 changes: 5 additions & 1 deletion lib/matcher/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ Module._load = function _load (request, parent) {

const { hyphenate } = require('../util')
const components = require('vuetify/es5/components')
const directives = require('vuetify/es5/directives')

Module._load = originalLoader

module.exports = Object.keys(components)
module.exports = {
components: Object.keys(components),
directives: Object.keys(directives)
}
2 changes: 1 addition & 1 deletion lib/matcher/index.js → lib/matcher/tag.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const components = require('./generator')
const components = require('./generator').components

module.exports = function match (_, { kebabTag, camelTag: tag }) {
if (!kebabTag.startsWith('v-')) return
Expand Down
19 changes: 19 additions & 0 deletions lib/runtime/installDirectives.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// IMPORTANT: Do NOT use ES2015 features in this file (except for modules).
// This module is a runtime utility for cleaner component module output and will
// be included in the final webpack user bundle.

module.exports = function installDirectives (component, directives) {
var options = typeof component.exports === 'function'
? component.exports.extendOptions
: component.options

if (typeof component.exports === 'function') {
options.directives = component.exports.options.directives
}

options.directives = options.directives || {}

for (var i in directives) {
options.directives[i] = options.directives[i] || directives[i]
}
}

0 comments on commit a92e559

Please sign in to comment.