1- const path = require ( 'path' )
21const loaderUtils = require ( 'loader-utils' )
2+ const acorn = require ( 'acorn' )
3+ const acornWalk = require ( 'acorn-walk' )
34
45const vuetifyMatcher = require ( './matcher/tag' )
56const 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
8956module . 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+ }
0 commit comments