Skip to content

Commit c0dd7cf

Browse files
authored
feat: support vue 2.7 (#247)
closes #240
1 parent fb6cf15 commit c0dd7cf

File tree

13 files changed

+615
-575
lines changed

13 files changed

+615
-575
lines changed

dev5/package.json

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"license": "UNLICENSED",
77
"private": true,
88
"scripts": {
9-
"dev": "cross-env NODE_ENV=development node --preserve-symlinks node_modules/.bin/webpack serve --open --hot",
9+
"dev": "cross-env NODE_ENV=development node --preserve-symlinks node_modules/.bin/webpack serve --hot",
1010
"build:dev": "cross-env NODE_ENV=development node --preserve-symlinks node_modules/.bin/webpack --progress",
1111
"build": "cross-env NODE_ENV=production node --preserve-symlinks node_modules/.bin/webpack --progress"
1212
},
@@ -20,16 +20,15 @@
2020
"sass": "^1.32.4",
2121
"sass-loader": "^10.1.1",
2222
"url-loader": "^4.1.1",
23-
"vue": "^2.6.12",
24-
"vue-loader": "^15.9.6",
23+
"vue": "^2.7.2",
24+
"vue-loader": "^15.10.0",
2525
"vue-style-loader": "^4.1.2",
26-
"vue-template-compiler": "^2.6.12",
27-
"vuetify": "^2.4.2",
26+
"vuetify": "^2.6.6",
2827
"vuetify-loader": "link:../",
29-
"webpack": "^5.15.0",
28+
"webpack": "^5.73.0",
3029
"webpack-bundle-analyzer": "^4.3.0",
31-
"webpack-cli": "^4.3.1",
32-
"webpack-dev-server": "^3.11.2"
30+
"webpack-cli": "^4.10.0",
31+
"webpack-dev-server": "^3.11.3"
3332
},
3433
"browserslist": "last 2 Chrome versions"
3534
}

dev5/src/App.vue

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
<template>
22
<v-app>
33
<v-container>
4-
<v-card v-ripple>
4+
<v-card v-ripple v-resize="onResize">
55
<div style="text-align: center">
66
<v-img src="@/vuetify.png" style="display: inline-flex"></v-img>
77
</div>
88
<v-card-text>
99
<v-text-field></v-text-field>
1010
</v-card-text>
1111
</v-card>
12+
<pre>{{ {
13+
directives: [{
14+
name: "resize"
15+
}]
16+
} }}</pre>
17+
<setup-component />
1218
</v-container>
1319
</v-app>
1420
</template>
1521

1622
<script>
23+
import SetupComponent from './Setup.vue'
1724
const img = require('@/vuetify.png?vuetify-preload')
1825
console.log(img, img.default)
19-
export default {}
26+
export default {
27+
components: {
28+
SetupComponent
29+
}
30+
}
2031
</script>
21-
22-
<docs>
23-
This is the documentation for App.vue
24-
</docs>

dev5/src/Setup.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<template>
2+
<v-card v-ripple>
3+
Setup component
4+
</v-card>
5+
</template>
6+
7+
<script setup>
8+
//
9+
</script>

dev5/webpack.config.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const isProd = process.env.NODE_ENV === 'production'
99
function sassLoaderOptions (indentedSyntax = false) {
1010
return {
1111
implementation: require('sass'),
12-
additionalData: `@import "~@/_variables.scss"` + (indentedSyntax ? '' : ';'),
12+
// additionalData: `@import "~@/_variables.scss"` + (indentedSyntax ? '' : ';'),
1313
sassOptions: { indentedSyntax },
1414
}
1515
}
@@ -67,7 +67,8 @@ module.exports = {
6767
plugins: [
6868
new VueLoaderPlugin(),
6969
new VuetifyLoaderPlugin({
70-
progressiveImages: true
70+
progressiveImages: true,
71+
registerStylesSSR: false,
7172
}),
7273
new BundleAnalyzerPlugin({
7374
analyzerMode: 'static',

dev5/yarn.lock

Lines changed: 397 additions & 372 deletions
Large diffs are not rendered by default.

lib/loader.js

Lines changed: 100 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
const path = require('path')
21
const loaderUtils = require('loader-utils')
2+
const acorn = require('acorn')
3+
const acornWalk = require('acorn-walk')
34

45
const vuetifyMatcher = require('./matcher/tag')
56
const vuetifyAttrsMatcher = require('./matcher/attr')
6-
const { camelize, capitalize, hyphenate, requirePeer } = require('./util')
7-
const runtimePaths = {
8-
installComponents: require.resolve('./runtime/installComponents'),
9-
installDirectives: require.resolve('./runtime/installDirectives')
10-
}
11-
12-
let compiler
13-
try {
14-
compiler = require('vue/compiler-sfc')
15-
} catch (e) {
16-
compiler = require('vue-template-compiler')
17-
}
7+
const { camelize, capitalize, hyphenate } = require('./util')
188

19-
function getMatches (type, items, matches, component) {
9+
function getMatches (type, items, matches) {
2010
const imports = []
2111

2212
items.forEach(item => {
@@ -25,7 +15,6 @@ function getMatches (type, items, matches, component) {
2515
[`kebab${type}`]: hyphenate(item),
2616
[`camel${type}`]: capitalize(camelize(item)),
2717
path: this.resourcePath.substring(this.rootContext.length + 1),
28-
component
2918
})
3019
if (match) {
3120
imports.push(match)
@@ -46,44 +35,22 @@ function injectStylesSSR (imports) {
4635

4736
if (styles.size) {
4837
return `
49-
if (process.env.VUE_ENV === 'server') {
50-
const options = typeof component.exports === 'function'
51-
? component.exports.extendOptions
52-
: component.options
53-
const existing = options.beforeCreate
54-
const hook = function () {
55-
${[...styles].map((style) => ` require('vuetify/${style}').__inject__(this.$ssrContext)`).join('\n')}
56-
}
57-
options.beforeCreate = existing
58-
? [].concat(existing, hook)
59-
: [hook]
60-
}
61-
`
62-
}
63-
return ""
64-
}
65-
66-
function install (install, content, imports, options = {}) {
67-
if (imports.length) {
68-
let newContent = '/* vuetify-loader */\n'
69-
newContent += `import ${install} from ${loaderUtils.stringifyRequest(this, '!' + runtimePaths[install])}\n`
70-
newContent += imports.map(i => i[1]).join('\n') + '\n'
71-
newContent += `${install}(component, {${imports.map(i => i[0]).join(',')}})\n`
72-
73-
if (options.registerStylesSSR) {
74-
newContent += injectStylesSSR(imports, newContent)
75-
}
76-
77-
// Insert our modification before the HMR code
78-
const hotReload = content.indexOf('/* hot reload */')
79-
if (hotReload > -1) {
80-
content = content.slice(0, hotReload) + newContent + '\n\n' + content.slice(hotReload)
81-
} else {
82-
content += '\n\n' + newContent
38+
render._vuetifyStyles = function (component) {
39+
if (process.env.VUE_ENV === 'server') {
40+
const options = typeof component.exports === 'function'
41+
? component.exports.extendOptions
42+
: component.options
43+
const existing = options.beforeCreate
44+
const hook = function () {
45+
${[...styles].map((style) => ` require('vuetify/${style}').__inject__(this.$ssrContext)`).join('\n')}
8346
}
47+
options.beforeCreate = existing
48+
? [].concat(existing, hook)
49+
: [hook]
8450
}
85-
86-
return content
51+
}`
52+
}
53+
return ''
8754
}
8855

8956
module.exports = async function (content, sourceMap) {
@@ -103,52 +70,96 @@ module.exports = async function (content, sourceMap) {
10370
options.match.push(vuetifyMatcher)
10471
options.attrsMatch.push(vuetifyAttrsMatcher)
10572

106-
if (!this.resourceQuery) {
107-
const readFile = path => new Promise((resolve, reject) => {
108-
this.fs.readFile(path, function (err, data) {
109-
if (err) reject(err)
110-
else resolve(data)
111-
})
112-
})
11373

74+
const qs = new URLSearchParams(this.resourceQuery)
75+
if (qs.has('vue') && qs.get('type') === 'template') {
11476
this.addDependency(this.resourcePath)
11577

116-
const tags = new Set()
117-
const attrs = new Set()
118-
const file = (await readFile(this.resourcePath)).toString('utf8')
119-
const component = compiler.parseComponent(file)
120-
if (component.template) {
121-
if (component.template.src) {
122-
const externalFile = (await new Promise((resolve, reject) =>
123-
this.resolve(path.dirname(this.resourcePath), component.template.src, (err, result) => {
124-
if (err) reject(err)
125-
else resolve(result)
126-
})
127-
))
128-
const externalContent = (await readFile(externalFile)).toString('utf8')
129-
component.template.content = externalContent
78+
const matches = {
79+
components: [],
80+
directives: [],
81+
}
82+
83+
const ast = acorn.parse(content, { sourceType: 'module', ecmaVersion: 'latest' })
84+
acornWalk.simple(ast, {
85+
CallExpression (node) {
86+
if (node.callee.name === '_c') {
87+
if (node.arguments[0].type === 'Literal') {
88+
matches.components.push([node.arguments[0].value, node.arguments[0].start, node.arguments[0].end])
89+
}
90+
if (node.arguments.length >= 2 && node.arguments[1].type === 'ObjectExpression') {
91+
const props = node.arguments[1].properties
92+
props.forEach(prop => {
93+
if (prop.key.type === 'Identifier' && prop.key.name === 'directives' && prop.value.type === 'ArrayExpression') {
94+
prop.value.elements.forEach(directive => {
95+
if (directive.type === 'ObjectExpression') {
96+
directive.properties.forEach(prop => {
97+
if (prop.key.type === 'Identifier' && prop.key.name === 'name') {
98+
matches.directives.push([prop.value.value, prop.start, prop.end])
99+
}
100+
})
101+
}
102+
})
103+
}
104+
})
105+
}
106+
}
130107
}
131-
if (component.template.lang === 'pug') {
132-
const pug = requirePeer('pug')
133-
try {
134-
component.template.content = pug.render(component.template.content, {filename: this.resourcePath})
135-
} catch (err) {/* Ignore compilation errors, they'll be picked up by other loaders */}
108+
})
109+
110+
const components = getMatches.call(this, 'Tag', dedupe(matches.components), options.match)
111+
const directives = getMatches.call(this, 'Attr', dedupe(matches.directives), options.attrsMatch)
112+
113+
const allMatches = [...matches.components.map(v => ({
114+
type: 'component',
115+
name: capitalize(camelize(v[0])),
116+
start: v[1],
117+
end: v[2],
118+
})), ...matches.directives.map(v => ({
119+
type: 'directive',
120+
name: capitalize(camelize(v[0])),
121+
start: v[1],
122+
end: v[2],
123+
}))].sort((a, b) => a.start - b.start)
124+
125+
for (let i = allMatches.length - 1; i >= 0; i--) {
126+
const tag = allMatches[i]
127+
if (tag.type === 'component') {
128+
if (!components.some(c => c[0] === tag.name)) continue
129+
content = content.slice(0, tag.start) + tag.name + content.slice(tag.end)
130+
} else {
131+
if (!directives.some(c => c[0] === tag.name)) continue
132+
const indent = content.slice(0, tag.start).match(/\s*$/)[0]
133+
content = content.slice(0, tag.start) +
134+
'def: ' + tag.name + ',' +
135+
indent + content.slice(tag.start)
136136
}
137-
compiler.compile(component.template.content, {
138-
modules: [{
139-
postTransformNode: node => {
140-
if ("directives" in node) {
141-
node.directives.forEach(({ name }) => attrs.add(name))
142-
}
143-
tags.add(node.tag)
144-
}
145-
}]
146-
})
147137
}
148138

149-
content = install.call(this, 'installComponents', content, getMatches.call(this, 'Tag', tags, options.match, component), options)
150-
content = install.call(this, 'installDirectives', content, getMatches.call(this, 'Attr', attrs, options.attrsMatch, component))
139+
const imports = [...components, ...directives]
140+
if (imports.length) {
141+
content = imports.map(v => v[1]).join('\n') + '\n\n' + content
142+
}
143+
144+
if (options.registerStylesSSR) {
145+
content += injectStylesSSR(imports)
146+
}
147+
} else if (options.registerStylesSSR && !this.resourceQuery) {
148+
this.addDependency(this.resourcePath)
149+
const newContent = 'if (render._vuetifyStyles) { render._vuetifyStyles(component) }'
150+
151+
// Insert our modification before the HMR code
152+
const hotReload = content.indexOf('/* hot reload */')
153+
if (hotReload > -1) {
154+
content = content.slice(0, hotReload) + newContent + '\n\n' + content.slice(hotReload)
155+
} else {
156+
content += '\n\n' + newContent
157+
}
151158
}
152159

153160
this.callback(null, content, sourceMap)
154161
}
162+
163+
function dedupe (matches) {
164+
return [...new Set(matches.map(i => capitalize(camelize(i[0]))))]
165+
}

lib/matcher/attr.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const { directives } = require('./generator')
22

33
module.exports = function match (_, { kebabAttr, camelAttr: attr }) {
4-
if (directives.includes(attr)) return [attr, `import ${attr} from 'vuetify/lib/directives/${kebabAttr}'`]
4+
if (directives.includes(attr)) return [attr, `import ${attr} from 'vuetify/lib/directives/${kebabAttr}';`]
55
}

lib/matcher/generator.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ readdirSync(dir).forEach(group => {
4141
// This is required so that groups picks up dependencies they have to other groups.
4242
// For example VTabs depends on the style from VSlideGroup (VSlideGroup.sass).
4343
// As VSlideGroup will be loaded before (alphabetically), `Module._load` wouldn't be called for it when processing VTabs (as it would be already in the require cache).
44-
// By busting the require cache for each groups we unsure that when loading VTabs we do call `Module._load` for `VSlideGroup.sass` and it gets added to the dependencies.
44+
// By busting the require cache for each groups we ensure that when loading VTabs we do call `Module._load` for `VSlideGroup.sass` and it gets added to the dependencies.
4545
decache(`vuetify/es5/components/${group}`)
4646
})
4747

lib/plugin.js

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@ class VuetifyLoaderPlugin {
1818
)
1919
}
2020

21-
vueRules.forEach(this.updateVueRule.bind(this))
22-
23-
const rules = [...compiler.options.module.rules]
24-
vueRules.forEach(({ rule, index }) => {
25-
rules[index] = rule
21+
compiler.options.module.rules.unshift({
22+
test: /\.vue$/,
23+
use: {
24+
loader: require.resolve('./loader'),
25+
options: {
26+
match: this.options.match || [],
27+
attrsMatch: this.options.attrsMatch || [],
28+
registerStylesSSR: this.options.registerStylesSSR || false
29+
}
30+
},
2631
})
27-
compiler.options.module.rules = rules
32+
33+
vueRules.forEach(this.updateVueRule.bind(this))
2834

2935
if (this.options.progressiveImages) {
3036
const options = typeof this.options.progressiveImages === 'boolean'
@@ -65,27 +71,6 @@ class VuetifyLoaderPlugin {
6571
vueLoaderOptions.compilerOptions.modules = vueLoaderOptions.compilerOptions.modules || []
6672
vueLoaderOptions.compilerOptions.modules.push(progressiveLoaderModule)
6773
}
68-
69-
rule.oneOf = [
70-
{
71-
resourceQuery: '?',
72-
use: rule.use
73-
},
74-
{
75-
use: [
76-
{
77-
loader: require.resolve('./loader'),
78-
options: {
79-
match: this.options.match || [],
80-
attrsMatch: this.options.attrsMatch || [],
81-
registerStylesSSR: this.options.registerStylesSSR || false
82-
}
83-
},
84-
...rule.use
85-
]
86-
},
87-
]
88-
delete rule.use
8974
}
9075
}
9176

0 commit comments

Comments
 (0)