Skip to content

Commit

Permalink
fix: css extraction
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Apr 7, 2018
1 parent ce86da7 commit d293194
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 9 deletions.
12 changes: 12 additions & 0 deletions lib/webpack/RemoveEmptyChunkPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
module.exports = class Plugin {
apply (compiler) {
compiler.hooks.emit.tap('vuepress-remove-empty-chunk', compilation => {
Object.keys(compilation.assets).forEach(name => {
if (/_assets\/js\/styles\.\w{8}\.js$/.test(name)) {
delete compilation.assets[name]
}
})
})
}
}
45 changes: 38 additions & 7 deletions lib/webpack/baseConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module.exports = function createBaseConfig ({
publicPath,
themePath,
notFoundPath
}, { debug } = {}) {
}, { debug } = {}, isServer) {
const markdown = require('../markdown')(siteConfig)
const Config = require('webpack-chain')
const { VueLoaderPlugin } = require('vue-loader')
Expand Down Expand Up @@ -142,10 +142,12 @@ module.exports = function createBaseConfig ({
applyLoaders(normalRule, false)

function applyLoaders (rule, modules) {
if (isProd) {
rule.use('extract-css-loader').loader(CSSExtractPlugin.loader)
} else {
rule.use('vue-style-loader').loader('vue-style-loader')
if (!isServer) {
if (isProd) {
rule.use('extract-css-loader').loader(CSSExtractPlugin.loader)
} else {
rule.use('vue-style-loader').loader('vue-style-loader')
}
}

rule.use('css-loader').loader('css-loader').options({
Expand Down Expand Up @@ -174,10 +176,39 @@ module.exports = function createBaseConfig ({
.plugin('vue-loader')
.use(VueLoaderPlugin)

if (isProd) {
if (isProd && !isServer) {
config
.plugin('extract-css')
.use(CSSExtractPlugin, [{ filename: '_assets/css/styles.[hash:8].css' }])
.use(CSSExtractPlugin, [{
filename: '_assets/css/styles.[chunkhash:8].css'
}])

// ensure all css are extracted together.
// since most of the CSS will be from the theme and very little
// CSS will be from async chunks
config
.set('optimization', {
splitChunks: {
cacheGroups: {
chunks: 'all',
styles: {
name: 'styles',
// necessary for extraction to include md files as well
test: m => /css-extract/.test(m.type),
chunks: 'all',
enforce: true
}
}
}
})

// enforcing all styles extraction leaves an empty styles chunk.
// prevent it from being emitted.
// this is a bug in mini-css-extract-plugin
// https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
config
.plugin('remove-empty-chunk')
.use(require('./RemoveEmptyChunkPlugin'))
}

return config
Expand Down
8 changes: 7 additions & 1 deletion lib/webpack/clientConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ module.exports = function createClientConfig (options, cliOptions) {

// generate client manifest only during build
if (process.env.NODE_ENV === 'production') {
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
// TODO this is a temp build of vue-server-renderer/client-plugin.
// Switch back after problems are resolved.
// Fixes two things:
// 1. Include CSS in preload files
// 2. filter out useless styles.xxxxx.js chunk from mini-css-extract-plugin
// https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
const VueSSRClientPlugin = require('./clientPlugin')
config
.plugin('ssr-client')
.use(VueSSRClientPlugin, [{
Expand Down
99 changes: 99 additions & 0 deletions lib/webpack/clientPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Temporarily copied from a dev build

'use strict'

/* */

var isJS = function (file) { return /\.js(\?[^.]+)?$/.test(file) }

var isCSS = function (file) { return /\.css(\?[^.]+)?$/.test(file) }

var onEmit = function (compiler, name, hook) {
if (compiler.hooks) {
// Webpack >= 4.0.0
compiler.hooks.emit.tapAsync(name, hook)
} else {
// Webpack < 4.0.0
compiler.plugin('emit', hook)
}
}

var hash = require('hash-sum')
var uniq = require('lodash.uniq')
var VueSSRClientPlugin = function VueSSRClientPlugin (options) {
if (options === void 0) options = {}

this.options = Object.assign({
filename: 'vue-ssr-client-manifest.json'
}, options)
}

VueSSRClientPlugin.prototype.apply = function apply (compiler) {
var this$1 = this

onEmit(compiler, 'vue-client-plugin', function (compilation, cb) {
var stats = compilation.getStats().toJson()

var allFiles = uniq(stats.assets
.map(function (a) { return a.name }))
.filter(file => {
return !/styles\.\w{8}\.js$/.test(file)
})

var initialFiles = uniq(Object.keys(stats.entrypoints)
.map(function (name) { return stats.entrypoints[name].assets })
.reduce(function (assets, all) { return all.concat(assets) }, [])
.filter(function (file) { return isJS(file) || isCSS(file) }))
.filter(file => {
return !/styles\.\w{8}\.js$/.test(file)
})

var asyncFiles = allFiles
.filter(function (file) { return isJS(file) || isCSS(file) })
.filter(function (file) { return initialFiles.indexOf(file) < 0 })

var manifest = {
publicPath: stats.publicPath,
all: allFiles,
initial: initialFiles,
async: asyncFiles,
modules: { /* [identifier: string]: Array<index: number> */ }
}

var assetModules = stats.modules.filter(function (m) { return m.assets.length })
var fileToIndex = function (file) { return manifest.all.indexOf(file) }
stats.modules.forEach(function (m) {
// ignore modules duplicated in multiple chunks
if (m.chunks.length === 1) {
var cid = m.chunks[0]
var chunk = stats.chunks.find(function (c) { return c.id === cid })
if (!chunk || !chunk.files) {
return
}
var id = m.identifier.replace(/\s\w+$/, '') // remove appended hash
var files = manifest.modules[hash(id)] = chunk.files.map(fileToIndex)
// find all asset modules associated with the same chunk
assetModules.forEach(function (m) {
if (m.chunks.some(function (id) { return id === cid })) {
files.push.apply(files, m.assets.map(fileToIndex))
}
})
}
})

// const debug = (file, obj) => {
// require('fs').writeFileSync(__dirname + '/' + file, JSON.stringify(obj, null, 2))
// }
// debug('stats.json', stats)
// debug('client-manifest.json', manifest)

var json = JSON.stringify(manifest, null, 2)
compilation.assets[this$1.options.filename] = {
source: function () { return json },
size: function () { return json.length }
}
cb()
})
}

module.exports = VueSSRClientPlugin
2 changes: 1 addition & 1 deletion lib/webpack/serverConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = function createServerConfig (options, cliOptions) {
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const CopyPlugin = require('copy-webpack-plugin')

const config = createBaseConfig(options, cliOptions)
const config = createBaseConfig(options, cliOptions, true /* isServer */)
const { sourceDir, outDir } = options

config
Expand Down

0 comments on commit d293194

Please sign in to comment.