Browse Source

inline css links in renderStyles()

Evan You 9 years ago
parent
commit
2efc0446b3

+ 15 - 7
src/server/template-renderer/index.js

@@ -3,15 +3,13 @@
 const path = require('path')
 const serialize = require('serialize-javascript')
 
+import { isJS, isCSS } from '../util'
 import TemplateStream from './template-stream'
 import { parseTemplate } from './parse-template'
 import { createMapper } from './create-async-file-mapper'
 import type { ParsedTemplate } from './parse-template'
 import type { AsyncFileMapper } from './create-async-file-mapper'
 
-const JS_RE = /\.js($|\?)/
-export const isJS = (file: string): boolean => JS_RE.test(file)
-
 type TemplateRendererOptions = {
   template: ?string;
   inject?: boolean;
@@ -100,8 +98,18 @@ export default class TemplateRenderer {
   }
 
   renderStyles (context: Object): string {
-    // context.styles is a getter exposed by vue-style-loader
-    return context.styles || ''
+    const cssFiles = this.clientManifest
+      ? this.clientManifest.all.filter(isCSS)
+      : []
+    return (
+      // render links for css files
+      (cssFiles.length
+        ? cssFiles.map(file => `<link rel="stylesheet" href="${this.publicPath}/${file}">`).join('')
+        : '') +
+      // context.styles is a getter exposed by vue-style-loader which contains
+      // the inline component styles collected during SSR
+      (context.styles || '')
+    )
   }
 
   renderResourceHints (context: Object): string {
@@ -117,8 +125,8 @@ export default class TemplateRenderer {
         const ext = path.extname(withoutQuery).slice(1)
         const type = getPreloadType(ext)
         const shouldPreload = this.options.shouldPreload
-        // by default, we only preload scripts
-        if (!shouldPreload && type !== 'script') {
+        // by default, we only preload scripts or css
+        if (!shouldPreload && type !== 'script' && type !== 'style') {
           return ''
         }
         // user wants to explicitly control what to preload

+ 4 - 3
src/server/template-renderer/template-stream.js

@@ -49,9 +49,10 @@ export default class TemplateStream extends Transform {
         this.push(links)
       }
 
-      // inline server-rendered CSS collected by vue-style-loader
-      if (this.context.styles) {
-        this.push(this.context.styles)
+      // CSS files and inline server-rendered CSS collected by vue-style-loader
+      const styles = this.renderer.renderStyles(this.context)
+      if (styles) {
+        this.push(styles)
       }
     }
 

+ 5 - 0
src/server/util.js

@@ -0,0 +1,5 @@
+/* @flow */
+
+export const isJS = (file: string): boolean => /\.js($|\?)/.test(file)
+
+export const isCSS = (file: string): boolean => /\.css($|\?)/.test(file)

+ 5 - 5
src/server/webpack-plugin/client.js

@@ -15,20 +15,20 @@ export default class VueSSRClientPlugin {
       const allFiles = stats.assets
         .map(a => a.name)
 
-      const initialScripts = Object.keys(stats.entrypoints)
+      const initialFiles = Object.keys(stats.entrypoints)
         .map(name => stats.entrypoints[name].assets)
         .reduce((assets, all) => all.concat(assets), [])
         .filter(isJS)
 
-      const asyncScripts = allFiles
+      const asyncFiles = allFiles
         .filter(isJS)
-        .filter(file => initialScripts.indexOf(file) < 0)
+        .filter(file => initialFiles.indexOf(file) < 0)
 
       const manifest = {
         publicPath: stats.publicPath,
         all: allFiles,
-        initial: initialScripts,
-        async: asyncScripts,
+        initial: initialFiles,
+        async: asyncFiles,
         modules: { /* [identifier: string]: Array<index: number> */ }
       }
 

+ 1 - 1
src/server/webpack-plugin/util.js

@@ -21,4 +21,4 @@ export const validate = compiler => {
   }
 }
 
-export const isJS = file => /\.js($|\?)/.test(file)
+export { isJS, isCSS } from '../util'

+ 1 - 1
test/ssr/compile-with-webpack.js

@@ -16,7 +16,7 @@ export function compileWithWebpack (file, extraConfig, cb) {
           loader: require.resolve('./async-loader')
         },
         {
-          test: /\.(png|woff2)$/,
+          test: /\.(png|woff2|css)$/,
           loader: 'file-loader',
           options: {
             name: '[name].[ext]'

+ 1 - 0
test/ssr/fixtures/async-foo.js

@@ -1,4 +1,5 @@
 // import image and font
+import './test.css'
 import font from './test.woff2'
 import image from './test.png'
 

+ 0 - 0
test/ssr/fixtures/test.css


+ 8 - 4
test/ssr/ssr-template.spec.js

@@ -225,11 +225,14 @@ describe('SSR: template option', () => {
       `<link rel="preload" href="/manifest.js" as="script">` +
       `<link rel="preload" href="/main.js" as="script">` +
       `<link rel="preload" href="/0.js" as="script">` +
+      `<link rel="preload" href="/test.css" as="style">` +
       // images and fonts are only preloaded when explicitly asked for
       (preloadOtherAssets ? `<link rel="preload" href="/test.png" as="image">` : ``) +
       (preloadOtherAssets ? `<link rel="preload" href="/test.woff2" as="font" type="font/woff2" crossorigin>` : ``) +
       // unused chunks should have prefetch
       `<link rel="prefetch" href="/1.js" as="script">` +
+      // css assets should be loaded
+      `<link rel="stylesheet" href="/test.css">` +
     `</head><body>` +
       `<div data-server-rendered="true"><div>async test.woff2 test.png</div></div>` +
       // manifest chunk should be first
@@ -243,7 +246,7 @@ describe('SSR: template option', () => {
   createClientManifestAssertions(false)
 
   function createClientManifestAssertions (runInNewContext) {
-    it('bundleRenderer + renderToString + clientManifest', done => {
+    it('bundleRenderer + renderToString + clientManifest ()', done => {
       createRendererWithManifest('split.js', { runInNewContext }, renderer => {
         renderer.renderToString({}, (err, res) => {
           expect(err).toBeNull()
@@ -257,7 +260,7 @@ describe('SSR: template option', () => {
       createRendererWithManifest('split.js', {
         runInNewContext,
         shouldPreload: (file, type) => {
-          if (type === 'image' || type === 'script' || type === 'font') {
+          if (type === 'image' || type === 'script' || type === 'font' || type === 'style') {
             return true
           }
         }
@@ -278,7 +281,7 @@ describe('SSR: template option', () => {
       createRendererWithManifest('split.js', {
         runInNewContext,
         template: `<html>` +
-          `<head>{{{ renderResourceHints() }}}</head>` +
+          `<head>{{{ renderResourceHints() }}}{{{ renderStyles() }}}</head>` +
           `<body><!--vue-ssr-outlet-->{{{ renderScripts() }}}</body>` +
         `</html>`,
         inject: false
@@ -303,7 +306,8 @@ describe('SSR: template option', () => {
 
           const customOutput =
             `<html><head>${
-              context.renderResourceHints()
+              context.renderResourceHints() +
+              context.renderStyles()
             }</head><body>${
               res +
               context.renderScripts()