Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for SSR style injection #126

Merged
merged 1 commit into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,15 @@ Type: `Boolean`
Default: `false`

Use GraphicsMagic instead of ImageMagick

##### `registerStylesSSR`

Type: `Boolean`
Default: `false`

Register Vuetify styles in [vue-style-loader](https://github.com/vuejs/vue-style-loader).

This fixes styles not being loaded when doing SSR (for example when using [@nuxtjs/vuetify](https://github.com/nuxt-community/vuetify-module)).
As Vuetify imports styles with JS, without this option, they do not get picked up by SSR.

⚠️ This option requires having `manualInject` set to `true` in [`vue-style-loader`](https://github.com/vuejs/vue-style-loader#options) config.
34 changes: 32 additions & 2 deletions lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,42 @@ function getMatches (type, items, matches, component) {
return imports
}

function install (install, content, imports) {
function injectStylesSSR (imports) {
const styles = imports.map(componentImport => (componentImport[2] || [])).reduce((acc, styles) => {
styles && styles.forEach(style => acc.add(style))
return acc
}, new Set())

if (styles.size) {
return `
KaelWD marked this conversation as resolved.
Show resolved Hide resolved
if (process.env.VUE_ENV === 'server') {
const options = typeof component.exports === 'function'
? component.exports.extendOptions
: component.options
const existing = options.beforeCreate
const hook = function () {
${[...styles].map((style) => ` require('vuetify/${style}').__inject__(this.$ssrContext)`).join('\n')}
}
options.beforeCreate = existing
? [].concat(existing, hook)
: [hook]
}
`
}
return ""
}

function install (install, content, imports, options = {}) {
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`

if (options.registerStylesSSR) {
newContent += injectStylesSSR(imports, newContent)
}

// Insert our modification before the HMR code
const hotReload = content.indexOf('/* hot reload */')
if (hotReload > -1) {
Expand All @@ -58,6 +87,7 @@ module.exports = async function (content, sourceMap) {
const options = {
match: [],
attrsMatch: [],
registerStylesSSR: false,
...loaderUtils.getOptions(this)
}

Expand Down Expand Up @@ -110,7 +140,7 @@ module.exports = async function (content, sourceMap) {
})
}

content = install.call(this, 'installComponents', content, getMatches.call(this, 'Tag', tags, options.match, component))
content = install.call(this, 'installComponents', content, getMatches.call(this, 'Tag', tags, options.match, component), options)
content = install.call(this, 'installDirectives', content, getMatches.call(this, 'Attr', attrs, options.attrsMatch, component))
}

Expand Down
30 changes: 26 additions & 4 deletions lib/matcher/generator.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
const Module = require('module')
const decache = require('decache')
const originalLoader = Module._load
const { readdirSync, statSync } = require('fs')
const { dirname, join } = require('path')
const { dirname, join, relative } = require('path')

let groupStyleDependencies = new Set()
const vuetifyRootPath = join(require.resolve('vuetify/es5/components'), '../../..')

Module._load = function _load (request, parent) {
if (request.endsWith('.styl')) return
if (request.endsWith('.scss')) return
if (request.endsWith('.sass')) return
if (request.endsWith('.sass')) {
groupStyleDependencies.add(relative(vuetifyRootPath, join(dirname(parent.filename), request)))
}
else return originalLoader(request, parent)
}

Expand All @@ -16,21 +22,37 @@ const directives = Object.keys(require('vuetify/es5/directives'))
const dir = dirname(require.resolve('vuetify/es5/components'))

const components = new Map()
const styles = new Map()
readdirSync(dir).forEach(group => {
if (!statSync(join(dir, group)).isDirectory()) return

groupStyleDependencies = new Set()
const component = require(`vuetify/es5/components/${group}`).default
if (component.hasOwnProperty('$_vuetify_subcomponents')) {
Object.keys(component.$_vuetify_subcomponents)
.forEach(name => components.set(name, group))
.forEach(name => {
components.set(name, group)
styles.set(name, groupStyleDependencies)
})
} else {
components.set(group, group)
styles.set(group, groupStyleDependencies)
}
// This is required so that groups picks up dependencies they have to other groups.
// For example VTabs depends on the style from VSlideGroup (VSlideGroup.sass).
// 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).
// 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.
decache(`vuetify/es5/components/${group}`)
})

// This makes sure Vuetify main styles will be injected.
// Using VApp as it's must be present for Vuetify to work, and it must only be there once.
styles.get('VApp').add('src/styles/main.sass')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure about the style ordering here, but we can always come back and fix it if it turns out to be broken.


Module._load = originalLoader

module.exports = {
directives,
components
components,
styles
}
4 changes: 2 additions & 2 deletions lib/matcher/tag.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const { components } = require('./generator')
const { components, styles } = require('./generator')

module.exports = function match (_, { kebabTag, camelTag: tag }) {
if (!kebabTag.startsWith('v-')) return

if (components.has(tag)) {
return [tag, `import { ${tag} } from 'vuetify/lib/components/${components.get(tag)}';`]
return [tag, `import { ${tag} } from 'vuetify/lib/components/${components.get(tag)}';`, styles.get(tag)]
}
}
3 changes: 2 additions & 1 deletion lib/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ class VuetifyLoaderPlugin {
loader: require.resolve('./loader'),
options: {
match: this.options.match || [],
attrsMatch: this.options.attrsMatch || []
attrsMatch: this.options.attrsMatch || [],
registerStylesSSR: this.options.registerStylesSSR || false
}
},
...rule.use
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"homepage": "https://github.com/vuetifyjs/vuetify-loader#readme",
"dependencies": {
"decache": "^4.5.1",
"file-loader": "^4.0.0",
"loader-utils": "^1.2.0"
},
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,11 @@ cache-base@^1.0.1:
union-value "^1.0.0"
unset-value "^1.0.0"

callsite@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=

chokidar@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26"
Expand Down Expand Up @@ -712,6 +717,13 @@ debug@^3.1.0:
dependencies:
ms "^2.1.1"

decache@^4.5.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/decache/-/decache-4.5.1.tgz#94a977a88a4188672c96550ec4889582ceecdf49"
integrity sha512-5J37nATc6FmOTLbcsr9qx7Nm28qQyg1SK4xyEHqM0IBkNhWFp0Sm+vKoWYHD8wq+OUEb9jLyaKFfzzd1A9hcoA==
dependencies:
callsite "^1.0.0"

decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
Expand Down